audioz-utils

Batch downloading, post hiding, etc. for AudioZ

目前为 2024-09-20 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name audioz-utils
  3. // @author Metaller
  4. // @description Batch downloading, post hiding, etc. for AudioZ
  5. // @grant GM_xmlhttpRequest
  6. // @match https://audioz.download/*
  7. // @run-at document-end
  8. // @version 1.0.3
  9. // @license MIT
  10. // @namespace https://greasyfork.org/users/753942
  11. // ==/UserScript==
  12. function getTextArrayFromInput(input) {
  13. return input.trim().split(",").map((category) => category.trim()).filter((category) => category !== "" && category !== ",");
  14. }
  15. function shouldFilterPostByCategory(post, filteredCategories) {
  16. const categories = post.querySelectorAll("header > span > a");
  17. for (const category of categories)
  18. if (shouldFilterCategory(category, filteredCategories))
  19. return !0;
  20. return !1;
  21. }
  22. function shouldFilterCategory(category, filteredCategories) {
  23. const href = category.getAttribute("href");
  24. if (href === null)
  25. return !1;
  26. for (const filteredCategory of filteredCategories)
  27. if (href.includes(filteredCategory))
  28. return !0;
  29. return !1;
  30. }
  31. function shouldFilterPostByText(post, filteredTexts) {
  32. const innerText = post.innerText;
  33. for (const filteredText of filteredTexts)
  34. if (innerText.includes(filteredText))
  35. return !0;
  36. return !1;
  37. }
  38. function createInputForm({ title, inputFields, idPrefix }) {
  39. const inputForm = document.createElement("div");
  40. inputForm.style.fontSize = "14px";
  41. const inputFieldsHtml = inputFields.map(
  42. (field) => `
  43. <label for="${idPrefix}-${field.storageKey}" style="margin-bottom: 5px;">${field.title}:</label>
  44. <textarea id="${idPrefix}-${field.storageKey}" type="text" title="Separate the entries with ','" style="font-size: 12px; width: 100%; background: none; overflow: hidden">
  45. ${field.values.join(", ")},
  46. </textarea>
  47. `
  48. ).join("");
  49. inputForm.innerHTML = `
  50. <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">
  51. <h4 style="color: #997e33; align-self: start; padding-left: 10px; margin-bottom: 5px;">${title}</h4>
  52. <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;">
  53. ${inputFieldsHtml}
  54. <input id="${idPrefix}-save-btn" type="button" style="font-size: 12px; margin-bottom: 10px;" class="fbutton doaddcomment" value="Save">
  55. <div class="ostats_area" id="${idPrefix}-console">
  56. <input type="button" style="font-size: 10px; width: 100%;" class="fbutton doaddcomment" value="Refresh the page"></input>
  57. </div>
  58. </form>
  59. <div/>
  60. `;
  61. const saveButton = inputForm.querySelector(`#${idPrefix}-save-btn`);
  62. if (!(saveButton instanceof HTMLInputElement))
  63. return;
  64. const message = inputForm.querySelector(`#${idPrefix}-console`);
  65. if (!(message instanceof HTMLElement))
  66. return;
  67. const messageButton = message.firstElementChild;
  68. if (!(messageButton instanceof HTMLInputElement))
  69. return;
  70. saveButton.onclick = () => {
  71. for (const field of inputFields) {
  72. const inputElement = inputForm.querySelector(`#${idPrefix}-${field.storageKey}`);
  73. inputElement && window.localStorage.setItem(field.storageKey, inputElement.value);
  74. }
  75. message.style.display = "";
  76. }, message.style.display = "none", messageButton.onclick = () => {
  77. message.style.display = "none", window.location.reload();
  78. };
  79. const menu = document.getElementById("StickyNav");
  80. menu == null || menu.appendChild(inputForm);
  81. }
  82. const categoryStorageKey$1 = "AudiozUtils_HiddenPosts_categories", textStorageKey$1 = "AudiozUtils_HiddenPosts_texts";
  83. function hidePosts() {
  84. hidePostsByCategory(
  85. getTextArrayFromInput(window.localStorage.getItem(categoryStorageKey$1) ?? "samples/loop"),
  86. getTextArrayFromInput(window.localStorage.getItem(textStorageKey$1) ?? "REQ")
  87. );
  88. }
  89. function hidePostsByCategory(filteredCategories, filteredTexts) {
  90. var _a;
  91. const posts = document.querySelectorAll("article"), postsToHide = [];
  92. for (const post of posts) {
  93. if (((_a = post.parentElement) == null ? void 0 : _a.id) === "inside")
  94. return;
  95. (shouldFilterPostByCategory(post, filteredCategories) || shouldFilterPostByText(post, filteredTexts)) && postsToHide.push(post);
  96. }
  97. createPostContainer$1(postsToHide), createInputForm({
  98. title: "Hidden Posts",
  99. idPrefix: "hidden-posts",
  100. inputFields: [
  101. {
  102. title: "Hidden Categories",
  103. storageKey: categoryStorageKey$1,
  104. values: filteredCategories
  105. },
  106. {
  107. title: "Hidden Words",
  108. storageKey: textStorageKey$1,
  109. values: filteredTexts
  110. }
  111. ]
  112. });
  113. }
  114. function createPostContainer$1(posts) {
  115. if (posts.length === 0)
  116. return;
  117. const container = document.createElement("div");
  118. container.setAttribute("id", "hidden"), container.className = "post neon nBrown";
  119. const containerTitle = document.createElement("h3");
  120. containerTitle.innerHTML = `
  121. <span>hidden posts<span>
  122. `, container.appendChild(containerTitle);
  123. for (const originalPost of posts) {
  124. const originalPostLink = originalPost.querySelector("article > a");
  125. if (originalPostLink === null)
  126. continue;
  127. const originalTitle = originalPostLink.firstElementChild.innerText, postSummary = originalPostLink.cloneNode();
  128. postSummary.style.fontSize = "14px", postSummary.innerText = originalTitle, container.appendChild(postSummary), originalPost.style.display = "none";
  129. }
  130. const main2 = document.querySelector("main"), nav = (main2 == null ? void 0 : main2.lastElementChild) ?? void 0;
  131. main2 !== null && nav !== void 0 && main2.insertBefore(container, nav);
  132. }
  133. const downloadHosterStorageKey = "AudiozUtils_DownloadLinks_host", categoryStorageKey = "AudiozUtils_DownloadLinks_categories", textStorageKey = "AudiozUtils_DownloadLinks_texts";
  134. function extractDownloadLinks() {
  135. const selectedHoster = getTextArrayFromInput(window.localStorage.getItem(downloadHosterStorageKey) ?? "katfile"), filteredCategories = getTextArrayFromInput(window.localStorage.getItem(categoryStorageKey) ?? ""), filteredTexts = getTextArrayFromInput(window.localStorage.getItem(textStorageKey) ?? "");
  136. createInputForm({
  137. title: "Download Links",
  138. idPrefix: "download-links",
  139. inputFields: [
  140. {
  141. title: "Hosts",
  142. storageKey: downloadHosterStorageKey,
  143. values: selectedHoster
  144. },
  145. {
  146. title: "Selected Categories",
  147. storageKey: categoryStorageKey,
  148. values: filteredCategories
  149. },
  150. {
  151. title: "Selected Words",
  152. storageKey: textStorageKey,
  153. values: filteredTexts
  154. }
  155. ]
  156. });
  157. const posts = document.querySelectorAll("article");
  158. return processPosts(Array.from(posts), selectedHoster, filteredCategories, filteredTexts);
  159. }
  160. async function processPosts(posts, hosts, filteredCategories, filteredTexts) {
  161. const foundLinks = /* @__PURE__ */ new Set(), { progressElm, startElm, container } = createDownloadLinksSection(foundLinks);
  162. await Promise.all(
  163. posts.map(async (post) => {
  164. const postLink = post.querySelector("a.permalink");
  165. if (!(postLink instanceof HTMLAnchorElement))
  166. return;
  167. const postProgressElm = addPostProgressMessage(postLink, progressElm);
  168. let found = 1;
  169. try {
  170. found = await processPost(
  171. foundLinks,
  172. postLink,
  173. container,
  174. progressElm,
  175. hosts,
  176. filteredCategories,
  177. filteredTexts
  178. );
  179. } catch (error) {
  180. console.error(`Error processing post ${postLink.href}`, error), found = 1;
  181. }
  182. postProgressElm.remove(), !(found === 0 || found === 2 || postLink.href === window.location.href) && addPostErrorMessage(postLink, post, progressElm);
  183. })
  184. ), startElm.remove();
  185. }
  186. function createDownloadLinksSection(foundLinks) {
  187. const container = createPostContainer();
  188. addCopyLinksButton(foundLinks, container);
  189. const { progressElm, startElm } = addProgressElement(container), main2 = document.querySelector("main"), header = (main2 == null ? void 0 : main2.querySelector("header")) ?? void 0;
  190. return main2 !== null && header !== void 0 && header.appendChild(container), { progressElm, startElm, container };
  191. }
  192. function addProgressElement(container) {
  193. const progressElm = document.createElement("div"), startElm = document.createElement("p");
  194. return startElm.innerHTML = "Extracting download links... (grant permissions if prompted)", progressElm.appendChild(startElm), container.appendChild(progressElm), { progressElm, startElm };
  195. }
  196. function addCopyLinksButton(foundLinks, container) {
  197. const copyButton = document.createElement("button");
  198. copyButton.innerText = "Copy all download links", copyButton.style.marginTop = "10px", copyButton.addEventListener("click", () => {
  199. const downloadLinks = [...foundLinks].map((link) => link.downloadLink).join(`
  200. `);
  201. navigator.clipboard.writeText(downloadLinks).catch((err) => {
  202. console.error("Failed to copy text: ", err);
  203. });
  204. }), container.appendChild(copyButton);
  205. }
  206. function addPostProgressMessage(postLink, progressElm) {
  207. const postProgressElm = document.createElement("p");
  208. return postProgressElm.innerHTML = `Processing ${postLink.href}...`, postProgressElm.style.fontSize = "8px", progressElm.appendChild(postProgressElm), postProgressElm;
  209. }
  210. function addPostErrorMessage(postLink, post, progressElm) {
  211. var _a;
  212. const noPeeplink = document.createElement("div");
  213. noPeeplink.style.display = "flex", noPeeplink.style.alignItems = "center";
  214. const a = document.createElement("a");
  215. 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);
  216. const noPeeplinkText = document.createElement("p");
  217. 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);
  218. }
  219. async function processPost(foundLinks, postLink, container, progressElm, hosts, filteredCategories, filteredTexts) {
  220. const postElement = await fetchPostElement(postLink);
  221. if (postElement === null)
  222. return 1;
  223. if (filteredCategories.length > 0 && !shouldFilterPostByCategory(postElement, filteredCategories) || filteredTexts.length > 0 && !shouldFilterPostByText(postElement, filteredTexts))
  224. return 2;
  225. const peeplinks = postElement.querySelectorAll("a[href*='peeplink']");
  226. let found = 1;
  227. for (const peeplink of peeplinks) {
  228. if (!(peeplink instanceof HTMLAnchorElement)) {
  229. addNoPeeplinkMessage(postLink, progressElm);
  230. continue;
  231. }
  232. if (peeplink.href !== "http://peeplink.in/" && (found = await fetchAndProcessPeeplink(peeplink, hosts, postElement, container, progressElm, postLink, foundLinks), found === 0 || found === 2))
  233. break;
  234. }
  235. return found;
  236. }
  237. function addPeeplinkLink(peeplink, container, progressElm) {
  238. const downloadLink = peeplink.cloneNode();
  239. downloadLink.style.fontSize = "14px", downloadLink.innerText = peeplink.href, container.insertBefore(downloadLink, progressElm);
  240. }
  241. async function fetchAndProcessPeeplink(peeplink, hosts, postElement, container, progressElm, postLink, foundLinks) {
  242. return typeof GM > "u" ? (addNoGMMessage(progressElm), addPeeplinkLink(peeplink, container, progressElm), 0) : await new Promise((resolve) => {
  243. GM.xmlHttpRequest({
  244. method: "GET",
  245. url: peeplink.href,
  246. onload: (response) => {
  247. let found = 1;
  248. for (const host of hosts) {
  249. const dlLink = new DOMParser().parseFromString(response.responseText, "text/html").querySelector(`a[href*='${host}']`);
  250. if (dlLink === null)
  251. continue;
  252. const { postTitle, downloadLink } = addDownloadLinkButton(dlLink, postElement, host, container, progressElm), audioDownloadInfo = addRemmoveButton(postLink, postTitle, dlLink, downloadLink, foundLinks);
  253. foundLinks.add(audioDownloadInfo), found = 0;
  254. break;
  255. }
  256. resolve(found);
  257. }
  258. });
  259. });
  260. }
  261. async function fetchPostElement(postLink) {
  262. if (postLink.href === window.location.href)
  263. return window.document.querySelector("article");
  264. const postText = await (await fetchWithRetry(postLink.href)).text();
  265. return new DOMParser().parseFromString(postText, "text/html").querySelector("article");
  266. }
  267. function addDownloadLinkButton(dlLink, postElement, host, container, progressElm) {
  268. const downloadLink = dlLink.cloneNode();
  269. downloadLink.style.fontSize = "14px";
  270. const postTitle = postElement.querySelector("h1");
  271. 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 };
  272. }
  273. function addRemmoveButton(postLink, postTitle, dlLink, downloadLink, foundLinks) {
  274. const removeButton = document.createElement("span");
  275. 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";
  276. const audioDownloadInfo = {
  277. postLink: postLink.href,
  278. title: (postTitle == null ? void 0 : postTitle.innerText) ?? "",
  279. downloadLink: dlLink.href
  280. };
  281. return removeButton.addEventListener("click", (e) => {
  282. downloadLink.remove(), removeButton.remove(), foundLinks.delete(audioDownloadInfo), e.preventDefault();
  283. }), downloadLink.appendChild(removeButton), audioDownloadInfo;
  284. }
  285. function addNoPeeplinkMessage(postLink, progressElm) {
  286. if (postLink.href === window.location.href)
  287. return;
  288. const noPeeplink = document.createElement("p");
  289. noPeeplink.innerHTML = `No peeplink found in ${postLink.href}`, noPeeplink.style.color = "red", progressElm.appendChild(noPeeplink);
  290. }
  291. function addNoGMMessage(progressElm) {
  292. const noGMAPI = document.createElement("p");
  293. noGMAPI.innerHTML = "GM API is not available. Please install Tampermonkey or Violentmonkey.", noGMAPI.style.color = "red", progressElm.appendChild(noGMAPI);
  294. }
  295. function createPostContainer() {
  296. const container = document.createElement("section");
  297. container.setAttribute("id", "download-links"), container.className = "feed neon nBrown";
  298. const containerTitle = document.createElement("h3");
  299. return containerTitle.innerHTML = `
  300. <span>Download Links<span>
  301. `, container.appendChild(containerTitle), container.style.marginTop = "20px", container;
  302. }
  303. async function fetchWithRetry(url, options = {}, retries = 3, delay = 1e3) {
  304. for (let attempt = 0; attempt < retries; attempt++)
  305. try {
  306. const response = await fetch(url, options);
  307. if (!response.ok)
  308. throw new Error(`HTTP error! status: ${response.status}`);
  309. return response;
  310. } catch (error) {
  311. if (attempt < retries - 1)
  312. console.warn(`Fetch attempt ${attempt + 1} failed. Retrying in ${delay}ms...`, error), await new Promise((resolve) => setTimeout(resolve, delay));
  313. else
  314. throw console.error(`Fetch failed after ${retries} attempts`, error), error;
  315. }
  316. throw new Error("Fetch failed");
  317. }
  318. function main() {
  319. return hidePosts(), extractDownloadLinks();
  320. }
  321. main().catch(console.error);
  322. //# sourceMappingURL=audioz-utils.js.map