// ==UserScript== // @name Snahp Movie/TV Template Generator // @version 2.0.1 // @description Creates a BBCode template for the Movie/TV section on Snahp forums. // @author BiliTheBox // @icon https://www.google.com/s2/favicons?sz=64&domain=fora.snahp.eu // @homepage https://github.com/Bilibox/Snahp-Template-Generators/ // @homepageURL https://github.com/Bilibox/Snahp-Template-Generators/ // @supportURL https://github.com/Bilibox/Snahp-Template-Generators/issues/ // @updateURL https://github.com/Bilibox/Snahp-Template-Generators/raw/Omdb/script.user.js // @downloadURL https://github.com/Bilibox/Snahp-Template-Generators/raw/Omdb/script.user.js // @match https://fora.snahp.eu/posting.php?mode=post&f=* // @require https://code.jquery.com/jquery-3.7.1.min.js // @require https://code.jquery.com/ui/1.13.3/jquery-ui.js // @require https://raw.githubusercontent.com/Semantic-Org/UI-Search/master/search.js // @require https://raw.githubusercontent.com/Semantic-Org/UI-Api/master/api.js // @grant GM_addStyle // @grant GM.xmlHttpRequest // @grant GM.setValue // @grant GM.getValue // @connect omdbapi.com // ==/UserScript== /////////////////////////////////////////////////////////////////////////// // GLOBAL VARIABLES // /////////////////////////////////////////////////////////////////////////// const TAB_URL = window.location.href; const CURRENT_SUBSECTION = parseInt(TAB_URL.match(/\d+/)[0]); const ALLOWED_SNAHP_SECTIONS = [ 26, 29, 30, 31, 32, 33, 42, 55, 56, 57, 61, 62, 64, 65, 66, 72, 73, 74, 75, 84, 88, ]; // Exit script if we are on the post preview page or on a subforum that's not supported. if ( TAB_URL.includes('preview') || !ALLOWED_SNAHP_SECTIONS.includes(CURRENT_SUBSECTION) ) { throw new Error(`${SCRIPT_NAME}: Invalid forum or page is a post preview`); } const SCRIPT_NAME = 'Snahp IMDB Template Generator'; const [MOVIES_SECTIONS, SERIES_SECTIONS] = [ [26, 29, 30, 30, 42, 55, 56, 66, 72, 73, 88], [31, 32, 33, 57, 61, 62, 64, 65, 74, 75, 84], ]; const SUBSECTION_TYPE = MOVIES_SECTIONS.includes(CURRENT_SUBSECTION) ? 'movie' : SERIES_SECTIONS.includes(CURRENT_SUBSECTION) ? 'series' : ''; const HTML_TEMPLATE = `
     
`; const OMDB_INPUT = `
     
`; /////////////////////////////////////////////////////////////////////////// // Shortcut Keys // /////////////////////////////////////////////////////////////////////////// /** * Handle keydown events to allow the user to hide the script with the "Escape" key * * @param {Event} event - The keydown event that occurred */ document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { $('#omdb-generator').hide(); document.getElementById('show-template').style.display = 'block'; } }); /////////////////////////////////////////////////////////////////////////// // Utility // /////////////////////////////////////////////////////////////////////////// /** * Hides the "Show" button and shows the OMDB template generator. * * @return {void} No return value. */ const ShowTemplate = () => { document.getElementById('show-template').style.display = 'none'; $('#omdb-generator').show(); }; /** * Shows the "Show" button and hides the OMDB template generator. * * @return {void} No return value. */ const HideTemplate = () => { document.getElementById('show-template').style.display = 'block'; $('#omdb-generator').hide(); }; /** * Sets the value of the 'message' input field to the provided forumBBCode * optionally sets the value of the 'subject' input field to the provided title. * * @param {string} forumBBCode - The forum BBCode to be set as the value of the 'message' input field. * @param {string} title - The title to be set as the value of the 'subject' input field (optional). * @param {boolean} titleBool - A flag indicating whether the value of the 'subject' input field should be set or not (optional). * @return {void} This function does not return anything. */ const SubmitToForum = (forumBBCode, title, titleBool) => { try { document.getElementsByName('message')[0].value = forumBBCode; } catch (err) { alert( `Something went wrong! Please report to my Developer.... I get scared when I crash ☹️\n\n err` ); } finally { if (titleBool) { document.getElementsByName('subject')[0].value = title; } } }; /////////////////////////////////////////////////////////////////////////// // Network Requests // /////////////////////////////////////////////////////////////////////////// /** * This function makes asynchronous http requests using GM.xmlHttpRequest * @param {string} method - HTTP method to use * @param {string} url - URL of the request * @param {object} data - Request payload * @param {object} headers - Request headers * @returns {Promise} - A promise that resolves with the response of the request */ const RequestUrl = async (method, url, data, headers) => { return await new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: method, url: url, data: data, headers: headers, onload: (response) => { resolve(response); }, onerror: (response) => { reject(response); }, }); }); }; /////////////////////////////////////////////////////////////////////////// // Search // /////////////////////////////////////////////////////////////////////////// const SectionSearch = (apiKey) => { let queryType = SUBSECTION_TYPE ? `&type=${SUBSECTION_TYPE}` : ''; var query = `https://www.omdbapi.com/?apikey=${apiKey}&r=JSON&s={query}${queryType}`; $('#search-box').search({ type: 'category', apiSettings: { url: query, onResponse: (myfunc) => { var response = { results: {}, }; $.each(myfunc.Search, (index, item) => { var category = item.Type.toUpperCase() || 'Unknown', maxResults = 10; if (index >= maxResults) { return false; } if (response.results[category] === undefined) { response.results[category] = { name: '~~~~~~~~~~' + category + '~~~~~~~~~~', results: [], }; } var Name = `${item.Title} (${item.Year})`; response.results[category].results.push({ title: Name, description: Name, imdbID: item.imdbID, }); }); return response; }, }, fields: { results: 'results', title: 'name', }, onSelect: function (response) { document.getElementById('hidden-id-value').value = response.imdbID; document.getElementById('omdb-search-box').value = response.title; }, minCharacters: 3, }); }; /////////////////////////////////////////////////////////////////////////// // Parsers // /////////////////////////////////////////////////////////////////////////// class Parser { // parse download links downloadLinks = (downloadLinks, megaDomains) => { let downloadLinkBBCode = '[hide][b]'; if (downloadLinks == null) { downloadLinkBBCode += '[url=][size=150]Download Link[/size][/url]'; } else { for (let link of downloadLinks) { if (megaDomains.some((el) => link.includes(el))) { downloadLinkBBCode += `[url=${link}][size=150][color=#FF0000]MEGA[/color][/size][/url]\n`; } else if (link.includes('zippyshare.com')) { downloadLinkBBCode += `[url=${link}][size=150][color=#FFFF00]ZippyShare[/color][/size][/url]\n`; } else if (link.includes('drive.google.com')) { downloadLinkBBCode += `[url=${link}][size=150][color=#00FF00]Gdrive[/color][/size][/url]\n`; } else { downloadLinkBBCode += `[url=${link}][size=150]Download Link[/size][/url]\n`; } } } return `[size=200][color=#fac51c][B]Download Links:[/B][/COLOR][/size]\n\n[center]\n${downloadLinkBBCode}[/b][/hide][/CENTER]\n`; }; // parse screenshot links screenshots = (screenshots) => { var screen = `\n[hr][/hr][size=200][color=#fac51c][b]Screenshots:[/b][/color][/size]\n\n`; for (let ss of screenshots) { screen += `[img]${ss}[/img]`; } screen += `\n`; return screen; }; // Parses Mediainfo for Title values mediaInfo = (mediaInfo, premadeTitle) => { let videoInfo = mediaInfo.match(/(Video|Video #1)$.^[\s\S]*?(?=\n{2,})/ms); if (videoInfo) { videoInfo = videoInfo[0]; let videoWidth = videoInfo.match(/Width.*/); if (videoWidth) { videoWidth = videoWidth[0]; if (videoWidth.includes('3 840')) { premadeTitle += ' 2160p'; } else if (videoWidth.includes('1 920')) { premadeTitle += ' 1080p'; } else if (videoWidth.includes('1 280')) { premadeTitle += ' 720p'; } else if (videoWidth.includes('720')) { premadeTitle += ' 480p'; } } let videoWritingLib = videoInfo.match(/Writing library.*/); if ( videoWritingLib & (videoWritingLib[0].includes('x265') | videoWritingLib[0].includes('x264')) ) { videoWritingLib = videoWritingLib[0]; if (videoWritingLib.includes('x265')) { premadeTitle += ' x265'; } else if (videoWritingLib.includes('x264')) { premadeTitle += ' x264'; } } else { let videoFormat = videoInfo.match(/Format.*/); if (videoFormat) { videoFormat = videoFormat[0]; if (videoFormat.includes('HEVC')) { premadeTitle += ' HEVC'; } else if (videoFormat.includes('AVC')) { premadeTitle += ' AVC'; } } } let videoBitDepth = videoInfo.match(/Bit depth.*/); if (videoBitDepth) { videoBitDepth = videoBitDepth[0]; premadeTitle += videoBitDepth.match(/\d.*/) ? ` ${videoBitDepth.match(/\d.*/)[0].replace(' bits', 'bit')}` : ''; } } let audioInfo = mediaInfo.match(/(Audio|Audio #1)$.^[\s\S]*?(?=\n{2,})/ms); if (audioInfo) { audioInfo = audioInfo[0]; let audioCodec = audioInfo.match(/Codec ID.*/); if (audioCodec) { audioCodec = audioCodec[0]; premadeTitle += audioCodec.match(/(?<=A_).*/) ? ` ${audioCodec.match(/(?<=A_).*/)[0]}` : ''; } } if (SUBSECTION_TYPE === 'movie') { let generalInfo = mediaInfo.match(/General$.^[\s\S]*?(?=\n{2,})/ms); if (generalInfo) { generalInfo = generalInfo[0]; let mediaSize = generalInfo.match(/File size.*/); if (mediaSize) { mediaSize = mediaSize[0]; premadeTitle += mediaSize.match(/\d.*/) ? ` [${mediaSize.match(/\d.*/)[0]}]` : ''; } } } return premadeTitle; }; } /** * Saves the OMDB API key entered by the user. * If successful, sets the API key in local storage and triggers the main function. * Displays an alert and throws an error if the entered key is missing or invalid. * * @throws {Error} An error is thrown if the entered key is invalid or missing. * * @returns {void} */ const SaveApiKey = () => { // Grab value from user input field const omdbKey = document.getElementById('omdb-api-key').value; // Throw error if we didn't get a value if (!omdbKey) { alert('Did you enter your omdb key?'); throw new Error( `${SCRIPT_NAME}: No input was detected in the omdb-api-key element.` ); } // Validate api key before saving RequestUrl('GET', `https://www.omdbapi.com/?apikey=${omdbKey}&i=tt0848228`) .then((response) => { if (response.status === 200) { GM.setValue('APIKEY', omdbKey); document.getElementById('template-generator-container').remove(); Main(); return; } if (response.status === 401) { let data = JSON.parse(response.responseText); alert( `Unable to verify API key. Check the message from OMDB Below. \n${ data.message || data.Error }` ); console.error(`${SCRIPT_NAME}`, response); throw Error(`${SCRIPT_NAME}: 401 Response`); } throw Error( `${SCRIPT_NAME}: Unable To Verify API Key. \n HTTP STATUS CODE: ${response.status}` ); }) .catch((error) => { console.error(error); if (error.message === `${SCRIPT_NAME}: 401 Response`) { return; } alert( 'An error occurred while testing the validity of your omdb key.\nPlease try again later!\nThe error was sent to your browsers devtools.' ); throw new Error(`${SCRIPT_NAME}: Unable to verify API Key.`); }); }; const GenerateTemplate = async (apiKey) => { var [imdbID, downloadLinks, mediainfo, screenshots] = [ document.getElementById('hidden-id-value').value ? document.getElementById('hidden-id-value').value : document.getElementById('omdb-search-box').value, document.getElementById('DownloadLink').value, document.getElementById('mediainfo-textarea').value, document.getElementById('screen-links').value, ]; if (!imdbID) { alert("You Didn't Select A Title or Enter a IMDB ID!"); return; } if (imdbID.includes('imdb')) { imdbID = imdbID.match(/tt\d+/)[0]; } // Create Prefix placeholder per DDLs let titleBool = !document.getElementsByName('subject')[0].value; let titlePrefix = ''; var megaDomains = ['mega.nz', 'mega.co.nz']; // In case using old Mega link if (titleBool) { if (megaDomains.some((el) => downloadLinks.includes(el))) { titlePrefix += '[Mega]'; } if (downloadLinks.includes('zippyshare.com')) { titlePrefix += '[Zippy]'; } if (downloadLinks.includes('drive.google.com')) { titlePrefix += '[Gdrive]'; } } screenshots = screenshots ? new Parser().screenshots(screenshots.split(' ')) : ''; const response = await RequestUrl( 'GET', `http://www.omdbapi.com/?apikey=${apiKey}&i=${imdbID}&plot=full&y&r=json` ); let json = JSON.parse(response.responseText); let poster = json.Poster && json.Poster !== 'N/A' ? `[center][img]${json.Poster}[/img]\n` : ''; if (json.Title && json.Title !== 'N/A') { var title = `${json.Title}`; } else { alert( "You Messed Up! Check That You've Entered Something Into The IMDB Field!" ); } let year = json.Year && json.Year !== 'N/A' ? ` (${json.Year})` : ''; let fullName = `[b][size=150][url='/search.php?keywords=${imdbID}&sk=x']${title}${year}[/url][/size][/b]\n`; let imdbId = json.imdbID && json.imdbID !== 'N/A' ? `[url=https://www.imdb.com/title/${json.imdbID}][img]https://i.imgur.com/rcSipDw.png[/img][/url]` : ''; let rating = json.imdbRating && json.imdbRating !== 'N/A' ? `[size=150][b]${json.imdbRating}[/b]/10[/size]\n` : ''; let imdbvotes = json.imdbVotes && json.imdbVotes !== 'N/A' ? `[size=150][img]https://i.imgur.com/sEpKj3O.png[/img]${json.imdbVotes}[/size][/center]\n` : ''; let plot = json.Plot && json.Plot !== 'N/A' ? `[hr][/hr][size=200][color=#fac51c][b]Plot:[/b][/color][/size]\n\n ${json.Plot}\n` : ''; let movieInfo = ''; if (json.Rated && json.Rated !== 'N/A') { movieInfo += `[B]Rating: [/B] ${json.Rated}\n`; } if (json.Genre && json.Genre !== 'N/A') { movieInfo += `[*][B]Genre: [/B] ${json.Genre}\n`; } if (json.Director && json.Director !== 'N/A') { movieInfo += `[*][B]Directed By: [/B] ${json.Director}\n`; } if (json.Writer && json.Writer !== 'N/A') { movieInfo += `[*][B]Written By: [/B] ${json.Writer}\n`; } if (json.Actors && json.Actors !== 'N/A') { movieInfo += `[*][B]Starring: [/B] ${json.Actors}\n`; } if (json.Released && json.Released !== 'N/A') { movieInfo += `[*][B]Release Date: [/B] ${json.Released}\n`; } if (json.Runtime && json.Runtime !== 'N/A') { movieInfo += `[*][B]Runtime: [/B] ${json.Runtime}\n`; } if (json.Production && json.Production !== 'N/A') { movieInfo += `[*][B]Production: [/B] ${json.Production}\n`; } if (movieInfo) { movieInfo = `\n[hr][/hr][size=200][color=#fac51c][b]Movie Info:[/b][/color][/size]\n\n[LIST][*]${movieInfo}[/LIST]\n`; } let premadeTitle = titleBool ? `${titlePrefix} ${json.Title} (${json.Year})` : ''; if (titleBool && mediainfo) { premadeTitle = new Parser().mediaInfo(mediainfo, premadeTitle); } mediainfo = mediainfo ? `[hr][/hr][size=200][color=#fac51c][b]Media Info:[/b][/color][/size]\n\n[mediainfo]${mediainfo}\n[/mediainfo]\n` : ''; let downloadLinkBBCode = new Parser().downloadLinks( downloadLinks.split(' '), megaDomains ); let forumBBCode = `${poster}${fullName}${imdbId}${rating}${imdbvotes}${plot}${screenshots}${movieInfo}${mediainfo}${downloadLinkBBCode}`; SubmitToForum(forumBBCode, premadeTitle, titleBool); }; function Main() { GM.getValue('APIKEY', 'foo').then((apiKey) => { const htmlpush = document.getElementsByTagName('dl')[0]; if (apiKey !== 'foo') { htmlpush.insertAdjacentHTML('afterend', HTML_TEMPLATE); } else { htmlpush.insertAdjacentHTML('afterend', OMDB_INPUT); } document.getElementById('hide-template').addEventListener( 'click', () => { HideTemplate(); }, false ); document.getElementById('show-template').addEventListener( 'click', () => { ShowTemplate(); }, false ); if (apiKey !== 'foo') { SectionSearch(apiKey); document.getElementById('generate-template').addEventListener( 'click', () => { GenerateTemplate(apiKey); }, false ); } else { document.getElementById('save-key').addEventListener( 'click', () => { SaveApiKey(); }, false ); } }); } // Add css to the page GM_addStyle(` @media screen and (min-width: 300px) { .inputbox { max-width: 330px; } .result { max-height: 10px; display: unset; } .content { overflow: unset; min-height: unset; cursor: pointer; padding-bottom: unset; line-height: unset; } #mediainfo-textarea { width: 100% !important; } } `); Main();