audioz-utils

Batch downloading, post hiding, etc. for AudioZ

// ==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.1.0
// @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 and Refresh">
        </form>
    <div/>
    `;
  const saveButton = inputForm.querySelector(`#${idPrefix}-save-btn`);
  if (!(saveButton 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);
    }
    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", checkboxStateStorageKey = "AudiozUtils_DownloadLinks_enabled";
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:not([style*='display: none'])");
  return processPosts(Array.from(posts), selectedHoster, filteredCategories, filteredTexts);
}
async function processPosts(posts, hosts, filteredCategories, filteredTexts) {
  const foundLinks = /* @__PURE__ */ new Set(), { container, checkbox } = createDownloadLinksSection();
  if (!checkbox.checked)
    return;
  addCopyLinksButton(foundLinks, container);
  const { progressElm, startElm } = addProgressElement(container);
  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() {
  const container = createPostContainer(), checkbox = addCheckbox(container), main2 = document.querySelector("main"), header = (main2 == null ? void 0 : main2.querySelector("header")) ?? void 0;
  return main2 !== null && header !== void 0 && header.appendChild(container), { container, checkbox };
}
function addCheckbox(container) {
  const checkboxLabel = document.createElement("label");
  checkboxLabel.style.margin = "10px", checkboxLabel.className = "fbutton";
  const checkbox = document.createElement("input");
  checkbox.type = "checkbox", checkbox.style.margin = "10px";
  const savedCheckboxState = localStorage.getItem(checkboxStateStorageKey);
  return checkbox.checked = savedCheckboxState === null ? !0 : JSON.parse(savedCheckboxState), checkboxLabel.innerText = checkbox.checked ? "Disable and Refresh" : "Enable and Refresh", checkbox.addEventListener("change", (event) => {
    localStorage.setItem(checkboxStateStorageKey, JSON.stringify(checkbox.checked)), window.location.reload();
  }), checkboxLabel.appendChild(checkbox), container.appendChild(checkboxLabel), checkbox;
}
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.margin = "10px", copyButton.style.fontSize = "14px", copyButton.className = "fbutton", 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.background = "none", 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