qui - quiCKIE

A quiCKIE way to send torrents from various trackers to qui!

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==

// ----------------------------------- MetaData --------------------------------------

// @name        qui - quiCKIE
// @author      WirlyWirly + contributors 🫶
// @version     0.4
// @description A quiCKIE way to send torrents from various trackers to qui!
//              To be used with a running instance of qui: https://getqui.com/
//              Written on LibreWolf via Violentmonkey

// @icon        https://gist.github.com/user-attachments/assets/b3d2b863-6aaf-48ab-a4d5-28d0c5df3bae
// @namespace   https://github.com/WirlyWirly
// @run-at      document-end

// @resource    configMenuCSS https://gist.github.com/WirlyWirly/1ffd87e5a3d3f7ce206860d8c100df88/raw/quiCKIEConfigMenu.css
// @require     https://cdn.jsdelivr.net/gh/sizzlemctwizzle/GM_config@43fd0fe4de1166f343883511e53546e87840aeaf/gm_config.js

// ----------------------------------- Permissions --------------------------------------

// @grant   GM_getResourceText
// @grant   GM_getValue
// @grant   GM_listValues
// @grant   GM_registerMenuCommand
// @grant   GM_setValue
// @grant   GM_xmlhttpRequest

// ----------------------------------- Matches --------------------------------------

// How to add new trackers: https://gist.github.com/WirlyWirly/1ffd87e5a3d3f7ce206860d8c100df88?permalink_comment_id=5974494#gistcomment-5974494

// @match   https://animebytes.tv/artist.php?id=*
// @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://bibliotik.me/collections/*
// @match   https://bibliotik.me/torrents/*

// @match   https://broadcasthe.net/collages.php?id=*
// @match   https://broadcasthe.net/series.php?id=*
// @match   https://broadcasthe.net/torrents.php*
//
// @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://www.empornium.sx/collage/*
// @match   https://www.empornium.sx/top10.php*
// @match   https://www.empornium.sx/torrents.php*
// @match   https://www.empornium.sx/user.php?id=*
//
// @match   https://gazellegames.net/collections.php?id=*
// @match   https://gazellegames.net/torrents.php*

// @match   https://www.happyfappy.org/collage/*
// @match   https://www.happyfappy.org/top10.php*
// @match   https://www.happyfappy.org/torrents.php*
// @match   https://www.happyfappy.org/user.php?id=*

// @match   https://hdbits.org/browse.php*
// @match   https://hdbits.org/details.php?id=*
// @match   https://hdbits.org/film/info?id=*

// @match   https://www.myanonamouse.net/
// @match   https://www.myanonamouse.net/t/*
// @match   https://www.myanonamouse.net/tor/browse.php*

// @match   https://orpheus.network/artist.php?id=*
// @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://redacted.sh/artist.php?id=*
// @match   https://redacted.sh/collages.php?id=*
// @match   https://redacted.sh/torrents.php*


// ----------------------------------- Script Links --------------------------------------
//
// @homepage    https://gist.github.com/WirlyWirly/1ffd87e5a3d3f7ce206860d8c100df88
// ==/UserScript==


// =================================== TRACKER LABELS ======================================

// @trackerSettingsPanelEntries
const settingsPanelEntries = {
    // Each entry below uses the tracker's unique domain (lowercase) as the property, followed by the row label (TitleCase) as the value.
    // Keep the list alphabetical, as these entries will be used to generate a row for each tracker in the settings panel.
    // Example: https://broadcasthe.net/ --> broadcasthe
    // Example: https://www.myanonamouse.net/ --> myanonamouse 

    'animebytes': 'AnimeBytes',
    'bibliotik': 'Biblitok',
    'broadcasthe': 'BroadcasTheNet',
    'deepbassnine': 'DeepBassNine', // @tartuffe
    'empornium': 'Empornium',
    'gazellegames': 'GazelleGames',
    'happyfappy': 'HappyFappy', // @Tamlar
    'hdbits': 'HDBits',
    'myanonamouse': 'MyAnonaMouse',
    'orpheus': 'Orpheus',
    'passthepopcorn': 'PassThePopcorn',
    'redacted': 'Redacted',

}

// =================================== CONFIG MENU ======================================

// @trackerFieldGeneration
let gmConfigTrackerFields = {}
let trackerDomains = Object.keys(settingsPanelEntries)
for ( let trackerDomain of trackerDomains ) {
    // For each trackerDomain (property) of the settingsPanelEntries object, generate the fields that will be used by GM_config() to save\load settings. 
    // Each tracker MUST have the fields displayed in the settings panel; Category (+ row label), SavePath, Tags, RatioLimit, Paused, Piece

    // --- GM_config() Fields ---
    let generatedTrackerFields = {
        [`${trackerDomain}-category`]: {
            'label': settingsPanelEntries[trackerDomain],
            'type': 'text'
        },
        [`${trackerDomain}-savePath`]: {
            'type': 'text'
        },
        [`${trackerDomain}-tags`]: {
            'type': 'text'
        },
        [`${trackerDomain}-ratioLimit`]: {
            'type': 'text'
        },
        [`${trackerDomain}-startPaused`]: {
            'type': 'checkbox',
            'default': false
        },
        [`${trackerDomain}-seqPieces`]: {
            'type': 'checkbox',
            'default': false
        }
    }

    gmConfigTrackerFields = {...gmConfigTrackerFields, ...generatedTrackerFields}

}

// The element the settings menu 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
GM_config.init({
    // The quiCKIE settings menu, which can then be displayed by calling 'GM_config.open()'
    'id': 'quiCKIE_config',
    'frame': configFrame,
    'title': `
        <div>
            <div style="padding: 35px 0 0 0"></div>
            🐰
            <span style="user-select: none; font-family: 'Bebas Neue', Helvetica, Tahoma, Geneva, sans-serif; background: none; background-color: #FFFFFF; -webkit-background-clip: text; -webkit-text-fill-color: transparent; -webkit-filter: brightness(110%); filter: brightness(110%); text-shadow: 0 0 20px rgba(0, 124, 255, 0.60); transition: all 0.3s; font-weight: bold;"><a href="${GM_info.script.homepage}" target="_blank" style="text-decoration: none; background: none;">quiCKIE</a></span>
            🐰
            <div style="margin-top: 15px"><span style="color: #95a5a6; display: block; font-size: 10pt">Settings marked * are required. Hover over column headers for details.</span></div>
        </div>
    `,

    'fields': {...gmConfigTrackerFields, ...{
        // Merge these two field objects so that GM_config reads them properly

        'quiURL': {
            'label': 'quiURL*',
            'type': 'text',
            'default': '',
            'title': 'The full URL to a running qui instance\n\https://qui.mydomain.com/instances/1'
        },
        'quiApiKey': {
            'label': 'ApiKey*',
            'type': 'text',
            'default': '',
            'title': 'The qui generated API Key'
        },
    }},
    'events': {
        'open': function (doc) {
            // Actions to take When GM_config.open() is called...
            

            let panelStyle = this.frame.style
            panelStyle.border = '1px solid rgb(41, 52, 87)'
            panelStyle.borderRadius = '10px'
            panelStyle.boxShadow = '0px 4px 12px #191d2a'
            panelStyle.height = 'auto'
            panelStyle.inset = ''
            panelStyle.left = '50%'
            panelStyle.maxHeight = '90%'
            panelStyle.position = 'fixed'
            panelStyle.top = '50%'
            panelStyle.transform = 'translate(-50%,-50%)'
            panelStyle.width = '1000px'

            // Placeholders for the text-input fields
            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_bibliotik-savePath').placeholder = '/downloads/Bibliotik/'
            document.getElementById('quiCKIE_config_field_gazellegames-category').placeholder = 'GazelleGames'
            document.getElementById('quiCKIE_config_field_orpheus-tags').placeholder = 'music,private'
            document.getElementById('quiCKIE_config_field_happyfappy-ratioLimit').placeholder = '1.25'

            reloadWindow = false

            // Instead of a bunch of stacked elements that GM_config generates for each field, we'll display them in a <table> with columns/rows 
            let table = document.createElement('table')
            table.id = 'quiCKIE_config_table'

            let tcolg = document.createElement('colgroup')
            tcolg.id = 'quiCKIE_config_table_colg'

            let thead = document.createElement('thead')
            thead.id = 'quiCKIE_config_table_thead'

            let tbody = document.createElement('tbody')
            tbody.id = 'quiCKIE_config_table_tbody'

            table.appendChild(tcolg)
            table.appendChild(thead)
            table.appendChild(tbody)

            // Give the <table> an id for use in CSS

            // Insert the <table> after the GM_config header
            document.getElementById('quiCKIE_config_header').insertAdjacentElement('afterend', table)

            // Generate <th> (table header) for each column
            let headersRow = document.createElement('tr')
            for (let columnHeader of ['Tracker', 'Category', 'SavePath', 'Tags', 'Ratio', 'Paused', 'SeqPieces']) {
                let columnGroupElement = document.createElement('col')
                columnGroupElement.id = `quiCKIE_config_table_colg_col_${columnHeader.toLowerCase()}`
                columnGroupElement.classList.add(`quiCKIE_config_table_colg_col`)
                columnGroupElement.span = 1
                tcolg.appendChild(columnGroupElement)

                let headerElement = document.createElement('th')
                headerElement.innerHTML = columnHeader
                headerElement.id = `quiCKIE_config_table_thead_th_${columnHeader.toLowerCase()}`
                headerElement.classList.add('quiCKIE_config_table_thead_th')
                headersRow.appendChild(headerElement)
            }

            // Append the headers to the <thead> (tableHeader) element
            thead.appendChild(headersRow)

            // Add the mouse-over text for each column header
            document.getElementById('quiCKIE_config_table_thead_th_tracker').setAttribute('title', 'The tracker for which these fields will be applied to')
            document.getElementById('quiCKIE_config_table_thead_th_category').setAttribute('title', 'Specify the category to apply for these torrents')
            document.getElementById('quiCKIE_config_table_thead_th_savepath').setAttribute('title', 'Specify the full-path for where to save these torrents\n\n* The path must be accessible by the torrent client itself')
            document.getElementById('quiCKIE_config_table_thead_th_tags').setAttribute('title', 'A comma seperated list of tags for these torrents\n\nFilms, Private Tracker, Videos')
            document.getElementById('quiCKIE_config_table_thead_th_ratio').setAttribute('title', 'Stop the torrents when they have seeded to this ratio limit')
            document.getElementById('quiCKIE_config_table_thead_th_paused').setAttribute('title', 'Pause torrents when they are added')
            document.getElementById('quiCKIE_config_table_thead_th_seqpieces').setAttribute('title', 'Download torrent pieces sequentially to allow for media playback while downloading\n* May impact download speed')


            // The field suffixes as specified in @trackerFieldGeneration
            let fieldSuffixes = ['category', 'savePath', 'tags', 'ratioLimit', 'startPaused', 'seqPieces']
            let uniqueDomains = Object.keys(settingsPanelEntries)
            for (let uniqueDomainKey of uniqueDomains) {
                // For each tracker, create 1 <tr> (tablerow). For each field of that tracker, create 1 <td> (tabledata). Populate each <td> with 1 field from that tracker.

                // 1 <tr> for this tracker, appended to the <tbody> (tableBody)
                let trackerRow = document.createElement('tr')
                trackerRow.classList.add('quiCKIE_config_table_tbody_tr')
                tbody.appendChild(trackerRow)

                // Move the label field from the 'category' element as the first <td> of the <tr>
                let labelData = document.createElement('td')
                labelData.classList.add('quiCKIE_config_table_td_label')

                labelData.appendChild(document.getElementById(`quiCKIE_config_${uniqueDomainKey}-category_field_label`))
                trackerRow.appendChild(labelData)

                for (let fieldSuffix of fieldSuffixes) {
                    // Create a <td> for each input field and move the GM_config field into it

                    let fieldElement = document.getElementById(`quiCKIE_config_field_${uniqueDomainKey}-${fieldSuffix}`)

                    let dataElement = document.createElement('td')
                    dataElement.classList.add('quiCKIE_config_table_td_field')

                    // Move the GM_Config field into the <td>
                    dataElement.appendChild(fieldElement)

                    trackerRow.appendChild(dataElement)

                    // Clean-up: Remove the now empty GM_config element
                    document.getElementById(`quiCKIE_config_${uniqueDomainKey}-${fieldSuffix}_var`).remove()

                }

            }

            // Create GitHub version element
            let githubSVG = '<svg width="16" height="16" viewBox="0 0 98 96" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_730_27136)"><path d="M41.4395 69.3848C28.8066 67.8535 19.9062 58.7617 19.9062 46.9902C19.9062 42.2051 21.6289 37.0371 24.5 33.5918C23.2559 30.4336 23.4473 23.7344 24.8828 20.959C28.7109 20.4805 33.8789 22.4902 36.9414 25.2656C40.5781 24.1172 44.4062 23.543 49.0957 23.543C53.7852 23.543 57.6133 24.1172 61.0586 25.1699C64.0254 22.4902 69.2891 20.4805 73.1172 20.959C74.457 23.543 74.6484 30.2422 73.4043 33.4961C76.4668 37.1328 78.0937 42.0137 78.0937 46.9902C78.0937 58.7617 69.1934 67.6621 56.3691 69.2891C59.623 71.3945 61.8242 75.9883 61.8242 81.252L61.8242 91.2051C61.8242 94.0762 64.2168 95.7031 67.0879 94.5547C84.4102 87.9512 98 70.6289 98 49.1914C98 22.1074 75.9883 6.69539e-07 48.9043 4.309e-07C21.8203 1.92261e-07 -1.9479e-07 22.1074 -4.3343e-07 49.1914C-6.20631e-07 70.4375 13.4941 88.0469 31.6777 94.6504C34.2617 95.6074 36.75 93.8848 36.75 91.3008L36.75 83.6445C35.4102 84.2188 33.6875 84.6016 32.1562 84.6016C25.8398 84.6016 22.1074 81.1563 19.4277 74.7441C18.375 72.1602 17.2266 70.6289 15.0254 70.3418C13.877 70.2461 13.4941 69.7676 13.4941 69.1934C13.4941 68.0449 15.4082 67.1836 17.3223 67.1836C20.0977 67.1836 22.4902 68.9063 24.9785 72.4473C26.8926 75.2227 28.9023 76.4668 31.2949 76.4668C33.6875 76.4668 35.2187 75.6055 37.4199 73.4043C39.0469 71.7773 40.291 70.3418 41.4395 69.3848Z" fill="white"/></g><defs><clipPath id="clip0_730_27136"><rect width="98" height="96" fill="white"/></clipPath></defs></svg>'

            let versionElement = document.createElement('a')
            versionElement.classList = 'version_label reset'
            versionElement.title = 'Source Code on GistHub'
            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)

            // 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)
            })

        },
        '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('configMenuCSS')
    'css': `
    #quiCKIE_config {
        background: #191d2a;
        margin: 0;
        padding: 10px 20px;
        color: #ffffff;
        line-height: 20px;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    }

    #quiCKIE_config_header {
        color: #ffffff;
        padding-bottom: 10px;
        font-weight: 100;
        justify-content: center;
        align-items: center;
        text-align: center;
        border-bottom: none;
        background: transparent;
        margin: 0px 0x 10px 0px;
    }

    #quiCKIE_config_table {
        background: none;
        border-collapse: collapse;
        border-radius: 5px;
        box-shadow: none !important;
        margin: 0px auto 25px auto;
        max-width: 85%;
        text-align: center;
    }

    /* --- Table Columns --- */
    #quiCKIE_config_table_colg_col_tracker {
        width: 105px;
    }

    #quiCKIE_config_table_colg_col_category {
        width: 120px;
    }

    #quiCKIE_config_table_colg_col_savepath {
        width: 200px;
    }

    #quiCKIE_config_table_colg_col_tags {
        width: 130px;
    }

    #quiCKIE_config_table_colg_col_ratio {
        width: 55px;
    }

    #quiCKIE_config_table_colg_col_paused {
        width: 105px;
    }

    #quiCKIE_config_table_colg_col_playback {
        width: 105px;
    }

    #quiCKIE_config_table_colg col.quiCKIE_config_table_colg_col {
        text-align: center;
        background: none;
        box-shadow: none;
        border: none;

    }

    #quiCKIE_config_table_thead tr:first-child {
        border-top-left-radius: 5px;
    }

    #quiCKIE_config_table_thead th.quiCKIE_config_table_thead_th {
        background: #2c3e50;
        border-color: #191d2a;
        border-style: none solid none solid;
        border-width: 3px;
        color: #FFFFFF;
        cursor: pointer;
        font-size: 18px;
        margin: 5px 0px 5px 0px;
        padding: 5px 20px;
        text-align: center;
    }

    #quiCKIE_config_table_tbody .quiCKIE_config_table_tbody_tr:nth-child(even) {
        background-color: #2c3e50;
    }

    #quiCKIE_config_table thead {
        font-weight: bolder;
    }

    #quiCKIE_config_table tbody tr td {
        font-size: 12px;
        padding: 5px 0px 5px 0px;
        text-align: center;
    }


    #quiCKIE_config .config_var {
        border-bottom: none;
        margin: 0px auto 5px auto;
        padding: 5px 0px 5px 0px;
        text-align: center;
    }

    #quiCKIE_config .field_label {
        color: #bfbfbf;
        font-size: 12px;
        font-weight: normal;
        text-align: center;
        user-select: none;
    }

    #quiCKIE_config_field_quiURL,
    #quiCKIE_config_field_quiApiKey,
    #quiCKIE_config_table_tbody td.quiCKIE_config_table_td_field input[type="text"] {
        background: rgba(255, 255, 255, 0.9);
        border-radius: 3px;
        border: 1px solid #ddd;
        box-shadow: none;
        box-sizing: border-box;
        color: #191d2a;
        font-size: 12px;
        height: 50%;
        margin: 0 2px 0 2px;
        padding: 2px 2px 2px 2px;
        text-align: center;
        transition: all 0.3s ease;
        width: 95%
    }

    #quiCKIE_config_table_tbody td.quiCKIE_config_table_td_field input[type="text"]:focus {
        border-color: #2C3E50;
        box-shadow: 0 0 5px rgba(0, 124, 255, 0.60);
        outline: none;
    }

    #quiCKIE_config_table_tbody td.quiCKIE_config_table_td_field input[type="checkbox"] {
        box-shadow: none;
        cursor: pointer;
        margin: 5px 0px 5px 0px;
    }

    #quiCKIE_config_quiURL_field_label {
        width: 45px;
    }

    #quiCKIE_config_field_quiApiKey,
    #quiCKIE_config_field_quiURL {
        width: 300px;
    }

    #quiCKIE_config .reset {
        color: #95a5a6;
        text-decoration: none;
        user-select: none;
    }

    #quiCKIE_config_buttons_holder {
        align-items: center;
        column-gap: 20px;
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 1fr 1fr;
        height: 95px;
        margin: 0px auto 10px auto;
        padding: 10px 0px 10px 0px;
        row-gap: 10px;
        text-align: center;
        max-width: 35%;
    }

    #quiCKIE_config .reset_holder {
        grid-column: 2;
        grid-row: 2;
    }

    #quiCKIE_config .version_label {
        grid-column: 1;
        grid-row: 2;
        text-align: center;
    }

    #quiCKIE_config_resetLink {
        text-transform: lowercase;
        background: transparent;
        color: #95a5a6;
    }

    #quiCKIE_config .version_label:hover,
    #quiCKIE_config_resetLink:hover {
        text-decoration: underline;
    }

    #quiCKIE_config .saveclose_buttons {
        margin: 0px 10px 0px 10px;
        width: 100%;
    }

    #quiCKIE_config_saveBtn {
        background: none;
        background-color: #2C3E50;
        border-radius: 5px;
        border: none;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        color: #FFFFFF;
        cursor: pointer;
        font-size: 1rem;
        font-weight: 500;
        grid-column: 1;
        grid-row: 1;
        padding-bottom: 6px !important;
        padding-top: 6px !important;
        padding: 15px 20px;
        transition: background-color 0.2s ease, transform 0.2s ease;
        will-change: background-color, transform;
    }

    #quiCKIE_config_saveBtn:hover {
        background-color: #34495E;
        transform: translateY(-2px);
    }

    #quiCKIE_config_saveBtn:active {
        background-color: rgba(0, 124, 255);
        transform: translateY(1px);
    }

    #quiCKIE_config_saveBtn.success {
        box-shadow: 0 0 6px 3px rgba(0, 124, 255, 0.60);
    }

    #quiCKIE_config_closeBtn {
        background: none;
        background-color: #2C3E50;
        border-radius: 5px;
        border: none;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        color: #FFFFFF;
        cursor: pointer;
        font-size: 1rem;
        font-weight: 500;
        grid-column: 2;
        grid-row: 1;
        padding-bottom: 6px !important;
        padding-top: 6px !important;
        padding: 15px 20px;
        transition: background-color 0.2s ease, transform 0.2s ease;
        will-change: background-color, transform;
    }

    #quiCKIE_config_closeBtn:hover {
        background-color: #34495E;
        transform: translateY(-2px);
    }

    #quiCKIE_config_closeBtn:active {
        background-color: #2C3E50;
        transform: translateY(1px);
    }

    /* Tooltip styling */
    #quiCKIE_config .field_label[title]:hover::after {
        content: attr(title);
        position: absolute;
        background: #2C3E50;
        color: white;
        padding: 5px 10px;
        border-radius: 3px;
        font-size: 0.8em;
        max-width: 300px;
        z-index: 100;
        margin-top: 25px;
        left: 20px;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
    }
    `
})

// Register the settings menu to be opened from the UserScript manager dialouge
GM_registerMenuCommand('Settings', () => {
    GM_config.open()
})


// =================================== FUNCTIONS ======================================

function quiAddTorrent(quiURL, quiApiKey, torrentURL, category = '', savePath = '', tags = '', ratioLimit = '', startPaused = false, seqPieces = false) {
    // Add a torrent to qui using the provided arguments

    // Generate the API endpoint url to send the POST
    let quiHost = quiURL.match(/^(.*)\/(instances\/\d+)/)[1]
    let quiInstance = quiURL.match(/^(.*)\/(instances\/\d+)/)[2]
    let quiApiAddTorrentURL = `${quiHost}/api/${quiInstance}/torrents`

    // The form data that will be passed to qui
    let form = new FormData()
    form.append('urls', torrentURL)
    form.append('category', category)
    form.append('savepath', savePath)
    form.append('tags', tags)
    form.append('ratioLimit', ratioLimit)
    form.append('paused', startPaused)

    if ( seqPieces == true ) {
        // Allow for playback while downloading by enabling "Sequential Piece Downloading" AND "First\Last Piece Priority" 
        form.append('sequentialDownload', true)
        form.append('firstLastPiecePrio', true)
    }

    GM_xmlhttpRequest({
        // Use the internal GM function to prevent source-origin errors
        method: 'POST',
        url: quiApiAddTorrentURL,
        data: form,
        headers: {
            'X-API-Key': quiApiKey,
        },
        onload: function(response) {
            // Actions to take after the request has completed 
            
            if (response.status == 201) {
                // Success: The torrent has been added to qui

                document.getElementById('__CLICKED__').textContent = ' ✔️ '
                document.getElementById('__CLICKED__').removeAttribute('id')

            } else {
                // Failed: The torrent was NOT added to qui, log the response and display an alert...

                document.getElementById('__CLICKED__').textContent = ' ❌ '
                document.getElementById('__CLICKED__').removeAttribute('id')

                console.log(response)
                window.alert(`❌ quiCKIE Failed to Add the Torrent to qui ❌\n\nStatus Code: ${response.status}\n\n${response.responseText}`)

            }

        }
    })

}

function createBunnyButton(torrentURL, buttonText = ' 🐰 ', fontSize='inherit') {
    // Create the bunnyButton that will be displayed on the site

    let bunnyButton = document.createElement('a')
    bunnyButton.classList.add('quiCKIE_bunnyButton')
    bunnyButton.href = 'javascript:void(0)'
    bunnyButton.textContent = buttonText
    bunnyButton.title = `quiCKIE\n-----------------\nCategory: ${SETTINGS.category}\nSavePath: ${SETTINGS.savePath}\nTags: ${SETTINGS.tags}\nRatioLimit: ${SETTINGS.ratioLimit}\nStartPaused: ${SETTINGS.startPaused}\nSeqPiece: ${SETTINGS.seqPieces}`

    bunnyButton.setAttribute('style', `font-size: ${fontSize}; text-align: center; text-decoration: none`)

    bunnyButton.addEventListener('mouseover', function(event) {
        // When this bunnyButton is hovered over...
        
        this.style.textShadow = '0 0 5px rgb(4, 184, 255)'
    })

    bunnyButton.addEventListener('mouseout', function(event) {
        // When this bunnyButton is hovered out...
        
        this.style.textShadow = ''
    })

    bunnyButton.addEventListener('mouseup', function(event) {
        // When this bunnyButton is clicked, determine what kind of click it was and respond accordingly...

        if ( event.ctrlKey ) {
            // Ctrl-Click: Open the quiURL in a new tab

            window.open(SETTINGS.quiURL).focus()

        } else if ( event.shiftKey ) {
            // Shift-Click: Open the quiURL in a new window

            window.open(SETTINGS.quiURL, '_blank').focus()
            
        } else if ( event.button == 0 ) {
            // Left-Click: Add a torrent to qui

            this.id = '__CLICKED__'
            this.textContent = ' 🕓 '
            quiAddTorrent(SETTINGS.quiURL, SETTINGS.quiApiKey, torrentURL, SETTINGS.category, SETTINGS.savePath, SETTINGS.tags, SETTINGS.ratioLimit, SETTINGS.startPaused, SETTINGS.seqPieces)

        } else if ( event.button == 1 ) {
            // Middle-Click: Open the quiURL in a new tab
            
            window.open(SETTINGS.quiURL, '_blank').focus()
        }

    })

    return bunnyButton

}


// =================================== CODE ======================================

// To save resources while allowing cross-site compatibility, the domain of the site is used when saving settings and creating GM_config fields
// Example: https://broadcasthe.net/ --> broadcasthe
let trackerDomain = document.location.hostname.match(/^(www\.)?(.*?)(\.\w+)$/)[2].toLowerCase()

const SETTINGS = {
    // The saved settings (cache) of the current tracker
    quiURL: GM_config.get('quiURL'),
    quiApiKey: GM_config.get('quiApiKey'),
    category: GM_config.get(`${trackerDomain}-category`),
    savePath: GM_config.get(`${trackerDomain}-savePath`),
    tags: GM_config.get(`${trackerDomain}-tags`),
    ratioLimit: GM_config.get(`${trackerDomain}-ratioLimit`),
    startPaused: GM_config.get(`${trackerDomain}-startPaused`),
    seqPieces: GM_config.get(`${trackerDomain}-seqPieces`),
}


// @trackerIfBlocks
// Because the site's domain is unique, we can use it to determine what tracker this is and how bunnyButtons should be generated
//     ! This is the same domain used when creating the tracker's settings fields below @trackerFields
if ( trackerDomain == 'animebytes' ) {
    // ----------------------------------- AnimeBytes -----------------------------------
    // Browse | Collages | Company | Series 

    // An array of all the torrent download elements on the page 
    let allDownloadElements = document.querySelectorAll('a[href^="/torrent/"][title="Download torrent"]')

    for (let downloadElement of allDownloadElements) {
        // For each download element, generate a bunnyButton and insert it after the download element

        let bunnyButton = createBunnyButton(downloadElement.href)

        // Insert the bunnyButton after the site's download element
        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        
        // Insert a '|' between the site's download element and the new bunnyButton
        downloadElement.insertAdjacentText('afterend', '|')

    }

} else if ( trackerDomain == 'bibliotik' ) {
    // ----------------------------------- Bibliotik -----------------------------------
    // Browse | Details

    let allDownloadElements = document.querySelectorAll('a[href^="/torrents/"][title="Download"]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '  ')

    }

} else if ( trackerDomain == 'broadcasthe' ) {
    // ----------------------------------- BroadcasTheNet -----------------------------------
    // Browse | Series | Season\Episodes

    let allDownloadElements = document.querySelectorAll('a[href^="torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', ' |')

    }

} else if ( trackerDomain == 'deepbassnine' ) {
    // ----------------------------------- DeepBassNine -----------------------------------
    // Album | Artist | Browse

    let allDownloadElements = document.querySelectorAll('a[href^="torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '|')

    }

} else if ( trackerDomain == 'empornium' ) {
    // ----------------------------------- Empornium -----------------------------------
    // Browse | Collages | Details | Top10 
    
    let allDownloadElements = document.querySelectorAll('a[href^="/torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href, '🐰', '125%')

        if ( document.location.pathname.match(/\/collage\/\d+/) ) {
            // Collage Page: Insert bunnyButton in the same row as the other buttons
            downloadElement.parentElement.insertAdjacentElement('afterend', bunnyButton)

        } else {
            downloadElement.insertAdjacentElement('afterend', bunnyButton)
            downloadElement.insertAdjacentText('afterend', '  ')
        }

    }

} else if ( trackerDomain == 'gazellegames' ) {
    // ----------------------------------- GazelleGames -----------------------------------
    // Browse | Bundles | Game

    let allDownloadElements = document.querySelectorAll('a[href^="torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '|')

    }

} else if ( trackerDomain == 'happyfappy' ) {
    // ----------------------------------- HappyHappy -----------------------------------
    // Browse | Details | Top10 | Collages

    let allDownloadElements = document.querySelectorAll('a[href^="/torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href, '🐰', '125%')

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '  ')

    }

} else if ( trackerDomain == 'hdbits' ) {
    // ----------------------------------- HDBits -----------------------------------
    // Browse | Details | Film  

    let allDownloadElements = document.querySelectorAll('a.js-download[href^="/download.php/"]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href, '🐰', '140%')

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '  ')

    }

} else if ( trackerDomain == 'myanonamouse' ) {
    // ----------------------------------- MyAnonaMouse -----------------------------------
    // Browse | Details | Homepage

    if ( document.URL.match(/\/t\/\d+/) ) {
        // The book details page, which doesn't require a MutationObserver

        let downloadButton = document.querySelector('a[href^="/tor/download.php/"][title*="Download"]')

        let bunnyButton = createBunnyButton(downloadButton.href, '🐰', '150%')

        downloadButton.insertAdjacentElement('afterend', bunnyButton)


    } else {
        // The Browse 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 {

                let allDownloadElements = document.querySelectorAll('a[href^="/tor/download.php/"][title*="Download"]')

                for (let downloadElement of allDownloadElements) {

                    let bunnyButton = createBunnyButton(downloadElement.href, '🐰', '150%')

                    downloadElement.insertAdjacentElement('afterend', bunnyButton)

                }

            } catch(error) {
                // console.log(error)
                return

            }
        })

        let target = document.getElementById('ssr')
        let config = { childList: true }

        observer.observe(target, config)
    }

} else if ( trackerDomain == 'orpheus' ) {
    // ----------------------------------- Orpheus -----------------------------------
    // Album | Artist | Browse | Collages

    let allDownloadElements = document.querySelectorAll('a[href^="torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '|')

    }

} else if ( trackerDomain == 'passthepopcorn' ) {
    // ----------------------------------- PassThepopcorn -----------------------------------
    // Film
    
    let allDownloadElements = document.querySelectorAll('a[href^="torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', ' |')

    }

} else if ( trackerDomain == 'redacted' ) {
    // ----------------------------------- Redacted -----------------------------------
    // Album | Artist | Browse

    let allDownloadElements = document.querySelectorAll('a[href^="torrents.php?action=download&id="]')

    for (let downloadElement of allDownloadElements) {

        let bunnyButton = createBunnyButton(downloadElement.href)

        downloadElement.insertAdjacentElement('afterend', bunnyButton)
        downloadElement.insertAdjacentText('afterend', '|')

    }

}