Dakidex Scraper

Scrape product details and open a new tab with the data

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Dakidex Scraper
// @namespace    http://dakidex.com
// @version      1.6
// @description  Scrape product details and open a new tab with the data
// @author       Dakidex
// @match        http*://booth.pm/*/items/*
// @match        http*://*.booth.pm/items/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
  "use strict";

  // Material mapping from Japanese names to material tags
  const japaneseToMaterialTag = {
    アクライクラ: "material:fules_aqualycra",
    アクアライクラ: "material:fules_aqualycra",
    アクアヴェール: "material:fules_aquaveil",
    アクアプレミア: "material:fules_aquapremier",
    ライクトロンリッチ: "material:aj_lyctron_rich",
    ライクトロン: "material:aj_lyctron",
    ツーウェイロイカ: "material:2way_roika",
    白桃: "material:shiromoufu_hakutou",
    桜餅: "material:shiromoufu_sakuramochi",
    メイプルシロップ: "material:maple_syrup",
  };

  class ProductScraper {
    /**
     * Gets the material tag for a Japanese material name
     * @param {string} japaneseMaterialName - The Japanese material name
     * @returns {string|null} The corresponding material tag, or null if not mapped
     */
    static getMaterialTag(japaneseMaterialName) {
      const normalized = japaneseMaterialName.trim();
      return japaneseToMaterialTag[normalized] || null;
    }

    /**
     * Extracts material name from product description
     * Finds the first known material that appears in the text
     * @param {string} descriptionText - The product description text
     * @returns {string|null} The material name or null if not found
     */
    static extractMaterialFromDescription(descriptionText) {
      let firstMaterial = null;
      let earliestIndex = Infinity;

      // Check each known material and find which appears first in the text
      for (const materialName of Object.keys(japaneseToMaterialTag)) {
        const index = descriptionText.indexOf(materialName);
        if (index !== -1 && index < earliestIndex) {
          earliestIndex = index;
          firstMaterial = materialName;
        }
      }

      return firstMaterial;
    }

    /**
     * Extracts release date from the product page
     * Looks for the published date in "商品公開日時:YYYY-MM-DD HH:MM" format
     * @returns {string|null} ISO format date string or null if not found
     */
    static extractReleaseDate() {
      const dateElement = document.querySelector("#js-item-published-date");
      if (!dateElement) {
        return null;
      }

      const text = dateElement.textContent;
      // Match pattern: 商品公開日時:YYYY-MM-DD HH:MM
      const dateMatch = text.match(/(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})/);

      if (dateMatch) {
        const [, year, month, day, hour, minute] = dateMatch;
        // Create ISO format date string (using JST timezone)
        const date = new Date(`${year}-${month}-${day}T${hour}:${minute}:00+09:00`);
        return date.toISOString();
      }

      return null;
    }

    /**
     * Extracts all material tags from the product description
     * @returns {string[]} Array of material tags found
     */
    static extractMaterialTags() {
      const tags = [];

      // Try multiple selectors for different site layouts
      let descElement =
        document.querySelector(".js-market-item-detail-description") ||
        document.querySelector('[class*="description"]') ||
        document.querySelector('[class*="detail"]') ||
        document.querySelector(".product-description") ||
        document.querySelector(".item-explanation");

      if (!descElement) {
        return tags;
      }

      const allText = descElement.textContent;

      const materialName = this.extractMaterialFromDescription(allText);
      if (materialName) {
        const materialTag = this.getMaterialTag(materialName);
        if (materialTag) {
          tags.push(materialTag);
        }
      }

      // Check for limited order period
      if (allText.includes("受付期間")) {
        tags.push("meta:limited_order_period");
      }

      return tags;
    }

    static async scrape() {
      const title = document.querySelector(".summary h2")?.textContent?.trim();
      const price = document.querySelector(".variation-price")?.textContent;

      let productUrl = window.location.href;
      const realUrl = document.querySelector(
        'a[data-product-list="from market_show via market_item_detail to shop_index"]'
      )?.href;
      if (realUrl) {
        productUrl = realUrl + "items/" + productUrl.split("/").pop();
      }

      // Parse product images
      let imageUrls = [];
      const imageElements = document.querySelectorAll(".market-item-detail-item-image-wrapper img");
      imageElements.forEach((img) => {
        const imageUrl = img.getAttribute("data-origin") ?? img.getAttribute("src");
        if (imageUrl) {
          imageUrls.push(imageUrl);
        }
      });

      // Ensure imageUrls are unique
      imageUrls = [...new Set(imageUrls)];
      // Move first image to last if there are more than 1 image
      if (imageUrls.length > 1) {
        imageUrls.push(imageUrls.shift());
      }

      // Extract material tags
      const materialTags = this.extractMaterialTags();

      // Extract release date
      const releaseDate = this.extractReleaseDate();

      const data = {
        name: title || "",
        price: this.parsePrice(price || ""),
        currency: "JPY",
        urls: [productUrl],
        tags: materialTags,
        release_date: releaseDate,
        variants: [
          {
            name: "Default",
            imageUrls: imageUrls,
            nsfw: true,
          },
        ],
      };

      const encodedData = btoa(encodeURIComponent(JSON.stringify(data)));
      window.open(`https://dakidex.com/create?data=${encodedData}`, "_blank");
    }

    static parsePrice(price) {
      return Number(price.replace(/[^0-9]/g, ""));
    }
  }

  const button = document.createElement("button");
  button.textContent = "Add to Dakidex";
  button.style.position = "fixed";
  button.style.bottom = "10px";
  button.style.right = "10px";
  button.style.zIndex = 1000;
  button.style.padding = "10px";
  button.style.borderRadius = "6px";
  button.style.fontWeight = "bold"; // Made font bold
  button.style.backgroundColor = "rgba(112, 112, 112, 0.7)"; // 70% gray background
  button.style.color = "white"; // White text
  button.addEventListener("click", () => ProductScraper.scrape());
  document.body.appendChild(button);
})();