FAB Free Asset Getter Latest

A script to get all free assets from the FAB marketplace

// ==UserScript==
// @name        FAB Free Asset Getter Latest
// @namespace   Violentmonkey Scripts
// @copyright   https://greasyfork.org/zh-CN/users/313682-没拖鞋 | 2024, subtixx (https://openuserjs.org/users/subtixx)
// @match       https://www.fab.com/channels/*
// @match       https://www.fab.com/zh-cn/channels/*
// @match       https://www.fab.com/search*
// @match       https://www.fab.com/zh-cn/search*
// @grant       none
// @license     AGPL-3.0-or-later
// @version     2.0
// @author      Noslipper <[email protected]> | Dominic Hock <[email protected]>
// @description A script to get all free assets from the FAB marketplace
// ==/UserScript==

(function () {
    `use strict`;
    var added = false;
    var notificationQueueContainer = null;
    var assetProgressbar = null;
    var innerAssetsProgressbar = null;
    var assetStatus = null;
    // 更新选择器以适应网站变化
    const resultGridID = ".oeSuy4_9, .vL3jJySf, [class*='SearchResults'], main";

    // 检测页面语言
    function detectLanguage() {
      const url = window.location.href;
      if (url.includes("/zh-cn/")) {
        return "zh-cn";
      } else {
        return "en";
      }
    }

    // 获取当前语言
    const currentLanguage = detectLanguage();
    console.log("Detected language:", currentLanguage);

    // 多语言文本
    const translations = {
      "en": {
        "startGetting": "Starting to get free assets...",
        "checkingAssets": "Checking all assets regardless of ownership status",
        "tryingToAddAPI": "Trying to add via API: ",
        "addedSuccessfully": "Successfully added ",
        "addedToLibrary": " to your library",
        "apiFailed": "API failed, trying iframe method",
        "loadingAssetPage": "Loading asset page: ",
        "processing": "Processing: ",
        "buttonNotFound": "Add button not found, skipping: ",
        "processingError": "Error processing asset: ",
        "skipping": ", skipping",
        "iframeError": "Iframe method error: ",
        "progress": "Progress: ",
        "noResults": "Failed to find results! Try refreshing the page.",
        "noItems": "No items found? Check console!",
        "tooManyListings": "Too many listings, splitting into 24 chunks!",
        "needToCheck": "Need to check ",
        "listings": " listings",
        "scrollingMore": "Scrolling to load more items (attempt ",
        "processingNewItems": "Processing newly loaded items...",
        "noNewItems": "No new items loaded, attempts: ",
        "reachedBottom": "Reached the bottom of the page, no more items",
        "processedBatches": "Completed! Processed ",
        "batches": " batches of items",
        "error": "Error: ",
        "addFreeAssets": "Get Free Assets",
        "scriptLoaded": "FAB Free Asset Getter v2.0 loaded!",
        "checking": "Checking",
        "filteredAssets": "Found {0} assets to add out of {1} total assets",
        "allAssetsOwned": "All assets appear to be already in your library",
        "alreadyOwned": "{0} is already in your library - skipping"
      },
      "zh-cn": {
        "startGetting": "开始获取免费资产...",
        "checkingAssets": "检查所有资产,不考虑是否已在库中",
        "tryingToAddAPI": "尝试使用API添加: ",
        "addedSuccessfully": "成功添加 ",
        "addedToLibrary": " 到您的库中",
        "apiFailed": "API添加失败,尝试使用iframe方法",
        "loadingAssetPage": "正在加载资产页面: ",
        "processing": "正在处理: ",
        "buttonNotFound": "未找到添加按钮,跳过: ",
        "processingError": "处理资产时出错: ",
        "skipping": ",跳过",
        "iframeError": "iframe方法出错: ",
        "progress": "进度: ",
        "noResults": "未找到结果!请尝试刷新页面。",
        "noItems": "未找到项目?请查看控制台!",
        "tooManyListings": "项目过多,分批处理!",
        "needToCheck": "需要检查 ",
        "listings": " 个项目",
        "scrollingMore": "滚动加载更多项目(第 ",
        "processingNewItems": "正在处理新加载的项目...",
        "noNewItems": "没有新项目加载,尝试次数: ",
        "reachedBottom": "已到达页面底部,没有更多项目",
        "processedBatches": "完成!已处理 ",
        "batches": " 批项目",
        "error": "错误: ",
        "addFreeAssets": "添加免费资产",
        "scriptLoaded": "FAB免费资产获取器 v2.0 已加载!",
        "checking": "正在检查",
        "filteredAssets": "在 {1} 个资产中找到 {0} 个需要添加的资产",
        "allAssetsOwned": "所有资产似乎都已在您的库中",
        "alreadyOwned": "{0} 已在您的库中 - 跳过"
      },
      // 德语支持已移除
    };

    // 获取翻译文本
    function t(key) {
      if (translations[currentLanguage] && translations[currentLanguage][key]) {
        return translations[currentLanguage][key];
      } else if (translations["en"][key]) {
        return translations["en"][key]; // 默认使用英文
      } else {
        console.warn("Missing translation for key:", key);
        return key;
      }
    }

      // Function to show toast
      function showToast(message, type = 'success', duration = 3000) {
          const toast = document.createElement('div');
          toast.textContent = message;
          //toast.style.position = 'fixed';
          //toast.style.bottom = '20px';
          //toast.style.right = '20px';
          toast.style.margin = "5px 0 5px 0";
          toast.style.padding = '15px';
          toast.style.backgroundColor = type === 'success' ? '#28a745' : (type === 'warning' ? '#ffc107' : '#dc3545'); // Green for success, yellow for warning, red for error
          toast.style.color = type === 'warning' ? 'black' : 'white'; // Black text for warning (yellow background)
          toast.style.borderRadius = '5px';
          toast.style.zIndex = '10000';
          toast.style.fontFamily = 'Arial, sans-serif';
          toast.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
          toast.style.opacity = '0';
          toast.style.transition = 'opacity 0.5s ease';

          // Append to body
          notificationQueueContainer.appendChild(toast);

          // Fade in
          setTimeout(() => {
              toast.style.opacity = '1';
          }, 100);

          // Auto-remove after specified duration
          setTimeout(() => {
              toast.style.opacity = '0';
              setTimeout(() => {
                if (toast.parentNode) {
                  toast.parentNode.removeChild(toast);
                }
              }, 500);
          }, duration);
      }

    function getCSRFToken() {
      // Get from fab_csrftoken cookie
      let cookies = document.cookie.split(";");
      for (let i = 0; i < cookies.length; i++) {
        let cookie = cookies[i].trim();
        if (cookie.startsWith("fab_csrftoken=")) {
          return cookie.split("=")[1];
        }
      }
      return "";
    }

    async function getAcquiredIds(listings) {
      assetStatus.innerText = t("needToCheck") + listings.length + t("listings");

      console.log("Getting acquired ids");
      // max listings is 24 so just cut
      if (listings.length > 24) {
        showToast(t("tooManyListings"), "error");
        console.error("Too many listings");
        return [];
      }
      // 记录原始列表长度
      console.log("Original listings count:", listings.length);

      // 过滤掉已拥有的资产
      let filteredListings = listings.filter(listing => {
        const isFiltered = !listing.isOwned;
        if (!isFiltered) {
          console.log("Filtering out owned asset:", listing.name, listing.id);
        }
        return isFiltered;
      });

      console.log("过滤后的资产数量:", filteredListings.length, "原始数量:", listings.length);

      // 如果过滤后没有资产,可能是因为检测不准确,提示用户
      if (filteredListings.length === 0 && listings.length > 0) {
        console.log("所有资产似乎都已在库中,但可能检测不准确");
        showToast(t("allAssetsOwned"));
      } else {
        showToast(t("filteredAssets").replace("{0}", filteredListings.length).replace("{1}", listings.length));
      }

      // 检查是否有资产需要检查
      if (filteredListings.length === 0) {
        console.log("No listings to check after filtering");
        return [];
      }

      try {
        // Convert uid array to listing_ids=X&listing_ids=Y&listing_ids=Z
        let ids = filteredListings
          .map(listing => listing.id)
          .join("&listing_ids=");

        // 检查ids是否为空
        if (!ids || ids.trim() === "") {
          console.log("No valid listing IDs to check");
          return [];
        }

        console.log("Fetching listing states for IDs:", ids);

        //[{"uid":"5059af80-527f-4dda-8e75-7dde4dfcdf81","acquired":true,"rating":null}]
        let result = await fetch("https://www.fab.com/i/users/me/listings-states?listing_ids=" + ids, {
          "credentials": "include",
          "headers": {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "en",
            "X-Requested-With": "XMLHttpRequest",
            "X-CsrfToken": getCSRFToken(),
            "Sec-GPC": "1",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-origin"
          },
          "referrer": "https://www.fab.com/channels/unreal-engine?is_free=1&sort_by=-createdAt&is_ai_generated=0",
          "method": "GET",
          "mode": "cors"
        });

        if (!result.ok) {
          console.error("Failed to fetch listing states:", result.status, result.statusText);
          // 如果API调用失败,我们假设没有资产被获取
          return [];
        }

        let json = await result.json();
        let acquired = [];
        for (let i = 0; i < json.length; i++) {
          if (json[i].acquired) {
            acquired.push(json[i].uid);
          }
        }

        let alreadyAcquired = listings.filter(listing => listing.isOwned).length;
        console.log("Acquired " + acquired.length + " of " + listings.length + " listings (" + alreadyAcquired + " already acquired were skipped)");
        return acquired;
      } catch (error) {
        console.error("Error fetching acquired IDs:", error);
        // 如果出现错误,我们假设没有资产被获取
        return [];
      }
    }

    async function getIds() {
      // 尝试使用多个选择器找到结果网格
      let resultGrid = null;
      const selectors = resultGridID.split(", ");

      for (let i = 0; i < selectors.length; i++) {
        const elements = document.querySelectorAll(selectors[i]);
        if (elements && elements.length > 0) {
          // 找到第一个包含资产项的元素
          for (let j = 0; j < elements.length; j++) {
            if (elements[j].querySelectorAll(".fabkit-Stack-root, .d6kADL5Y, [class*='_fyPHkQI']").length > 0) {
              resultGrid = elements[j];
              break;
            }
          }
          if (resultGrid) break;
        }
      }

      if(!resultGrid) {
        console.error("Failed to find results grid with selectors:", selectors);
        showToast(t("noResults"), "error");
        return;
      }

      console.log("Found result grid:", resultGrid);

      // 尝试多种可能的选择器来适应网站的变化
      // 添加更多可能的选择器以适应网站结构变化
      const itemSelectors = [
        ".fabkit-Stack-root.d6kADL5Y.Bf_zHIaU",
        ".fabkit-Stack-root.d6kADL5Y",
        ".d6kADL5Y._fyPHkQI",
        // 添加更通用的选择器
        "div[class*='Card']",
        "div[class*='card']",
        "div[class*='Item']",
        "div[class*='item']",
        "div[class*='Asset']",
        "div[class*='asset']",
        "div[class*='Product']",
        "div[class*='product']",
        // 尝试查找包含链接的容器
        "div > a[href*='/listings/']",
        "a[href*='/listings/']"
      ];

      // 记录所有尝试的选择器
      console.log("Trying to find items with selectors:", itemSelectors.join(", "));

      // 尝试每个选择器
      let foundItems = null;
      for (const selector of itemSelectors) {
        const items = resultGrid.querySelectorAll(selector);
        if (items && items.length > 0) {
          console.log(`Found ${items.length} items with selector: ${selector}`);
          foundItems = items;
          break;
        }
      }

      // 如果所有选择器都失败,尝试一个最后的备用方法:查找所有链接
      if (!foundItems || foundItems.length === 0) {
        console.log("All specific selectors failed, trying to find any links to listings");
        const allLinks = resultGrid.querySelectorAll("a");
        const listingLinks = Array.from(allLinks).filter(link =>
          link.href && link.href.includes("/listings/")
        );

        if (listingLinks.length > 0) {
          console.log(`Found ${listingLinks.length} listing links as fallback`);
          foundItems = listingLinks;
        }
      }

      if(!foundItems || foundItems.length === 0){
        showToast(t("noItems"), "error");
        console.error("Result grid found but no items inside:", resultGrid);
        console.log("HTML of result grid:", resultGrid.innerHTML);
        return;
      }
      console.log("Found " + foundItems.length + " items");

      let currentListings = [];

      // 显示初始进度
      showToast(t("progress") + "0/" + foundItems.length + " (0%)", "success");

      for (let i = 0; i < foundItems.length; i++) {
        // 每处理10个项目或处理到最后一个项目时更新进度
        if (i % 2 === 0 || i === foundItems.length - 1) {
          const percent = ((i + 1) / foundItems.length * 100).toFixed(1);
          showToast(t("progress") + (i + 1) + "/" + foundItems.length + " (" + percent + "%)", "success");
        }

        let root = foundItems[i];

        // 尝试多种可能的选择器来获取名称
        let nameContainer = root.querySelector("a > div.fabkit-Typography-ellipsisWrapper") ||
                           root.querySelector("div.fabkit-Typography-ellipsisWrapper") ||
                           root.querySelector("[class*='ellipsisWrapper']") ||
                           root.querySelector("[class*='title']") ||
                           root.querySelector("h3") ||
                           root.querySelector("h2");

        // 特殊处理 _fyPHkQI 类的元素
        if (!nameContainer && root.classList.contains("_fyPHkQI")) {
          // 尝试从父元素获取名称
          const parentItem = root.closest(".fabkit-Stack-root") || root.parentElement;
          if (parentItem) {
            nameContainer = parentItem.querySelector("[class*='ellipsisWrapper']") ||
                           parentItem.querySelector("[class*='title']") ||
                           parentItem.querySelector("h3") ||
                           parentItem.querySelector("h2");
          }

          // 如果仍然找不到,尝试从相邻元素获取
          if (!nameContainer && root.previousElementSibling) {
            const prevItem = root.previousElementSibling;
            nameContainer = prevItem.querySelector("[class*='ellipsisWrapper']") ||
                           prevItem.querySelector("[class*='title']") ||
                           prevItem.querySelector("h3") ||
                           prevItem.querySelector("h2");
          }
        }

        // 如果仍然找不到名称容器,使用一个默认名称并继续
        let name = "Unknown Asset";
        if (nameContainer) {
          name = nameContainer.innerText || nameContainer.textContent || "Unknown Asset";
        } else {
          console.log("Cannot find name container in:", root, "- using default name");
          // 不中断处理,继续尝试获取其他信息
        }

        // 尝试多种可能的选择器来获取链接
        let linkElement = root.querySelector("a") || root.closest("a");

        // 特殊处理 _fyPHkQI 类的元素
        if (!linkElement && root.classList.contains("_fyPHkQI")) {
          // 尝试从父元素获取链接
          const parentItem = root.closest(".fabkit-Stack-root") || root.parentElement;
          if (parentItem) {
            linkElement = parentItem.querySelector("a");
          }

          // 如果仍然找不到,尝试从相邻元素获取
          if (!linkElement && root.previousElementSibling) {
            linkElement = root.previousElementSibling.querySelector("a");
          }
        }

        if (!linkElement) {
          console.log("Cannot find link in:", root, "- skipping item");
          continue;
        }

        let url = linkElement.href;

        // 查找资产是否显示"Saved in My Library"或"Free"
        // 这是判断资产是否在库中的最准确方法
        let isOwned = false;
        let ownedReason = "";

        // 获取资产卡片中的所有文本
        const cardText = root.innerText || root.textContent || "";

        // 检查是否包含"Saved in My Library"文本
        if (cardText.includes("Saved in My Library") ||
            cardText.includes("已保存在我的库中")) {
          isOwned = true;
          ownedReason = "Text 'Saved in My Library' found";
          console.log(`Asset marked as owned: ${name} - reason: ${ownedReason}`);
        }

        // 检查是否包含"Free"文本但不在库中
        if ((cardText.includes("Free") ||
             cardText.includes("免费")) && !isOwned) {
          isOwned = false;
          console.log(`Asset marked as NOT owned: ${name} - has 'Free' text`);
        }

        // 记录资产的完整文本,用于调试
        console.log(`Asset card text for ${name}: "${cardText.substring(0, 100)}..."`);

        // 记录资产的所有权状态
        console.log(`Asset ${name} ownership status: ${isOwned ? "Owned" : "Not owned"}`);

        if (!url || url === undefined) {
          console.log("Failed to get valid URL for:", name, "- skipping item");
          continue;
        }

        // Extract id
        let id = url.split("/").pop();
        if(!id){
          console.log("Can't extract ID from URL:", url, "- skipping item");
          continue;
        }

        console.log(id, name, isOwned, url);

        currentListings.push({
          isOwned: isOwned,
          name: name,
          id: id
        });
      }

      assetStatus.style.display = "block";

      let acquired = [];
      console.log("Need to check " + currentListings.length + " listings");
      assetStatus.innerText = t("needToCheck") + currentListings.length + t("listings");
      if (currentListings.length > 24) {
        showToast(t("tooManyListings"));
        console.log("Too many listings, splitting into 24 chunks");
        // Slice, request, join, until we are finished
        for (let i = 0; i < currentListings.length; i += 24) {
          let partial = await getAcquiredIds(currentListings.slice(i, i + 24));
          acquired = acquired.concat(partial);
          await new Promise(resolve => setTimeout(resolve, 1000));
        }
      }
      else {
        acquired = await getAcquiredIds(currentListings);
      }
      await new Promise(resolve => setTimeout(resolve, 1000));

      assetProgressbar.style.display = "block";
      // [{id:"",offerId:""}]
      let offers = [];
      for (let i = 0; i < currentListings.length; i++) {
        // 更新状态文本和进度条
        assetStatus.innerText = t("checking") + " " + currentListings[i].name + " (" + currentListings[i].id + ")";
        innerAssetsProgressbar.style.width = (i / currentListings.length * 100) + "%";

        // 每处理5个资产或处理到最后一个资产时更新气泡进度
        if (i % 5 === 0 || i === currentListings.length - 1) {
          const percent = ((i + 1) / currentListings.length * 100).toFixed(1);
          showToast(t("progress") + (i + 1) + "/" + currentListings.length + " (" + percent + "%)", "success");
        }

        let currentListing = currentListings[i];

        // 检查资产是否已在库中
        let isAlreadyOwned = false;

        // 检查UI标记
        if (currentListing.isOwned) {
          isAlreadyOwned = true;
          console.log(currentListing.name + " (" + currentListing.id + ") marked as owned in UI");
        }

        // 检查API返回的数据
        if (acquired.includes(currentListing.id)) {
          isAlreadyOwned = true;
          console.log(currentListing.name + " (" + currentListing.id + ") found in acquired list from API");
        }

        // 如果资产已经在库中,跳过处理
        if (isAlreadyOwned) {
          console.log(currentListing.name + " (" + currentListing.id + ") already in library - skipping");
          showToast(t("alreadyOwned").replace("{0}", currentListing.name));
          continue;
        }

        // 记录我们将尝试添加这个资产
        console.log("Will try to add " + currentListing.name + " (" + currentListing.id + ") to your library");

        // 记录我们将要处理的资产
        console.log("Processing asset that is NOT in library:", currentListing.name, currentListing.id);

        let result = await fetch("https://www.fab.com/i/listings/" + currentListing.id, {
          "credentials": "include",
          "headers": {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0",
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "en",
            "X-Requested-With": "XMLHttpRequest",
            "X-CsrfToken": getCSRFToken(),
            "Sec-GPC": "1",
            "Sec-Fetch-Dest": "empty",
            "Sec-Fetch-Mode": "cors",
            "Sec-Fetch-Site": "same-origin",
            "Priority": "u=0"
          },
          "referrer": "https://www.fab.com/listings/" + currentListing.id,
          "method": "GET",
          "mode": "cors"
        });

        // licenses -> foreach -> get where price 0 -> buy
        let json = await result.json();
        let listingOffers = [];
        for (let j = 0; j < json.licenses.length; j++) {
          let license = json.licenses[j];
          if (license.priceTier.price != 0) {
            continue;
          }

          offers.push({
            name: currentListing.name,
            id: currentListing.id,
            offerId: license.offerId
          });
          listingOffers.push(license.offerId);
          console.log("Found free offer for " + currentListing.name + " (" + currentListing.id + ")");
        }
        if (listingOffers.length == 0) {
          console.log("No free offers found for " + currentListing.name + " (" + currentListing.id + ")");
        }
        await new Promise(resolve => setTimeout(resolve, 500));
      }

      // 不再需要弹出窗口警告

      // 创建一个隐藏的iframe来加载资产页面
      let assetFrame = document.getElementById('fab-asset-frame');
      if (!assetFrame) {
        assetFrame = document.createElement('iframe');
        assetFrame.id = 'fab-asset-frame';
        assetFrame.style.width = '1px';
        assetFrame.style.height = '1px';
        assetFrame.style.position = 'fixed';
        assetFrame.style.top = '-100px';
        assetFrame.style.left = '-100px';
        assetFrame.style.border = 'none';
        assetFrame.style.opacity = '0.1'; // 稍微可见,便于调试
        document.body.appendChild(assetFrame);
      }

      // 显示初始进度
      showToast(t("progress") + "0/" + offers.length + " (0%)", "success");

      for (let i = 0; i < offers.length; i++) {
        console.log("Trying to add " + offers[i].name + " (" + offers[i].id + ")");

        // 每处理3个资产或处理到最后一个资产时更新气泡进度
        if (i % 3 === 0 || i === offers.length - 1) {
          const percent = ((i + 1) / offers.length * 100).toFixed(1);
          showToast(t("progress") + (i + 1) + "/" + offers.length + " (" + percent + "%)", "success");
        }

        // 尝试两种方法添加资产:1. 使用API 2. 使用iframe
        let success = false;

        // 方法1:尝试使用API直接添加资产
        try {
          showToast(t("tryingToAddAPI") + offers[i].name);

          // 构建API请求
          const addUrl = "https://www.fab.com/i/listings/" + offers[i].id + "/add-to-library";
          const csrfToken = getCSRFToken();

          if (!csrfToken) {
            console.log("无法获取CSRF令牌,尝试使用iframe方法");
          } else {
            // 检查是否有offerId
            if (!offers[i].offerId) {
              console.log("缺少offerId,尝试获取许可证信息");

              try {
                // 先获取资产的许可证信息
                const licenseUrl = "https://www.fab.com/i/listings/" + offers[i].id;
                const licenseResult = await fetch(licenseUrl, {
                  "credentials": "include",
                  "headers": {
                    "Accept": "application/json, text/plain, */*",
                    "X-Requested-With": "XMLHttpRequest"
                  },
                  "method": "GET"
                });

                if (licenseResult.ok) {
                  const licenseData = await licenseResult.json();
                  console.log("获取到许可证信息:", licenseData);

                  // 查找免费许可证
                  if (licenseData && licenseData.licenses && licenseData.licenses.length > 0) {
                    for (const license of licenseData.licenses) {
                      if (license.priceTier && license.priceTier.price === 0) {
                        offers[i].offerId = license.offerId;
                        console.log("找到免费许可证:", license.offerId);
                        break;
                      }
                    }

                    // 如果没有找到免费许可证,使用第一个许可证
                    if (!offers[i].offerId && licenseData.licenses.length > 0) {
                      offers[i].offerId = licenseData.licenses[0].offerId;
                      console.log("未找到免费许可证,使用第一个许可证:", offers[i].offerId);
                    }
                  }
                } else {
                  console.log("获取许可证信息失败:", await licenseResult.text());
                }
              } catch (e) {
                console.error("获取许可证信息出错:", e.message);
              }
            }

            // 如果仍然没有offerId,尝试使用iframe方法
            if (!offers[i].offerId) {
              console.log("无法获取offerId,尝试使用iframe方法");
              success = false; // 确保尝试iframe方法
              break; // 跳出当前的else块
            }

            // 准备请求数据
            const formData = new FormData();
            formData.append("offer_id", offers[i].offerId);

            console.log("发送API请求,offerId:", offers[i].offerId);

            // 发送API请求
            const result = await fetch(addUrl, {
              "credentials": "include",
              "headers": {
                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
                "Accept": "application/json, text/plain, */*",
                "Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
                "X-Requested-With": "XMLHttpRequest",
                "X-CsrfToken": csrfToken
                // 不要设置Content-Type,让浏览器自动设置
              },
              "referrer": "https://www.fab.com/listings/" + offers[i].id,
              "method": "POST",
              "mode": "cors",
              "body": formData
            });

            // 检查响应
            if (result.ok) {
              showToast(t("addedSuccessfully") + offers[i].name + t("addedToLibrary"), "success");
              success = true;
              console.log("API添加成功: " + offers[i].name);
            } else {
              const responseText = await result.text();
              console.log("API添加失败: " + responseText);
              showToast(t("apiFailed"), "warning");
            }
          }
        } catch (e) {
          console.error("API添加出错: " + e.message);
          showToast(t("apiFailed"), "warning");
        }

        // 如果API方法失败,尝试使用iframe方法
        if (!success) {
          try {
            // 使用iframe加载资产页面
            showToast(t("loadingAssetPage") + offers[i].name);

            // 设置iframe源
            assetFrame.src = "https://www.fab.com/listings/" + offers[i].id;

            // 等待页面加载
            console.log("等待iframe加载: " + offers[i].name);
            await new Promise(resolve => {
              const frameLoadTimeout = setTimeout(() => {
                console.log("iframe加载超时");
                resolve();
              }, 10000);

              assetFrame.onload = () => {
                clearTimeout(frameLoadTimeout);
                console.log("iframe已加载");
                resolve();
              };
            });

            // 尝试在iframe中查找并点击"添加到我的库"按钮
            try {
              // 等待额外时间确保页面完全加载
              await new Promise(resolve => setTimeout(resolve, 3000));

              // 尝试访问iframe内容
              const frameDoc = assetFrame.contentDocument || assetFrame.contentWindow.document;

              // 如果无法访问iframe内容,跳过当前资产
              if (!frameDoc || !frameDoc.body) {
                console.log("无法访问iframe内容,跳过当前资产");
                showToast("无法处理资产: " + offers[i].name + ",跳过", "warning");

                // 继续处理下一个资产
                continue;
              }

              console.log("成功访问iframe内容");
              showToast(t("processing") + offers[i].name);

              // 在这里可以添加与之前相同的许可证选择和添加到库的逻辑
              // 但是操作对象从assetWindow变为frameDoc

              // 这里简化处理,直接尝试查找并点击"添加到我的库"按钮
              const addButtons = frameDoc.querySelectorAll("button");
              let addButton = null;

              for (const button of addButtons) {
                const text = button.textContent || button.innerText || "";
                if (text && (
                    text.includes("Add to My Library") ||
                    text.includes("添加到我的库") ||
                    text.includes("Add to Library") ||
                    text.includes("Add to cart") ||
                    text.includes("添加到购物车")
                  )) {
                  addButton = button;
                  break;
                }
              }

              if (addButton) {
                console.log("找到添加按钮,点击中...");
                addButton.click();
                showToast(t("addedSuccessfully") + offers[i].name + t("addedToLibrary"), "success");
                success = true;

                // 等待添加操作完成
                await new Promise(resolve => setTimeout(resolve, 2000));
              } else {
                console.log("在iframe中未找到添加按钮");
                showToast(t("buttonNotFound") + offers[i].name, "warning");
              }
            } catch (e) {
              console.error("处理iframe内容时出错: " + e.message);
              showToast(t("processingError") + offers[i].name + t("skipping"), "error");
            }
          } catch (e) {
            console.error("iframe方法出错: " + e.message);
            showToast(t("iframeError") + e.message, "error");
          }
        }

        console.log("Progress: " + (i + 1) + "/" + offers.length + " (" + ((i + 1) / offers.length * 100).toFixed(2) + "%)");

        // 等待一段时间再处理下一个资产
        await new Promise(resolve => setTimeout(resolve, 1000));
      }

      return foundItems[foundItems.length - 1];
    }

    async function getAll() {
      showToast(t("startGetting"), "success");
      console.log("开始获取免费资产...");

      let last;
      let totalProcessed = 0;
      let scrollAttempts = 0;
      const maxScrollAttempts = 3; // 如果连续几次没有新项目,则停止

      try {
        last = await getIds();

        if (!last) {
          showToast("没有找到可滚动的项目", "error");
          return;
        }

        for (let i = 0; i < 64; i++) {
          // 滚动到最后一个项目并等待加载
          if (last && typeof last.scrollIntoView === 'function') {
            showToast(t("scrollingMore") + (i+1) + ")...");
            console.log(`滚动加载更多项目 (第 ${i+1} 次)...`);

            // 平滑滚动到元素
            last.scrollIntoView({ behavior: 'smooth', block: 'end' });

            // 等待页面加载新内容
            await new Promise(resolve => setTimeout(resolve, 5000));

            // 获取新加载的项目
            showToast(t("processingNewItems"));
            const prevLast = last;
            last = await getIds();

            // 检查是否有新项目加载
            if (!last || last === prevLast) {
              scrollAttempts++;
              console.log(`没有新项目加载,尝试次数: ${scrollAttempts}/${maxScrollAttempts}`);

              if (scrollAttempts >= maxScrollAttempts) {
                showToast(t("reachedBottom"), "success");
                console.log("已到达页面底部,没有更多项目");
                break;
              }
            } else {
              // 重置计数器,因为找到了新项目
              scrollAttempts = 0;
              totalProcessed++;
            }

            showToast(`已处理 ${totalProcessed} 批项目!`);
          } else {
            showToast(t("noResults"), "error");
            console.error("无法滚动,元素不存在或不支持滚动");
            break;
          }
        }

        showToast(t("processedBatches") + totalProcessed + t("batches"), "success");
        console.log(`完成! 已处理 ${totalProcessed} 批项目`);
      } catch (error) {
        console.error("获取资产时出错:", error);
        showToast(t("error") + error.message, "error");
      }
    }

    function getSortContainer() {
      // 尝试多种可能的选择器
      return document.querySelector(`div.odQtzXCJ > ul._oqSjPnA`) ||
             document.querySelector(`ul._oqSjPnA`) ||
             document.querySelector(`[class*="FilterBar"]`) ||
             document.querySelector(`header`);
    }

    function getTitleContainer() {
      // 尝试多种可能的选择器
      return document.querySelector(".ArhVH7Um") ||
             document.querySelector("main > div") ||
             document.querySelector("main") ||
             document.body;
    }

    function addControls() {
      // 创建通知容器
      notificationQueueContainer = document.createElement("div");
      notificationQueueContainer.style.position = 'fixed';
      notificationQueueContainer.style.bottom = '20px';
      notificationQueueContainer.style.right = '20px';
      notificationQueueContainer.style.zIndex = '10000';
      document.body.appendChild(notificationQueueContainer);

      // 创建获取资产按钮
      var getAssetsButton = document.createElement("button");
      getAssetsButton.className = "fabkit-Button-root fabkit-Button--sm fabkit-Button--menu";
      getAssetsButton.type = "button";
      getAssetsButton.innerHTML = `<span class="fabkit-Button-label" style="font-size: 13px; line-height: 1;">${t("addFreeAssets")}</span>`;
      getAssetsButton.style.margin = "10px";
      getAssetsButton.style.padding = "8px 20px"; // 增加水平内边距,使按钮更宽
      getAssetsButton.style.minWidth = "120px"; // 设置最小宽度
      getAssetsButton.style.backgroundColor = "#45C761";
      getAssetsButton.style.color = "#1C1C20";
      getAssetsButton.style.border = "none";
      getAssetsButton.style.borderRadius = "4px";
      getAssetsButton.style.cursor = "pointer";
      getAssetsButton.style.fontWeight = "bold";
      getAssetsButton.style.whiteSpace = "nowrap"; // 防止文本换行
      getAssetsButton.style.display = "inline-flex"; // 使用inline-flex布局
      getAssetsButton.style.alignItems = "center"; // 垂直居中
      getAssetsButton.style.justifyContent = "center"; // 水平居中
      getAssetsButton.style.height = "32px"; // 固定高度
      getAssetsButton.addEventListener(`click`, function () {
        getAll();
      });

      // 创建进度条
      assetProgressbar = document.createElement("div");
      assetProgressbar.style.width = "100%";
      assetProgressbar.style.height = "32px";
      assetProgressbar.style.background = "#1C1C20";
      assetProgressbar.style.margin = "0 0 15px 0";
      assetProgressbar.style.display = "none";
      assetProgressbar.style.borderRadius = "4px";
      assetProgressbar.style.overflow = "hidden";

      innerAssetsProgressbar = document.createElement("div");
      innerAssetsProgressbar.style.width = "0";
      innerAssetsProgressbar.style.height = "32px";
      innerAssetsProgressbar.style.background = "#45C761";
      innerAssetsProgressbar.style.color = "#1C1C20";
      innerAssetsProgressbar.style.fontWeight = "bold";
      innerAssetsProgressbar.style.padding = "6px";
      innerAssetsProgressbar.style.boxSizing = "border-box";
      innerAssetsProgressbar.style.transition = "width 0.3s ease";
      assetProgressbar.appendChild(innerAssetsProgressbar);

      // 创建状态显示
      assetStatus = document.createElement("div");
      assetStatus.style.fontSize = "14px";
      assetStatus.style.fontWeight = "normal";
      assetStatus.style.background = "#45C761";
      assetStatus.style.color = "#1C1C20";
      assetStatus.style.padding = "10px";
      assetStatus.style.borderRadius = "4px";
      assetStatus.style.marginBottom = "10px";
      assetStatus.style.display = "none";

      // 添加到页面
      var titleContainer = getTitleContainer();
      if(!titleContainer) {
        showToast(t("error") + "Failed to find title container", "error");
        titleContainer = document.body;
      }

      // 创建一个容器来放置我们的UI元素
      var uiContainer = document.createElement("div");
      uiContainer.style.padding = "10px";
      uiContainer.style.margin = "10px 0";
      uiContainer.style.backgroundColor = "rgba(0,0,0,0.1)";
      uiContainer.style.borderRadius = "5px";
      uiContainer.style.zIndex = "1000";

      uiContainer.appendChild(assetStatus);
      uiContainer.appendChild(assetProgressbar);

      titleContainer.prepend(uiContainer);

      // 添加按钮到排序容器或页面顶部
      var sortContainer = getSortContainer();
      if(!sortContainer) {
        showToast(t("error") + "Failed to find sort container", "error");
        uiContainer.prepend(getAssetsButton);
      } else {
        sortContainer.appendChild(getAssetsButton);
      }

      // 浮动按钮已被移除

      showToast(t("scriptLoaded"), "success");
      added = true;
    }

    function onBodyChange() {
      if (!added) {
        addControls();
      }
    }

    var mo = new MutationObserver(onBodyChange);
    mo.observe(document.body, {
      childList: true,
      subtree: true
    });
  })();