Mediux Titlecards Fix

Adds fixes and functions to MediUx

目前为 2024-10-28 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Mediux Titlecards Fix
  3. // @license MIT
  4. // @version 2.1
  5. // @description Adds fixes and functions to MediUx
  6. // @author azuravian
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
  8. // @match https://mediux.pro/*
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @run-at document-end
  13. // @namespace https://greasyfork.org/users/1025348
  14. // ==/UserScript==
  15.  
  16. waitForKeyElements(
  17. "code.whitespace-pre-wrap",
  18. start);
  19.  
  20. function sleep(ms) {
  21. return new Promise(resolve => setTimeout(resolve, ms));
  22. }
  23.  
  24. function isString(value) {
  25. return typeof value === 'string';
  26. }
  27.  
  28. function isNonEmptyObject(obj) {
  29. return (
  30. typeof obj === 'object' && // Check if it's an object
  31. obj !== null && // Check that it's not null
  32. !Array.isArray(obj) && // Ensure it's not an array
  33. Object.keys(obj).length > 0 // Check if it has keys
  34. );
  35. }
  36.  
  37. function addTooltips() {
  38. const buttons = [fcbutton, fpbutton, bsetbutton]; // Assuming these are your button variables
  39.  
  40. buttons.forEach((button, index) => {
  41. switch (index) {
  42. case 0:
  43. button.title = "Copy links to collection images"; // Tooltip for fcbutton
  44. break;
  45. case 1:
  46. button.title = "Another action"; // Tooltip for fpbutton
  47. break;
  48. case 2:
  49. button.title = "Perform a batch action"; // Tooltip for bsetbutton
  50. break;
  51. }
  52. });
  53. }
  54.  
  55. function showNotification(message) {
  56. // Create the notification div
  57. const notification = document.createElement('div');
  58. const myleftDiv = document.querySelector('#myleftdiv');
  59. const parentDiv = $(myleftDiv).parent();
  60.  
  61. // Set the styles directly
  62. notification.style.width = '50%';
  63. notification.style.height = '50%';
  64. notification.style.backgroundColor = 'rgba(200, 200, 200, 0.85)'; // Semi-transparent
  65. notification.style.color = 'black';
  66. notification.style.padding = '20px';
  67. notification.style.borderRadius = '5px';
  68. notification.style.justifyContent = 'center';
  69. notification.style.alignItems = 'center';
  70. notification.style.zIndex = '1000'; // Ensure it’s on top
  71. notification.style.display = 'none'; // Initially hidden
  72. // Set the message
  73. notification.innerText = message;
  74.  
  75. $(myleftDiv).after(notification);
  76.  
  77. // Show the notification
  78. notification.style.display = 'flex';
  79.  
  80. // Hide after 2-3 seconds
  81. setTimeout(() => {
  82. notification.style.display = 'none';
  83. parentDiv.removeChild(notification); // Remove it from the DOM
  84. }, 3000); // Adjust the time as needed
  85. }
  86.  
  87. function get_posters() {
  88. const regexpost = /posterCheck/g
  89. var scriptlist = document.querySelectorAll('script')
  90. for (let i = scriptlist.length - 1; i >= 0; i--) {
  91. const element = scriptlist[i];
  92. if (regexpost.test(element.textContent)) {
  93. var str1 = element.textContent.replace('self.__next_f.push(', '');
  94. var str1 = str1.substring(0, str1.length - 1);
  95. var jsonString = JSON.parse(str1)[1].split('{"set":')[1];
  96. var fullJson = `{"set":${jsonString}`;
  97. var parsedObject = JSON.parse(fullJson.substring(0, fullJson.length - 2));
  98. return parsedObject.set.files;
  99. }
  100. }
  101. }
  102.  
  103. function get_sets() {
  104. const regexpost = /posterCheck/g
  105. var scriptlist = document.querySelectorAll('script')
  106. for (let i = scriptlist.length - 1; i >= 0; i--) {
  107. const element = scriptlist[i];
  108. if (regexpost.test(element.textContent)) {
  109. var str1 = element.textContent.replace('self.__next_f.push(', '');
  110. var str1 = str1.substring(0, str1.length - 1);
  111. var jsonString = JSON.parse(str1)[1].split('{"set":')[1];
  112. var fullJson = `{"set":${jsonString}`;
  113. var parsedObject = JSON.parse(fullJson.substring(0, fullJson.length - 2));
  114. GM_setValue('creator', parsedObject.set.user_created.username);
  115. return parsedObject.set.boxset.sets;
  116. }
  117. }
  118. }
  119.  
  120. function get_set(setnum) {
  121. return new Promise((resolve, reject) => {
  122. GM_xmlhttpRequest({
  123. method: 'GET',
  124. url: `https://mediux.pro/sets/` + setnum,
  125. timeout: 30000,
  126. onload: (response) => {
  127. resolve(response.responseText); // Resolve the promise with the response
  128. },
  129. onerror: () => {
  130. console.log('[Mediux Fixes] An error occurred loading set ${setnum}');
  131. reject(new Error('Request failed'));
  132. },
  133. ontimeout: () => {
  134. console.log('[Mediux Fixes] It took too long to load set ${setnum}');
  135. reject(new Error('Request timed out'));
  136. }
  137. });
  138. });
  139. }
  140.  
  141.  
  142.  
  143. async function load_boxset(codeblock) {
  144. const button = document.querySelector('#bsetbutton');
  145. let originalText = codeblock.textContent; // Store the original content
  146. originalText += `\n`;
  147. const sets = get_sets();
  148. const creator = GM_getValue('creator');
  149. const startTime = Date.now();
  150. let elapsedTime = 0;
  151. let latestMovieTitle = ""; // Variable to store the latest movie title
  152.  
  153. // Replace codeblock text with a timer
  154. codeblock.innerText = "Processing... 0 seconds";
  155.  
  156. const timerInterval = setInterval(() => {
  157. elapsedTime = Math.floor((Date.now() - startTime) / 1000);
  158. codeblock.innerText = `Processing... ${elapsedTime} seconds\nProcessed Movies: ${latestMovieTitle}`;
  159. }, 1000);
  160.  
  161. for (const set of sets) {
  162. try {
  163. const response = await get_set(set.id);
  164. const response2 = response.replaceAll('\\', '');
  165. const regexfiles = /"files":(\[{"id":.*?}]),"boxset":/s;
  166. const match = response2.match(regexfiles);
  167.  
  168.  
  169. if (match && match[1]) {
  170. let filesArray;
  171. try {
  172. filesArray = JSON.parse(match[1]);
  173. } catch (error) {
  174. console.error('Error parsing filesArray:', error);
  175. return;
  176. }
  177. const filteredFiles = filesArray.filter(file => !file.title.trim().endsWith('Collection'))
  178. filteredFiles.sort((a, b) => a.title.localeCompare(b.title))
  179. for (const f of filteredFiles) {
  180. if (f.movie_id !== null) {
  181. const posterId = f.fileType = 'poster' && f.id.length > 0 ? f.id : 'N/A';
  182. const movieId = isNonEmptyObject(f.movie_id) ? f.movie_id.id : 'N/A';
  183. const movieTitle = isString(f.title) && f.title.length > 0 ? f.title.trimEnd() : 'N/A';
  184. originalText += ` ${movieId}: # ${movieTitle} Poster by ${creator} on MediUX. https://mediux.pro/sets/${set.id}\n url_poster: https://api.mediux.pro/assets/${posterId}\n `;
  185. latestMovieTitle = latestMovieTitle + movieTitle + ', '; // Update the latest movie title
  186. console.log(`Title: ${f.title}\nPoster: ${posterId}\n`);
  187. }
  188. else if (f.movie_id_backdrop !== null) {
  189. const backdropId = f.fileType = 'backdrop' && f.id.length > 0 ? f.id : 'N/A';
  190. const movieId = isNonEmptyObject(f.movie_id_backdrop) ? f.movie_id_backdrop.id : 'N/A';
  191. originalText += `url_background: https://api.mediux.pro/assets/${backdropId}\n\n`
  192. console.log(`Backdrop: ${backdropId}\nMovie id: ${movieId}\n`);
  193. }
  194. }
  195. } else {
  196. console.log('No match found');
  197. }
  198. } catch (error) {
  199. console.error('Error fetching set:', error);
  200. }
  201. }
  202.  
  203. // Stop the timer
  204. clearInterval(timerInterval);
  205. codeblock.innerText = "Processing complete!"; // Temporary message
  206.  
  207. // Create a clickable link for copying the results
  208. const copyLink = document.createElement('a');
  209. copyLink.href = "#";
  210. copyLink.innerText = "Click here to copy the results";
  211. copyLink.style.color = 'blue'; // Styling for visibility
  212. copyLink.style.cursor = 'pointer';
  213.  
  214. // Add click event listener to copy the results
  215. copyLink.addEventListener('click', async (e) => {
  216. e.preventDefault(); // Prevent default link behavior
  217. try {
  218. await navigator.clipboard.writeText(originalText);
  219. codeblock.innerText = originalText;
  220. color_change(button);
  221. showNotification("Results copied to clipboard!"); // Feedback to the user
  222. } catch (err) {
  223. console.error('Failed to copy: ', err);
  224. }
  225. });
  226.  
  227. // Append the link to the codeblock
  228. codeblock.appendChild(copyLink);
  229. const totalTime = Math.floor((Date.now() - startTime) / 1000);
  230. console.log(`Total time taken: ${totalTime} seconds`);
  231. }
  232.  
  233. function color_change(button) {
  234. button.classList.remove('bg-gray-500');
  235. button.classList.add('bg-green-500');
  236.  
  237. // After 3 seconds, change it back to bg-gray-500
  238. setTimeout(() => {
  239. button.classList.remove('bg-green-500');
  240. button.classList.add('bg-gray-500');
  241. }, 3000); // 3000 milliseconds = 3 seconds
  242. }
  243.  
  244. function fix_posters(codeblock) {
  245. const button = document.querySelector('#fpbutton');
  246. var yaml = codeblock.textContent;
  247. var posters = get_posters();
  248. var seasons = posters.filter((poster) => poster.title.includes("Season"));
  249. for (i in seasons) {
  250. var current = seasons.filter((season) => season.title.includes(`Season ${i}`));
  251. yaml = yaml + ` ${i}:\n url_poster: https://api.mediux.pro/assets/${current[0].id}\n`;
  252. }
  253. codeblock.innerText = yaml;
  254. navigator.clipboard.writeText(yaml);
  255. showNotification("Results copied to clipboard!");
  256. color_change(button);
  257. }
  258.  
  259. function fix_cards(codeblock) {
  260. const button = document.querySelector('#fcbutton');
  261. const str = codeblock.innerText;
  262. const regextest = /(seasons:\n)( episodes:)/g;
  263. const regex = /( episodes:)/g;
  264. let counter = 1;
  265. if (regextest.test(str)) {
  266. const modifiedStr = str.replace(regex, (match) => {
  267. const newLine = ` ${counter++}:\n`; // Create the new line with the counter
  268. return `${newLine}${match}`; // Return the new line followed by the match
  269. });
  270. codeblock.innerText = modifiedStr;
  271. navigator.clipboard.writeText(modifiedStr);
  272. showNotification("Results copied to clipboard!");
  273. color_change(button);
  274. }
  275. }
  276.  
  277. function start() {
  278. const codeblock = document.querySelector('code.whitespace-pre-wrap');
  279. const myDiv = document.querySelector('.flex.flex-col.space-y-1\\.5.text-center.sm\\:text-left');
  280. $(myDiv).children('h2, p').wrapAll('<div class="flex flex-row" style="align-items: center"><div id="myleftdiv" style="width: 25%; align: left"></div></div>');
  281. const myleftdiv = document.querySelector('#myleftdiv');
  282.  
  283. var fcbutton = $('<button id="fcbutton" title = "Fix missing season numbers in TitleCard YAML" class="duration-500 top-1 left-1 text-xs py-1 px-2 bg-gray-500 text-white rounded flex items-center justify-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gallery-vertical-end w-5 h-5"><path d="M7 2h10"></path><path d="M5 6h14"></path><rect width="18" height="12" x="3" y="10" rx="2"></rect></svg></button>');
  284. // Set the onclick event to call the runner function
  285. fcbutton.on('click', () => fix_cards(codeblock));
  286.  
  287. var fpbutton = $('<button id="fpbutton" title = "Fix missing season posters YAML" class="duration-500 top-1 left-1 text-xs py-1 px-2 bg-gray-500 text-white rounded flex items-center justify-center" style="margin-left:10px""><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gallery-horizontal-end w-5 h-5"><path d="M2 7v10"></path><path d="M6 5v14"></path><rect width="12" height="18" x="10" y="3" rx="2"></rect></svg></button>');
  288. // Set the onclick event to call the runner function
  289. fpbutton.on('click', () => fix_posters(codeblock));
  290.  
  291. var bsetbutton = $('<button id="bsetbutton" title = "Generate YAML for associated boxset" class="duration-500 top-1 left-1 text-xs py-1 px-2 bg-gray-500 text-white rounded flex items-center justify-center" style="margin-left:10px"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gallery-horizontal-end w-5 h-5""><rect width="18" height="18" x="3" y="3" rx="2"></rect><path d="M7 7v10"></path><path d="M11 7v10"></path><path d="m15 7 2 10"></path></svg></button>');
  292. // Set the onclick event to call the runner function
  293. bsetbutton.on('click', () => load_boxset(codeblock));
  294.  
  295. var wrappedButtons = $('<div id="extbuttons" class="flex flex-row" style="margin-top: 10px"></div>').append(fcbutton).append(fpbutton).append(bsetbutton);
  296. $(myleftdiv).append(wrappedButtons);
  297. $(myleftdiv).parent().append('<div style="width: 25%;"></div>');
  298. }
  299.  
  300.  
  301. /*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
  302. that detects and handles AJAXed content.
  303. Usage example:
  304. waitForKeyElements (
  305. "div.comments"
  306. , commentCallbackFunction
  307. );
  308. //--- Page-specific function to do what we want when the node is found.
  309. function commentCallbackFunction (jNode) {
  310. jNode.text ("This comment changed by waitForKeyElements().");
  311. }
  312. IMPORTANT: This function requires your script to have loaded jQuery.
  313. */
  314. function waitForKeyElements (
  315. selectorTxt, /* Required: The jQuery selector string that
  316. specifies the desired element(s).
  317. */
  318. actionFunction, /* Required: The code to run when elements are
  319. found. It is passed a jNode to the matched
  320. element.
  321. */
  322. bWaitOnce, /* Optional: If false, will continue to scan for
  323. new elements even after the first match is
  324. found.
  325. */
  326. iframeSelector /* Optional: If set, identifies the iframe to
  327. search.
  328. */
  329. ) {
  330. var targetNodes, btargetsFound;
  331.  
  332. if (typeof iframeSelector == "undefined")
  333. targetNodes = jQuery(selectorTxt);
  334. else
  335. targetNodes = jQuery(iframeSelector).contents ()
  336. .find (selectorTxt);
  337.  
  338. if (targetNodes && targetNodes.length > 0) {
  339. btargetsFound = true;
  340. /*--- Found target node(s). Go through each and act if they
  341. are new.
  342. */
  343. targetNodes.each ( function () {
  344. var jThis = jQuery(this);
  345. var alreadyFound = jThis.data ('alreadyFound') || false;
  346.  
  347. if (!alreadyFound) {
  348. //--- Call the payload function.
  349. var cancelFound = actionFunction (jThis);
  350. if (cancelFound)
  351. btargetsFound = false;
  352. else
  353. jThis.data ('alreadyFound', true);
  354. }
  355. } );
  356. }
  357. else {
  358. btargetsFound = false;
  359. }
  360.  
  361. //--- Get the timer-control variable for this selector.
  362. var controlObj = waitForKeyElements.controlObj || {};
  363. var controlKey = selectorTxt.replace (/[^\w]/g, "_");
  364. var timeControl = controlObj [controlKey];
  365.  
  366. //--- Now set or clear the timer as appropriate.
  367. if (btargetsFound && bWaitOnce && timeControl) {
  368. //--- The only condition where we need to clear the timer.
  369. clearInterval (timeControl);
  370. delete controlObj [controlKey]
  371. }
  372. else {
  373. //--- Set a timer, if needed.
  374. if ( ! timeControl) {
  375. timeControl = setInterval ( function () {
  376. waitForKeyElements ( selectorTxt,
  377. actionFunction,
  378. bWaitOnce,
  379. iframeSelector
  380. );
  381. },
  382. 300
  383. );
  384. controlObj [controlKey] = timeControl;
  385. }
  386. }
  387. waitForKeyElements.controlObj = controlObj;
  388. }