// ==UserScript==
// @name Mediux Titlecards Fix
// @license MIT
// @version 2.1
// @description Adds fixes and functions to MediUx
// @author azuravian
// @require https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @match https://mediux.pro/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-end
// @namespace https://greasyfork.org/users/1025348
// ==/UserScript==
waitForKeyElements(
"code.whitespace-pre-wrap",
start);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function isString(value) {
return typeof value === 'string';
}
function isNonEmptyObject(obj) {
return (
typeof obj === 'object' && // Check if it's an object
obj !== null && // Check that it's not null
!Array.isArray(obj) && // Ensure it's not an array
Object.keys(obj).length > 0 // Check if it has keys
);
}
function addTooltips() {
const buttons = [fcbutton, fpbutton, bsetbutton]; // Assuming these are your button variables
buttons.forEach((button, index) => {
switch (index) {
case 0:
button.title = "Copy links to collection images"; // Tooltip for fcbutton
break;
case 1:
button.title = "Another action"; // Tooltip for fpbutton
break;
case 2:
button.title = "Perform a batch action"; // Tooltip for bsetbutton
break;
}
});
}
function showNotification(message) {
// Create the notification div
const notification = document.createElement('div');
const myleftDiv = document.querySelector('#myleftdiv');
const parentDiv = $(myleftDiv).parent();
// Set the styles directly
notification.style.width = '50%';
notification.style.height = '50%';
notification.style.backgroundColor = 'rgba(200, 200, 200, 0.85)'; // Semi-transparent
notification.style.color = 'black';
notification.style.padding = '20px';
notification.style.borderRadius = '5px';
notification.style.justifyContent = 'center';
notification.style.alignItems = 'center';
notification.style.zIndex = '1000'; // Ensure it’s on top
notification.style.display = 'none'; // Initially hidden
// Set the message
notification.innerText = message;
$(myleftDiv).after(notification);
// Show the notification
notification.style.display = 'flex';
// Hide after 2-3 seconds
setTimeout(() => {
notification.style.display = 'none';
parentDiv.removeChild(notification); // Remove it from the DOM
}, 3000); // Adjust the time as needed
}
function get_posters() {
const regexpost = /posterCheck/g
var scriptlist = document.querySelectorAll('script')
for (let i = scriptlist.length - 1; i >= 0; i--) {
const element = scriptlist[i];
if (regexpost.test(element.textContent)) {
var str1 = element.textContent.replace('self.__next_f.push(', '');
var str1 = str1.substring(0, str1.length - 1);
var jsonString = JSON.parse(str1)[1].split('{"set":')[1];
var fullJson = `{"set":${jsonString}`;
var parsedObject = JSON.parse(fullJson.substring(0, fullJson.length - 2));
return parsedObject.set.files;
}
}
}
function get_sets() {
const regexpost = /posterCheck/g
var scriptlist = document.querySelectorAll('script')
for (let i = scriptlist.length - 1; i >= 0; i--) {
const element = scriptlist[i];
if (regexpost.test(element.textContent)) {
var str1 = element.textContent.replace('self.__next_f.push(', '');
var str1 = str1.substring(0, str1.length - 1);
var jsonString = JSON.parse(str1)[1].split('{"set":')[1];
var fullJson = `{"set":${jsonString}`;
var parsedObject = JSON.parse(fullJson.substring(0, fullJson.length - 2));
GM_setValue('creator', parsedObject.set.user_created.username);
return parsedObject.set.boxset.sets;
}
}
}
function get_set(setnum) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'GET',
url: `https://mediux.pro/sets/` + setnum,
timeout: 30000,
onload: (response) => {
resolve(response.responseText); // Resolve the promise with the response
},
onerror: () => {
console.log('[Mediux Fixes] An error occurred loading set ${setnum}');
reject(new Error('Request failed'));
},
ontimeout: () => {
console.log('[Mediux Fixes] It took too long to load set ${setnum}');
reject(new Error('Request timed out'));
}
});
});
}
async function load_boxset(codeblock) {
const button = document.querySelector('#bsetbutton');
let originalText = codeblock.textContent; // Store the original content
originalText += `\n`;
const sets = get_sets();
const creator = GM_getValue('creator');
const startTime = Date.now();
let elapsedTime = 0;
let latestMovieTitle = ""; // Variable to store the latest movie title
// Replace codeblock text with a timer
codeblock.innerText = "Processing... 0 seconds";
const timerInterval = setInterval(() => {
elapsedTime = Math.floor((Date.now() - startTime) / 1000);
codeblock.innerText = `Processing... ${elapsedTime} seconds\nProcessed Movies: ${latestMovieTitle}`;
}, 1000);
for (const set of sets) {
try {
const response = await get_set(set.id);
const response2 = response.replaceAll('\\', '');
const regexfiles = /"files":(\[{"id":.*?}]),"boxset":/s;
const match = response2.match(regexfiles);
if (match && match[1]) {
let filesArray;
try {
filesArray = JSON.parse(match[1]);
} catch (error) {
console.error('Error parsing filesArray:', error);
return;
}
const filteredFiles = filesArray.filter(file => !file.title.trim().endsWith('Collection'))
filteredFiles.sort((a, b) => a.title.localeCompare(b.title))
for (const f of filteredFiles) {
if (f.movie_id !== null) {
const posterId = f.fileType = 'poster' && f.id.length > 0 ? f.id : 'N/A';
const movieId = isNonEmptyObject(f.movie_id) ? f.movie_id.id : 'N/A';
const movieTitle = isString(f.title) && f.title.length > 0 ? f.title.trimEnd() : 'N/A';
originalText += ` ${movieId}: # ${movieTitle} Poster by ${creator} on MediUX. https://mediux.pro/sets/${set.id}\n url_poster: https://api.mediux.pro/assets/${posterId}\n `;
latestMovieTitle = latestMovieTitle + movieTitle + ', '; // Update the latest movie title
console.log(`Title: ${f.title}\nPoster: ${posterId}\n`);
}
else if (f.movie_id_backdrop !== null) {
const backdropId = f.fileType = 'backdrop' && f.id.length > 0 ? f.id : 'N/A';
const movieId = isNonEmptyObject(f.movie_id_backdrop) ? f.movie_id_backdrop.id : 'N/A';
originalText += `url_background: https://api.mediux.pro/assets/${backdropId}\n\n`
console.log(`Backdrop: ${backdropId}\nMovie id: ${movieId}\n`);
}
}
} else {
console.log('No match found');
}
} catch (error) {
console.error('Error fetching set:', error);
}
}
// Stop the timer
clearInterval(timerInterval);
codeblock.innerText = "Processing complete!"; // Temporary message
// Create a clickable link for copying the results
const copyLink = document.createElement('a');
copyLink.href = "#";
copyLink.innerText = "Click here to copy the results";
copyLink.style.color = 'blue'; // Styling for visibility
copyLink.style.cursor = 'pointer';
// Add click event listener to copy the results
copyLink.addEventListener('click', async (e) => {
e.preventDefault(); // Prevent default link behavior
try {
await navigator.clipboard.writeText(originalText);
codeblock.innerText = originalText;
color_change(button);
showNotification("Results copied to clipboard!"); // Feedback to the user
} catch (err) {
console.error('Failed to copy: ', err);
}
});
// Append the link to the codeblock
codeblock.appendChild(copyLink);
const totalTime = Math.floor((Date.now() - startTime) / 1000);
console.log(`Total time taken: ${totalTime} seconds`);
}
function color_change(button) {
button.classList.remove('bg-gray-500');
button.classList.add('bg-green-500');
// After 3 seconds, change it back to bg-gray-500
setTimeout(() => {
button.classList.remove('bg-green-500');
button.classList.add('bg-gray-500');
}, 3000); // 3000 milliseconds = 3 seconds
}
function fix_posters(codeblock) {
const button = document.querySelector('#fpbutton');
var yaml = codeblock.textContent;
var posters = get_posters();
var seasons = posters.filter((poster) => poster.title.includes("Season"));
for (i in seasons) {
var current = seasons.filter((season) => season.title.includes(`Season ${i}`));
yaml = yaml + ` ${i}:\n url_poster: https://api.mediux.pro/assets/${current[0].id}\n`;
}
codeblock.innerText = yaml;
navigator.clipboard.writeText(yaml);
showNotification("Results copied to clipboard!");
color_change(button);
}
function fix_cards(codeblock) {
const button = document.querySelector('#fcbutton');
const str = codeblock.innerText;
const regextest = /(seasons:\n)( episodes:)/g;
const regex = /( episodes:)/g;
let counter = 1;
if (regextest.test(str)) {
const modifiedStr = str.replace(regex, (match) => {
const newLine = ` ${counter++}:\n`; // Create the new line with the counter
return `${newLine}${match}`; // Return the new line followed by the match
});
codeblock.innerText = modifiedStr;
navigator.clipboard.writeText(modifiedStr);
showNotification("Results copied to clipboard!");
color_change(button);
}
}
function start() {
const codeblock = document.querySelector('code.whitespace-pre-wrap');
const myDiv = document.querySelector('.flex.flex-col.space-y-1\\.5.text-center.sm\\:text-left');
$(myDiv).children('h2, p').wrapAll('<div class="flex flex-row" style="align-items: center"><div id="myleftdiv" style="width: 25%; align: left"></div></div>');
const myleftdiv = document.querySelector('#myleftdiv');
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>');
// Set the onclick event to call the runner function
fcbutton.on('click', () => fix_cards(codeblock));
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>');
// Set the onclick event to call the runner function
fpbutton.on('click', () => fix_posters(codeblock));
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>');
// Set the onclick event to call the runner function
bsetbutton.on('click', () => load_boxset(codeblock));
var wrappedButtons = $('<div id="extbuttons" class="flex flex-row" style="margin-top: 10px"></div>').append(fcbutton).append(fpbutton).append(bsetbutton);
$(myleftdiv).append(wrappedButtons);
$(myleftdiv).parent().append('<div style="width: 25%;"></div>');
}
/*--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
that detects and handles AJAXed content.
Usage example:
waitForKeyElements (
"div.comments"
, commentCallbackFunction
);
//--- Page-specific function to do what we want when the node is found.
function commentCallbackFunction (jNode) {
jNode.text ("This comment changed by waitForKeyElements().");
}
IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = jQuery(selectorTxt);
else
targetNodes = jQuery(iframeSelector).contents ()
.find (selectorTxt);
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each ( function () {
var jThis = jQuery(this);
var alreadyFound = jThis.data ('alreadyFound') || false;
if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction (jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data ('alreadyFound', true);
}
} );
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}