// ==UserScript== // ----------------------------------- MetaData -------------------------------------- // @name qui - quiCKIE // @author WirlyWirly + Contributors 🫢 // @version 1.47 // @homepage https://github.com/WirlyWirly/quiCKIE // @description A UserScript to quickly send torrents from a tracker to a client, with customizable per-site settings and presets 🐰 // Orignally written for qui, later extended to support more torrent clients // Written on LibreWolf via Violentmonkey // @namespace https://github.com/WirlyWirly // @icon https://raw.githubusercontent.com/WirlyWirly/quiCKIE/main/icon.webp?raw=true // @run-at document-end // ----------------------------------- Matches -------------------------------------- // Adding a New Tracker: https://github.com/WirlyWirly/quiCKIE/wiki/Adding-a-New-Tracker // @match https://aither.cc/ // @match https://aither.cc/*/bookmarks* // @match https://aither.cc/playlists/* // @match https://aither.cc/torrents* // @match https://alpharatio.cc/top10.php* // @match https://alpharatio.cc/torrents.php* // @match https://animebytes.tv/alltorrents.php?*&userid=* // @match https://animebytes.tv/artist.php?id=* // @match https://animebytes.tv/bookmarks.php* // @match https://animebytes.tv/collage.php?*id=* // @match https://animebytes.tv/company.php?id=* // @match https://animebytes.tv/series.php?id=* // @match https://animebytes.tv/torrents* // @match https://animez.to/ // @match https://animez.to/torrents* // @match https://animez.to/torrent-bookmarks* // @match https://anthelion.me/torrents.php* // @match https://avistaz.to/ // @match https://avistaz.to/*/bookmark* // @match https://avistaz.to/torrent* // @match https://bakabt.me/torrent/* // @match https://beyond-hd.me/ // @match https://beyond-hd.me/bookmarks* // @match https://beyond-hd.me/download/* // @match https://beyond-hd.me/library* // @match https://beyond-hd.me/torrents* // @match https://beyond-hd.me/torrents/seed* // @match https://beyond-hd.me/watchlist* // @match https://bibliotik.me/collections/* // @match https://bibliotik.me/notifications/torrents* // @match https://bibliotik.me/torrents/* // @match https://bibliotik.me/users/*/leeching* // @match https://bibliotik.me/users/*/seeding* // @match https://bibliotik.me/users/*/snatches* // @match https://bibliotik.me/users/*/uploads* // @match https://bitporn.eu/ // @match https://bitporn.eu/*/bookmarks* // @match https://bitporn.eu/playlists/* // @match https://bitporn.eu/torrents* // @match https://broadcasthe.net/collages.php?id=* // @match https://broadcasthe.net/series.php?id=* // @match https://broadcasthe.net/torrents.php* // @match https://cinemaz.to/ // @match https://cinemaz.to/*/bookmark* // @match https://cinemaz.to/torrent/* // @match https://clearjav.com/ // @match https://clearjav.com/*/bookmarks* // @match https://clearjav.com/movies/* // @match https://clearjav.com/playlists/* // @match https://clearjav.com/torrents* // @match https://www.deepbassnine.com/artist.php?id=* // @match https://www.deepbassnine.com/collages.php?id=* // @match https://www.deepbassnine.com/torrents.php* // @match https://digitalcore.club/ // @match https://digitalcore.club/alltorrents* // @match https://digitalcore.club/apps* // @match https://digitalcore.club/bookmarks* // @match https://digitalcore.club/games* // @match https://digitalcore.club/movies* // @match https://digitalcore.club/music* // @match https://digitalcore.club/other* // @match https://digitalcore.club/torrent-lists/* // @match https://digitalcore.club/torrent/* // @match https://digitalcore.club/tvseries* // @match https://digitalcore.club/xxx* // @include /^https://(www\.empornium|emparadise)\.(sx|rs)/collage/\d.*/ // @include /^https://(www\.empornium|emparadise)\.(sx|rs)/top10\.php.*/ // @include /^https://(www\.empornium|emparadise)\.(sx|rs)/torrents\.php.*/ // @include /^https://(www\.empornium|emparadise)\.(sx|rs)/user\.php\?id=\d+/ // @match https://e*hentai.org/gallerytorrents.php* // @match https://exoticaz.to/ // @match https://exoticaz.to/*/bookmark* // @match https://exoticaz.to/torrent* // @match https://femdomcult.org/collage/* // @match https://femdomcult.org/torrents.php* // @match https://gazellegames.net/collections.php?id=* // @match https://gazellegames.net/torrents.php* // @match https://gazellegames.net/bookmarks.php* // @match https://www.happyfappy.net/collage/* // @match https://www.happyfappy.net/top10.php* // @match https://www.happyfappy.net/torrents.php* // @match https://www.happyfappy.net/user.php?id=* // @match https://hdbits.org/bookmarks* // @match https://hdbits.org/browse.php* // @match https://hdbits.org/details.php?id=* // @match https://hdbits.org/film/info?id=* // @include /^https://iptorrents\.\w+/details.php?id=.*/ // @include /^https://iptorrents\.\w+/t.*/ // @include /^https://iptorrents\.\w+/torrent.php?id=.*/ // @match https://jpopsuki.eu/artist.php?id=* // @match https://jpopsuki.eu/collages.php?id=* // @match https://jpopsuki.eu/top10.php* // @match https://jpopsuki.eu/torrents.php* // @match https://karagarga.in/details.php* // @match https://karagarga.in/browse.php* // @match https://kufirc.com/bookmarks.php* // @match https://kufirc.com/collages.php* // @match https://kufirc.com/top10.php* // @match https://kufirc.com/torrents.php* // @match https://lat-team.com/ // @match https://lat-team.com/*/bookmarks // @match https://lat-team.com/playlists/* // @match https://lat-team.com/torrents* // @match https://lst.gg/ // @match https://lst.gg/*/bookmarks* // @match https://lst.gg/playlists/* // @match https://lst.gg/torrents* // @match https://luminarr.me/ // @match https://luminarr.me/*/bookmarks // @match https://luminarr.me/playlists/* // @match https://luminarr.me/torrents* // @match https://materialize.is/collages.php?id=* // @match https://materialize.is/top10.php* // @match https://materialize.is/torrents.php* // @match https://www.morethantv.me/collage/* // @match https://www.morethantv.me/top10.php* // @match https://www.morethantv.me/torrents/browse* // @match https://www.myanonamouse.net/ // @match https://www.myanonamouse.net/stats/top10Tor.php* // @match https://www.myanonamouse.net/t/* // @match https://www.myanonamouse.net/tor/browse.php* // @match https://www.myanonamouse.net/tor/search.php* // @match https://nebulance.io/bookmarks.php* // @match https://nebulance.io/top10.php* // @match https://nebulance.io/torrents.php* // @match https://nebulance.io/details.php* // @include /^https://(sukebei\.)?nyaa\.\w+/.*/ // @include /^https://(sukebei\.)?nyaa\.\w+/view/.*/ // @match https://oldtoons.world/ // @match https://oldtoons.world/*/bookmarks // @match https://oldtoons.world/playlists/* // @match https://oldtoons.world/torrents* // @match https://orpheus.network/artist.php?id=* // @match https://orpheus.network/bookmarks.php* // @match https://orpheus.network/collages.php?id=* // @match https://orpheus.network/top10.php* // @match https://orpheus.network/torrents.php* // @match https://passthepopcorn.me/torrents.php?id=* // @match https://portugas.org/ // @match https://portugas.org/*/bookmarks // @match https://portugas.org/playlists/* // @match https://portugas.org/torrents* // @match https://privatehd.to/ // @match https://privatehd.to/torrent* // @match https://privatehd.to/*/bookmark* // @match https://redacted.sh/artist.php?id=* // @match https://redacted.sh/bookmarks.php* // @match https://redacted.sh/collage*.php?id=* // @match https://redacted.sh/top10.php* // @match https://redacted.sh/torrents.php* // @match https://redacted.sh/userhistory.php?action=subscribed_collages // @match https://retro-movies.club/ // @match https://retro-movies.club/*/bookmarks // @match https://retro-movies.club/playlists/* // @match https://retro-movies.club/torrents* // @match https://secret-cinema.pw/artist.php?id=* // @match https://secret-cinema.pw/collages.php?id=* // @match https://secret-cinema.pw/top10.php* // @match https://secret-cinema.pw/torrents.php* // @match https://thegeeks.click/browse.php* // @match https://thegeeks.click/details.php?id=* // @match https://www.torrentleech.org/torrent* // @match https://tv-vault.me/torrents.php?id=* // ----------------------------------- Permissions -------------------------------------- // @grant GM_addStyle // @grant GM_getResourceText // @grant GM_getValue // @grant GM_listValues // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_xmlhttpRequest // ----------------------------------- Dependencies -------------------------------------- // @resource settingsPanelCSS https://raw.githubusercontent.com/WirlyWirly/quiCKIE/main/quiCKIE.css?raw=true // @resource presetsMenuCSS https://raw.githubusercontent.com/WirlyWirly/quiCKIE/main/contextMenu.css?raw=true // @require https://raw.githubusercontent.com/WirlyWirly/quiCKIE/main/contextMenu.js?raw=true // @require https://raw.githubusercontent.com/WirlyWirly/UserScripts/main/HelperScripts/simpleLogger.js // @require https://cdn.jsdelivr.net/gh/sizzlemctwizzle/GM_config@43fd0fe4de1166f343883511e53546e87840aeaf/gm_config.js // ----------------------------------- Development -------------------------------------- // localhost urls used during development. Easily served over http by MiniServe: https://github.com/svenstaro/miniserve // resource settingsPanelCSS http://localhost:12345/quiCKIE.css // resource presetsMenuCSS http://localhost:12345/ContextMenu.css // require http://localhost:12345/ContextMenu.js // ----------------------------------- Script Links -------------------------------------- // @updateURL https://raw.githubusercontent.com/WirlyWirly/quiCKIE/main/quiCKIE.user.js?raw=true // @downloadURL https://raw.githubusercontent.com/WirlyWirly/quiCKIE/main/quiCKIE.user.js?raw=true // // ==/UserScript== // Set to true to enable verbose console logging, useful during development or troubleshooting const verboseConsoleLogging = false // This string helps prevent various JavaScript oddities when working with variables 'use strict' // =================================== SETTINGS PANEL TRACKERS ====================================== // @quickieSettingsPanelTrackers const settingsPanelTrackers = [ // Keep this list alphabetical, as each tracker here will appear as a row in the quiCKIE settings panel // Each tracker requires 3 things; A TitleCase name, the homepage URL, and the primaryDomain of the tracker // Here are examples of identify the domain of a tracker... // https://broadcasthe.net/ --> broadcasthe // https://www.myanonamouse.net/ --> myanonamouse // https://sukebei.nyaa.si/ --> nyaa // ℹ️ If the tracker has more than one domain that it can be accessed from (common for public trackers), you may also include the 'otherDomains' property, which should consist of an array (list) of different domains. This will make it so that these domains all share the same tracker settings from within the quiCKIE settings panel. // otherDomains: ['domain1', 'domain2', 'domain3'], { trackerName: 'Aither', // @holy-elbow homepageURL: 'https://aither.cc', primaryDomain: 'aither', }, { trackerName: 'AlphaRatio', homepageURL: 'https://alpharatio.cc', primaryDomain: 'alpharatio', }, { trackerName: 'AnimeBytes', homepageURL: 'https://animebytes.tv', primaryDomain: 'animebytes', }, { trackerName: 'AnimeZ', // @holy-elbow homepageURL: 'https://animez.to', primaryDomain: 'animez', }, { trackerName: 'Anthelion', // @malefis homepageURL: 'https://anthelion.me', primaryDomain: 'anthelion', }, { trackerName: 'AvistaZ', // @fercats99 homepageURL: 'https://avistaz.to', primaryDomain: 'avistaz', }, { trackerName: 'BakaBT', homepageURL: 'https://bakabt.me', primaryDomain: 'bakabt', }, { trackerName: 'Beyond-HD', // @empUser homepageURL: 'https://beyond-hd.me', primaryDomain: 'beyond-hd', }, { trackerName: 'Bibliotik', homepageURL: 'https://bibliotik.me', primaryDomain: 'bibliotik', }, { trackerName: 'BitPorn', homepageURL: 'https://bitporn.eu', primaryDomain: 'bitporn', }, { trackerName: 'BroadcasTheNet', homepageURL: 'https://broadcasthe.net', primaryDomain: 'broadcasthe', }, { trackerName: 'CinemaZ', // @fercats99 homepageURL: 'https://cinemaz.to', primaryDomain: 'cinemaz', }, { trackerName: 'ClearJAV', // @holy-elbow homepageURL: 'https://clearjav.com', primaryDomain: 'clearjav', }, { trackerName: 'DeepBassNine', // @tartuffe homepageURL: 'https://www.deepbassnine.com', primaryDomain: 'deepbassnine', }, { trackerName: 'DigitalCore', // @holy-elbow homepageURL: 'https://digitalcore.club/', primaryDomain: 'digitalcore', }, { trackerName: 'E-Hentai', // @holy-elbow homepageURL: 'https://e-hentai.org', primaryDomain: 'e-hentai', otherDomains: ['exhentai'], }, { trackerName: 'Empornium', homepageURL: 'https://emparadise.rs', primaryDomain: 'empornium', otherDomains: ['emparadise'] }, { trackerName: 'ExoticaZ', // @fercats99 > @holy-elbow homepageURL: 'https://exoticaz.to', primaryDomain: 'exoticaz', }, { trackerName: 'Femdomcult', // @holy-elbow homepageURL: 'https://femdomcult.org', primaryDomain: 'femdomcult', }, { trackerName: 'GazelleGames', homepageURL: 'https://gazellegames.net', primaryDomain: 'gazellegames', }, { trackerName: 'HappyFappy', // @empUser homepageURL: 'https://www.happyfappy.org', primaryDomain: 'happyfappy', }, { trackerName: 'HDBits', homepageURL: 'https://hdbits.org', primaryDomain: 'hdbits', }, { trackerName: 'IP-Torrents', homepageURL: 'https://iptorrents.me', primaryDomain: 'iptorrents', }, { trackerName: 'JPopsuki', // @tartuffe homepageURL: 'https://jpopsuki.eu', primaryDomain: 'jpopsuki', }, { trackerName: 'Karagarga', // @fercats99 homepageURL: 'https://karagarga.in', primaryDomain: 'karagarga', }, { trackerName: 'Kufirc', // @holy-elbow homepageURL: 'https://kufirc.com', primaryDomain: 'kufirc', }, { trackerName: 'Lat-Team', homepageURL: 'https://lat-team.com', primaryDomain: 'lat-team', }, { trackerName: 'LST', // @LilithOfTheValley homepageURL: 'https://lst.gg', primaryDomain: 'lst', }, { trackerName: 'Luminarr', // @holy-elbow homepageURL: 'https://lumniarr.me', primaryDomain: 'luminarr', }, { trackerName: 'Materialize', homepageURL: 'https://materialize.is', primaryDomain: 'materialize', }, { trackerName: 'MoreThanTV', // @holy-elbow homepageURL: 'https://www.morethantv.me', primaryDomain: 'morethantv', }, { trackerName: 'MyAnonaMouse', homepageURL: 'https://www.myanonamouse.net', primaryDomain: 'myanonamouse', }, { trackerName: 'Nebulance', // @malefis homepageURL: 'https://nebulance.io', primaryDomain: 'nebulance', }, { trackerName: 'Nyaa', homepageURL: 'https://nyaa.si', primaryDomain: 'nyaa', }, { trackerName: 'Oldtoons', homepageURL: 'https://oldtoons.world', primaryDomain: 'oldtoons', }, { trackerName: 'Orpheus', homepageURL: 'https://orpheus.network', primaryDomain: 'orpheus', }, { trackerName: 'PassThePopcorn', homepageURL: 'https://passthepopcorn.me', primaryDomain: 'passthepopcorn', }, { trackerName: 'Portugas', // @Phreaker homepageURL: 'https://portugas.org', primaryDomain: 'portugas', }, { trackerName: 'PrivateHD', // @holy-elbow homepageURL: 'https://privatehd.to', primaryDomain: 'privatehd', }, { trackerName: 'Redacted', homepageURL: 'https://redacted.sh', primaryDomain: 'redacted', }, { trackerName: 'RetroMoviesClub', // @LilithOfTheValley homepageURL: 'https://retro-movies.club', primaryDomain: 'retro-movies', }, { trackerName: 'Secret-Cinema', // @tartuffe homepageURL: 'https://secret-cinema.pw', primaryDomain: 'secret-cinema', }, { trackerName: 'TheGeeks', homepageURL: 'https://thegeeks.click', primaryDomain: 'thegeeks', }, { trackerName: 'TorrentLeech', // @holy-elbow homepageURL: 'https://www.torrentleech.org', primaryDomain: 'torrentleech', }, { trackerName: 'TV-Vault', homepageURL: 'https://tv-vault.me', primaryDomain: 'tv-vault', }, ] // =================================== quiCKIE SETTINGS ====================================== // The domain of the current site, which MUST be registerd to one of the trackers in the settingsPanelTrackers array // Example: https://broadcasthe.net/ --> broadcasthe let trackerDomain = document.location.hostname.match(/^(\w+\.)?(.+?)\..+$/)[2].toLowerCase() // A simple logger, which will only console log messages when it has been enabled above let logger = new simpleLogger({ enabled: verboseConsoleLogging, scriptName: 'quiCKIE'}) // Everything related to the GM_config library, which is used for saving and creating the settings panel: https://github.com/sizzlemctwizzle/GM_config let [ primaryDomain, allPrimaryDomains, allTrackerNames, primaryDomainToTrackerName, primaryDomainToHomepage, trackerNameToPrimaryDomain, presetCount ] = createGMConfigSettingsPanel(trackerDomain) // Retrieve the settings and presetMenuItems that are relevant to the current tracker let SETTINGS = getTrackerSettings(primaryDomain) let presetMenuItems = createPresetItems([SETTINGS.primaryDomain]) // All the emojis that may be displayed on bunnyButtons, defined as a RegExp so that they can be replaced during different stages of the script const emojiRegex = new RegExp('🐰|🌱|🍁|πŸ“’|πŸ’Ž|πŸ’Έ|🀝|🌎|🧲|πŸ§‘|πŸ•“|βœ”οΈ|❌|πŸ’Ύ|πŸ§€', 'g') // The full URL and Path of the current page, useful for figuring out exactly what page you are on using pageURL.match(/regex/) const pageURL = document.URL const pagePath = document.location.pathname // =================================== TRACKER SPECIFIC HANDLING ====================================== // @trackerSpecificHandling // Because the primaryDomain is unique for each quiCKIE supported tracker, we can use it to determine what tracker this is and how to proceed from there if ( primaryDomain == 'animebytes' ) { // ----------------------------------- AnimeBytes ----------------------------------- // Bookmarks | Browse | Collages | Company | Seeding\Snatched\Leeching | Series let trackerHandlingOptions = { // ------------------------- REQUIRED ------------------------- // A valid CSS selector that is unique to ONLY the download elements (download buttons) on the page downloadElementsSelector: 'a[href^="/torrent/"][title="Download torrent"]', // --------------------------OPTIONAL ------------------------- // The following properties are all optional and can be applied on a per-tracker basis or when appropriate to do so depending on the page of the tracker // ℹ️ Tip: To apply options on a per-page basis, use an 'if' statement to determine the current page based on the pageURL. From there, you can apply the options relevant to that page (See the BroadCasTheNet\Empornium\IP-Torrents\MyAnonaMouse\Nyaa blocks for examples) // - - - - - - - - - PRESENTATION - - - - - - - - - // Options to assist with the visual presentation\styling of bunnyButtons // The text that will be displayed by EVERY bunnyButton, useful for removing the surrounding whitespace included with the default setting, or when performing advanced styling bunnyButtonText: ' 🐰 ', // Default = ' 🐰 ' || Options = Any string // The font-size of EVERY bunnyButton, useful for re-sizing them to better fit the page bunnyButtonfontSize: 'inherit', // Default = 'inherit' || Options = Any percentile | A valid 'font-size' css value // Additional CSS style properties that will be applied to EVERY bunnyButton, useful for advanced styling (see the BakabT\HDBits\MyAnonaMouse\Nyaa blocks for examples) bunnyButtonAddStyles: '', // Default = '' || Options = A string containing css style properties // Additional class names that will be applied to EVERY bunnyButton, useful for advanced styling bunnyButtonAddClasses: [], // Default = [] || Options = An array of strings // If EVERY bunnyButton should be placed alongside its respective downloadElement.parentElement, which may result in the bunnyButton appearing on the same row as the downloadElement bunnyButtonParentPlacement: false, // Default = false || Options = true | false // If the downloadElement.parentElement should be hidden When 'πŸ™ˆ Hide Download Button' (settings panel) and the above 'bunnyButtonParentPlacement' option are both enabled. Some sites, but not all, will show a noticable gap when both those options are enabled, so this options exists to try preventing that gap. downloadElementHideParentElementGap: false, // Default = false || Options = true | false // The separator used between EVERY bunnyButton and the downloadElement elementsSeparator: 'automatic', // Default = 'automatic' || options = 'automatic' | Any string | false // A function that will be called after all the bunnyButtons have been created, useful for advanced styling when certain bunnyButtons on the page have individual styling needs (see the BakaBT\Empornium\MyAnonaMouse blocks for examples) // The provided 'elements' parameter will be an object consisting of three arrays: elements = { bunnyButtons: [], downloadElements: [], pairedElements:[] } // ⚠️ quiCKIE is a NON-DESTRUCTIVE UserScript that does NOT break or destroy the default site elements. This ensures quiCKIE is friendly\compatible with other UserScripts. Always adhere to this principle by only manipulating the bunnyButtons created by quiCKIE itself afterBunnyButtonCreation: false, // Default = false || options = false | function(elements) {...} // - - - - - - - - - EMOJIOGRAPHY - - - - - - - - - // Options that allow you to add "Emojiography" support to a tracker by providing a string representing a JavaScript comparison. If that check completes successfully and is 'true', it indicates a torrent is seeding\snatched\freeleech and as a result will have its bunnyButton emoji updated to reflect this // The string may start with 'downloadElement' (representing a DL button) then be followed by a chain of '.closest()' and\or '.querySelector()' methods in order to locate a 'targetElement' (which refers to a element in the HTML indicating the torrent has a certain status). If the targetElement is found, the check is considered to be 'true' // See the AnimeBytes\BroadcasTheNet\Empornium\Orpheus\PassThePopcorn\Redacted blocks for examples // ℹ️ Tip: If the targetElement has a unique .textContent but not attributes, one option is to perform a `targetElement.textContent.match(/regex/)`. If the regex match is found, the check is considered to be 'true' (see the MyAnonaMouse\Redacted block for examples) // ℹ️ Tip: When inspecting the page from your browser, right-click the DL button HTML element and select the 'Use in Console' option. This will create a variable in your console that you can use to test your chaining. Once you have a working chain that to locate the target element, you can use it for these options. // A string representing a JavaScript comparison, that if 'true' indicates a torrent has the status of 'seeding', so the bunnyButton emoji will be changed to '🌱' seedingStatusSelector: null, // Default = null || Options = 'downloadElement...' // A string representing a JavaScript comparison, that if 'true' indicates a torrent has the status of 'snatched', so the bunnyButton emoji will be changed to '🍁' snatchedStatusSelector: `downloadElement.closest('td').querySelector('a.snatched-torrent')`, // Default = null || Options = 'downloadElement...' // A string representing a JavaScript comparison, that if 'true' indicates a torrent has the status of 'freeleech', so the bunnyButton emoji will be changed to 'πŸ’Ž' freeleechStatusSelector: `downloadElement.closest('td').querySelector('img[alt^="Freeleech"]')`, // Default = null || Options = 'downloadElement...' // A string representing a JavaScript comparison, that if 'true' indicates a torrent has the status of 'featured', so the bunnyButton emoji will be changed to 'πŸ“’' featuredStatusSelector: null, // Default = null || Options = 'downloadElement...' // - - - - - - - - - PAGINATION - - - - - - - - - // Options that can assist when the tracker has pagination, which is when the page does not perform a full refresh for new DL buttons, which results in those new DL buttons that are loaded into the page not having bunnyButtons //⚠️ You should not enable or use these options unless the tracker you are adding actually has pagination, in which case the URL's with pagination should be filtered through an if check so as to not apply these options to the entire site: pageURL.match(/pageURLRegex/) ? trackerHandlingOptions.enablePaginationLooping = true : null // If quiCKIE should repeatedly check for new download elements, which works as a simple approach for handling pagination enablePaginationLooping: false, // Default = false || Options = true | false // - - - - - - - - - OTHER - - - - - - - - - // Options to perform a vareity of tasks that may or may not apply to certain trackers // The element that will be used as the root when performing .querySelectorAll(downloadElementsSelector) to find all the downloadElements queryFromElement: document, // Default = document || Options = document | A specific element (node) // The name of the downloadElement attribute that contains the torrentURL downloadElementsTorrentURLAttribute: 'href', // Default = 'href' || Options = A string matching a attribute name of the download element // If quiCKIE should ALWAYS download the .torrent file through the browser before sending it to the torrent client, useful if the torrentURL authentication doesn't actually work // Magnet links are ALWAYS sent directly to the torrent client, as they are not proper http links that can be downloaded through the browser forceTorrentFile: false, // Default = false || Options = true | false // If quiCKIE should attach the right-click presetsMenu to the new bunnyButtons, useful for advanced presetsMenu manipulation bunnyButtonAttachPresetsMenu: true, // Default = true || Options = true | false } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'aither' ) { // ----------------------------------- Aither ----------------------------------- // Bookmarks | Browse | Details | Playlists unit3dTrackerHandler('a[href*="/download"]') } else if ( primaryDomain == 'alpharatio' ) { // ----------------------------------- AlphaRatio ----------------------------------- // Browse | Details | Top 10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'animez' ) { // --------------------------------- AnimeZ ------------------------------------ // Home | Browse| Bookmarks let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="https://animez.to/torrents/"][href$="/download"]', } if ( pageURL.match(/\/torrents\/\d+/) ) { trackerHandlingOptions.bunnyButtonText = '🐰 quiCKIE' trackerHandlingOptions.bunnyButtonAddStyles = ` background: #4263eb; border-radius: 8px; border; 1px solid transparent; color: #f9fafb; box-shadow: 0 1px 1px rgba(229, 231, 235, .06); padding: 0.6875rem 1.5rem; font-size: 1rem; font-weight: 500; line-height: 1.5rem; ` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'anthelion' ) { // ----------------------------------- Anthelion ----------------------------------- // Browse | Collages | Film let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'avistaz' ) { // ----------------------------------- AvistaZ ----------------------------------- // Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="https://avistaz.to/download/torrent/"]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'bakabt' ) { // ----------------------------------- BakaBT ----------------------------------- // Details let trackerHandlingOptions = { downloadElementsSelector: 'a.download_link[href^="/download/"]', bunnyButtonFontSize: '140%', bunnyButtonText: '🐰 quiCKIE', freeleechStatusSelector: "document.querySelector('span.freeleech')", bunnyButtonAddStyles: ` background: #153245; border-radius: 4px; border: #B6D3E7 solid 2px; color: #B6D3E7; display: inline; font-weight: normal; margin: 0px 5px 0px 5px; padding: 3px 4px 4px 4px; vertical-align: super; `, afterBunnyButtonCreation: function(elements) { // The actions to take after the bunnyButtons have been created... // Decrease the width of the site DL button so that everything fits onto a single row elements.downloadElements[0].style.width = '395px' }, } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'beyond-hd' ) { // ----------------------------------- Beyond-HD ----------------------------------- // Browse | Details | Homepage | Library unit3dTrackerHandler('a[href^="https://beyond-hd.me/download/"]') } else if ( primaryDomain == 'bibliotik' ) { // ----------------------------------- Bibliotik ----------------------------------- // Browse | Details | Seeing\Snatched\Leeching\Uploads let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/torrents/"][title="Download"]', bunnyButtonFontSize: '120%', bunnyButtonAddStyles: 'vertical-align: bottom', // These status selectors are currently only working on the users seeding\snatched page seedingStatusSelector: `downloadElement.closest('tr').querySelector('a.seeders').nextSibling.baseURI.match(/seeding/)`, snatchedStatusSelector: `downloadElement.closest('tr').querySelector('a.snatches').nextSibling.baseURI.match(/snatches/)`, } // This is a details page, so apply styling to the only bunnyButton if ( pagePath.match(/\/torrents\/\d+/) ) { trackerHandlingOptions.bunnyButtonText = '🐰 quiCKIE' trackerHandlingOptions.bunnyButtonAddStyles = ` background: #153245 !important; border-radius: 3px; border: #B6D3E7 solid 1px; color: #B6D3E7; font-size: 90%; margin: 0px 2px 0px 8px; padding: 2px 5px 2px 5px; vertical-align: center; ` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'bitporn' ) { // ----------------------------------- BitPorn ----------------------------------- // Browse | Details unit3dTrackerHandler('a[href^="https://bitporn.eu/torrents/download/"]') } else if ( primaryDomain == 'broadcasthe' ) { // ----------------------------------- BroadcasTheNet ----------------------------------- // Browse | Series | Season\Episodes let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', seedingStatusSelector: "downloadElement.closest('td').querySelector('a.tor_highlight_seed')", snatchedStatusSelector: "downloadElement.closest('td').querySelector('a.tor_highlight_snatch')", } // This is a browse page (not Season\Episode), so apply styling to all bunnyButtons if ( pageURL.match(/torrents\.php(?!\?id=\d+)/) ) { trackerHandlingOptions.afterBunnyButtonCreation = function(elements) { // The actions to take after the bunnyButtons have been created... // Style all bunnyButtons, giving them a square-type look to more closely match the site buttons for ( let bunnyButton of elements.bunnyButtons ) { bunnyButton.textContent = '🐰' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText} background: #153245; border-radius: 3px; border: #B6D3E7 solid 1px; color: #B6D3E7; font-size: 80%; padding: 2px 2px 2px 2px; vertical-align: unset;`) } } } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'cinemaz' ) { // ----------------------------------- CinemaZ ----------------------------------- // Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="https://cinemaz.to/download/torrent/"]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'clearjav' ) { // ----------------------------------- ClearJAV ----------------------------------- // Bookmarks | Browse | Details | Movies| Playlists unit3dTrackerHandler('a[href^="https://clearjav.com/torrents/download/"]') } else if ( primaryDomain == 'deepbassnine' ) { // ----------------------------------- DeepBassNine ----------------------------------- // Album | Artist | Browse let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'digitalcore' ) { // ---------------------------------- DigitalCore --------------------------------- // Browse | Details | Home if ( pageURL.match(/\/torrent\/\d+/) ) { // The torrent details page let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/api/v1/torrents/download"]:has(i.fa-download)', bunnyButtonText: '🐰 quiCKIE', bunnyButtonAddStyles: ` background: #252525; border-radius: 3px; border: #323232 solid 1px; color: #ccc; font-size: 12px; margin-left: 11.033px; padding: 1px 39.817px; vertical-align: middle; ` } let observer = new MutationObserver(function(mutations) { quickieTrackerHandler(trackerHandlingOptions) }) let target = document.getElementById('contentContainer') let config = { childList: true } observer.observe(target, config) } else if ( pagePath.match(/^\/?$/) ) { // The homepage let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/api/v1/torrents/download"]', enablePaginationLooping: true, } quickieTrackerHandler(trackerHandlingOptions) } else { // The browse page let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/api/v1/torrents/download"]', } let pageObserver = new MutationObserver(async function(pageMutations) { // Wait until the of is loaded... let tbodyElement = await waitForElement('torrents-table[torrents] tbody', document.getElementById('contentContainer')) try { let torrentObserver = new MutationObserver(function(torrentMutations) { // The actions to take when there are changes to the quickieTrackerHandler(trackerHandlingOptions) }) torrentObserver.observe(tbodyElement, { childList: true }) } catch(error) { return } }) let target = document.getElementById('contentContainer') let config = { childList: true } pageObserver.observe(target, config) } } else if ( primaryDomain == 'e-hentai' ) { // ----------------------------------- E-Hentai ----------------------------------- // Torrents let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="https://ehtracker.org/get/"]', } if ( pageURL.match(/exhentai/) ) { trackerHandlingOptions.downloadElementsSelector = 'a[href^="https://exhentai.org/torrent/"]' } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'empornium' ) { // ----------------------------------- Empornium ----------------------------------- // Browse | Collages | Details | Top10 | UserProfile let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/torrents.php?action=download&id="]', bunnyButtonFontSize: '130%', seedingStatusSelector: "downloadElement.parentElement.querySelector('i.torrent_icons.seeding')", snatchedStatusSelector: "downloadElement.parentElement.querySelector('i.torrent_icons.snatched')", freeleechStatusSelector: "downloadElement.closest('span.torrent_icon_container').querySelector('i.font_icon.unlimited_leech')" } // This is a collage page, so place the bunnyButton alongside the parentElement if ( pageURL.match(/\/collage\/\d+/) ) { trackerHandlingOptions.bunnyButtonParentPlacement = true trackerHandlingOptions.downloadElementHideParentElementGap = true } // This is a details page, so apply styling to certain bunnyButtons if ( pageURL.match(/torrents\.php\?id=\d+/) ) { trackerHandlingOptions.afterBunnyButtonCreation = function(elements) { // The actions to take after the bunnyButtons have been created... // The main downloadElement, which will be referenced to determine the current torrentStatus let mainDownloadElement = document.querySelector(`#user-sidebar ${trackerHandlingOptions.downloadElementsSelector}`) for ( let bunnyButton of elements.bunnyButtons ) { // Style the bunnyButtons that match this CSS selector, giving them a bar-type look to more closely match the larger site buttons if ( bunnyButton.matches('span.torrent_buttons a.quickie_bunnyButton, #user-sidebar > a.quickie_bunnyButton') ) { bunnyButton.setAttribute('style', `${bunnyButton.style.cssText} border-radius: 5px; font-size: 100%; font-weight: Bold; margin: 0px 8px 0px 0px; padding: 4px 10px 4px 10px; vertical-align: middle;`) if ( bunnyButton.dataset.torrenturl.match(/&usetoken=1/) ) { // This is a Freeleech button bunnyButton.textContent = 'πŸ’Έ Freeleech' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}border: #A0DA83 solid 1px; color: #A0DA83; background: #113400;`) bunnyButton.setAttribute('data-emojospecified', 'true') } else if ( bunnyButton.dataset.torrenturl.match(/&usetoken=2/) ) { // This is a Doubleseed button bunnyButton.textContent = '🌱 Doubleseed' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}border: #F09D63 solid 1px; color: #F09D63; background: #431C00`) bunnyButton.setAttribute('data-emojospecified', 'true') } else { // This is a standard Download button bunnyButton.textContent = '🐰 quiCKIE' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}border: #B6D3E7 solid 1px; color: #B6D3E7; background: #153245;`) // Check for torrentStatus relative to the mainDownloadElement mainDownloadElement.closest('.torrent_icon_container').querySelector('i.font_icon.unlimited_leech') != null ? bunnyButtonTorrentStatus(bunnyButton, 'freeleech') : null // Freeleech mainDownloadElement.querySelector('i.torrent_icons.snatched') != null ? bunnyButtonTorrentStatus(bunnyButton, 'snatched') : null // Snatched mainDownloadElement.querySelector('i.torrent_icons.seeding') != null ? bunnyButtonTorrentStatus(bunnyButton, 'seeding') : null // Seeding } } } } } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'exoticaz' ) { // ----------------------------------- ExoticaZ ----------------------------------- // Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="https://exoticaz.to/download/torrent/"]', } if ( pageURL.match(/\/torrent\/\d+/) ) { // This is a details page, so apply styling to the only bunnyButton trackerHandlingOptions.bunnyButtonText = '🐰 quiCKIE' trackerHandlingOptions.bunnyButtonAddStyles = ` background: #e94a93; border-radius: 0; border: #e8e6e3 solid 1px; border: 1px solid transparent; color: #e8e6e3; display: inline-block; font-family: 'Roboto'; font-size: .8203125rem; font-weight: 400 line-height: 1; margin-left: 5px; margin-right: 1px; margin-top: 3px; padding: .2rem .7rem; ` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'femdomcult' ) { // ----------------------------------- Femdomcult ----------------------------------- // Bookmarks | Browse | Collages | Details | let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/torrents.php?action=download&id="]', bunnyButtonFontSize: '125%', bunnyButtonParentPlacement: true, } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'gazellegames' ) { // ----------------------------------- GazelleGames ----------------------------------- // Browse | Bundles | Game let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', seedingStatusSelector: "downloadElement.closest('td').querySelector('#color_seeding')", snatchedStatusSelector: "downloadElement.closest('td').querySelector('#color_snatched')", freeleechStatusSelector: "downloadElement.closest('td').querySelector('.freeleech_label, .personal_freeleech_label')", } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'happyfappy' ) { // ----------------------------------- HappyFappy ----------------------------------- // Browse | Collages | Details | Top10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/torrents.php?action=download&id="]', bunnyButtonFontSize: '125%', bunnyButtonText: '🐰', seedingStatusSelector: `downloadElement.querySelector('span.icon_disk_seed')`, snatchedStatusSelector: `downloadElement.querySelector('span.icon_disk_snatched')`, freeleechStatusSelector: `downloadElement.parentElement.querySelector('img[alt="Freeleech"]')`, } // This is a details page, so apply styling to certain bunnyButtons if ( pageURL.match(/torrents\.php\?id=\d+/) ) { trackerHandlingOptions.afterBunnyButtonCreation = function(elements) { // The actions to take after the bunnyButtons have been created... // The main downloadElement, which will be referenced to determine the current torrentStatus let mainDownloadElement = document.querySelector(`#user-sidebar ${trackerHandlingOptions.downloadElementsSelector}`) for ( let bunnyButton of elements.bunnyButtons ) { // Style the bunnyButtons that match this CSS selector, giving them a bar-type look to more closely match the larger site buttons if ( bunnyButton.matches('span.torrent_buttons a.quickie_bunnyButton, #user-sidebar > a.quickie_bunnyButton') ) { bunnyButton.setAttribute('style', `${bunnyButton.style.cssText} border-radius: 5px; font-size: 125%; font-weight: Bold; margin: 0px 8px 0px 0px; padding: 4px 10px 4px 10px; vertical-align: auto;`) if ( bunnyButton.dataset.torrenturl.match(/&usetoken=1/) ) { // This is a Freeleech button bunnyButton.textContent = 'πŸ’Έ Freeleech' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}border: #A0DA83 solid 1px; color: #A0DA83; background: #113400;`) bunnyButton.setAttribute('data-emojospecified', 'true') } else if ( bunnyButton.dataset.torrenturl.match(/&usetoken=2/) ) { // This is a Doubleseed button bunnyButton.textContent = '🌱 Doubleseed' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}border: #F09D63 solid 1px; color: #F09D63; background: #431C00`) bunnyButton.setAttribute('data-emojospecified', 'true') } else { // This is a standard Download button bunnyButton.textContent = '🐰 quiCKIE' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}border: #B6D3E7 solid 1px; color: #B6D3E7; background: #153245;`) // Check for torrentStatus relative to the mainDownloadElement mainDownloadElement.parentElement.querySelector('img[alt="Freeleech"]') != null ? bunnyButtonTorrentStatus(bunnyButton, 'freeleech') : null // Freeleech mainDownloadElement.querySelector('span.icon_disk_snatched') != null ? bunnyButtonTorrentStatus(bunnyButton, 'snatched') : null // Snatched mainDownloadElement.querySelector('span.icon_disk_seed') != null ? bunnyButtonTorrentStatus(bunnyButton, 'seeding') : null // Seeding } } } } } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'hdbits' ) { // ----------------------------------- HDBits ----------------------------------- // Browse | Details | Film let trackerHandlingOptions = { downloadElementsSelector: 'a.js-download[href^="/download.php/"]', bunnyButtonFontSize: '140%', bunnyButtonText: '🐰', featuredStatusSelector: `downloadElement.closest('tr').className.match(/featured/i)`, freeleechStatusSelector: "downloadElement.closest('td').querySelector('a.fl')", } // This is a details page, so apply styling to the only bunnyButton if ( pageURL.match(/details\.php\?id=\d+/) ) { trackerHandlingOptions.seedingStatusSelector = "downloadElement.closest('td').querySelector('span.tag_seeding')" trackerHandlingOptions.snatchedStatusSelector = "downloadElement.closest('td').querySelector('span.tag_completed')" trackerHandlingOptions.freeleechStatusSelector = "downloadElement.closest('td').querySelector('span.tag.freeleech')" trackerHandlingOptions.bunnyButtonText = '🐰 quiCKIE' trackerHandlingOptions.bunnyButtonAddStyles = ` background: #153245; border-radius: 3px; border: #B6D3E7 solid 1px; color: #B6D3E7; font-size: 90%; margin: 0px 2px 0px 8px; padding: 2px 5px 2px 5px; vertical-align: unset; ` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'iptorrents' ) { // ----------------------------------- IP-Torrents ----------------------------------- // Browse | Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href*="download.php"]', bunnyButtonFontSize: '160%', bunnyButtonText: '🐰', } if ( pageURL.match(/(torrent|details)\.php\?id=\d+/) ) { // This is a details page, so apply styling to certain bunnyButtons trackerHandlingOptions.afterBunnyButtonCreation = function(elements) { // The actions to take after the bunnyButtons have been created... let firstBunnyButton = true for ( bunnyButton of elements.bunnyButtons ) { // The first bunnyButton, so apply special styling if ( firstBunnyButton == true ) { bunnyButton.setAttribute('style', `${bunnyButton.style.cssText} background: #153245; border-radius: 999px; border: #B6D3E7 solid 1px; font-size: 500%; margin: 5px 5px 5px 5px; padding: 20px; vertical-align: middle;`) // Check if this torrent is freeleech bunnyButton.closest('div.info').querySelector('span.free') ? bunnyButtonTorrentStatus(bunnyButton, 'freeleech') : null firstBunnyButton = false continue } // Check is this torrent is freeleech bunnyButton.closest('tr').querySelector('span.free') ? bunnyButtonTorrentStatus(bunnyButton, 'freeleech') : null } } } else { // This is NOT a details page, so do a regular torrentStatus check trackerHandlingOptions.freeleechStatusSelector = `downloadElement.closest('tr').querySelector('span.free')` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'jpopsuki' ) { // ----------------------------------- JpopSuki ----------------------------------- // Album | Artist | Browse let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'karagarga' ) { // ----------------------------------- Karagarga ----------------------------------- // Browse | Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/down.php/"]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'kufirc' ) { // ----------------------------------- Kufirc ----------------------------------- // Browse | Collages | Details | Top10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', bunnyButtonFontSize: "140%", } pageURL.match(/top10/) ? trackerHandlingOptions.enablePaginationLooping = true : null quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'lat-team' ) { // ----------------------------------- Lat-Team ----------------------------------- // Bookmarks | Browse | Details | Homepage | Playlists unit3dTrackerHandler('a[href^="https://lat-team.com/torrents/download"]') } else if ( primaryDomain == 'lst' ) { // ----------------------------------- LST ----------------------------------- // Bookmarks | Browse | Details | Homepage | Playlists | MediaHub unit3dTrackerHandler('a[href^="https://lst.gg/torrents/download"]') } else if ( primaryDomain == 'luminarr' ) { // ----------------------------------- Luminarr ----------------------------------- // Bookmarks | Browse | Details | Playlists unit3dTrackerHandler('a[href^="https://luminarr.me/torrents/download"]') } else if ( primaryDomain == 'materialize' ) { // ----------------------------------- Materialize ----------------------------------- // Browse | Collages | Details | Top10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href*="torrents.php?action=download&id="]', forceTorrentFile: true, } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'morethantv' ) { // ----------------------------------- MoreThanTV ----------------------------------- // Browse | Collages | Details | Top10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/torrents.php?action=download&id="]', elementsSeparator: '||', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'myanonamouse' ) { // ----------------------------------- MyAnonaMouse ----------------------------------- // Browse | Details | Homepage | Top10 if ( pageURL.match(/\/t\/\d+/) ) { // The book details page, which doesn't require a MutationObserver let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/tor/download.php/"][title*="Download"]', seedingStatusSelector: "document.getElementById('DLhistory').textContent.match(/seeding/i)", snatchedStatusSelector: "document.getElementById('DLhistory').textContent.match(/seeder/i)", freeleechStatusSelector: "document.getElementById('ratio').textContent.match(/freeleech/i)", bunnyButtonFontSize: '125%', bunnyButtonText: '🐰 quiCKIE', bunnyButtonAddStyles: ` background: #153245; border-radius: 8px; border: #B6D3E7 solid 1px; color: #B6D3E7; font-weight: Bold; margin: 0px 10px 0px 10px; padding: 3px 10px 5px 10px; vertical-align: middle; `, afterBunnyButtonCreation: function(elements) { // The actions to take after the bunnyButtons have been created... for ( let bunnyButton of elements.bunnyButtons ) { // If DL buttons are hidden, change bunnyButton display to block so the buttons are properly spaced apart SETTINGS.hideDL == true ? bunnyButton.style.display = 'block' : null // This is the Freeleech Wedge button, so apply different text\styles if ( bunnyButton.dataset.torrenturl.match(/tid=\d+&fl/) ) { bunnyButton.textContent = 'πŸ§€ Freeleech' bunnyButton.setAttribute('style', `${bunnyButton.style.cssText}background: #2E2400; border: #CBC29E solid 1px; color: #CBC29E;`) } } }, } quickieTrackerHandler(trackerHandlingOptions) } else { // The Search or Homepage, both of which require a MutationObserver let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/tor/download.php/"][title*="Download"]', bunnyButtonFontSize: '150%', bunnyButtonText: '🐰', seedingStatusSelector: "downloadElement.closest('tr').querySelector('div.browseAct').textContent.match(/Recently Seeding/i)", snatchedStatusSelector: "downloadElement.closest('tr').querySelector('div.browseInact')", freeleechStatusSelector: `downloadElement.closest('tr').querySelector('a[href$="&fl"][title*="Download"]') == null`, // There is no FL Wedge button, so this torrent must already be FL afterBunnyButtonCreation: function(elements) { // The actions to take after the bunnyButtons have been created... // Site download buttons are hidden, so change the Freelech Wedge button to cheese now that it won't conflict with the sites wedge button if ( SETTINGS.hideDL ) { for ( let bunnyButton of elements.bunnyButtons ) { bunnyButton.dataset.torrenturl.match(/tid=\d+&fl/) ? replaceEmojis(bunnyButton, 'πŸ§€') : null } } }, } let observer = new MutationObserver( function(mutations) { // Functionality to run when changes are detected to the target element quickieTrackerHandler(trackerHandlingOptions) }) let target = document.getElementById('ssr') // Search table pageURL.match(/\/top10Tor\.php/) ? target = document.getElementById('top10') : null // Top 10 let config = { childList: true } observer.observe(target, config) } } else if ( primaryDomain == 'nebulance' ) { // ----------------------------------- Nebulance ----------------------------------- // Bookmarks | Browse | Top 10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', bunnyButtonFontSize: '115%', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'nyaa' ) { // ----------------------------------- Nyaa ----------------------------------- // Browse | Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/download/"][href$=".torrent"]', } // The Details page, so apply styling to the single bunnyButton if ( pageURL.match(/view\/\d+/) ) { trackerHandlingOptions.bunnyButtonText = '🐰 quiCKIE' trackerHandlingOptions.bunnyButtonAddStyles = ` background: #153245; border-radius: 3px; border: #B6D3E7 solid 1px; color: #B6D3E7; font-size: 90%; margin: 0px 2px 0px 8px; padding: 2px 5px 2px 5px; vertical-align: top; ` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'oldtoons' ) { // ----------------------------------- OldToons ----------------------------------- // Browse | Details | Homepage | Playlists | Similar unit3dTrackerHandler('a[href^="https://oldtoons.world/torrents/download/"]') } else if ( primaryDomain == 'orpheus' ) { // ----------------------------------- Orpheus ----------------------------------- // Album | Artist | Browse | Collages let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', seedingStatusSelector: "downloadElement.closest('td').querySelector('strong.tl_seeding')", snatchedStatusSelector: "downloadElement.closest('td').querySelector('strong.tl_snatched')", freeleechStatusSelector: "downloadElement.closest('td').querySelector('strong.tl_free')" } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'passthepopcorn' ) { // ----------------------------------- PassThepopcorn ----------------------------------- // Film let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', seedingStatusSelector: `downloadElement.closest('td').querySelector('a.torrent-info-link[title="Seeding"]')`, snatchedStatusSelector: `downloadElement.closest('td').querySelector('a.torrent-info-link[title="Downloaded"]')`, freeleechStatusSelector: `downloadElement.closest('td').querySelector('a.torrent-info-link .torrent-info__download-modifier--free')` } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'portugas' ) { // ----------------------------------- Portugas ----------------------------------- // Browse | Album | Artist unit3dTrackerHandler('a[href^="https://portugas.org/torrents/download/"]') } else if ( primaryDomain == 'privatehd' ) { // ----------------------------------- PrivateHD ----------------------------------- // Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="https://privatehd.to/download/torrent/"]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'redacted' ) { // ----------------------------------- Redacted ----------------------------------- // Album | Artist | Bookmarks | Browse | Collages | Top10 let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', seedingStatusSelector: "downloadElement.closest('tr').querySelector('.tl_notice').textContent.match(/seeding/i)", snatchedStatusSelector: "downloadElement.closest('tr').querySelector('.tl_snatched')", freeleechStatusSelector: "downloadElement.closest('tr').querySelector('.tl_free')" } if ( !pageURL.match(/collages?\.php\?id=\d+/) ) { // This is NOT a collage page, so it doesn't require a MutationObserver quickieTrackerHandler(trackerHandlingOptions) } else { // This is a collage page, which loads DL buttons after the '+' button of the album is clicked (pagination). Setup nested observation. trackerHandlingOptions.downloadElementsSelector = `#discog_table tbody ${trackerHandlingOptions.downloadElementsSelector}` let pageObserver = new MutationObserver( async function(pageMutations) { // The actions to take when new PAGES are loaded // DL elements are already present, meaning the user has the account setting 'Torrent group display' toggled to 'Open' document.querySelector(trackerHandlingOptions.downloadElementsSelector) ? quickieTrackerHandler(trackerHandlingOptions) : null try { // Wait until the of a new page is loaded... var tbodyElement = await waitForElement('#discog_table tbody', document.getElementById('discog_table')) } catch (error) { // There was an error, likely this mutation did not contain the waitForElement query target logger.debug(error) return } try { let tbodyObserver = new MutationObserver( function() { // The actions to take when the '+' button of a is clicked, which will load the DL buttons onto the page quickieTrackerHandler(trackerHandlingOptions) }) tbodyObserver.observe(tbodyElement, { childList: true } ) } catch (error) { logger.debug(error) return } }) let target = document.querySelector('div[data-component="TorrentCollageView"]') let config = { childList: true } pageObserver.observe(target, config) } } else if ( primaryDomain == 'retro-movies' ) { // ----------------------------------- RetroMoviesClub ----------------------------------- // Browse | Details | Homepage | Playlists | Similar unit3dTrackerHandler('a[href^="https://retro-movies.club/torrents/download/"]') } else if ( primaryDomain == 'secret-cinema' ) { // ----------------------------------- Secret-Cinema ----------------------------------- // Artist (no DL links as of script creation) | Browse | Movie let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'thegeeks' ) { // ----------------------------------- TheGeeks ----------------------------------- // Browse | Details let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="download.php/"]', } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'torrentleech' ) { // ----------------------------------- TorrentLeech ----------------------------------- // Browse | Top let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="/download/"]', bunnyButtonFontSize: '200%', } if ( pageURL.match(/(browse|top)/) ) { // The Browse and Top pages, both of which have pagination trackerHandlingOptions.bunnyButtonParentPlacement = true trackerHandlingOptions.enablePaginationLooping = true } quickieTrackerHandler(trackerHandlingOptions) } else if ( primaryDomain == 'tv-vault' ) { // ----------------------------------- TV-Vault ----------------------------------- // Series let trackerHandlingOptions = { downloadElementsSelector: 'a[href^="torrents.php?action=download&id="]', } quickieTrackerHandler(trackerHandlingOptions) } else { // ----------------------------------- NONE ----------------------------------- console.error(`---------- ⚠️ quiCKIE ⚠️ ----------\n\nThere was no definied trackerHandler block for processing the current primaryDomain: ${primaryDomain}\n\nAll further quiCKIE code will be aborted as to prevent further errors.\n\nℹ️ If you are seeing this message while adding a new tracker, verify that you are using the correct primaryDomain in the 'else if' chain from step #3 of the 'Adding a New Tracker' guide`) throw new Error('Aborting further quiCKIE execution') } // =================================== THIRD-PARTY INTEGRATIONS ====================================== if ( SETTINGS.thirdPartyScan != 'Off' ) { // After a brief delay, query the document for any thirdParty '[data-quickie_torrenturl]' elements for which a bunnyButton should be created SETTINGS.thirdPartyDelay < 100 ? SETTINGS.thirdPartyDelay = 200 : null scanForThirdPartyTorrentURLS(SETTINGS.thirdPartyDelay) } // =================================== SCRIPT FUNCTIONS ====================================== function createGMConfigSettingsPanel(trackerDomain) { // Generate and initialize the GM_config settings panel. It has been done in this function for code cleanliness. // These array\objects will later allow us to easily cross-reference the settingsPanelTrackers data let allPrimaryDomains = [] let allTrackerNames = [] let primaryDomainToTrackerName = {} let primaryDomainToHomepage = {} let trackerNameToPrimaryDomain = {} let primaryDomain, registeredTracker = false for ( let tracker of settingsPanelTrackers ) { // Check the current trackerDomain against all the registered trackers to retrieve the trackers primaryDomain let trackerName = tracker.trackerName let settingsId = tracker.primaryDomain.toLowerCase().trim() let otherDomains = tracker.otherDomains // Populate the array\objects that will be returned and made global allPrimaryDomains.push(settingsId) allTrackerNames.push(trackerName.toLowerCase()) primaryDomainToTrackerName[settingsId] = trackerName primaryDomainToHomepage[settingsId] = tracker.homepageURL trackerNameToPrimaryDomain[trackerName.toLowerCase()] = settingsId if ( settingsId == trackerDomain ) { // This trackerDomain matches the primaryDomain primaryDomain = settingsId registeredTracker = true } else if ( otherDomains != undefined && otherDomains.includes(trackerDomain) ) { // This trackerDomain did not match the primaryDomain, but there are otherDomains and a match was found primaryDomain = settingsId registeredTracker = true } } logger.info(`primaryDomain: ${primaryDomain}`) // Verify that the trackerDomain is registered as an entry in the settingsPanelTrackers object, if not then abort all further exectuion of this script if ( registeredTracker == false ) { throw `---------- ⚠️ quiCKIE ⚠️ ----------\n\nThe domain of the current site if not registered to a tracker supported by quiCKIE: ${trackerDomain}\n\nAll further quiCKIE code will be aborted as to prevent further errors.\n\nℹ️ If you are seeing this message while adding a new tracker, verify that you have included this trackerDomain to a tracker in Step #2 of the 'Adding a New Tracker' guide` } // Determine the saved number of preset fields that should be generated in the settings panel and presets-menu let presetCount if ( GM_getValue('quiCKIE_config') !== undefined ) { // Parse the existing GM_config() settings object let quiCKIESettingsObject = JSON.parse(GM_getValue('quiCKIE_config')) // Get the previously specified presetCount to determine how many preset rows should be generated presetCount = quiCKIESettingsObject['presetCount'] } // New installs will not have a presetCount, so default to 3 presetCount == undefined ? presetCount = 3 : null logger.info(`presetCount: ${presetCount}`) // @trackerFieldGeneration // This array will later be used to generate the for each column in the settings panel. Create an entry in const trackerFieldSuffixes = ['category', 'savePath', 'tags', 'ratioLimit', 'seedTime', 'dlLimit', 'upLimit', 'instance', 'paginationLoop', 'leftClick', 'thirdPartyScan', 'hideDL', 'startPaused', 'subFolder', 'seqPieces', 'autoTMM', 'skipHash'] let gmConfigTrackerFields = {} for ( let primaryDomain of allPrimaryDomains ) { // For each tracker (primaryDomain) in the settingsPanelTrackers object, generate the fields that will be used by GM_config() to save\load settings for that primaryDomain // --- GM_config() Fields --- let generatedTrackerFields = { [`${primaryDomain}-${trackerFieldSuffixes[0]}`]: { 'label': primaryDomainToTrackerName[primaryDomain], 'type': 'text' }, [`${primaryDomain}-${trackerFieldSuffixes[1]}`]: { 'type': 'text' }, [`${primaryDomain}-${trackerFieldSuffixes[2]}`]: { 'type': 'text' }, [`${primaryDomain}-${trackerFieldSuffixes[3]}`]: { 'label': 'Ratio Limit', 'type': 'float', 'default': '' }, [`${primaryDomain}-${trackerFieldSuffixes[4]}`]: { 'label': 'Seed Time', 'type': 'int', 'default': '' }, [`${primaryDomain}-${trackerFieldSuffixes[5]}`]: { 'label': 'Download Limit', 'type': 'int', 'default': '' }, [`${primaryDomain}-${trackerFieldSuffixes[6]}`]: { 'label': 'Upload Limit', 'type': 'int', 'default': '' }, [`${primaryDomain}-${trackerFieldSuffixes[7]}`]: { 'label': 'Instance', 'type': 'int', 'default': '' }, [`${primaryDomain}-${trackerFieldSuffixes[8]}`]: { 'label': 'Pagination Loop', 'type': 'int', 'default': '' }, [`${primaryDomain}-${trackerFieldSuffixes[9]}`]: { 'type': 'select', 'options': ['Global', 'Tracker', 'Settings', 'Client', 'TorrentFile', 'Nothing'], 'default': 'Global', }, [`${primaryDomain}-${trackerFieldSuffixes[10]}`]: { 'type': 'select', 'options': ['Off', 'On', 'On + 🌎'], 'default': 'Off', }, [`${primaryDomain}-${trackerFieldSuffixes[11]}`]: { 'type': 'checkbox', 'default': false }, [`${primaryDomain}-${trackerFieldSuffixes[12]}`]: { 'type': 'checkbox', 'default': false }, [`${primaryDomain}-${trackerFieldSuffixes[13]}`]: { 'type': 'checkbox', 'default': false }, [`${primaryDomain}-${trackerFieldSuffixes[14]}`]: { 'type': 'checkbox', 'default': false }, [`${primaryDomain}-${trackerFieldSuffixes[15]}`]: { 'type': 'checkbox', 'default': false }, [`${primaryDomain}-${trackerFieldSuffixes[16]}`]: { 'type': 'checkbox', 'default': false } } gmConfigTrackerFields = {...gmConfigTrackerFields, ...generatedTrackerFields} } // @presetFieldGeneration const presetFieldSuffixes = ['preset', 'presetTrackers', 'category', 'savePath', 'tags', 'ratioLimit', 'seedTime', 'dlLimit', 'upLimit', 'instance', 'startPaused', 'subFolder', 'seqPieces', 'autoTMM', 'skipHash'] let gmConfigPresetsFields = {} for (let i = 1; i <= presetCount; i++) { // --- GM_config() Fields --- let genereatedPresetFields = { [`preset-${i}-${presetFieldSuffixes[0]}`]: { 'type': 'text' }, [`preset-${i}-${presetFieldSuffixes[1]}`]: { 'type': 'text' }, [`preset-${i}-${presetFieldSuffixes[2]}`]: { 'type': 'text' }, [`preset-${i}-${presetFieldSuffixes[3]}`]: { 'type': 'text' }, [`preset-${i}-${presetFieldSuffixes[4]}`]: { 'type': 'text', }, [`preset-${i}-${presetFieldSuffixes[5]}`]: { 'label': 'Ratio Limit', 'type': 'float', 'default': '' }, [`preset-${i}-${presetFieldSuffixes[6]}`]: { 'label': 'Seed Time', 'type': 'int', 'default': '' }, [`preset-${i}-${presetFieldSuffixes[7]}`]: { 'label': 'Download Limit', 'type': 'int', 'default': '' }, [`preset-${i}-${presetFieldSuffixes[8]}`]: { 'label': 'Upload Limit', 'type': 'int', 'default': '' }, [`preset-${i}-${presetFieldSuffixes[9]}`]: { 'label': 'Instance', 'type': 'int', 'default': '' }, [`preset-${i}-${presetFieldSuffixes[10]}`]: { 'type': 'checkbox', 'default': false }, [`preset-${i}-${presetFieldSuffixes[11]}`]: { 'type': 'checkbox', 'default': false }, [`preset-${i}-${presetFieldSuffixes[12]}`]: { 'type': 'checkbox', 'default': false }, [`preset-${i}-${presetFieldSuffixes[13]}`]: { 'type': 'checkbox', 'default': false }, [`preset-${i}-${presetFieldSuffixes[14]}`]: { 'type': 'checkbox', 'default': false } } gmConfigPresetsFields = {...gmConfigPresetsFields, ...genereatedPresetFields} } const panelTextData = { // The data that will be used as the '.textContent' and '.title' in the settings panel's elements. The key names are the '.toLowerCase()' of the items in the trackerFieldSuffixes array and the items in the presetFieldSuffixes array. 'globalsTitles': { 'torrentClient': "─── πŸ–₯️ Torrent Client πŸ–₯️ ───\n\nThe torrent client for where to send torrents\n\nNot all clients will support all the available quiCKIE settings\n\nquiCKIE was initially written for qui, with support for other clients being added much later on. As a result, the names of the various settings may not correlate exactly with what other clients would call them.", 'presetCount': "─── πŸš€ Presets πŸš€ ───\n\nThe number of presets that will be generated in the quiCKIE settings panel\n\n⚠️ Lowering this number will remove those rows, which in turn deletes their saved settings", 'globalLeftClickAction': "─── πŸ–±οΈ Left-Click \\ Tap πŸ–±οΈ ───\n\nThe action to take when performing a Left-Click\\Tap on a BunnyButton\n\nℹ️ Affects all trackers that have the 'πŸ–±οΈ' setting set to 'Global'", 'globalMiddleClickAction': '─── πŸ–±οΈ Middle-Click πŸ–±οΈ ───\n\nThe action to take when performing a Middle-Click on a BunnyButton', 'bunnyButtonPlacement': '─── ↔️ Placement ↔️ ───\n\nThe placement of the BunnyButtons relative to the sites download buttons', 'thirdPartyDelay': "─── 🀝 3rd Party Delay 🀝 ───\n\nThe delay in milliseconds to wait until scanning for third-party links that have quiCKIE integration\n\nℹ️ This only applies to trackers that have the '🀝' column enabled\n\n⚠️ Settings this too low can cause race issues between quiCKIE and the third-party UserScript, so the recommended time is +500ms", 'hiddenTrackers': "─── πŸ™ˆ Hidden trackers πŸ™ˆ ───\n\nA comma separated list of trackers to be removed from the quiCKIE settings panel\n\nHover over the names in the '🌎 Tracker' column for a 'πŸ™ˆ' button that will quickly add the tracker to this hidden list\n\nℹ️ This does not disable quiCKIE on those trackers, it simply hides the tracker from cluttering this settings Panel", 'globalForcedTorrentFile': '─── 🧲 Torrent File 🧲 ───\n\nForce all BunnyButtons to download the .torrent file through the browser before sending it to the client\n\nℹ️ By default, quiCKIE will determine for itself if the torrentURL can be sent directly to the client or should first be downloaded through the browser\n\nℹ️ Magnet links are ALWAYS sent directly to the client, as they are not proper http links that can be downloaded through the browser', 'quiURL': "─── πŸ”— quiURL πŸ”— ───\n\nThe full URL to a qui instance\n\nThis is usually the same URL you can copy-paste from your browser\n\nℹ️ Unless otherwise specified in the '🎯' column, this is the instance that all torrents will be sent to\n\nExample: http://localhost:7476/qui/instances/1\n\n────────────────\n\nSeedbox\\Swizzin users might try...\n\nhttps://username:password@seedboxDomain.com/qui/instances/1", 'quiApiKey': '─── πŸ”‘ ApiKey πŸ”‘ ───\n\nA valid and active ApiKey created by qui\n\nFrom the qui interface, you can generate a ApiKey by going to...\n\nSettings > API Keys > Create API Key', 'qBitTorrentURL': "─── πŸ”— qBitTorrentURL πŸ”— ───\n\nThe full URL to a running qBitTorrent service\n\nThis is usually the same URL you can copy-paste from your browser\n\nExample: http://localhost:8080", 'qBitTorrentUsername': '─── πŸ”‘ Username πŸ”‘ ───\n\nThe username for logging into qBitTorrent through the web interface', 'qBitTorrentPassword': '─── πŸ”‘ Password πŸ”‘ ───\n\nThe password for logging into qBitTorrent through the web interface', 'transmissionURL': "─── πŸ”— TransmissionURL πŸ”— ───\n\nThe full URL to a running Transmission service\n\nThis is usually the same URL you can copy-paste from your browser\n\nExample: http://localhost:9091\n\nℹ️ If Transmission is not using the default rpc, then specify the complete rpc url\n\nExample: http://localhost:9091/your/custom/rpc", 'transmissionUsername': '─── πŸ”‘ Username πŸ”‘ ───\n\nThe username for logging into Transmission through the web interface', 'transmissionPassword': '─── πŸ”‘ Password πŸ”‘ ───\n\nThe password for logging into Transmission through the web interface', 'delugeURL': "─── πŸ”— DelugeURL πŸ”— ───\n\nThe full URL to a running Deluge service\n\nThis is usually the same URL you can copy-paste from your browser\n\nExample: http://localhost:8112", 'delugePassword': '─── πŸ”‘ Password πŸ”‘ ───\n\nThe password for logging into Deluge through the web interface', 'ruTorrentURL': "─── πŸ”— ruTorrentURL πŸ”— ───\n\nThe full URL to a running ruTorrent service\n\nThis is usually the same URL you can copy-paste from your browser\n\nExample: http://localhost:8080", 'ruTorrentUsername': '─── πŸ”‘ Username πŸ”‘ ───\n\nThe username for logging into ruTorrent through the web interface', 'ruTorrentPassword': '─── πŸ”‘ Password πŸ”‘ ───\n\nThe password for logging into ruTorrent through the web interface', }, 'columnText': { 'tracker': '🌎 Tracker', 'preset': 'πŸš€ Name', 'presettrackers': 'πŸ‘€ Trackers', 'category': 'πŸ—ƒοΈ Category', 'savepath': 'πŸ’Ύ SavePath', 'tags': '🏷️ Tags', 'ratiolimit': 'βš–οΈ', 'seedtime': '🌱', 'dllimit': '⬇️', 'uplimit': '⬆️', 'instance': '🎯', 'paginationloop': 'πŸ”', 'leftclick': 'πŸ–±οΈ', 'thirdpartyscan': '🀝', 'hidedl': 'πŸ™ˆ', 'startpaused': '⏸️', 'subfolder': 'πŸ“', 'seqpieces': '🧩', 'autotmm': 'πŸ€–', 'skiphash': 'πŸ›‘οΈ', }, 'columnTitles': { 'tracker': `─── 🌎 Tracker 🌎 ───\n\nThe tracker (site) for which this row of settings will be applied to\n\nClicking a name below will open a new tab to the tracker's homepage\n\nℹ️ Hovering over a BunnyButton will provide a tooltip of the current tracker settings\n\n⭐ There is currently ${allPrimaryDomains.length} Supported Trackers!`, 'preset': "─── πŸš€ Name πŸš€ ───\n\nThe name that will be displayed in the presets menu (right-click)\n\nPresets without a name will NOT be displayed\n\nπŸŽͺ Special Entries: Naming your preset after one of these items will display a special menu entry...\nSettings, TorrentFile, Client, LeftClickAll, MiddleClickAll, Send, SendPaused\n\nUsing one of these characters will create a divider...\n. - = [space]\n\nℹ️ Hovering over a entry in the presets menu will provide a tooltip of the preset's settings", 'presettrackers': "─── πŸ‘€ Preset Trackers πŸ‘€ ───\n\nA comma seperated list of trackers on which to display this preset\n\nUse the name (case-insensitive) displayed in the '🌎 Tracker' column\n\nPresets without any trackers listed will NOT be displayed\n\nℹ️ Use the * wildcard to display this preset on ALL trackers\n\nExample: HDBits, secret-cinema, NYAA", 'category': '─── πŸ—ƒοΈ Category πŸ—ƒοΈ ───\n\nSpecify the category to apply to these these torrents', 'savepath': '─── πŸ’Ύ Save Path πŸ’Ύ ───\n\nSpecify the full-path for where to save these torrents\n\nℹ️ Specifying a save path implicitly disables ATMM (Auto Torrent Management Mode)\n\n⚠️ The path MUST be accessible and writable by the torrent client itself, otherwise it will use the default save path', 'tags': '─── 🏷️ Tags 🏷️ ───\n\nA comma seperated list of tags to apply to these torrents (case-sensitive)\n\nExample: Media, Movies, Private', 'ratiolimit': '─── βš–οΈ Ratio Limit βš–οΈ ───\n\nStop the torrents when they have seeded to the specified ratio limit\n\nℹ️ Use -1 to stop the torrents immediately after downloading is complete', 'seedtime': '─── 🌱 Seed Time 🌱 ───\n\nStop the torrents when they have seeded the specified number of minutes\n\nℹ️ Use -1 to stop the torrents immediately after downloading is complete\n\n⚠️ A clients reported seedtime and a trackers recorded seedtime are not always equal. Use caution to avoid Hit-and-Runs.', 'dllimit': '─── ⬇️ Download Limit ⬇️ ───\n\nThe speed limit in KB/s to apply when downloading these torrents', 'uplimit': '─── ⬆️ Upload Limit ⬆️ ───\n\nThe speed limit in KB/s to apply when uploading\\seeding these torrents', 'instance': '─── 🎯 Target Instance 🎯 ───\n\nSpecify a particular qui instance ID for where to send these torrents\n\nLeave this field blank to use the global instance saved as the quiURL\n\nℹ️ This does NOT support a full url, only a qui instance ID number', 'paginationloop': "─── πŸ” Pagination Loop πŸ” ───\n\nSpecify a time in milliseconds to repeatedly scan the page for new download buttons\n\nThis is useful for sites with pagination, which is when the browser does not do a full refresh between pages\\searches. Since the page is not actually refreshing, your UserScripts won't be triggered and you'll end up without BunnyButtons for the new DL buttons\n\nℹ️ For officially supported trackers, pagination should hopefully already be handled and thus make this setting unnecessary\n\n⚠️ You should NOT enable this setting unless you are on a site that actually has pagination and it isn't already handled by quiCKIE\n\n⚠️ Setting this too low can impact your browser, so the recommended time is +2000ms while the minimum is 500ms", 'thirdpartyscan': "─── 🀝 3rd Party Integrations 🀝 ───\n\nScan for third-party DL (Download) buttons with quiCKIE integration\n\nDevelopers of third-party UserScript may add quiCKIE integration to their UserScript. Enabling this setting will allow quiCKIE to check for such integrations.\n\nℹ️ On + 🌎: Allow third-party UserScripts to specify for which quiCKIE supported tracker their BunnyButtons should pull tracker settings from. If a tracker is not specified by the third-party UserScript, the settings for the current tracker will be used\n\n⚠️ You should NOT enable this feature unless you have installed a trusted UserScript that actually has quiCKIE integration", 'leftclick' : "─── πŸ–±οΈ Left-Click \\ Tap πŸ–±οΈ ───\n\nSpecify what action should be taken when the BunnyButton is left-clicked on a PC or tapped on a mobile\n\nℹ️ The 'Global' option will use the setting specified above", 'hidedl': "─── πŸ™ˆ Hide Download Button πŸ™ˆ ───\n\nHide the trackers download button from view\n\nThis will NOT apply to any DL buttons from third-party UserScripts\n\nℹ️ Hiding is not the same as removing. The button will still be there, it will just have a css style of 'display: none' applied making it hidden and unclickable. This may result in weird gaps\\results on some pages", 'startpaused': "─── ⏸️ Start Paused ⏸️ ───\n\nPause torrents when they are added so that they do not automatically begin downloading\n\nUseful for when you want to give yourself a chance to pick which files of the torrent should be downloaded\n\nℹ️ Performing a 'Shift-Ctrl-Click' on a BunnyButton or Preset will temporarily override those settings so that Start Paused is enabled", 'subfolder': '─── πŸ“ SubFolder πŸ“ ───\n\nFor single-file torrents, create a subfolder where the file will be saved into\n\nℹ️ This has no affect on torrents that are already in their own folder\n\nExample: audioBookFile.m4b --> audioBookFile/audioBookFile.m4b', 'seqpieces': '─── 🧩 Sequential Piece Download 🧩 ───\n\nDownload torrent pieces sequentially\n\nThis behaviour results in the files being downloaded in-order and also being capable of playback while downloading\n\n⚠️ This may impact download speeds', 'autotmm': "─── πŸ€– Auto Torrent Management πŸ€– ───\n\nFor these torrents, enable Auto Torrent Management\n\n⚠️ This will download the torrent to a folder based on the 'πŸ—ƒοΈ Category', ignoring whatever is specified in the 'πŸ’Ύ Save Path'", 'skiphash': '─── πŸ›‘οΈ Skip Hash Check πŸ›‘οΈ ───\n\nWhen Adding torrents, skip the initial hash check\n\n⚠️ Hash checks are used to verify file integrity and prevent corrupted data, although this check may take a long time with larger torrents. Know what you are doing before enabling this.', } } // The element the settings panel will be appended to, so that it's not a floating iFrame and can be inspected. let configFrame = document.createElement('div') document.body.appendChild(configFrame) let reloadWindow = false // The quiCKIE settings panel, which can then be displayed by calling 'GM_config.open()' GM_config.init({ 'id': 'quiCKIE_config', 'frame': configFrame, 'title': `
🐰 quiCKIE 🐰
β˜… Hover over emojis for details β˜…
`, 'fields': {...{ // Merge these two field objects so that GM_config reads them properly 'presetCount': { 'label': 'πŸš€ Presets:', 'type': 'int', 'default': 3, }, 'globalLeftClickAction': { 'label': 'πŸ–±οΈ Left-Click \\ Tap:', 'type': 'select', 'options': ['Tracker', 'Settings', 'Client', 'TorrentFile', 'Nothing'], 'default': 'Tracker', }, 'globalMiddleClickAction': { 'label': 'πŸ–±οΈ Middle-Click:', 'type': 'select', 'options': ['Tracker', 'Settings', 'Client', 'TorrentFile', 'Nothing'], 'default': 'Client', }, 'bunnyButtonPlacement': { 'label': '↔️ Placement:', 'type': 'select', 'options': ['After', 'Before'], 'default': 'After', }, 'globalForcedTorrentFile': { 'label': '🧲 Torrent File:', 'type': 'checkbox', 'default': false }, 'hiddenTrackers': { 'label': 'πŸ™ˆ Hidden Trackers:', 'type': 'text', 'default': '', }, 'thirdPartyDelay': { 'label': '🀝 Delay:', 'type': 'int', 'default': 2000, }, 'torrentClient': { 'label': 'πŸ–₯️ Client:', 'type': 'select', 'options': ['qui', 'qBitTorrent', 'Transmission', 'Deluge', 'ruTorrent πŸ› οΈ'], 'default': 'qui', }, 'settingsImport' : { 'label': 'Import Settings', 'type': 'button', 'size': 25, 'click': function() { // Prompt the user to select a file for which to import GM_config settings let inputElement = document.createElement('input'); inputElement.type = 'file'; inputElement.onchange = (event) => { // Getting a hold of the file reference let file = event.target.files[0] // Setting up the reader let reader = new FileReader() reader.readAsText(file,'UTF-8') // Here we tell the reader what to do when it's done reading... reader.onload = (readerEvent) => { let textContent = readerEvent.target.result // this is the content! try { // Parse the content of the selected file, making sure it is valid JSON let quickieSettings = JSON.stringify(JSON.parse(textContent)) GM_setValue('quiCKIE_config', quickieSettings) window.location.reload() } catch (error) { // The JSON parse has failed, so abort the import logger.error(error) window.alert(`🐰 quiCKIE\n\nThe imported settings file was not valid JSON\n\nThe settings were not imported`) } } } inputElement.click() }, }, 'settingsExport' : { 'label': 'Export Settings', 'type': 'button', 'size': 25, 'click': function() { // Export GM_Config settings to a file // Pretty-print the settings string to make it easier to read let jsonParsed = JSON.parse(GM_getValue('quiCKIE_config')) // convert preset-X-preset to preset-X-name (for v1.5) for (let i = 1; i <= presetCount; i++) { let nameValue = jsonParsed[`preset-${i}-preset`] jsonParsed[`preset-${i}-name`] = nameValue } let jsonString = JSON.stringify(jsonParsed, null, 4) // Save the quiCKIE settings to a local file saveToFile(jsonString, `quiCKIE-v${GM_info.script.version}-${new Date().toISOString().split('T')[0]}.json`) }, }, // ----- qui ----- 'quiURL': { 'label': 'πŸ”— qui:', 'type': 'text', }, 'quiApiKey': { 'label': 'πŸ”‘ ApiKey:', 'type': 'text', }, // ----- qBitTorrent ----- 'qBitTorrentURL': { 'label': 'πŸ”— qBitTorrent:', 'type': 'text', }, 'qBitTorrentUsername': { 'label': 'πŸ§‘ Username:', 'type': 'text', }, 'qBitTorrentPassword': { 'label': 'πŸ”‘ Password:', 'type': 'text', }, // ----- Transmission ----- 'transmissionURL': { 'label': 'πŸ”— Transmission:', 'type': 'text', }, 'transmissionUsername': { 'label': 'πŸ§‘ Username:', 'type': 'text', }, 'transmissionPassword': { 'label': 'πŸ”‘ Password:', 'type': 'text', }, // ----- Deluge ----- 'delugeURL': { 'label': 'πŸ”— Deluge:', 'type': 'text', }, 'delugePassword': { 'label': 'πŸ”‘ Password:', 'type': 'text', }, // ----- ruTorrent ----- 'ruTorrentURL': { 'label': 'πŸ”— ruTorrent:', 'type': 'text', }, 'ruTorrentUsername': { 'label': 'πŸ§‘ Username:', 'type': 'text', }, 'ruTorrentPassword': { 'label': 'πŸ”‘ Password:', 'type': 'text', }, 'welcomeMessage': { 'type': 'hidden', 'default': 'show', }, }, ...gmConfigTrackerFields, ...gmConfigPresetsFields}, 'events': { 'open': function (doc) { // Actions to take When GM_config.open() is called... reloadWindow = false let panelStyle = this.frame.style panelStyle.backdropFilter = 'blur(9px)' panelStyle.background = '#191d2aa3' panelStyle.border = '1px solid #2C3E50' panelStyle.borderRadius = '10px' panelStyle.boxShadow = '0px 0px 15px #2C3E50' panelStyle.color = '#ffffff' panelStyle.height = 'auto' panelStyle.inset = '50% auto auto 50%' panelStyle.lineHeight = '22px' panelStyle.margin = '0' panelStyle.maxHeight = '95%' panelStyle.padding = '0px 0px' panelStyle.position = 'fixed' panelStyle.transform = 'translate(-50%,-50%)' panelStyle.width = '1650px' panelStyle.overflowY = 'scroll' panelStyle.touchAction = 'pan-x pan-y' // ----------------------------------- TRACKERS TABLE ----------------------------------- // Convert the various primaryName
elements created by GM_config() into a with columns/rows let table = document.createElement('table') table.id = 'quiCKIE_config_tracker_table' table.classList.add('quiCKIE_config_table') let tcolg = document.createElement('colgroup') tcolg.id = 'quiCKIE_config_tracker_table_colg' tcolg.classList.add('quiCKIE_config_table_tcolg') table.appendChild(tcolg) let thead = document.createElement('thead') thead.id = 'quiCKIE_config_tracker_table_thead' thead.classList.add('quiCKIE_config_table_thead') table.appendChild(thead) let tbody = document.createElement('tbody') tbody.id = 'quiCKIE_config_tracker_table_tbody' tbody.classList.add('quiCKIE_config_table_tbody') table.appendChild(tbody) // Insert the
after the GM_config header document.getElementById('quiCKIE_config_header').insertAdjacentElement('afterend', table) // Generate the (column) and (tableHeader) element thead.appendChild(headersRow) for ( let panelDomain of allPrimaryDomains ) { // For each tracker, create 1 (tablerow). For each , create 1 for this tracker, appended it to the (tableBody) let tableRow = document.createElement('tr') tableRow.id = `quiCKIE_config_tracker_table_tr_${panelDomain}` tableRow.classList.add('quiCKIE_config_table_tbody_tr') tbody.appendChild(tableRow) // 1 let labelData = document.createElement('td') labelData.classList.add('quiCKIE_config_table_td_label') tableRow.appendChild(labelData) // The element to hold the trackerHomepage, append it to the . Move the GM_config field into that let dataElement = document.createElement('td') dataElement.classList.add('quiCKIE_config_table_tbody_td_field') dataElement.classList.add(`quiCKIE_config_table_td_${fieldSuffix}`) tableRow.appendChild(dataElement) // Move the GM_Config field into the
(table header) for each settings column let headersRow = document.createElement('tr') headersRow.classList.add('quiCKIE_config_table_thead_tr') let tableHeaders = [...['tracker'], ...trackerFieldSuffixes] for ( let columnHeader of tableHeaders ) { columnHeader = columnHeader.toLowerCase() let columnGroupElement = document.createElement('col') columnGroupElement.id = `quiCKIE_config_tracker_table_colg_col_${columnHeader}` columnGroupElement.classList.add(`quiCKIE_config_table_colg_col`) columnGroupElement.span = 1 tcolg.appendChild(columnGroupElement) let headerElement = document.createElement('th') headerElement.id = `quiCKIE_config_tracker_table_thead_th_${columnHeader}` headerElement.classList.add('quiCKIE_config_table_thead_th') headerElement.textContent = panelTextData.columnText[`${columnHeader}`] headerElement.setAttribute('title', panelTextData.columnTitles[`${columnHeader}`]) headersRow.appendChild(headerElement) } // Append the headers to the
(tabledata) to contain the tracker's hyperlink. Create the hyperlink then move the tracker's label into that element. // 1
to hold the trackerName for this tracker, appended it to the
let trackerHyperlinkElement = document.createElement('a') trackerHyperlinkElement.href = primaryDomainToHomepage[panelDomain] trackerHyperlinkElement.target = '_blank' labelData.appendChild(trackerHyperlinkElement) // Move the trackerLabel field into the hyperlink let trackerLabelElement = document.getElementById(`quiCKIE_config_${panelDomain}-category_field_label`) trackerLabelElement.removeAttribute('for') trackerLabelElement.classList.add('quiCKIE_config_field_tracker_label') trackerHyperlinkElement.appendChild(trackerLabelElement) // The field suffixes as specified in @trackerFieldGeneration for (let fieldSuffix of trackerFieldSuffixes) { // Create a for each input field and append it to the torrents
// Create the and append it to the torrents
let fieldElement = document.getElementById(`quiCKIE_config_field_${panelDomain}-${fieldSuffix}`) fieldElement.setAttribute('data-fieldtype', fieldSuffix) dataElement.appendChild(fieldElement) // Clean-up: Remove the now empty GM_config
element document.getElementById(`quiCKIE_config_${panelDomain}-${fieldSuffix}_var`).remove() } } // ----------------------------------- PRESETS TABLE ----------------------------------- if ( presetCount > 0 ) { // Convert the various preset
elements created by GM_config() into a with columns/rows table = document.createElement('table') table.id = 'quiCKIE_config_preset_table' table.classList.add('quiCKIE_config_table') tcolg = document.createElement('colgroup') tcolg.id = 'quiCKIE_config_preset_table_colg' tcolg.classList.add('quiCKIE_config_table_tcolg') thead = document.createElement('thead') thead.id = 'quiCKIE_config_preset_table_thead' thead.classList.add('quiCKIE_config_table_thead') tbody = document.createElement('tbody') tbody.id = 'quiCKIE_config_preset_table_tbody' tbody.classList.add('quiCKIE_config_table_tbody') table.appendChild(tcolg) table.appendChild(thead) table.appendChild(tbody) // Insert the
after the tracker table document.getElementById('quiCKIE_config_tracker_table').insertAdjacentElement('afterend', table) // Create the "Presets" header element let presetsHeader = document.createElement('div') presetsHeader.setAttribute('style', 'font-size: 20pt; text-align: center; padding: 0px 0px 10px 0px;') presetsHeader.innerHTML = ` πŸš€ Presets πŸš€ ` // presetsHeader.setAttribute('style', "text-align: center; background: none; text-shadow: 0 0 20px rgba(0, 124, 255, 0.60); padding: 10px 0px 10px 0px; font-family: 'Lilita One', 'Roboto Condensed', Tahoma, Geneva, sans-serif; font-size: 35px; font-weight: 400; font-style: normal; line-height: 30px") document.getElementById('quiCKIE_config_tracker_table').insertAdjacentElement('afterend', presetsHeader) // Generate (tableHeader) element thead.appendChild(headersRow) for ( let i = 1; i <= presetCount ; i++) { // For each preset, create 1 (tablerow). For each field of that preset, create 1 for this preset, appended to the (tableBody) let tableRow = document.createElement('tr') tableRow.classList.add('quiCKIE_config_table_tbody_tr') tbody.appendChild(tableRow) // The field suffixes as specified in @presetFieldGeneration for (let fieldSuffix of presetFieldSuffixes) { // Create a dataElement.appendChild(fieldElement) tableRow.appendChild(dataElement) // Clean-up: Remove the now empty GM_config element document.getElementById(`quiCKIE_config_preset-${i}-${fieldSuffix}_var`).remove() } } // Create the list of selectable items that appears when typing to the presetTrackers field let trackerTitles = Object.entries(primaryDomainToTrackerName).sort().map ( ([key, value]) => [value] ) let datalistElement = document.createElement('datalist') datalistElement.id = 'presetTrackersList' // Append the list somewhere nearby, in this case into the presetTrackers column document.getElementById('quiCKIE_config_preset_table_thead_th_presettrackers').appendChild(datalistElement) for ( let tracker of trackerTitles ) { let datalistItem = document.createElement('option') datalistItem.value = tracker datalistElement.appendChild(datalistItem) } let allPresetTrackersField = table.querySelectorAll('input[data-fieldtype="presetTrackers"]') for ( let presetField of allPresetTrackersField ) { // Associate the datalistElement with each presetTrackers input field presetField.setAttribute('list', 'presetTrackersList') } } // ----------------------------------- TIDY UP THE SETTINGS PANEL ----------------------------------- // Remove the tracker rows that should be hidden for ( let trackerLabel of GM_config.get('hiddenTrackers').split(',') ) { trackerLabel = trackerLabel.toLowerCase().trim() let primaryDomain = trackerNameToPrimaryDomain[trackerLabel] let trackerRow = document.getElementById(`quiCKIE_config_tracker_table_tr_${primaryDomain}`) trackerRow ? trackerRow.remove() : null } // For each tracker, add the hideButton that will remove the table row and add the tracker to the hidden list for ( let trackerLabelData of document.querySelectorAll('.quiCKIE_config_table_td_label') ) { let trackerRow = trackerLabelData.parentElement let hideButton = document.createElement('a') hideButton.textContent = 'πŸ™ˆ ' hideButton.style.display = 'none' hideButton.style.cursor = 'pointer' hideButton.addEventListener('mouseover', () => { hideButton.style.textShadow = '0px 0px 1px black, 0 0 5px #B6D3E7' } ) hideButton.addEventListener('mouseout', () => { hideButton.style.textShadow = 'none' } ) hideButton.addEventListener('click', () => { // Add the selected tracker to the hiddenTrackers list let hiddenTrackersField = document.getElementById('quiCKIE_config_field_hiddenTrackers') let hiddenTrackers = hiddenTrackersField.value.split(',') hiddenTrackers.push(trackerLabelData.children[1].textContent) // Format the new list, sorting it alphabetically and removing surrounding whitespace let updatedList = [] hiddenTrackers.forEach( tracker => { if ( tracker == '' ) { return } updatedList.push(tracker.trim()) }) updatedList.sort() hiddenTrackersField.value = updatedList.join(', ') trackerRow.remove() }) trackerLabelData.addEventListener('mouseover', () => { hideButton.style.display = '' } ) trackerLabelData.addEventListener('mouseout', () => { hideButton.style.display = 'none'} ) trackerLabelData.insertBefore(hideButton, trackerLabelData.firstElementChild) } // Set the placeholder examples for the various input fields try { document.getElementById('quiCKIE_config_field_quiURL').placeholder = 'http://localhost:7476/qui/instances/1' document.getElementById('quiCKIE_config_field_quiApiKey').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_qBitTorrentURL').placeholder = 'http://localhost:8080' document.getElementById('quiCKIE_config_field_qBitTorrentUsername').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_qBitTorrentPassword').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_transmissionURL').placeholder = 'http://localhost:9091' document.getElementById('quiCKIE_config_field_transmissionUsername').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_transmissionPassword').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_delugeURL').placeholder = 'http://localhost:8112' document.getElementById('quiCKIE_config_field_delugePassword').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_ruTorrentURL').placeholder = 'http://localhost:8080' document.getElementById('quiCKIE_config_field_ruTorrentUsername').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_ruTorrentPassword').placeholder = 'abc123' document.getElementById('quiCKIE_config_field_hiddenTrackers').placeholder = 'HDBits, Nyaa, Secret-Cinema' document.getElementById('quiCKIE_config_field_broadcasthe-savePath').placeholder = '/downloads/BroadcasTheNet' document.getElementById('quiCKIE_config_field_broadcasthe-category').placeholder = 'BroadcasTheNet' document.getElementById('quiCKIE_config_field_broadcasthe-tags').placeholder = 'series, media' document.getElementById('quiCKIE_config_field_broadcasthe-ratioLimit').placeholder = '8.50' document.getElementById('quiCKIE_config_field_broadcasthe-seedTime').placeholder = '1440' document.getElementById('quiCKIE_config_field_broadcasthe-dlLimit').placeholder = '10000' document.getElementById('quiCKIE_config_field_broadcasthe-upLimit').placeholder = '5000' document.getElementById('quiCKIE_config_field_broadcasthe-instance').placeholder = '2' document.getElementById('quiCKIE_config_field_broadcasthe-paginationLoop').placeholder = '3000' document.getElementById('quiCKIE_config_field_gazellegames-savePath').placeholder = '/downloads/GazelleGames' document.getElementById('quiCKIE_config_field_gazellegames-category').placeholder = 'GazelleGames' document.getElementById('quiCKIE_config_field_gazellegames-tags').placeholder = 'games' document.getElementById('quiCKIE_config_field_gazellegames-ratioLimit').placeholder = '5.75' document.getElementById('quiCKIE_config_field_gazellegames-seedTime').placeholder = '10080' document.getElementById('quiCKIE_config_field_gazellegames-dlLimit').placeholder = '12000' document.getElementById('quiCKIE_config_field_gazellegames-upLimit').placeholder = '15000' document.getElementById('quiCKIE_config_field_gazellegames-instance').placeholder = '3' document.getElementById('quiCKIE_config_field_gazellegames-paginationLoop').placeholder = '2000' document.getElementById('quiCKIE_config_field_nyaa-savePath').placeholder = '/downloads/Nyaa' document.getElementById('quiCKIE_config_field_nyaa-category').placeholder = 'Nyaa' document.getElementById('quiCKIE_config_field_nyaa-tags').placeholder = 'anime, media, public' document.getElementById('quiCKIE_config_field_nyaa-ratioLimit').placeholder = '1.25' document.getElementById('quiCKIE_config_field_nyaa-seedTime').placeholder = '40320' document.getElementById('quiCKIE_config_field_nyaa-dlLimit').placeholder = '25000' document.getElementById('quiCKIE_config_field_nyaa-upLimit').placeholder = '5000' document.getElementById('quiCKIE_config_field_nyaa-instance').placeholder = '2' document.getElementById('quiCKIE_config_field_nyaa-paginationLoop').placeholder = '2000' document.getElementById('quiCKIE_config_field_secret-cinema-savePath').placeholder = '/downloads/Secret-Cinema' document.getElementById('quiCKIE_config_field_secret-cinema-category').placeholder = 'Secret-Cinema' document.getElementById('quiCKIE_config_field_secret-cinema-tags').placeholder = 'films, media, private' document.getElementById('quiCKIE_config_field_secret-cinema-ratioLimit').placeholder = '3.25' document.getElementById('quiCKIE_config_field_secret-cinema-seedTime').placeholder = '80640' document.getElementById('quiCKIE_config_field_secret-cinema-dlLimit').placeholder = '18000' document.getElementById('quiCKIE_config_field_secret-cinema-upLimit').placeholder = '7500' document.getElementById('quiCKIE_config_field_secret-cinema-instance').placeholder = '3' document.getElementById('quiCKIE_config_field_secret-cinema-paginationLoop').placeholder = '5000' } catch (error) { // Likely an error from the trackerRow having been hidden already } // Move global settings below the header let settingsDivFirst = document.createElement('div') settingsDivFirst.id = 'quiCKIE_settingsDivFirst' settingsDivFirst.classList.add('quiCKIE_settingsDiv') document.getElementById('quiCKIE_config_header').insertAdjacentElement('afterend', settingsDivFirst) // --- Presets --- let presetCountLabel = document.getElementById('quiCKIE_config_presetCount_field_label') let presetCountField = document.getElementById('quiCKIE_config_field_presetCount') presetCountLabel.classList.add('settingsDivLabel') presetCountLabel.title = panelTextData.globalsTitles.presetCount settingsDivFirst.appendChild(presetCountLabel) settingsDivFirst.appendChild(presetCountField) // --- Left-Click --- let leftClickLabel = document.getElementById('quiCKIE_config_globalLeftClickAction_field_label') let leftClickField = document.getElementById('quiCKIE_config_field_globalLeftClickAction') leftClickLabel.classList.add('settingsDivLabel') leftClickLabel.title = panelTextData.globalsTitles.globalLeftClickAction settingsDivFirst.appendChild(leftClickLabel) settingsDivFirst.appendChild(leftClickField) // --- Middle-Click --- let middleClickLabel = document.getElementById('quiCKIE_config_globalMiddleClickAction_field_label') let middleClickField = document.getElementById('quiCKIE_config_field_globalMiddleClickAction') middleClickLabel.classList.add('settingsDivLabel') middleClickLabel.title = panelTextData.globalsTitles.globalMiddleClickAction settingsDivFirst.appendChild(middleClickLabel) settingsDivFirst.appendChild(middleClickField) // --- Placement --- let bunnyButtonPlacementLabel = document.getElementById('quiCKIE_config_bunnyButtonPlacement_field_label') let bunnyButtonPlacementField = document.getElementById('quiCKIE_config_field_bunnyButtonPlacement') bunnyButtonPlacementLabel.classList.add('settingsDivLabel') bunnyButtonPlacementLabel.title = panelTextData.globalsTitles.bunnyButtonPlacement settingsDivFirst.appendChild(bunnyButtonPlacementLabel) settingsDivFirst.appendChild(bunnyButtonPlacementField) // --- ForcedTorrentFile --- let globalForcedTorrentFileLabel = document.getElementById('quiCKIE_config_globalForcedTorrentFile_field_label') let globalForcedTorrentFileField = document.getElementById('quiCKIE_config_field_globalForcedTorrentFile') globalForcedTorrentFileLabel.classList.add('settingsDivLabel') globalForcedTorrentFileLabel.title = panelTextData.globalsTitles.globalForcedTorrentFile settingsDivFirst.appendChild(globalForcedTorrentFileLabel) settingsDivFirst.appendChild(globalForcedTorrentFileField) // ------ SECOND ROW ------ let settingsDivSecond = document.createElement('div') settingsDivSecond.classList.add('quiCKIE_settingsDiv') settingsDivSecond.id = 'quiCKIE_settingsDivSecond' settingsDivFirst.insertAdjacentElement('afterend', settingsDivSecond) // --- Hidden Trackers --- let hiddenTrackersLabel = document.getElementById('quiCKIE_config_hiddenTrackers_field_label') let hiddenTrackersField = document.getElementById('quiCKIE_config_field_hiddenTrackers') hiddenTrackersLabel.classList.add('settingsDivLabel') hiddenTrackersLabel.title = panelTextData.globalsTitles.hiddenTrackers settingsDivSecond.appendChild(hiddenTrackersLabel) settingsDivSecond.appendChild(hiddenTrackersField) // --- 3rd Party Delay --- let thirdPartyDelayLabel = document.getElementById('quiCKIE_config_thirdPartyDelay_field_label') let thirdPartyDelayField = document.getElementById('quiCKIE_config_field_thirdPartyDelay') thirdPartyDelayLabel.classList.add('settingsDivLabel') thirdPartyDelayLabel.title = panelTextData.globalsTitles.thirdPartyDelay settingsDivSecond.appendChild(thirdPartyDelayLabel) settingsDivSecond.appendChild(thirdPartyDelayField) // --- TorrentClient --- let torrentClientLabel = document.getElementById('quiCKIE_config_torrentClient_field_label') let torrentClientField = document.getElementById('quiCKIE_config_field_torrentClient') torrentClientLabel.classList.add('settingsDivLabel') torrentClientLabel.title = panelTextData.globalsTitles.torrentClient settingsDivSecond.appendChild(torrentClientLabel) settingsDivSecond.appendChild(torrentClientField) // --- quiURL --- let quiURLLabel = document.getElementById('quiCKIE_config_quiURL_field_label') let quiURLField = document.getElementById('quiCKIE_config_field_quiURL') let quiURLTooltip = panelTextData.globalsTitles.quiURL quiURLLabel.classList.add('settingsDivLabel') quiURLLabel.title = quiURLTooltip quiURLField.title = quiURLTooltip quiURLField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(quiURLLabel) settingsDivSecond.appendChild(quiURLField) // --- quiApiKey --- let quiApiKeyLabel = document.getElementById('quiCKIE_config_quiApiKey_field_label') let quiApiKeyField = document.getElementById('quiCKIE_config_field_quiApiKey') quiApiKeyLabel.classList.add('settingsDivLabel') quiApiKeyLabel.title = panelTextData.globalsTitles.quiApiKey quiApiKeyField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(quiApiKeyLabel) settingsDivSecond.appendChild(quiApiKeyField) // --- qBitTorrentURL --- let qBitTorrentURLLabel = document.getElementById('quiCKIE_config_qBitTorrentURL_field_label') let qBitTorrentURLField = document.getElementById('quiCKIE_config_field_qBitTorrentURL') let qBitTorrentURLTooltip = panelTextData.globalsTitles.qBitTorrentURL qBitTorrentURLLabel.classList.add('settingsDivLabel') qBitTorrentURLLabel.title = qBitTorrentURLTooltip qBitTorrentURLField.title = qBitTorrentURLTooltip qBitTorrentURLField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(qBitTorrentURLLabel) settingsDivSecond.appendChild(qBitTorrentURLField) // --- qBitTorrentUsername --- let qBitTorrentUsernameLabel = document.getElementById('quiCKIE_config_qBitTorrentUsername_field_label') let qBitTorrentUsernameField = document.getElementById('quiCKIE_config_field_qBitTorrentUsername') qBitTorrentUsernameLabel.classList.add('settingsDivLabel') qBitTorrentUsernameLabel.title = panelTextData.globalsTitles.qBitTorrentUsername qBitTorrentUsernameField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(qBitTorrentUsernameLabel) settingsDivSecond.appendChild(qBitTorrentUsernameField) // --- qBitTorrentPassword --- let qBitTorrentPasswordLabel = document.getElementById('quiCKIE_config_qBitTorrentPassword_field_label') let qBitTorrentPasswordField = document.getElementById('quiCKIE_config_field_qBitTorrentPassword') qBitTorrentPasswordLabel.classList.add('settingsDivLabel') qBitTorrentPasswordLabel.title = panelTextData.globalsTitles.qBitTorrentPassword qBitTorrentPasswordField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(qBitTorrentPasswordLabel) settingsDivSecond.appendChild(qBitTorrentPasswordField) // --- TransmissionURL --- let transmissionURLLabel = document.getElementById('quiCKIE_config_transmissionURL_field_label') let transmissionURLField = document.getElementById('quiCKIE_config_field_transmissionURL') let transmissionURLTooltip = panelTextData.globalsTitles.transmissionURL transmissionURLLabel.classList.add('settingsDivLabel') transmissionURLLabel.title = transmissionURLTooltip transmissionURLField.title = transmissionURLTooltip transmissionURLField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(transmissionURLLabel) settingsDivSecond.appendChild(transmissionURLField) // --- TransmissionUsername --- let transmissionUsernameLabel = document.getElementById('quiCKIE_config_transmissionUsername_field_label') let transmissionUsernameField = document.getElementById('quiCKIE_config_field_transmissionUsername') transmissionUsernameLabel.classList.add('settingsDivLabel') transmissionUsernameLabel.title = panelTextData.globalsTitles.transmissionUsername transmissionUsernameField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(transmissionUsernameLabel) settingsDivSecond.appendChild(transmissionUsernameField) // --- TransmissionPassword --- let transmissionPasswordLabel = document.getElementById('quiCKIE_config_transmissionPassword_field_label') let transmissionPasswordField = document.getElementById('quiCKIE_config_field_transmissionPassword') transmissionPasswordLabel.classList.add('settingsDivLabel') transmissionPasswordLabel.title = panelTextData.globalsTitles.transmissionPassword transmissionPasswordField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(transmissionPasswordLabel) settingsDivSecond.appendChild(transmissionPasswordField) // --- DelugeURL --- let delugeURLLabel = document.getElementById('quiCKIE_config_delugeURL_field_label') let delugeURLField = document.getElementById('quiCKIE_config_field_delugeURL') let delugeURLTooltip = panelTextData.globalsTitles.delugeURL delugeURLLabel.classList.add('settingsDivLabel') delugeURLLabel.title = delugeURLTooltip delugeURLField.title = delugeURLTooltip delugeURLField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(delugeURLLabel) settingsDivSecond.appendChild(delugeURLField) // --- DelugePassword --- let delugePasswordLabel = document.getElementById('quiCKIE_config_delugePassword_field_label') let delugePasswordField = document.getElementById('quiCKIE_config_field_delugePassword') delugePasswordLabel.classList.add('settingsDivLabel') delugePasswordLabel.title = panelTextData.globalsTitles.delugePassword delugePasswordField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(delugePasswordLabel) settingsDivSecond.appendChild(delugePasswordField) // --- ruTorrentURL --- let ruTorrentURLLabel = document.getElementById('quiCKIE_config_ruTorrentURL_field_label') let ruTorrentURLField = document.getElementById('quiCKIE_config_field_ruTorrentURL') let ruTorrentURLTooltip = panelTextData.globalsTitles.ruTorrentURL ruTorrentURLLabel.classList.add('settingsDivLabel') ruTorrentURLLabel.title = ruTorrentURLTooltip ruTorrentURLField.title = ruTorrentURLTooltip ruTorrentURLField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(ruTorrentURLLabel) settingsDivSecond.appendChild(ruTorrentURLField) // --- ruTorrentUsername --- let ruTorrentUsernameLabel = document.getElementById('quiCKIE_config_ruTorrentUsername_field_label') let ruTorrentUsernameField = document.getElementById('quiCKIE_config_field_ruTorrentUsername') ruTorrentUsernameLabel.classList.add('settingsDivLabel') ruTorrentUsernameLabel.title = panelTextData.globalsTitles.ruTorrentUsername ruTorrentUsernameField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(ruTorrentUsernameLabel) settingsDivSecond.appendChild(ruTorrentUsernameField) // --- ruTorrentPassword --- let ruTorrentPasswordLabel = document.getElementById('quiCKIE_config_ruTorrentPassword_field_label') let ruTorrentPasswordField = document.getElementById('quiCKIE_config_field_ruTorrentPassword') ruTorrentPasswordLabel.classList.add('settingsDivLabel') ruTorrentPasswordLabel.title = panelTextData.globalsTitles.ruTorrentPassword ruTorrentPasswordField.classList.add('quiCKIE_obfuscate') settingsDivSecond.appendChild(ruTorrentPasswordLabel) settingsDivSecond.appendChild(ruTorrentPasswordField) document.getElementById('quiCKIE_config_field_torrentClient').querySelector('[value="ruTorrent πŸ› οΈ"]').disabled = true ruTorrentPasswordField.disabled = true ruTorrentURLField.disabled = true ruTorrentUsernameField.disabled = true // Obfuscate the client credentials on blur for ( let inputField of document.querySelectorAll('.quiCKIE_obfuscate') ) { inputField.type = 'password' inputField.addEventListener('focus', () => { inputField.type = 'text' }) inputField.addEventListener('blur', () => { inputField.type = 'password' }) } // Set 'int' and 'float' fields to blank values when appropriate for ( let fieldName of ['ratioLimit', 'seedTime', 'upLimit', 'dlLimit', 'instance', 'paginationLoop'] ) { for ( let inputField of document.getElementById('quiCKIE_config').querySelectorAll(`input[data-fieldtype=${fieldName}`) ) { if ( fieldName == 'paginationLoop' ) { inputField.value < 500 ? inputField.value = '' : null } else if (fieldName == 'ratioLimit' || fieldName == 'seedTime' ) { inputField.value == 0 ? inputField.value = '' : null } else { inputField.value <= 0 ? inputField.value = '' : null } } } function torrentClientChange() { // The torernt client was changed, so disable the quiCKIE fields that no longer apply and hide the clientFields not belonging to this client // 1: Enable all previously disabled fields, 2: Disable fields not applicable to the selected client, 3: Hide client settings not applicable to the selected client let allClientFields = ['qui', 'qBitTorrent', 'transmission', 'deluge', 'ruTorrent'] // An array of field names to be hidden let hideClientFields let allInputFields = document.querySelectorAll('input.quiCKIE_disabledField') for ( inputField of allInputFields ) { // Enable any disabled fields inputField.disabled = false inputField.classList.remove('quiCKIE_disabledField') } // The currently selected torrent client from the dropdown list let torrentClient = document.getElementById('quiCKIE_config_field_torrentClient').value if ( torrentClient == 'qui' ) { hideClientFields = allClientFields.filter(function(item) { return item != 'qui' }) } else if ( torrentClient == 'qBitTorrent' ) { hideClientFields = allClientFields.filter(function(item) { return item != 'qBitTorrent' }) for ( let inputField of document.querySelectorAll('input[data-fieldtype="instance"]') ) { inputField.disabled = true inputField.classList.add('quiCKIE_disabledField') } } else if ( torrentClient == 'Transmission' ) { hideClientFields = allClientFields.filter(function(item) { return item != 'transmission' }) let fieldsToDisable = ['category', 'ratioLimit', 'seedTime', 'dlLimit', 'upLimit', 'instance', 'subFolder', 'autoTMM', 'skipHash'] for ( field of fieldsToDisable ) { let allInputFields = document.querySelectorAll(`input[data-fieldtype="${field}"]`) for ( inputField of allInputFields ) { inputField.disabled = true inputField.classList.add('quiCKIE_disabledField') } } } else if ( torrentClient == 'Deluge' ) { hideClientFields = allClientFields.filter(function(item) { return item != 'deluge' }) let fieldsToDisable = ['category', 'tags', 'ratioLimit', 'seedTime', 'instance', 'subFolder', 'autoTMM', 'skipHash'] for ( field of fieldsToDisable ) { let allInputFields = document.querySelectorAll(`input[data-fieldtype="${field}"]`) for ( inputField of allInputFields ) { inputField.disabled = true inputField.classList.add('quiCKIE_disabledField') } } } else if ( torrentClient == 'ruTorrent' ) { // ruTorrent is selected hideClientFields = allClientFields.filter(function(item) { return item != 'ruTorrent' }) let fieldsToDisable = ['category', 'ratioLimit', 'seedTime', 'dlLimit', 'upLimit', 'instance', 'subFolder', 'autoTMM', 'skipHash'] for ( field of fieldsToDisable ) { let allInputFields = document.querySelectorAll(`input[data-fieldtype="${field}"]`) for ( inputField of allInputFields ) { inputField.disabled = true inputField.classList.add('quiCKIE_disabledField') } } } for ( let clientFieldName of allClientFields ) { // Start by revealing all client fields try { document.getElementById(`quiCKIE_config_${clientFieldName}ApiKey_field_label`).style.display = '' document.getElementById(`quiCKIE_config_field_${clientFieldName}ApiKey`).style.display = '' } catch (error) {} try { document.getElementById(`quiCKIE_config_${clientFieldName}URL_field_label`).style.display = '' document.getElementById(`quiCKIE_config_field_${clientFieldName}URL`).style.display = '' } catch (error) {} try { document.getElementById(`quiCKIE_config_${clientFieldName}Username_field_label`).style.display = '' document.getElementById(`quiCKIE_config_field_${clientFieldName}Username`).style.display = '' } catch (error) {} try { document.getElementById(`quiCKIE_config_${clientFieldName}Password_field_label`).style.display = '' document.getElementById(`quiCKIE_config_field_${clientFieldName}Password`).style.display = '' } catch (error) {} } for ( let clientFieldName of hideClientFields ) { // Hide client fields not applicable to the selected client try { document.getElementById(`quiCKIE_config_${clientFieldName}ApiKey_field_label`).style.display = 'none' document.getElementById(`quiCKIE_config_field_${clientFieldName}ApiKey`).style.display = 'none' } catch (error) {} try { document.getElementById(`quiCKIE_config_${clientFieldName}URL_field_label`).style.display = 'none' document.getElementById(`quiCKIE_config_field_${clientFieldName}URL`).style.display = 'none' } catch (error) {} try { document.getElementById(`quiCKIE_config_${clientFieldName}Username_field_label`).style.display = 'none' document.getElementById(`quiCKIE_config_field_${clientFieldName}Username`).style.display = 'none' } catch (error) {} try { document.getElementById(`quiCKIE_config_${clientFieldName}Password_field_label`).style.display = 'none' document.getElementById(`quiCKIE_config_field_${clientFieldName}Password`).style.display = 'none' } catch (error) {} } } document.getElementById('quiCKIE_config_field_torrentClient').addEventListener('change', function() { torrentClientChange() }) torrentClientChange() // Create GitHub version element let githubSVG = '' let versionElement = document.createElement('a') versionElement.classList = 'version_label reset' versionElement.title = 'Source Code on GitHub' versionElement.target = '_blank' versionElement.href = `${GM_info.script.homepage}` versionElement.innerHTML = `${githubSVG} Version ${GM_info.script.version}` doc.getElementById('quiCKIE_config_buttons_holder').appendChild(versionElement) // Adjust the Import\Export buttons let exportButton = document.getElementById('quiCKIE_config_field_settingsExport') exportButton.classList.add('saveclose_buttons') let importButton = document.getElementById('quiCKIE_config_field_settingsImport') importButton.classList.add('saveclose_buttons') let buttonsHolderElement = document.getElementById('quiCKIE_config_buttons_holder') buttonsHolderElement.appendChild(importButton) buttonsHolderElement.appendChild(exportButton) // Remove the now empty
elements for ( let emptyElement of document.getElementById('quiCKIE_config').querySelectorAll('div.config_var:empty') ) { emptyElement.remove() } // Add success animation to save button let saveButton = doc.getElementById('quiCKIE_config_saveBtn') saveButton.addEventListener('click', () => { // When the save button is clicked, temporarily assign a css class to produce the animation saveButton.classList.add('success') setTimeout(() => saveButton.classList.remove('success'), 500) }) if ( this.get('welcomeMessage') == 'show' ) { // Display this welcomeMessage when first opening the quiCKIE settings panel confirm("🐰 Welcome to quiCKIE! 🐰\n\nMany of the trackers supported by quiCKIE were contributed by a member of that tracker. If there's a tracker that you'd like to see included, check the quiCKIE GitHub WiKi for a simple 3-step guide on how to get it added, no programming experience required!\n\nquiCKIE was originally written for qui, with the other clients being added in at the tail-end of development.\n\nIf during your usage you encounter either a buggy feature, missing url, or broken\\dead tracker, open an issue on the quiCKIE GitHub page.\n\nEnjoy your quiCKIE, hover over the emojis for details, and finally a big shout-out to the people that have come together and kept this community thriving 🫢\n\n - Wirly") this.set('welcomeMessage', 'hide') } }, 'save': function () { // Actions to take when the 'Save' button is clicked reloadWindow = true // Clear cached data when settings are saved GM_listValues().forEach(key => { if (key !== 'quiCKIE_config') { GM_setValue(key, null) } }) }, 'close': function () { // Actions to take when the 'Close' button is clicked if (reloadWindow) { if (this.frame) { window.location.reload() } else { setTimeout(() => { window.location.reload() }, 250) } } }, 'reset': function () { // Actions to take when the 'Reset' button is clicked if (typeof resetToDefaults === 'function') { resetToDefaults() } } }, // The CSS to use for the menu, loaded through the @resource line 'css': GM_getResourceText('settingsPanelCSS') }) // Register the settings panel to be opened from the UserScript manager dialouge GM_registerMenuCommand('Settings', () => { GM_config.open() }) return [ primaryDomain, allPrimaryDomains, allTrackerNames, primaryDomainToTrackerName, primaryDomainToHomepage, trackerNameToPrimaryDomain, presetCount ] } function getTrackerSettings(primaryDomain) { // Define the main SETTINGS object and populate it with the current primaryDomain specific settings // @trackerSettings let SETTINGS = { primaryDomain: primaryDomain, forceTorrentFile: false, firstTrackerHandlerScan: true, firstThirdPartyScan: true, // The global quiCKIE saved settings bunnyButtonPlacement: GM_config.get('bunnyButtonPlacement'), globalLeftClickAction: GM_config.get('globalLeftClickAction'), globalMiddleClickAction: GM_config.get('globalMiddleClickAction'), globalForcedTorrentFile: GM_config.get('globalForcedTorrentFile'), thirdPartyDelay: GM_config.get('thirdPartyDelay'), torrentClient: { 'client': GM_config.get('torrentClient'), 'quiURL': GM_config.get('quiURL'), 'quiApiKey': GM_config.get('quiApiKey'), 'qBitTorrentURL': GM_config.get('qBitTorrentURL'), 'qBitTorrentUsername': GM_config.get('qBitTorrentUsername'), 'qBitTorrentPassword': GM_config.get('qBitTorrentPassword'), 'transmissionURL': GM_config.get('transmissionURL'), 'transmissionUsername': GM_config.get('transmissionUsername'), 'transmissionPassword': GM_config.get('transmissionPassword'), 'delugeURL': GM_config.get('delugeURL'), 'delugePassword': GM_config.get('delugePassword'), 'ruTorrentURL': GM_config.get('ruTorrentURL'), 'ruTorrentUsername': GM_config.get('ruTorrentUsername'), 'ruTorrentPassword': GM_config.get('ruTorrentPassword'), }, // The saved settings of the current tracker category: GM_config.get(`${primaryDomain}-category`), savePath: GM_config.get(`${primaryDomain}-savePath`), tags: GM_config.get(`${primaryDomain}-tags`), ratioLimit: GM_config.get(`${primaryDomain}-ratioLimit`), seedTime: GM_config.get(`${primaryDomain}-seedTime`), dlLimit: GM_config.get(`${primaryDomain}-dlLimit`), upLimit: GM_config.get(`${primaryDomain}-upLimit`), instance: GM_config.get(`${primaryDomain}-instance`), paginationLoop: GM_config.get(`${primaryDomain}-paginationLoop`), leftClick: GM_config.get(`${primaryDomain}-leftClick`), thirdPartyScan: GM_config.get(`${primaryDomain}-thirdPartyScan`), hideDL: GM_config.get(`${primaryDomain}-hideDL`), startPaused: GM_config.get(`${primaryDomain}-startPaused`), subFolder: GM_config.get(`${primaryDomain}-subFolder`), seqPieces: GM_config.get(`${primaryDomain}-seqPieces`), autoTMM: GM_config.get(`${primaryDomain}-autoTMM`), skipHash: GM_config.get(`${primaryDomain}-skipHash`), } // GM_config() saves what should be blank int/float fields as 0, which qbitTorrent interprets problematically, so change 0 to '' SETTINGS.ratioLimit == 0 ? SETTINGS.ratioLimit = '' : null SETTINGS.seedTime == 0 ? SETTINGS.seedTime = '' : null SETTINGS.dlLimit <= 0 ? SETTINGS.dlLimit = '' : null SETTINGS.upLimit <= 0 ? SETTINGS.upLimit = '' : null SETTINGS.instance <= 0 ? SETTINGS.instance = '' : null SETTINGS.paginationLoop < 500 ? SETTINGS.paginationLoop = '' : null return SETTINGS } function createPresetItems(primaryDomains) { // For all the primaryDomains (array), generate and return a object who's properties equal the presetMenu items applicable to that primaryDomain defaultItems = { 'send': { content: '☁️ Send', events: { 'click': function(event) { // This menuItem was clicked, so use basic settings to add the torrent let bunnyButtonId = this.dataset.targetid let bunnyButton = document.getElementById(bunnyButtonId) let startPaused = false if ( event.shiftKey && event.ctrlKey && event.button == 0 ) { // Shift-Ctrl-Click: Add the torrent in a paused state startPaused = true } addTorrent({ torrentURL: bunnyButton.dataset.torrenturl, torrentClient: SETTINGS.torrentClient, bunnyButtonId: bunnyButtonId, startPaused: startPaused, }) }, 'mouseover': function(event) { this.title = `☁️ Send the torrent to ${SETTINGS.torrentClient.client} with no custom settings` } } }, 'sendPaused': { content: '☁️ Send Paused', events: { 'click': function(event) { // This menuItem was clicked, so use basic settings to add the torrent, but in a paused state let bunnyButtonId = this.dataset.targetid let bunnyButton = document.getElementById(bunnyButtonId) addTorrent({ torrentURL: bunnyButton.dataset.torrenturl, torrentClient: SETTINGS.torrentClient, bunnyButtonId: bunnyButtonId, startPaused: true, }) }, 'mouseover': function(event) { this.title = `☁️ Send the torrent to ${SETTINGS.torrentClient.client} with only StartPaused enabled` } } }, 'settings': { content: 'πŸ› οΈ Settings', events: { 'click': function(event) { // This menuItem was clicked, so open the quiCKIE settings panel GM_config.open() }, 'mouseover': function(event) { this.title = `πŸ› οΈ Open the quiCKIE Settings Panel` } } }, 'leftClickAll': { content: '🐰 Left-Click All', events: { 'click': function(event) { // Simulate a left-click on every bunnyButton of the page let leftClickAction SETTINGS.leftClick == 'Global' ? leftClickAction = SETTINGS.globalLeftClickAction : leftClickAction = SETTINGS.leftClick let confirmation = confirm(`🐰 quiCKIE 🐰\n\n You have selected the '🐰 Left-Click All' action\n\nAre you sure you want to Left-Click every BunnyButton on the page?\n\nLeft-Click Action: ${leftClickAction}`) if ( confirmation == false ) { // The user has chosen to abort the Left-Click All return } let allBunnyButtons = document.querySelectorAll('a.quickie_bunnyButton') let delay = 0 for ( let bunnyButton of allBunnyButtons ) { // Add the 'leftClickAllTriggered' class to this bunnyButton, which will be used to identify this 'mouseup' event bunnyButton.classList.add('leftClickAllTriggered') setTimeout(() => { // After the delay, trigger a 'mouseup' event on this bunnyButton let clickEvent = new Event('mouseup', { bubbles: true, cancelable: true }); bunnyButton.dispatchEvent(clickEvent); bunnyButton.classList.remove('leftClickAllTriggered') }, delay) delay += 50 } }, 'mouseover': function(event) { this.title = '🐰 Perform a Left-Click on every BunnyButton of the page' } } }, 'middleClickAll': { content: '🐰 Middle-Click All', events: { 'click': function(event) { // Simulate a left-click on every bunnyButton of the page let confirmation = confirm(`🐰 quiCKIE 🐰\n\n You have selected the '🐰 Middle-Click All' action\n\nAre you sure you want to Middle-Click every BunnyButton on the page?\n\nMiddle-Click Action: ${SETTINGS.globalMiddleClickAction}`) if ( confirmation == false ) { // The user has chosen to abort the Middle-Click All return } let allBunnyButtons = document.querySelectorAll('a.quickie_bunnyButton') let delay = 0 for ( let bunnyButton of allBunnyButtons ) { // Add the 'middleClickAllTriggered' class to this bunnyButton, which will be used to identify this 'mouseup' event bunnyButton.classList.add('middleClickAllTriggered') setTimeout(() => { // After the delay, trigger a 'mouseup' event on this bunnyButton let clickEvent = new Event('mouseup', { bubbles: true, cancelable: true }) bunnyButton.dispatchEvent(clickEvent) bunnyButton.classList.remove('middleClickAllTriggered') }, delay) delay += 50 } }, 'mouseover': function(event) { this.title = '🐰 Perform a Middle-Click on every BunnyButton of the page' } } }, 'client': { content: `πŸ–₯️ ${SETTINGS.torrentClient.client}`, events: { 'click': function(event) { // This menuItem was clicked, so open the webUI of the active torrent client if ( SETTINGS.torrentClient.client == 'qui') { window.open(SETTINGS.torrentClient.quiURL, '_blank').focus() } else if ( SETTINGS.torrentClient.client == 'qBitTorrent') { window.open(SETTINGS.torrentClient.qBitTorrentURL, '_blank').focus() } else if ( SETTINGS.torrentClient.client == 'Transmission') { window.open(SETTINGS.torrentClient.transmissionURL, '_blank').focus() } else if ( SETTINGS.torrentClient.client == 'Deluge') { window.open(SETTINGS.torrentClient.delugeURL, '_blank').focus() } else if ( SETTINGS.torrentClient.client == 'ruTorrent') { window.open(SETTINGS.torrentClient.ruTorrentURL, '_blank').focus() } }, 'mouseover': function(event) { this.title = `πŸ–₯️ Open the ${SETTINGS.torrentClient.client} Web Interface` } } }, 'torrentfile' : { content: 'πŸ’Ύ TorrentFile', events: { 'click': function(event) { // This menuItem was clicked, so simulate a click of the torrentURL\magnetLink to download\open the .torrent file let bunnyButtonId = this.dataset.targetid let bunnyButton = document.getElementById(bunnyButtonId) let fileElement = document.createElement('a') fileElement.href = bunnyButton.dataset.torrenturl document.body.appendChild(fileElement) fileElement.click() document.body.removeChild(fileElement) fileElement.remove() replaceEmojis(bunnyButton, 'πŸ’Ύ') }, 'mouseover': function(event) { this.title = `πŸ’Ύ Download the .torrent file\n\nℹ️ BunnyButtons based on MagnetLinks will instead open the MagnetLink` } } }, } let allPresetItems = {} for ( let primaryDomain of primaryDomains ) { // For each primaryDomain, determine the presets that apply to that tracker and add them to the object let menuItems = [] for ( let i=1; i <= presetCount; i++ ) { // for each preset, create a menuItem object to put in the right-click presets-menu let presetName = GM_config.get(`preset-${i}-preset`) // let presetName = GM_config.get(`preset-${i}-name`) let presetTrackersList = GM_config.get(`preset-${i}-presetTrackers`).toLowerCase() if ( presetName == '' || presetTrackersList == '') { // A empty preset name or no specified trackers, so don't add this item to the presets-menu continue } // Check if the list of trackers in the presetTrackers field contains a match against the settings panel label of this tracker let settingsPanelLabel = primaryDomainToTrackerName[primaryDomain].toLowerCase() if ( !presetTrackersList.match(/\*/) && !presetTrackersList.match(settingsPanelLabel) ) { // Neither a wildcard nor a matching tracker label, so don't add this item to the presets-menu continue } if ( presetName.match(/^leftclickall$/i) ){ // This preset item should perform a Left-Click action on EVERY bunnyButton on the page var presetItem = defaultItems['leftClickAll'] } else if ( presetName.match(/^middleclickall$/i) ){ // This preset item should perform a Middle-Click action on EVERY bunnyButton on the page var presetItem = defaultItems['middleClickAll'] } else if ( presetName.match(/^send$/i) ) { // This preset item should send the torrent to the client no custom settings var presetItem = defaultItems['send'] } else if ( presetName.match(/^sendpaused$/i) ) { // This preset item should send the torrent to the client no custom settings, except for startPaused = true var presetItem = defaultItems['sendPaused'] } else if ( presetName.match(/^settings$/i) ) { // This preset item should open the quiCKIE Settings panel var presetItem = defaultItems['settings'] } else if ( presetName.match(/^client$/i) ) { // This preset item should open a tab to the torrent client var presetItem = defaultItems['client'] } else if ( presetName.match(/^torrentfile$/i) ) { // This preset item should open the torrentURL var presetItem = defaultItems['torrentfile'] } else if ( presetName.match(/^[-=\.\s]+$/) ) { // This preset item is a menu separator, so create a menuItem that does nothing when clicked let dividerText = presetName // Replace - = . with their respective symbols if ( presetName.includes('-') ) { dividerText = presetName.replaceAll(/./g, '─') } else if ( presetName.includes('=') ) { dividerText = presetName.replaceAll(/./g, '═') } else if ( presetName.includes('.') ) { dividerText = presetName.replaceAll(/./g, 'Β·') } var presetItem = { content: dividerText, divider: 'true', } } else { // For this preset, create a menuItem entry to be clickable in the presets-menu let presetSettings = { category: GM_config.get(`preset-${i}-category`), savePath: GM_config.get(`preset-${i}-savePath`), tags: GM_config.get(`preset-${i}-tags`), ratioLimit: GM_config.get(`preset-${i}-ratioLimit`), seedTime: GM_config.get(`preset-${i}-seedTime`), dlLimit: GM_config.get(`preset-${i}-dlLimit`), upLimit: GM_config.get(`preset-${i}-upLimit`), instance: GM_config.get(`preset-${i}-instance`), startPaused: GM_config.get(`preset-${i}-startPaused`), subFolder: GM_config.get(`preset-${i}-subFolder`), seqPieces: GM_config.get(`preset-${i}-seqPieces`), autoTMM: GM_config.get(`preset-${i}-autoTMM`), skipHash: GM_config.get(`preset-${i}-skipHash`), } if ( presetSettings.ratioLimit == 0 ) { presetSettings.ratioLimit = '' } if ( presetSettings.seedTime == 0 ) { presetSettings.seedTime = '' } if ( presetSettings.dlLimit == 0 ) { presetSettings.dlLimit = '' } if ( presetSettings.upLimit == 0 ) { presetSettings.upLimit = '' } if ( presetSettings.instance == 0 ) { presetSettings.instance = '' } var presetItem = { content: presetName, events: { 'click': function(event) { // This menuItem was clicked, so use the selected preset to add the torrent // The bunnyButtonId, which was set as a data set as a dataset attribute when the bunnyButton was right-clicked let bunnyButtonId = this.dataset.targetid let bunnyButton = document.getElementById(bunnyButtonId) let torrentURL = bunnyButton.dataset.torrenturl let startPaused = presetSettings.startPaused if ( event.shiftKey && event.ctrlKey && event.button == 0 ) { // Shift-Ctrl-Click: Add the torrent in a paused state startPaused = true } addTorrent({ torrentURL: torrentURL, torrentClient: SETTINGS.torrentClient, bunnyButtonId: bunnyButtonId, instance: presetSettings.instance, category: presetSettings.category, savePath: presetSettings.savePath, tags: presetSettings.tags, ratioLimit: presetSettings.ratioLimit, seedTime: presetSettings.seedTime, dlLimit: presetSettings.dlLimit, upLimit: presetSettings.upLimit, startPaused: startPaused, subFolder: presetSettings.subFolder, seqPieces: presetSettings.seqPieces, autoTMM: presetSettings.autoTMM, skipHash: presetSettings.skipHash}) }, 'mouseover': function(event) { let bunnyButtonId = this.dataset.targetid let bunnyButton = document.getElementById(bunnyButtonId) this.title = ` ─── πŸš€ ${presetName} πŸš€ ─── πŸ—ƒοΈ = ${presetSettings.category} πŸ’Ύ = ${presetSettings.savePath} 🏷️ = ${presetSettings.tags} βš–οΈ = ${presetSettings.ratioLimit} 🌱 = ${presetSettings.seedTime} ⬇️ = ${presetSettings.dlLimit} ⬆️ = ${presetSettings.upLimit} 🎯 = ${presetSettings.instance} ⏸️ = ${presetSettings.startPaused} πŸ“ = ${presetSettings.subFolder} 🧩 = ${presetSettings.seqPieces} πŸ€– = ${presetSettings.autoTMM} πŸ›‘οΈ = ${presetSettings.skipHash} πŸ–₯️ ${SETTINGS.torrentClient.client} πŸ”— ${bunnyButton.dataset.torrenturl}` } } } } menuItems.push(presetItem) } if ( menuItems.length == 0 ) { // No presets were detected for this tracker, so let the user know and provide some default options menuItems = [ { content: 'πŸš€ No Presets for this Tracker πŸš€', events: { 'mouseover': function(event) { // this.parentElement.setAttribute('style', 'background: none !important; background-color: transparent !important') this.setAttribute('style', 'box-shadow: none !important; background-color: transparent !important') }, } }, defaultItems['settings'], defaultItems['torrentfile'], defaultItems['client'], defaultItems['send'], ] } allPresetItems[primaryDomain] = menuItems } return allPresetItems } let presetsMenuCSS = GM_getResourceText('presetsMenuCSS') GM_addStyle(presetsMenuCSS) function attachPresetsMenu(targetSelector, primaryDomain = primaryDomain) { // For the targetSelector elements, use the provided primaryDomain to append the appropriate menu items from the global presetMenuItems object const presetsMenu = new ContextMenu({ // targetSelector == CSS Selector target: targetSelector, // An array of objects to display in the presets-menu menuItems: presetMenuItems[primaryDomain] }) // init() will stack a 'contextmenu' eventlistener on elements, so don't call it more than once per bunnyButton presetsMenu.init() } // @quickieTrackerHandler function quickieTrackerHandler({ // A universal tracker handler that uses the provided arguments to generate bunnyButtons for all the queried downloadElements downloadElementsSelector, bunnyButtonFontSize = 'inherit', bunnyButtonText = ' 🐰 ' , bunnyButtonParentPlacement = false, downloadElementHideParentElementGap = false, elementsSeparator = 'automatic', bunnyButtonAddStyles = '', bunnyButtonAddClasses = [], seedingStatusSelector = null, snatchedStatusSelector = null, freeleechStatusSelector = null, featuredStatusSelector = null, afterBunnyButtonCreation = false, enablePaginationLooping = false, queryFromElement = document, downloadElementsTorrentURLAttribute = 'href', forceTorrentFile = false, bunnyButtonAttachPresetsMenu = true, }) { // Using the provided arguments, generate bunnyButtons for matching elements on this page logger.info('quickieTrackerHandler Settings') logger.log(arguments[0]) // If the .torrent file should be forced to download through the browser forceTorrentFile == true ? SETTINGS.forceTorrentFile = true : null // The global setting for where to place the bunnyButton relative to the downloadElement let bunnyButtonPlacement SETTINGS.bunnyButtonPlacement == 'After' ? bunnyButtonPlacement = 'afterend' : bunnyButtonPlacement = 'beforebegin' // If pagination looping should be enforced on this page enablePaginationLooping == true ? SETTINGS.paginationLoop = 750 : null // Determine if there is a reason to log the bunnyButtons so that they can be referenced after the query loop let logElements = false if ( seedingStatusSelector != null || snatchedStatusSelector != null || freeleechStatusSelector != null || typeof afterBunnyButtonCreation === 'function' ) { logElements = true } function bunnyButtonGeneration(delay) { // For all downloadElements queried by the downloadElementsSelector, create a accompanying bunnyButton according to the trackerHandlerOptions and user SETTINGS try { setTimeout(() => { // Using the provided CSS selector, get an array of all the downloadElements that have not yet been processed let allDownloadElements = queryFromElement.querySelectorAll(`${downloadElementsSelector}:not([data-quickie_done="true"])`) logger.info('allDownloadElements') logger.log(allDownloadElements) // There is a function to be performed after the bunnyButtons are created, so populate a object to store the elements logElements == true ? loggedElements = { bunnyButtons: [], downloadElements: [] , pairedElements: [] } : null if ( allDownloadElements.length >= 1 ) { // The query returned results that have not yet been processed, so generate a bunnyButton for each downloadElement // The separator used between the DL button and the BunnyButton elementsSeparator == 'automatic' ? elementsSeparator = getPageSeparator(allDownloadElements[0]) : null // Process each downloadElement in the list one at a time, generating a bunnyButton for each and then inserting it after the downloadElement for (let downloadElement of allDownloadElements) { // Use the supplied attribute (which should be a torrentURL) to create a bunnyButton for this downloadElement let bunnyButton = createBunnyButton({ torrentURL: downloadElement[downloadElementsTorrentURLAttribute], fontSize: bunnyButtonFontSize, buttonText: bunnyButtonText, torrentSettings: SETTINGS, addButtonStyles: bunnyButtonAddStyles, addButtonClasses: bunnyButtonAddClasses }) let placementElement bunnyButtonParentPlacement == true ? placementElement = downloadElement.parentElement : placementElement = downloadElement // Insert the bunnyButton after the placementElement placementElement.insertAdjacentElement(bunnyButtonPlacement, bunnyButton) if ( SETTINGS.hideDL == true ) { // Hide the DL button and therefore don't insert a separator // if ( downloadElementHideParentElementGap == true && bunnyButtonParentPlacement == true ) { downloadElement.parentElement.style.display = 'none' } else { downloadElement.style.display = 'none' } } else { // Insert the separator between the placementElement and the bunnyButton elementsSeparator == false ? null : placementElement.insertAdjacentText(bunnyButtonPlacement, elementsSeparator) } // Mark this downloadElement as having already been processed by assigning it a unique attribute, which will prevent it from being queried in any future loops\Mutations downloadElement.setAttribute('data-quickie_done', 'true') // Store the processed elements in the object to be passed to the afterBunnyButtonCreation() function if ( logElements == true ) { loggedElements['bunnyButtons'].push(bunnyButton) loggedElements['downloadElements'].push(downloadElement) loggedElements['pairedElements'].push({ bunnyButton: bunnyButton, downloadElement: downloadElement }) } } // After the bunnyButtons have been generated, call the function that will attach to them the right-click presetsMenu bunnyButtonAttachPresetsMenu == true ? attachPresetsMenu('a.quickie_newBunnyButton', SETTINGS.primaryDomain) : null } else { // The downloadElements query returned no results if ( SETTINGS.firstTrackerHandlerScan && !['myanonamouse'].includes(primaryDomain) ) { // This being the first scan, alert the user of the possible reasons the query might have failed and how to proceed console.error(`---------- ⚠️ quiCKIE ⚠️ ----------\n\nThe script has executed sucessfully, but the initial search found no download elements for which to make BunnyButtons 🐰\n\nIf you are not seeing any BunnyButtons, this usually means that either the CSS selector used for matching the ${primaryDomainToTrackerName[primaryDomain]} download buttons needs to be updated or that you are on a site\\page that has pagination.\n\nPaste this command into your browser console, if the returned list is empty, then the CSS Selector is returning no results and needs updating: document.querySelectorAll('${downloadElementsSelector}')\n\nRefer to the quiCKIE GitHub WiKi for a guide on adding a new tracker, which has a section on how to determine\\update the CSS selector.\n\nIf the CSS selector is returning results but there are still no BunnyButtons, it is likely due to pagination. Use quiCKIE's πŸ” setting for pagination compatability.\n\nℹ️ If you are reading this and your BunnyButtons are working fine, you can safely ignore this message. It is likely that the pagination of your current site did not finish loading before quiCKIE performed this first scan.\n\nℹ️ If this page has no download elements to begin with, it means that one of the @match URL's for ${primaryDomainToTrackerName[primaryDomain]} is running quiCKIE on pages it should not. Please report this so that quiCKIE won't waste your resources and can be improved.`) } } SETTINGS.firstTrackerHandlerScan = false if ( typeof afterBunnyButtonCreation === 'function' && loggedElements['bunnyButtons'].length > 0 ) { // A function was provided and there are newly created bunnyButtons, so run the function afterBunnyButtonCreation(loggedElements) } if ( seedingStatusSelector != null || snatchedStatusSelector != null || freeleechStatusSelector != null && loggedElements['bunnyButtons'].length > 0 ) { // A torrent status selector was provided and there are newly created bunnyButtons, so check the selector(s) for truthy for ( let pairedElements of loggedElements.pairedElements ) { let downloadElement = pairedElements.downloadElement let bunnyButton = pairedElements.bunnyButton try { if ( eval(`${seedingStatusSelector}`) && bunnyButton.dataset.emojospecified != 'true' ) { // A seedingStatusSelector was matched and an emoji has not already been specified for this bunnyButton bunnyButtonTorrentStatus(bunnyButton, 'seeding') continue } } catch (error) { // There was en error, likely due to either impossible method chaining for this downloadElement (signifying 'false') or invalid JavaScript logger.log(`---------- ⚠️ quiCKIE ⚠️ ----------\n\nThe seedingStatusSelector returned an error.\n\nIf you are reading this message and the 🌱 is present on the bunnyButtons that are indeed seeding, you can safely ignore this message. This error is normal, as comparisons that don't return 'true' are not always able to complete their code and will instead return this error.\n\nIf even the seeding torrents are not displaying the 🌱, it is most likely that either the seedingStatusSelector is incorrect or is not valid JavaScript.\n\ndownloadElement: ${downloadElement}\n\nseedingStatusSelector: ${seedingStatusSelector}\n\nError:${error}\n\n`) } try { if ( eval(`${snatchedStatusSelector}`) && bunnyButton.dataset.emojospecified != 'true' ) { // A snatchedStatusSelector was matched and an emoji has not already been specified for this bunnyButton bunnyButtonTorrentStatus(bunnyButton, 'snatched') continue } } catch (error) { // There was en error, likely due to either impossible method chaining for this downloadElement (signifying 'false') or invalid JavaScript logger.log(`---------- ⚠️ quiCKIE ⚠️ ----------\n\nThe snatchedStatusSelector returned an error.\n\nIf you are reading this message and the 🍁 is present on the bunnyButtons that are indeed snatched, you can safely ignore this message. This error is normal, as comparisons that don't return 'true' are not always able to complete their code and will instead return this error.\n\nIf even the snatched torrents are not displaying the 🍁, it is most likely that either the snatchedStatusSelector is incorrect or is not valid JavaScript.\n\ndownloadElement: ${downloadElement}\n\nsnatchedStatusSelector: ${snatchedStatusSelector}\n\nError:${error}\n\n`) } try { if ( eval(`${featuredStatusSelector}`) && bunnyButton.dataset.emojospecified != 'true' ) { // A featuredStatusSelector was matched and an emoji has not already been specified for this bunnyButton bunnyButtonTorrentStatus(bunnyButton, 'featured') continue } } catch (error) { // There was en error, likely due to either impossible method chaining for this downloadElement (signifying 'false') or invalid JavaScript logger.log(`---------- ⚠️ quiCKIE ⚠️ ----------\n\nThe featuredStatusSelector returned an error.\n\nIf you are reading this message and the πŸ“’ present on the bunnyButtons that are indeed featured, you can safely ignore this message. This error is normal, as comparisons that don't return 'true' are not always able to complete their code and will instead return this error.\n\nIf even the featured torrents are not displaying the πŸ“’, it is most likely that either the featuredStatusSelector is incorrect or is not valid JavaScript.\n\ndownloadElement: ${downloadElement}\n\nfeaturedStatusSelector: ${featuredStatusSelector}\n\nError:${error}\n\n`) } try { if ( eval(`${freeleechStatusSelector}`) && bunnyButton.dataset.emojospecified != 'true' ) { // A freeleechStatusSelector was matched and an emoji has not already been specified for this bunnyButton bunnyButtonTorrentStatus(bunnyButton, 'freeleech') continue } } catch (error) { // There was en error, likely due to either impossible method chaining for this downloadElement (signifying 'false') or invalid JavaScript logger.log(`---------- ⚠️ quiCKIE ⚠️ ----------\n\nThe freeleechStatusSelector returned an error.\n\nIf you are reading this message and the πŸ’Ž is present on the bunnyButtons that are indeed freeleech, you can safely ignore this message. This error is normal, as comparisons that don't return 'true' are not always able to complete their code and will instead return this error.\n\nIf even the freeleech torrents are not displaying the πŸ’Ž, it is most likely that either the freeleechStatusSelector is incorrect or is not valid JavaScript.\n\ndownloadElement: ${downloadElement}\n\nfreeleechStatusSelector: ${freeleechStatusSelector}\n\nError:${error}\n\n`) } } } if ( SETTINGS.paginationLoop >= 500 ) { // The paginationLoop timer has been set, so quiCKIE will continuosly scan the page for new downloadElements bunnyButtonGeneration(SETTINGS.paginationLoop) } }, delay ) } catch (error) { logger.error(error) } } bunnyButtonGeneration(0) } // @unit3dTrackerHandler function unit3dTrackerHandler(downloadElementsSelector) { // A tracker handler designed to support trackers running on the UNIT3D framework // ! This function is based on the HTML layout of 'Oldtoons' and is not WirlyWirly guaranteed for other UNIT3D sites // Mutable settings dependent on the current page let bunnyButtonAddStyles = '' let bunnyButtonPlacement let bunnyButtonText = ' 🐰 ' let queryFromElement = document let torrentDetailsPage = false if ( pagePath.match(/\/torrents\/\d+/) ) { // The torrents details page, so change the style of the only BunnyButton torrentDetailsPage = true // Give the bunnyButton a bar appearance, to fit in better with the other buttons bunnyButtonText = '🐰 quiCKIE' bunnyButtonAddStyles = ` background: #153245; border-radius: 999px; border: #B6D3E7 solid 1px; color: #B6D3E7; font-weight: bold; padding: 1.5%; width: 98%;` } else if ( pagePath.match(/(\/?|\/torrents[^/]*)$/) && SETTINGS.paginationLoop < 500 ) { // The search parge or homepage, both of which require a MutationObserver let observer = new MutationObserver( function(mutations) { // Functionality to run when changes are detected to the target element try { bunnyButtonGeneration(0) } catch (error) { logger.debug(error) } }) let target, config // The target element and configurations that will be used by the MutationObserver if ( pagePath.match(/(\/torrents[^/]*)$/) ) { // The search page, query for a valid target element to observe queryFromElement = document.querySelector('div.page__torrents') // Preferred: A
(table header) for each column headersRow = document.createElement('tr') headersRow.classList.add('quiCKIE_config_table_thead_tr') tableHeaders = [...presetFieldSuffixes] for ( let columnHeader of tableHeaders ) { columnHeader = columnHeader.toLowerCase() let columnGroupElement = document.createElement('col') columnGroupElement.id = `quiCKIE_config_preset_table_colg_col_${columnHeader}` columnGroupElement.classList.add(`quiCKIE_config_preset_table_colg_col`) columnGroupElement.span = 1 tcolg.appendChild(columnGroupElement) let headerElement = document.createElement('th') headerElement.id = `quiCKIE_config_preset_table_thead_th_${columnHeader}` headerElement.classList.add('quiCKIE_config_preset_table_thead_th') headerElement.textContent = panelTextData.columnText[`${columnHeader}`] headerElement.setAttribute('title', panelTextData.columnTitles[`${columnHeader}`]) headersRow.appendChild(headerElement) } // Append the headers to the
(tabledata). Populate each with 1 field from that preset. // 1
for each input field and move the GM_config field into it let dataElement = document.createElement('td') dataElement.classList.add('quiCKIE_config_table_tbody_td_field') dataElement.classList.add(`quiCKIE_config_table_td_${fieldSuffix}`) let fieldElement = document.getElementById(`quiCKIE_config_field_preset-${i}-${fieldSuffix}`) fieldElement.setAttribute('data-fieldtype', fieldSuffix) // Move the GM_Config field into the and then the into the