audioz-utils

Batch downloading, post hiding, etc. for AudioZ

目前為 2024-09-20 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name           audioz-utils
// @author         Metaller
// @description    Batch downloading, post hiding, etc. for AudioZ
// @grant          GM_xmlhttpRequest
// @match          https://audioz.download/*
// @run-at         document-end
// @version        1.0.3
// @license        MIT
// @namespace https://greasyfork.org/users/753942
// ==/UserScript==
function getTextArrayFromInput(input) {
  return input.trim().split(",").map((category) => category.trim()).filter((category) => category !== "" && category !== ",");
}
function shouldFilterPostByCategory(post, filteredCategories) {
  const categories = post.querySelectorAll("header > span > a");
  for (const category of categories)
    if (shouldFilterCategory(category, filteredCategories))
      return !0;
  return !1;
}
function shouldFilterCategory(category, filteredCategories) {
  const href = category.getAttribute("href");
  if (href === null)
    return !1;
  for (const filteredCategory of filteredCategories)
    if (href.includes(filteredCategory))
      return !0;
  return !1;
}
function shouldFilterPostByText(post, filteredTexts) {
  const innerText = post.innerText;
  for (const filteredText of filteredTexts)
    if (innerText.includes(filteredText))
      return !0;
  return !1;
}
function createInputForm({ title, inputFields, idPrefix }) {
  const inputForm = document.createElement("div");
  inputForm.style.fontSize = "14px";
  const inputFieldsHtml = inputFields.map(
    (field) => `
    <label for="${idPrefix}-${field.storageKey}" style="margin-bottom: 5px;">${field.title}:</label>
    <textarea id="${idPrefix}-${field.storageKey}" type="text" title="Separate the entries with ','" style="font-size: 12px; width: 100%; background: none; overflow: hidden">
${field.values.join(", ")},
    </textarea>
  `
  ).join("");
  inputForm.innerHTML = `
    <div style="padding-left: 5px; display: inline-flex; flex-direction: column; align-items: center; width: 100%; font-family: tradegothic,century gothic,CenturyGothic,AppleGothic,sans-serif">
        <h4 style="color: #997e33; align-self: start; padding-left: 10px; margin-bottom: 5px;">${title}</h4>
        <form id="hiddenPostsForm" class="post neon nBrown" style="margin-top: 5px; display: flex; flex-direction: column; width: 95%; font-size: 12px; word-wrap: break-word;">
            ${inputFieldsHtml}
            <input id="${idPrefix}-save-btn" type="button" style="font-size: 12px; margin-bottom: 10px;" class="fbutton doaddcomment" value="Save">
            <div class="ostats_area" id="${idPrefix}-console">
                <input type="button" style="font-size: 10px; width: 100%;" class="fbutton doaddcomment" value="Refresh the page"></input>
            </div>
        </form>
    <div/>
    `;
  const saveButton = inputForm.querySelector(`#${idPrefix}-save-btn`);
  if (!(saveButton instanceof HTMLInputElement))
    return;
  const message = inputForm.querySelector(`#${idPrefix}-console`);
  if (!(message instanceof HTMLElement))
    return;
  const messageButton = message.firstElementChild;
  if (!(messageButton instanceof HTMLInputElement))
    return;
  saveButton.onclick = () => {
    for (const field of inputFields) {
      const inputElement = inputForm.querySelector(`#${idPrefix}-${field.storageKey}`);
      inputElement && window.localStorage.setItem(field.storageKey, inputElement.value);
    }
    message.style.display = "";
  }, message.style.display = "none", messageButton.onclick = () => {
    message.style.display = "none", window.location.reload();
  };
  const menu = document.getElementById("StickyNav");
  menu == null || menu.appendChild(inputForm);
}
const categoryStorageKey$1 = "AudiozUtils_HiddenPosts_categories", textStorageKey$1 = "AudiozUtils_HiddenPosts_texts";
function hidePosts() {
  hidePostsByCategory(
    getTextArrayFromInput(window.localStorage.getItem(categoryStorageKey$1) ?? "samples/loop"),
    getTextArrayFromInput(window.localStorage.getItem(textStorageKey$1) ?? "REQ")
  );
}
function hidePostsByCategory(filteredCategories, filteredTexts) {
  var _a;
  const posts = document.querySelectorAll("article"), postsToHide = [];
  for (const post of posts) {
    if (((_a = post.parentElement) == null ? void 0 : _a.id) === "inside")
      return;
    (shouldFilterPostByCategory(post, filteredCategories) || shouldFilterPostByText(post, filteredTexts)) && postsToHide.push(post);
  }
  createPostContainer$1(postsToHide), createInputForm({
    title: "Hidden Posts",
    idPrefix: "hidden-posts",
    inputFields: [
      {
        title: "Hidden Categories",
        storageKey: categoryStorageKey$1,
        values: filteredCategories
      },
      {
        title: "Hidden Words",
        storageKey: textStorageKey$1,
        values: filteredTexts
      }
    ]
  });
}
function createPostContainer$1(posts) {
  if (posts.length === 0)
    return;
  const container = document.createElement("div");
  container.setAttribute("id", "hidden"), container.className = "post neon nBrown";
  const containerTitle = document.createElement("h3");
  containerTitle.innerHTML = `
  <span>hidden posts<span>
  `, container.appendChild(containerTitle);
  for (const originalPost of posts) {
    const originalPostLink = originalPost.querySelector("article > a");
    if (originalPostLink === null)
      continue;
    const originalTitle = originalPostLink.firstElementChild.innerText, postSummary = originalPostLink.cloneNode();
    postSummary.style.fontSize = "14px", postSummary.innerText = originalTitle, container.appendChild(postSummary), originalPost.style.display = "none";
  }
  const main2 = document.querySelector("main"), nav = (main2 == null ? void 0 : main2.lastElementChild) ?? void 0;
  main2 !== null && nav !== void 0 && main2.insertBefore(container, nav);
}
const downloadHosterStorageKey = "AudiozUtils_DownloadLinks_host", categoryStorageKey = "AudiozUtils_DownloadLinks_categories", textStorageKey = "AudiozUtils_DownloadLinks_texts";
function extractDownloadLinks() {
  const selectedHoster = getTextArrayFromInput(window.localStorage.getItem(downloadHosterStorageKey) ?? "katfile"), filteredCategories = getTextArrayFromInput(window.localStorage.getItem(categoryStorageKey) ?? ""), filteredTexts = getTextArrayFromInput(window.localStorage.getItem(textStorageKey) ?? "");
  createInputForm({
    title: "Download Links",
    idPrefix: "download-links",
    inputFields: [
      {
        title: "Hosts",
        storageKey: downloadHosterStorageKey,
        values: selectedHoster
      },
      {
        title: "Selected Categories",
        storageKey: categoryStorageKey,
        values: filteredCategories
      },
      {
        title: "Selected Words",
        storageKey: textStorageKey,
        values: filteredTexts
      }
    ]
  });
  const posts = document.querySelectorAll("article");
  return processPosts(Array.from(posts), selectedHoster, filteredCategories, filteredTexts);
}
async function processPosts(posts, hosts, filteredCategories, filteredTexts) {
  const foundLinks = /* @__PURE__ */ new Set(), { progressElm, startElm, container } = createDownloadLinksSection(foundLinks);
  await Promise.all(
    posts.map(async (post) => {
      const postLink = post.querySelector("a.permalink");
      if (!(postLink instanceof HTMLAnchorElement))
        return;
      const postProgressElm = addPostProgressMessage(postLink, progressElm);
      let found = 1;
      try {
        found = await processPost(
          foundLinks,
          postLink,
          container,
          progressElm,
          hosts,
          filteredCategories,
          filteredTexts
        );
      } catch (error) {
        console.error(`Error processing post ${postLink.href}`, error), found = 1;
      }
      postProgressElm.remove(), !(found === 0 || found === 2 || postLink.href === window.location.href) && addPostErrorMessage(postLink, post, progressElm);
    })
  ), startElm.remove();
}
function createDownloadLinksSection(foundLinks) {
  const container = createPostContainer();
  addCopyLinksButton(foundLinks, container);
  const { progressElm, startElm } = addProgressElement(container), main2 = document.querySelector("main"), header = (main2 == null ? void 0 : main2.querySelector("header")) ?? void 0;
  return main2 !== null && header !== void 0 && header.appendChild(container), { progressElm, startElm, container };
}
function addProgressElement(container) {
  const progressElm = document.createElement("div"), startElm = document.createElement("p");
  return startElm.innerHTML = "Extracting download links... (grant permissions if prompted)", progressElm.appendChild(startElm), container.appendChild(progressElm), { progressElm, startElm };
}
function addCopyLinksButton(foundLinks, container) {
  const copyButton = document.createElement("button");
  copyButton.innerText = "Copy all download links", copyButton.style.marginTop = "10px", copyButton.addEventListener("click", () => {
    const downloadLinks = [...foundLinks].map((link) => link.downloadLink).join(`
`);
    navigator.clipboard.writeText(downloadLinks).catch((err) => {
      console.error("Failed to copy text: ", err);
    });
  }), container.appendChild(copyButton);
}
function addPostProgressMessage(postLink, progressElm) {
  const postProgressElm = document.createElement("p");
  return postProgressElm.innerHTML = `Processing ${postLink.href}...`, postProgressElm.style.fontSize = "8px", progressElm.appendChild(postProgressElm), postProgressElm;
}
function addPostErrorMessage(postLink, post, progressElm) {
  var _a;
  const noPeeplink = document.createElement("div");
  noPeeplink.style.display = "flex", noPeeplink.style.alignItems = "center";
  const a = document.createElement("a");
  a.href = postLink.href, a.innerText = ((_a = post.querySelector("h2")) == null ? void 0 : _a.innerText) ?? postLink.href, a.style.fontSize = "12px", a.style.color = "red", a.style.marginRight = "10px", noPeeplink.appendChild(a);
  const noPeeplinkText = document.createElement("p");
  noPeeplinkText.innerText = "Error or no download link matched to the given hosts.", noPeeplinkText.style.color = "red", noPeeplinkText.style.fontSize = "12px", noPeeplink.appendChild(noPeeplinkText), noPeeplink.style.height = "18px", progressElm.appendChild(noPeeplink);
}
async function processPost(foundLinks, postLink, container, progressElm, hosts, filteredCategories, filteredTexts) {
  const postElement = await fetchPostElement(postLink);
  if (postElement === null)
    return 1;
  if (filteredCategories.length > 0 && !shouldFilterPostByCategory(postElement, filteredCategories) || filteredTexts.length > 0 && !shouldFilterPostByText(postElement, filteredTexts))
    return 2;
  const peeplinks = postElement.querySelectorAll("a[href*='peeplink']");
  let found = 1;
  for (const peeplink of peeplinks) {
    if (!(peeplink instanceof HTMLAnchorElement)) {
      addNoPeeplinkMessage(postLink, progressElm);
      continue;
    }
    if (peeplink.href !== "http://peeplink.in/" && (found = await fetchAndProcessPeeplink(peeplink, hosts, postElement, container, progressElm, postLink, foundLinks), found === 0 || found === 2))
      break;
  }
  return found;
}
function addPeeplinkLink(peeplink, container, progressElm) {
  const downloadLink = peeplink.cloneNode();
  downloadLink.style.fontSize = "14px", downloadLink.innerText = peeplink.href, container.insertBefore(downloadLink, progressElm);
}
async function fetchAndProcessPeeplink(peeplink, hosts, postElement, container, progressElm, postLink, foundLinks) {
  return typeof GM > "u" ? (addNoGMMessage(progressElm), addPeeplinkLink(peeplink, container, progressElm), 0) : await new Promise((resolve) => {
    GM.xmlHttpRequest({
      method: "GET",
      url: peeplink.href,
      onload: (response) => {
        let found = 1;
        for (const host of hosts) {
          const dlLink = new DOMParser().parseFromString(response.responseText, "text/html").querySelector(`a[href*='${host}']`);
          if (dlLink === null)
            continue;
          const { postTitle, downloadLink } = addDownloadLinkButton(dlLink, postElement, host, container, progressElm), audioDownloadInfo = addRemmoveButton(postLink, postTitle, dlLink, downloadLink, foundLinks);
          foundLinks.add(audioDownloadInfo), found = 0;
          break;
        }
        resolve(found);
      }
    });
  });
}
async function fetchPostElement(postLink) {
  if (postLink.href === window.location.href)
    return window.document.querySelector("article");
  const postText = await (await fetchWithRetry(postLink.href)).text();
  return new DOMParser().parseFromString(postText, "text/html").querySelector("article");
}
function addDownloadLinkButton(dlLink, postElement, host, container, progressElm) {
  const downloadLink = dlLink.cloneNode();
  downloadLink.style.fontSize = "14px";
  const postTitle = postElement.querySelector("h1");
  return postTitle !== null && (downloadLink.title = postTitle.innerText), downloadLink.innerText = `(${host}) ${((postTitle == null ? void 0 : postTitle.innerText) ?? "").substring(0, 100)}`, container.insertBefore(downloadLink, progressElm), { postTitle, downloadLink };
}
function addRemmoveButton(postLink, postTitle, dlLink, downloadLink, foundLinks) {
  const removeButton = document.createElement("span");
  removeButton.innerText = "❌", removeButton.title = "Remove this download link", removeButton.style.marginLeft = "10px", removeButton.style.fontSize = "x-small", removeButton.className = "fbutton", removeButton.style.display = "inline-block";
  const audioDownloadInfo = {
    postLink: postLink.href,
    title: (postTitle == null ? void 0 : postTitle.innerText) ?? "",
    downloadLink: dlLink.href
  };
  return removeButton.addEventListener("click", (e) => {
    downloadLink.remove(), removeButton.remove(), foundLinks.delete(audioDownloadInfo), e.preventDefault();
  }), downloadLink.appendChild(removeButton), audioDownloadInfo;
}
function addNoPeeplinkMessage(postLink, progressElm) {
  if (postLink.href === window.location.href)
    return;
  const noPeeplink = document.createElement("p");
  noPeeplink.innerHTML = `No peeplink found in ${postLink.href}`, noPeeplink.style.color = "red", progressElm.appendChild(noPeeplink);
}
function addNoGMMessage(progressElm) {
  const noGMAPI = document.createElement("p");
  noGMAPI.innerHTML = "GM API is not available. Please install Tampermonkey or Violentmonkey.", noGMAPI.style.color = "red", progressElm.appendChild(noGMAPI);
}
function createPostContainer() {
  const container = document.createElement("section");
  container.setAttribute("id", "download-links"), container.className = "feed neon nBrown";
  const containerTitle = document.createElement("h3");
  return containerTitle.innerHTML = `
  <span>Download Links<span>
  `, container.appendChild(containerTitle), container.style.marginTop = "20px", container;
}
async function fetchWithRetry(url, options = {}, retries = 3, delay = 1e3) {
  for (let attempt = 0; attempt < retries; attempt++)
    try {
      const response = await fetch(url, options);
      if (!response.ok)
        throw new Error(`HTTP error! status: ${response.status}`);
      return response;
    } catch (error) {
      if (attempt < retries - 1)
        console.warn(`Fetch attempt ${attempt + 1} failed. Retrying in ${delay}ms...`, error), await new Promise((resolve) => setTimeout(resolve, delay));
      else
        throw console.error(`Fetch failed after ${retries} attempts`, error), error;
    }
  throw new Error("Fetch failed");
}
function main() {
  return hidePosts(), extractDownloadLinks();
}
main().catch(console.error);
//# sourceMappingURL=audioz-utils.js.map