Dakidex Scraper

Scrape product details and open a new tab with the data

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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