Koharu Artist URL Collector

Collect URLs from koharu.to artist pages and store them in GM_storage

  1. // ==UserScript==
  2. // @name Koharu Artist URL Collector
  3. // @namespace https://koharu.to/
  4. // @version 1.7.2.1
  5. // @description Collect URLs from koharu.to artist pages and store them in GM_storage
  6. // @license MIT
  7. // @author viatana35
  8. // @match https://hoshino.one/tag/artist:*
  9. // @match https://hoshino.one/tag/circle:*
  10. // @match https://shupogaki.moe/tag/artist:*
  11. // @match https://shupogaki.moe/tag/circle:*
  12. // @match https://niyaniya.moe/tag/artist:*
  13. // @match https://niyaniya.moe/tag/circle:*
  14. // @grant GM_getValue
  15. // @grant GM_setValue
  16. // @grant GM_deleteValue
  17. // @grant GM_download
  18. // ==/UserScript==
  19.  
  20. (function () {
  21. 'use strict';
  22.  
  23. const urlKey = 'koharuUrls';
  24. const continueKey = 'continue_treatment';
  25. const artistsLibKey = 'artists_lib';
  26. let urlList = GM_getValue(urlKey, []);
  27. let continueTreatment = GM_getValue(continueKey, false);
  28. let artistsLib = GM_getValue(artistsLibKey, []);
  29. let isProcessing = false; // Flag to prevent infinite loop
  30. let lastProcessedUrl = '';
  31.  
  32. // Function to process the current page and store English articles
  33. function processPage() {
  34. const articles = document.querySelectorAll('article');
  35. articles.forEach(article => {
  36. //const isEnglish = article.querySelector('a > header > h3 > span > svg[id="flag-icons-gb"]');
  37. const isEnglish = article.querySelector('a > div > main > div > div > svg[id="flag-icons-gb"]');
  38. if (isEnglish) {
  39. const articleUrl = article.querySelector('a').href;
  40. if (!urlList.includes(articleUrl)) {
  41. urlList.push(articleUrl);
  42. }
  43. }
  44. });
  45.  
  46. GM_setValue(urlKey, urlList);
  47. }
  48.  
  49. // Function to navigate to the next page
  50. function goToNextPage() {
  51. const paginationList = document.querySelector('footer > nav > ul');
  52. if (paginationList) {
  53. const paginationItems = paginationList.children;
  54. const lastItem = paginationItems[paginationItems.length - 1]; // On prend le dernier élément de la liste
  55.  
  56. const nextPageLink = lastItem.querySelector('a');
  57. if (nextPageLink) {
  58. const currentPageNumber = parseInt(new URL(window.location.href).searchParams.get('page') || '1', 10);
  59. const nextPageNumber = parseInt(new URL(nextPageLink.href).searchParams.get('page'), 10);
  60.  
  61. if (nextPageNumber === currentPageNumber + 1) {
  62. console.log(currentPageNumber, "->", nextPageNumber);
  63. GM_setValue(continueKey, true);
  64. window.location.href = nextPageLink.href;
  65. } else {
  66. console.log("end of treatment 1");
  67. saveArtistData();
  68. GM_setValue(continueKey, false); // Pas de page suivante
  69. }
  70. } else {
  71. saveArtistData();
  72. GM_setValue(continueKey, false); // Pas de lien pour la page suivante
  73. }
  74. }
  75. else {
  76. console.log("end of treatment 2");
  77. saveArtistData();
  78. GM_setValue(continueKey, false); // Pas de page suivante
  79. }
  80. }
  81.  
  82. function treatArtist() {
  83. // If there is no pagination list, check if the URL contains "&page"
  84. const urlParams = new URLSearchParams(window.location.search);
  85. if (urlParams.has('page')) {
  86. showPopup("please go to page one to treat artist");
  87. } else {
  88. console.log("beginning of the treatment");
  89. processPage();
  90. goToNextPage();
  91. }
  92. }
  93.  
  94. function getArtistName() {
  95. let artistName = null;
  96.  
  97. // Get the pathname from the window location
  98. const pathname = window.location.pathname;
  99.  
  100. // Check if the pathname starts with '/tag/'
  101. if (pathname.startsWith('/tag/')) {
  102. // Extract the part after '/tag/'
  103. const tagPart = pathname.substring('/tag/'.length);
  104.  
  105. // Check if the tag part starts with 'artist:' or 'circle:'
  106. if (tagPart.startsWith('artist:') || tagPart.startsWith('circle:')) {
  107. // Extract the artist or circle name
  108. artistName = tagPart.split(':')[1];
  109.  
  110. // Remove caret and dollar sign characters from the artist name
  111. artistName = artistName.replace(/[\^$]/g, '');
  112. }
  113. }
  114.  
  115. return artistName;
  116. }
  117.  
  118.  
  119. function showPopup(message) {
  120. const popup = document.createElement('div');
  121. popup.id = 'popup';
  122. popup.style.position = 'fixed';
  123. popup.style.top = '50%';
  124. popup.style.left = '50%';
  125. popup.style.transform = 'translate(-50%, -50%)';
  126. popup.style.backgroundColor = '#f1f1f1';
  127. popup.style.padding = '20px';
  128. popup.style.border = '1px solid #ccc';
  129. popup.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.1)';
  130. popup.style.zIndex = '1001';
  131. popup.style.textAlign = 'center';
  132. popup.style.color = 'black'; // Set text color to black
  133. popup.style.opacity = '0';
  134. popup.style.transition = 'opacity 0.3s ease-in-out';
  135. popup.textContent = message;
  136.  
  137. document.body.appendChild(popup);
  138.  
  139. // Fade in the popup
  140. setTimeout(() => {
  141. popup.style.opacity = '1';
  142. }, 10);
  143.  
  144. // Fade out and remove the popup after 5 seconds
  145. setTimeout(() => {
  146. popup.style.opacity = '0';
  147. setTimeout(() => {
  148. document.body.removeChild(popup);
  149. }, 300);
  150. }, 5000);
  151. }
  152.  
  153.  
  154.  
  155.  
  156. // Function to save the current artist's data
  157. function saveArtistData() {
  158. const artistName = getArtistName();
  159. if (artistName && urlList.length > 0) {
  160. const artistData = {
  161. artist: artistName,
  162. lst_dl: urlList
  163. };
  164.  
  165. artistsLib = GM_getValue(artistsLibKey, []);
  166. // Add the artist data to the artistsLib
  167. artistsLib.push(artistData);
  168.  
  169. // Save the updated artistsLib
  170. GM_setValue(artistsLibKey, artistsLib);
  171.  
  172. // Clear the URL list for the next artist
  173. GM_setValue(urlKey, []);
  174. urlList = [];
  175.  
  176. showPopup(`Artist ${artistName} has been processed.`);
  177.  
  178. clearButton.disabled = false;
  179.  
  180. }
  181. }
  182.  
  183. // Function to handle automatic processing if continue_treatment is true
  184. function handleAutoProcessing() {
  185. if (isProcessing) return; // Prevent multiple processing
  186. isProcessing = true;
  187.  
  188. const currentUrl = window.location.href;
  189. if (currentUrl !== lastProcessedUrl && continueTreatment) {
  190. lastProcessedUrl = currentUrl;
  191. processPage();
  192. goToNextPage();
  193. } else {
  194. isProcessing = false;
  195. return;
  196. }
  197.  
  198. isProcessing = false;
  199. }
  200.  
  201. // Function to download the artists_lib as a JSON file
  202. function downloadArtistsLib() {
  203. const artistsLibData = GM_getValue(artistsLibKey, []);
  204. const jsonContent = JSON.stringify(artistsLibData, null, 2); // Pretty print JSON with indentation
  205. const blob = new Blob([jsonContent], { type: 'application/json' });
  206. const url = URL.createObjectURL(blob);
  207.  
  208. const a = document.createElement('a');
  209. a.href = url;
  210. a.download = 'artists_lib.json';
  211. a.click();
  212.  
  213. URL.revokeObjectURL(url); // Clean up the URL object
  214. }
  215.  
  216. function clearList() {
  217. if (confirm('Do you really want to clear the list ?')) {
  218. GM_deleteValue(urlKey);
  219. GM_deleteValue(artistsLibKey);
  220. urlList = [];
  221. artistsLib = [];
  222. clearButton.disabled = true;
  223. showPopup("list cleared");
  224. }
  225. }
  226.  
  227. // Create a container for the buttons
  228. const buttonContainer = document.createElement('div');
  229. buttonContainer.style.position = 'sticky';
  230. buttonContainer.style.top = '0';
  231. buttonContainer.style.backgroundColor = '#f1f1f1';
  232. buttonContainer.style.padding = '10px';
  233. buttonContainer.style.zIndex = 1000;
  234. buttonContainer.style.display = 'flex';
  235. buttonContainer.style.justifyContent = 'center';
  236. buttonContainer.style.alignItems = 'center';
  237. document.body.prepend(buttonContainer);
  238.  
  239. // Create a button to process the artist's pages
  240. const processButton = document.createElement('button');
  241. processButton.textContent = 'Treat artist';
  242. processButton.style.margin = '0 10px';
  243. processButton.style.padding = '10px';
  244. processButton.style.backgroundColor = '#4CAF50';
  245. processButton.style.color = 'white';
  246. processButton.style.border = 'none';
  247. processButton.style.cursor = 'pointer';
  248. processButton.onclick = () => {
  249. GM_setValue(continueKey, true);
  250. treatArtist();
  251. };
  252. buttonContainer.appendChild(processButton);
  253.  
  254. // Create a button to clear the list and artists_lib
  255. const clearButton = document.createElement('button');
  256. clearButton.textContent = 'Clear list';
  257. clearButton.style.margin = '0 10px';
  258. clearButton.style.padding = '10px';
  259. clearButton.style.backgroundColor = '#f44336';
  260. clearButton.style.color = 'white';
  261. clearButton.style.border = 'none';
  262. clearButton.style.cursor = 'pointer';
  263. clearButton.disabled = artistsLib.length === 0;
  264. clearButton.onclick = clearList;
  265. buttonContainer.appendChild(clearButton);
  266.  
  267. // Create a button to download the artists_lib JSON
  268. const downloadButton = document.createElement('button');
  269. downloadButton.textContent = 'Download the list';
  270. downloadButton.style.margin = '0 10px';
  271. downloadButton.style.padding = '10px';
  272. downloadButton.style.backgroundColor = '#2196F3';
  273. downloadButton.style.color = 'white';
  274. downloadButton.style.border = 'none';
  275. downloadButton.style.cursor = 'pointer';
  276. downloadButton.onclick = downloadArtistsLib;
  277. buttonContainer.appendChild(downloadButton);
  278.  
  279. // Enable the clear button if the list contains elements
  280. if (artistsLib.length > 0) {
  281. clearButton.disabled = false;
  282. }
  283.  
  284. // Observe the second div inside the body, which contains the section that gets replaced
  285. const targetDiv = document.querySelector('body > div:nth-of-type(2)');
  286.  
  287. if (targetDiv) {
  288. const observer = new MutationObserver((mutations) => {
  289. mutations.forEach((mutation) => {
  290. if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
  291. console.log('DOM changed');
  292. handleAutoProcessing();
  293. }
  294. });
  295. });
  296. observer.observe(targetDiv, { childList: true, subtree: true });
  297. }
  298.  
  299. // Initial processing
  300. document.addEventListener('DOMContentLoaded', () => {
  301. console.log('DOM fully loaded and parsed');
  302. handleAutoProcessing();
  303. });
  304.  
  305.  
  306.  
  307. })();