Dakidex Scraper

Scrape product details and open a new tab with the data

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==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);
})();