GoFile 增强

GoFile 文件批量下载。支持递归下载文件夹内容、直链下载。可以配合 AB Download Manager、Aria2、IDM 等下载器使用。

// ==UserScript==
// @name         GoFile 增强
// @name:en      GoFile Enhanced
// @namespace    https://github.com/ewigl/gofile-enhanced
// @version      0.7.8
// @description  GoFile 文件批量下载。支持递归下载文件夹内容、直链下载。可以配合 AB Download Manager、Aria2、IDM 等下载器使用。
// @description:en  Directly batch-download GoFiles. Supports recursive folder download, Supports direct links. Built-in support for download managers like AB Download Manager, Aria2, and IDM.
// @author       Licht
// @license      MIT
// @homepage     https://github.com/ewigl/gofile-enhanced
// @match        http*://gofile.io/*
// @icon         https://gofile.io/dist/img/favicon16.png
// @connect      localhost
// @connect      *
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// ==/UserScript==

;(function () {
    'use strict'

    const SUPPORTED_DOWNLOADERS = ['Direct', 'ABDM', 'Aria2', 'IDM']

    const DEFAULT_LANGUAGE = 'en-US'
    const CRLF = '\r\n'

    const GE_CONTAINER_ID = 'GofileEnhanced_Container'
    const GE_GORM_ID_PREFIX = 'GofileEnhanced_Form'

    const I18N = {
        'zh-CN': {
            abdm_connected: 'ABDM 连接成功',
            abdm_connection_fail: 'ABDM 连接失败',
            abdm_download_folder: 'ABDM 下载目录',
            abdm_download_folder_placeholder: '若留空则使用 ABDM 默认设置',
            abdm_port: 'ABDM 端口',
            abdm_port_not_configured: 'ABDM 端口未配置',
            abdm_port_placeholder: '默认为 15151',
            abdm_settings: ' AB Download Manager 设置',
            are_you_sure_to_download__these_files: '确定要下载下列文件吗?',
            aria2_connected: 'Aria2 连接成功',
            aria2_connection_fail: 'Aria2 连接失败',
            aria2_rpc_address: 'Aria2 RPC 地址',
            aria2_rpc_address_placeholder: '默认为 http://localhost:6800/jsonrpc',
            aria2_rpc_secret: 'Aria2 RPC 密钥',
            aria2_rpc_secret_placeholder: '若未设置留空即可',
            aria2_rpc_dir: 'Aria2 下载目录',
            aria2_rpc_dir_placeholder: '若留空则使用 Aria2 默认设置',
            aria2_settings: 'Aria2 设置',
            cancel: '取消',
            config: '配置',
            confirm: '确定',
            download_all: '下载全部',
            download_selected: '下载选中',
            empty_folder: '文件夹为空',
            empty_folder_description: '当前文件夹内容为空',
            error: '错误',
            export_all: '导出全部',
            export_selected: '导出选中',
            failed_to_fetch_folder_content: '获取文件夹内容失败',
            failed_to_send_to_abdm: '未成功发送至 ABDM',
            failed_to_send_to_aria2: '未成功发送至 Aria2',
            fetching_file_list: '正在获取文件列表',
            loading: '加载中...',
            loading_file_list: '正在加载文件列表',
            loading_please_wait: '正在加载,请稍候',
            no_file_selected: '未选择文件',
            no_file_selected_description: '请至少选择一个文件',
            please_make_sure_you_have_configured_download_folder: '请确保您已正确配置下载目录',
            recursion_download: '递归下载',
            reset_aria2: '重置 Aria2',
            send_all: '发送全部',
            send_selected: '发送选中',
            success: '成功',
            successfully_fetched_file_list: '成功获取文件列表',
            successfully_reset: '已重置',
            successfully_sent_to_abdm: '已成功发送至 ABDM',
            successfully_sent_to_aria2: '已成功发送至 Aria2',
            test_abdm: '测试 ABDM',
            test_aria2: '测试 Aria2',
            unknown_error: '未知错误',
            unsupported_format: '不支持的格式',
            request_aborted: '请求中断',
            request_timed_out: '请求超时',
        },
        'en-US': {
            abdm_connected: 'ABDM connected successfully',
            abdm_connection_fail: 'ABDM connection failed',
            abdm_download_folder: 'ABDM Download Folder',
            abdm_download_folder_placeholder: 'Leave empty to use ABDM default settings',
            abdm_port: 'ABDM Port',
            abdm_port_not_configured: 'ABDM port not configured',
            abdm_port_placeholder: 'Default is 15151',
            abdm_settings: 'AB Download Manager Settings',
            are_you_sure_to_download__these_files: 'Are you sure you want to download the following files?',
            aria2_connected: 'Aria2 connected successfully',
            aria2_connection_fail: 'Aria2 connection failed',
            aria2_rpc_address: 'Aria2 RPC Address',
            aria2_rpc_address_placeholder: 'Default is http://localhost:6800/jsonrpc',
            aria2_rpc_secret: 'Aria2 RPC Secret',
            aria2_rpc_secret_placeholder: 'Leave empty if not set',
            aria2_rpc_dir: 'Aria2 RPC Directory',
            aria2_rpc_dir_placeholder: 'Leave empty to use Aria2 default settings',
            aria2_settings: 'Aria2 Settings',
            cancel: 'Cancel',
            config: 'Config',
            confirm: 'Confirm',
            download_all: 'Download All',
            download_selected: 'Download Selected',
            empty_folder: 'Empty Folder',
            empty_folder_description: 'The current folder is empty',
            error: 'Error',
            export_all: 'Export All',
            export_selected: 'Export Selected',
            failed_to_fetch_folder_content: 'Failed to fetch folder content',
            failed_to_send_to_abdm: 'Failed to send to ABDM',
            failed_to_send_to_aria2: 'Failed to send to Aria2',
            fetching_file_list: 'Fetching file list',
            loading: 'Loading...',
            loading_file_list: 'Loading file list',
            loading_please_wait: 'Loading, please wait',
            no_file_selected: 'No File Selected',
            no_file_selected_description: 'Please select at least one file',
            please_make_sure_you_have_configured_download_folder: 'Please make sure you have configured the download folder',
            reset_aria2: 'Reset Aria2',
            recursion_download: 'Recursion Download',
            send_all: 'Send All',
            send_selected: 'Send Selected',
            success: 'Success',
            successfully_fetched_file_list: 'Successfully fetched file list',
            successfully_reset: 'successfully reset',
            successfully_sent_to_abdm: 'successfully sent to ABDM',
            successfully_sent_to_aria2: 'successfully sent to Aria2',
            test_abdm: 'Test ABDM',
            test_aria2: 'Test Aria2',
            unknown_error: 'Unknown Error',
            unsupported_format: 'Unsupported Format',
            request_aborted: 'Request Aborted',
            request_timed_out: 'Request Timed Out',
        },
    }

    const ICONS = {
        circle_down_s: 'fas fa-circle-down',
        circle_down_r: 'far fa-circle-down',
        circle_nodes_s: 'fas fa-circle-nodes',
        copy_s: 'fas fa-copy',
        copy_r: 'far fa-copy',
        file_s: 'fas fa-file',
        file_r: 'far fa-file',
        file_ziper_s: 'fas fa-file-zipper',
        file_ziper_r: 'far fa-file-zipper',
        folder_s: 'fas fa-folder',
        folder_r: 'far fa-folder',
        gear_s: 'fas fa-gear',
        google_plus: 'fa-brands fa-google-plus',
        key_s: 'fas fa-key',
        link_s: 'fas fa-link',
        plane_s: 'fas fa-paper-plane',
        plane_r: 'far fa-paper-plane',
        plug_s: 'fas fa-plug',
        rotate_left_s: 'fas fa-rotate-left',
    }

    const GE_CONFIG = {
        ABDM: {
            name: 'ABDM',
            id: 'ABDM',
            desc: 'AB Download Manager',
            homepage: 'https://github.com/amir1376/ab-download-manager',
            settings: {
                abdmPort: {
                    key: 'abdm_port',
                    defaultValue: '15151',
                    i18nKey: 'abdm_port',
                    icon: ICONS.plug_s,
                    placeholderI18nKey: 'abdm_port_placeholder',
                },
                abdmDownloadFolder: {
                    key: 'abdm_download_folder',
                    defaultValue: '',
                    i18nKey: 'abdm_download_folder',
                    icon: ICONS.folder_s,
                    placeholderI18nKey: 'abdm_download_folder_placeholder',
                },
            },
        },
        Aria2: {
            name: 'Aria2',
            id: 'Aria2',
            desc: 'Aria2 RPC Interface',
            homepage: 'https://aria2.github.io/manual/en/html/aria2c.html#rpc-interface',
            settings: {
                rpcAddress: {
                    key: 'aria2_rpc_address',
                    defaultValue: 'http://localhost:6800/jsonrpc',
                    i18nKey: 'aria2_rpc_address',
                    icon: ICONS.link_s,
                    placeholderI18nKey: 'aria2_rpc_address_placeholder',
                },
                rpcSecret: {
                    key: 'aria2_rpc_secret',
                    defaultValue: '',
                    i18nKey: 'aria2_rpc_secret',
                    icon: ICONS.key_s,
                    placeholderI18nKey: 'aria2_rpc_secret_placeholder',
                },
                rpcDir: {
                    key: 'aria2_rpc_dir',
                    defaultValue: '',
                    i18nKey: 'aria2_rpc_dir',
                    icon: ICONS.folder_s,
                    placeholderI18nKey: 'aria2_rpc_dir_placeholder',
                },
            },
        },
    }

    const utils = {
        getValue: (name) => GM_getValue(name),
        setValue(name, value) {
            GM_setValue(name, value)
        },
        gmFetch(url, options = {}) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: options.method || 'GET',
                    url,
                    headers: options.headers || {},
                    data: options.body || null,
                    responseType: options.responseType || 'text',
                    onload: (response) => {
                        resolve({
                            ok: response.status >= 200 && response.status < 300,
                            status: response.status,
                            statusText: response.statusText,
                            url: response.finalUrl,
                            text: () => Promise.resolve(response.responseText),
                            json: () => Promise.resolve(JSON.parse(response.responseText)),
                            xml: () => Promise.resolve(response.responseXML),
                            raw: response,
                        })
                    },
                    onerror: (err) => reject(err),
                    ontimeout: () => reject(new Error(utils.getTranslation('request_timed_out'))),
                    onabort: () => reject(new Error(utils.getTranslation('request_aborted'))),
                })
            })
        },
        getSettings(category, settingKey) {
            const setting = GE_CONFIG[category].settings[settingKey]
            return utils.getValue(setting.key) ?? setting.defaultValue
        },
        setSettings(category, settingKey, value) {
            const setting = GE_CONFIG[category].settings[settingKey]
            utils.setValue(setting.key, value)
        },
        getAllSettings(category) {
            const settings = GE_CONFIG[category].settings
            return Object.keys(settings).reduce((acc, key) => {
                acc[key] = utils.getSettings(category, key)
                return acc
            }, {})
        },
        resetAllSettings(category) {
            const settings = GE_CONFIG[category].settings
            Object.keys(settings).forEach((key) => {
                const setting = settings[key]
                utils.setValue(setting.key, setting.defaultValue)
                createNotification(utils.getTranslation('success'), `${utils.getTranslation(setting.i18nKey)} ${utils.getTranslation('successfully_reset')}`)
            })
        },
        initSettings() {
            Object.keys(GE_CONFIG).forEach((category) => {
                const settings = GE_CONFIG[category].settings
                Object.keys(settings).forEach((key) => {
                    const setting = settings[key]
                    if (utils.getValue(setting.key) === undefined) {
                        utils.setValue(setting.key, setting.defaultValue)
                    }
                })
            })
        },
        getTranslation(key) {
            const lang = I18N[navigator.language] ? navigator.language : DEFAULT_LANGUAGE
            return I18N[lang][key] || key
        },
        getToken: () => document.cookie,
        goDirectLinks(links) {
            links.forEach((link) => {
                window.open(link, link)
            })
        },
        async collectAllItems() {
            createAlert('loading', utils.getTranslation('fetching_file_list'))

            const mainContentData = appdata.fileManager.mainContent.data
            const tbdItems = []

            const cookie = utils.getToken()
            const authorization =
                cookie
                    .split(';')
                    .find((row) => row.startsWith('accountToken='))
                    ?.split('=')[1] || ''
            const wt = appdata.wt

            let toDownloadFiles = []

            const collectItems = async (contentData, parentPath = '') => {
                if (contentData.childrenCount > 0) {
                    for (const key of Object.keys(contentData.children)) {
                        const childItem = contentData.children[key]

                        const currentPath = `${parentPath}/${contentData.name}`

                        if (childItem.type === 'file') {
                            tbdItems.push({ ...childItem, downloadFolder: currentPath })
                            toDownloadFiles.push(`${currentPath}/${childItem.name}`)
                        } else if (childItem.type === 'folder') {
                            if (childItem.childrenCount === 0) {
                                continue
                            }
                            try {
                                const res = await utils.gmFetch(`https://api.gofile.io/contents/${childItem.id}?wt=${wt}`, {
                                    method: 'GET',
                                    headers: {
                                        Authorization: `Bearer ${authorization}`,
                                    },
                                })

                                if (res.ok) {
                                    const data = await res.json()
                                    const currentContentData = data.data

                                    if (data.status === 'ok') {
                                        await collectItems(currentContentData, currentPath)
                                    } else {
                                        createNotification(
                                            utils.getTranslation('error'),
                                            `${utils.getTranslation('failed_to_fetch_folder_content')} ${childItem.name}: ${data.message}`,
                                            'error'
                                        )
                                    }
                                } else {
                                    createNotification(utils.getTranslation('error'), `${utils.getTranslation('failed_to_fetch_folder_content')} / ${res.status} - ${res.statusText}`, 'error')
                                }
                            } catch (error) {
                                createNotification(utils.getTranslation('error'), `${utils.getTranslation('failed_to_fetch_folder_content')} ${childItem.name}`, 'error')
                            }
                        }
                    }
                }
            }

            await collectItems(mainContentData)
            closePopup()

            return { items: tbdItems, files: toDownloadFiles }
        },
        recursionDownload(toDownloadFiles, callback) {
            const fileList = toDownloadFiles.sort()

            createPopup({
                title: utils.getTranslation('successfully_fetched_file_list'),
                content: `
                    <div class="space-y-4">
                        <div class="bg-blue-900 bg-opacity-20 border border-blue-800 rounded-lg p-4">
                            <div class="flex items-center space-x-3">
                                <i class="fas fa-info-circle text-blue-400 text-xl"></i>
                                <p class="text-gray-300 text-sm">
                                    <span>${utils.getTranslation('are_you_sure_to_download__these_files')}</span>
                                    <span>${utils.getTranslation('please_make_sure_you_have_configured_download_folder')}</span>
                                </p>
                            </div>
                        </div>
                        
                        <form id="${GE_GORM_ID_PREFIX}_FILE_LIST" class="space-y-4">
                        
                            ${fileList.map((file) => `<p>${file}</p>`).join('')}

                            <button
                                type="submit"
                                class="w-full py-3 bg-blue-600 rounded-lg hover:bg-blue-700 transition duration-300
                                    ease-in-out text-center text-white font-semibold flex items-center justify-center space-x-2"
                            >
                                <i class="fas fa-check"></i>
                                <span> ${utils.getTranslation('confirm')} </span>
                            </button>
                        </form>
                    </div>
                `,
                icon: ICONS.copy_s,
            })

            const form = document.forms[`${GE_GORM_ID_PREFIX}_FILE_LIST`]

            if (form) {
                form.addEventListener('submit', (event) => {
                    event.preventDefault()

                    callback()

                    closePopup()
                })
            }
        },
        sendToABDM(tbdItems) {
            const { abdmPort, abdmDownloadFolder } = utils.getAllSettings('ABDM')
            const cookie = utils.getToken()

            if (!abdmPort) {
                return createNotification(utils.getTranslation('error'), utils.getTranslation('abdm_port_not_configured'), 'error')
            }

            const postDatas = tbdItems.map((item) => {
                return {
                    downloadSource: {
                        link: item.link,
                        headers: {
                            cookie,
                        },
                    },
                    name: item.name,
                    folder: item.downloadFolder || abdmDownloadFolder,
                }
            })

            postDatas.forEach(async (data) => {
                try {
                    const res = await utils.gmFetch(`http://localhost:${abdmPort}/start-headless-download`, {
                        method: 'POST',
                        body: JSON.stringify(data),
                    })
                    if (res.ok) {
                        createNotification(utils.getTranslation('success'), `${data.name} ${utils.getTranslation('successfully_sent_to_abdm')}`, 'success')
                    } else {
                        createNotification(utils.getTranslation('error'), `${data.name} ${utils.getTranslation('failed_to_send_to_abdm')} / ${res.status} - ${res.statusText}`, 'error')
                    }
                } catch (error) {
                    createNotification(utils.getTranslation('error'), `${data.name}  ${utils.getTranslation('failed_to_send_to_abdm')}`, 'error')
                }
            })
        },
        async testABDMConnection() {
            const port = utils.getSettings('ABDM', 'abdmPort')

            if (port) {
                try {
                    const res = await utils.gmFetch(`http://localhost:${port}/ping`)
                    if (res.ok) {
                        createNotification(utils.getTranslation('success'), utils.getTranslation('abdm_connected'), 'success')
                    } else {
                        createNotification(utils.getTranslation('error'), `${utils.getTranslation('abdm_connection_fail')} / ${res.status} - ${res.statusText}`, 'error')
                    }
                } catch (e) {
                    createNotification(utils.getTranslation('error'), utils.getTranslation('abdm_connection_fail'), 'error')
                }
            } else {
                createNotification(utils.getTranslation('error'), utils.getTranslation('abdm_not_configured'), 'error')
            }
        },
        async testAria2Connection() {
            const { rpcAddress, rpcSecret } = utils.getAllSettings('Aria2')

            try {
                const res = await utils.gmFetch(rpcAddress, {
                    method: 'POST',
                    body: JSON.stringify({
                        id: new Date().getTime(),
                        jsonrpc: '2.0',
                        method: 'aria2.getVersion',
                        params: [`token:${rpcSecret}`],
                    }),
                })

                if (res.ok) {
                    createNotification(utils.getTranslation('success'), utils.getTranslation('aria2_connected'), 'success')
                } else {
                    createNotification(utils.getTranslation('error'), `${utils.getTranslation('aria2_connection_fail')} / ${res.status} - ${res.statusText}`, 'error')
                }
            } catch (e) {
                createNotification(utils.getTranslation('error'), utils.getTranslation('aria2_connection_fail'), 'error')
            }
        },
        async sendToAria2(tbdItems) {
            const { rpcAddress, rpcSecret, rpcDir } = utils.getAllSettings('Aria2')

            const cookie = utils.getToken()

            const header = [`Cookie: ${cookie}`]

            const rpcData = tbdItems.map((item) => {
                return {
                    id: new Date().getTime(),
                    jsonrpc: '2.0',
                    method: 'aria2.addUri',
                    params: [
                        `token:${rpcSecret}`,
                        [item.link],
                        {
                            header,
                            dir: item.downloadFolder || rpcDir,
                        },
                    ],
                }
            })

            try {
                const res = await utils.gmFetch(rpcAddress, {
                    method: 'POST',
                    body: JSON.stringify(rpcData),
                })

                if (res.ok) {
                    const responseArray = await res.json()

                    responseArray.forEach((item) => {
                        if (item.error) {
                            createNotification(utils.getTranslation('error'), `${utils.getTranslation('failed_to_send_to_aria2')} / ${item.error.code} - ${item.error.message}`, 'error')
                        } else {
                            createNotification(utils.getTranslation('success'), `${utils.getTranslation('successfully_sent_to_aria2')} / ID: ${item.result}`)
                        }
                    })
                } else {
                    createNotification(utils.getTranslation('error'), `${utils.getTranslation('failed_to_send_to_aria2')} /  ${res.status} - ${res.statusText}`, 'error')
                }
            } catch (e) {
                createNotification(utils.getTranslation('error'), utils.getTranslation('failed_to_send_to_aria2'), 'error')
            }
        },
        exportToIDM(tbdItems) {
            const cookie = utils.getToken()
            const IDMFormatContent = tbdItems
                .map((item) => {
                    return `<${CRLF}${item.link}${CRLF}cookie: ${cookie}${CRLF}>${CRLF}`
                })
                .join('')

            utils.saveAsFile(IDMFormatContent, 'ef2')
        },
        saveAsFile(content, fileExtension) {
            const blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
            const url = URL.createObjectURL(blob)
            const link = document.createElement('a')
            link.href = url
            link.download = `${appdata.fileManager.mainContent.data.name}.${fileExtension}`
            link.click()
            URL.revokeObjectURL(url)
        },
        getHrLine() {
            const hrLine = document.createElement('li')
            hrLine.classList.add('border-b', 'border-gray-700')
            return hrLine
        },
        getButtonTemplate(icon, text) {
            return `
            <a href="javascript:void(0)" class="hover:text-blue-500 flex items-center gap-2" aria-label="${text}">
                <i class="${icon}"></i>
                ${text}
            </a>
            `
        },
        createButton(options = {}) {
            const { icon, text, onClick } = options

            const button = document.createElement('li')
            button.innerHTML = utils.getButtonTemplate(icon, text)

            if (onClick) {
                button.addEventListener('click', onClick)
            }

            return button
        },
        getRegularButtons(format) {
            // Header
            const formatTitleElement = document.createElement('li')
            formatTitleElement.innerHTML = `
            <span class="flex items-center gap-2 text-blue-500 font-bold">
                <i class="${ICONS.google_plus}"></i>
                ${format}
            </span>
            `

            let exportAllText, exportSelectedText, exportAllIcon, exportSelectedIcon

            switch (format) {
                case 'ABDM':
                case 'Aria2':
                    exportAllText = utils.getTranslation('send_all')
                    exportAllIcon = ICONS.plane_s
                    exportSelectedText = utils.getTranslation('send_selected')
                    exportSelectedIcon = ICONS.plane_r
                    break
                case 'IDM':
                    exportAllText = utils.getTranslation('export_all')
                    exportAllIcon = ICONS.file_s
                    exportSelectedText = utils.getTranslation('export_selected')
                    exportSelectedIcon = ICONS.file_r
                    break
                default:
                    exportAllText = utils.getTranslation('download_all')
                    exportAllIcon = ICONS.circle_down_s
                    exportSelectedText = utils.getTranslation('download_selected')
                    exportSelectedIcon = ICONS.circle_down_r
                    break
            }

            const exportAllButton = utils.createButton({
                text: exportAllText,
                icon: exportAllIcon,
                onClick: operations.handleExport.bind(null, {
                    selectMode: false,
                    format,
                }),
            })

            const exportSelectedButton = utils.createButton({
                text: exportSelectedText,
                icon: exportSelectedIcon,
                onClick: operations.handleExport.bind(null, {
                    selectMode: true,
                    format,
                }),
            })

            return [formatTitleElement, exportAllButton, exportSelectedButton]
        },
        getSpecialButtons(downloader) {
            const additionalButtons = []

            const settingsPanleTitle = utils.getTranslation(`${downloader.toLowerCase()}_settings`)
            const settingsButton = utils.createButton({
                icon: ICONS.gear_s,
                text: `${utils.getTranslation('config')} ${downloader}`,
                onClick: () => {
                    createPopup({
                        title: settingsPanleTitle,
                        content: utils.getConfigPanel(downloader),
                        icon: ICONS.gear_s,
                    })

                    const form = document.forms[`${GE_GORM_ID_PREFIX}_${downloader}`]

                    if (form) {
                        form.addEventListener('submit', (event) => {
                            event.preventDefault()

                            Object.entries(GE_CONFIG[downloader].settings).forEach(([settingKey, _value]) => {
                                utils.setSettings(downloader, settingKey, form.elements[_value.key].value)
                            })

                            closePopup()
                        })
                    }
                },
            })

            const abdmRecursionDownloadButton = utils.createButton({
                text: utils.getTranslation('recursion_download'),
                icon: ICONS.copy_s,
                onClick: operations.handleExport.bind(null, {
                    enableRecursion: true,
                    format: 'ABDM',
                }),
            })

            const testABDMButton = utils.createButton({
                icon: ICONS.circle_nodes_s,
                text: utils.getTranslation('test_abdm'),
                onClick: () => {
                    utils.testABDMConnection()
                },
            })

            const aria2RecursionDownloadButton = utils.createButton({
                text: utils.getTranslation('recursion_download'),
                icon: ICONS.copy_s,
                onClick: operations.handleExport.bind(null, {
                    enableRecursion: true,
                    format: 'Aria2',
                }),
            })

            const testAria2Button = utils.createButton({
                icon: ICONS.circle_nodes_s,
                text: utils.getTranslation('test_aria2'),
                onClick: () => {
                    utils.testAria2Connection()
                },
            })

            const rpcResetButton = utils.createButton({
                icon: ICONS.rotate_left_s,
                text: utils.getTranslation('reset_aria2'),
                onClick: () => {
                    utils.resetAllSettings('Aria2')
                },
            })

            switch (downloader) {
                case 'ABDM':
                    additionalButtons.push(abdmRecursionDownloadButton)
                    additionalButtons.push(settingsButton)
                    additionalButtons.push(testABDMButton)
                    break
                case 'Aria2':
                    additionalButtons.push(aria2RecursionDownloadButton)
                    additionalButtons.push(settingsButton)
                    additionalButtons.push(testAria2Button)
                    additionalButtons.push(rpcResetButton)
                    break
                default:
                    break
            }

            return additionalButtons
        },
        getButtonsByDownloader(downloader) {
            const regularButtons = utils.getRegularButtons(downloader)

            const additionalButtons = utils.getSpecialButtons(downloader)

            return [utils.getHrLine(), ...regularButtons, ...additionalButtons]
        },
        getFormInputItemTemplate(setting) {
            const { key, i18nKey, icon, placeholderI18nKey } = setting

            return `
            <div class="space-y-2">
                <label for="${key}" class="block text-sm font-medium text-gray-300">
                    ${utils.getTranslation(i18nKey)}
                </label>
                <div class="relative">
                    <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                        <i class="${icon} text-gray-400"></i>
                    </div>
                    <input 
                        type="text" 
                        id="${key}" 
                        key="${key}" 
                        class="w-full pl-10 pr-3 py-2 bg-gray-700 rounded-lg border border-gray-600 focus:ring-2
                            focus:ring-blue-500 focus:border-blue-500 focus:outline-none transition duration-200 text-white placeholder-gray-400"
                        value="${utils.getValue(key)}"
                        title="${utils.getTranslation(placeholderI18nKey)}"
                    >
                </div>
            </div>
            `
        },
        getConfigPanel(category) {
            const config = GE_CONFIG[category]

            return `
                <div class="space-y-4">
                    <div class="bg-blue-900 bg-opacity-20 border border-blue-800 rounded-lg p-4">
                        <div class="flex items-center space-x-3">
                            <i class="fas fa-info-circle text-blue-400 text-xl"></i>
                            <p class="text-gray-300 text-sm">
                                <a href="${config.homepage}" target="_blank" rel="noopener noreferrer"> ${config.homepage} </a>
                            </p>
                        </div>
                    </div>

                    <form id="${GE_GORM_ID_PREFIX}_${config.id}" class="space-y-4">

                    ${Object.entries(config.settings)
                        .map(([_key, setting]) => utils.getFormInputItemTemplate(setting))
                        .join('')}

                        <button
                            id="GofileEnhanced_${config.id}_Submit"
                            type="submit"
                            class="w-full py-3 bg-blue-600 rounded-lg hover:bg-blue-700 transition duration-300
                                ease-in-out text-center text-white font-semibold flex items-center justify-center space-x-2"
                        >
                            <i class="fas fa-check"></i>
                            <span> ${utils.getTranslation('confirm')} </span>
                        </button>
                    </form>
                </div>
            `
        },
    }

    const operations = {
        async handleExport(options) {
            const { selectMode, format, enableRecursion } = options
            const abdmDownloadFolder = utils.getSettings('ABDM', 'abdmDownloadFolder')
            const aria2RpcDir = utils.getSettings('Aria2', 'rpcDir')

            let tbdItems = []
            let toDownloadFiles = []

            if (enableRecursion) {
                const { items, files } = await utils.collectAllItems()
                tbdItems = items
                toDownloadFiles = files
            } else {
                const allFiles = appdata.fileManager.mainContent.data.children

                const selectedKeys = appdata.fileManager.contentsSelected
                // all file keys or selected file keys
                const fileKeys = Object.keys(selectMode ? selectedKeys : allFiles)
                // to be downloaded keys
                const tbdKeys = fileKeys.filter((key) => allFiles[key].type === 'file')

                tbdItems = tbdKeys.map((key) => allFiles[key])
            }

            if (tbdItems.length === 0) {
                return createNotification(
                    selectMode ? utils.getTranslation('no_file_selected') : utils.getTranslation('empty_folder'),
                    selectMode ? utils.getTranslation('no_file_selected_description') : utils.getTranslation('empty_folder_description'),
                    'warning'
                )
            }

            switch (format) {
                case 'Direct':
                    utils.goDirectLinks(tbdItems.map((item) => item.link))
                    break
                case 'ABDM':
                    if (enableRecursion) {
                        utils.recursionDownload(toDownloadFiles, () => {
                            utils.sendToABDM(tbdItems.map((item) => ({ ...item, downloadFolder: abdmDownloadFolder + item.downloadFolder })))
                        })
                    } else {
                        utils.sendToABDM(tbdItems)
                    }
                    break
                case 'Aria2':
                    if (enableRecursion) {
                        utils.recursionDownload(toDownloadFiles, () => {
                            utils.sendToAria2(tbdItems.map((item) => ({ ...item, downloadFolder: aria2RpcDir + item.downloadFolder })))
                        })
                    } else {
                        utils.sendToAria2(tbdItems)
                    }
                    break
                case 'IDM':
                    utils.exportToIDM(tbdItems)
                    break
                default:
                    createNotification(utils.getTranslation('error'), `${utils.getTranslation('unsupported_format')}`, 'error')
                    break
            }
        },
        // add buttons to sidebar
        addContainerToSidebar() {
            // create container
            const container = document.createElement('ul')
            container.id = GE_CONTAINER_ID
            // 'border-t', 'border-gray-700', 'mt-4',
            container.classList.add('pt-4', 'space-y-4')

            // append buttons to container
            SUPPORTED_DOWNLOADERS.forEach((downloader) => {
                utils.getButtonsByDownloader(downloader).forEach((item) => {
                    container.appendChild(item)
                })
            })

            // append container to sidebar
            document.querySelector('#index_sidebar').appendChild(container)
        },
    }

    const main = {
        init() {
            utils.initSettings()

            // Observe changes in the DOM
            const observer = new MutationObserver((_mutations, _obs) => {
                // Check if the target node is available
                const container = document.getElementById(GE_CONTAINER_ID)

                // Check if the mainContent is available
                if (appdata.fileManager?.mainContent?.data) {
                    // Add buttons to sidebar
                    !container && operations.addContainerToSidebar()
                    // Stop observing
                    // obs.disconnect()
                } else {
                    // remove GofileEnhanced_Container
                    container && container.remove()
                }
            })

            // Observe the target node "#index_main", which is in the DOM initially.
            const targetNode = document.getElementById('index_main')
            const config = { childList: true, subtree: true }
            if (targetNode) {
                observer.observe(targetNode, config)
            } else {
                console.error('[Gofile Enhanced] #index_main not found.')
            }
        },
    }

    // Script Entry Point
    main.init()
})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址