Greasy Fork

来自缓存

GoFile 增强

在 GoFile 文件页面添加一个按钮,导出当前页面全部文件下载链接。用以配合 IDM、aria2 等下载器使用。

目前为 2024-12-03 提交的版本。查看 最新版本

// ==UserScript==
// @name         GoFile 增强
// @name:en      GoFile Enhanced
// @namespace    https://github.com/ewigl/gofile-enhanced
// @version      0.4.6
// @description  在 GoFile 文件页面添加一个按钮,导出当前页面全部文件下载链接。用以配合 IDM、aria2 等下载器使用。
// @description:en Export all files' download link. Use along with IDM, aria2 and similar downloaders.
// @author       Licht
// @license      MIT
// @homepage     https://github.com/ewigl/gofile-enhanced
// @match        http*://gofile.io/d/*
// @icon         https://gofile.io/dist/img/favicon16.png
// @connect      localhost
// @connect      *
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// ==/UserScript==

;(function () {
    'use strict'

    // New Api
    // appdata: literally, app data

    // function createNotification(title, message, type = 'success', duration = 3000)
    // function createPopup({ title, content, icon = null, backgroundOpacity = true, showCloseButton = true })

    // IDM Exported Format (support CRLF(\r\n) only):
    // <
    // url
    // cookie: accountToken=ABCDEFG
    // >

    const styleCSS = `
    .gofile-enhanced-form {
        display: grid;
    }

    .gofile-enhanced-form input {
        margin: 0.5rem 0;
    }
    `

    GM_addStyle(styleCSS)

    // constants
    const DEFAULT_LANGUAGE = 'en-US'

    const CRLF = '\r\n'

    const EXPORT_FORMAT = {
        // plain text
        txt: 'txt',
        // IDM
        ef2: 'ef2',
        // aria2
        aria2: 'aria2',
    }

    // const FOLDER_TYPE = 'folder'
    const FILE_TYPE = 'file'

    const I18N = {
        'zh-CN': {
            // Button
            allToTXT: '全部链接 -> TXT',
            selectedToTXT: '选中链接 -> TXT',
            allToEF2: '全部链接 -> IDM',
            selectedToEF2: '选中链接 -> IDM',
            allToARIA2: '全部 -> Aria2 RPC',
            selectedToARIA2: '选中 -> Aria2 RPC',
            aria2RpcSettings: 'Aria2 RPC 设置',
            aria2RpcReset: '重置 RPC 设置',
            // Notification
            noFileSelected: '未选中任何文件',
            noFileSelectedDescription: '请先选中文件',
            noFiles: '没有文件可以下载',
            noFilesDescription: '没有可以下载的文件 暂不支持文件夹下载',
            // RPC
            rpcSendSuccess: '已通过 RPC 发送至 Aria2 下载',
            rpcSendFailed: '通过 RPC 发送至 Aria2 失败',
            unknownError: '未知错误',
            // RPC Settings
            rpcAddress: 'RPC 地址',
            rpcSecret: 'RPC 密钥',
            rpcDir: 'RPC 下载目录',
            // Common
            ok: '确定',
            cancel: '取消',
            success: '成功',
            fail: '失败',
            reset: '重置',
            to: '为',
        },
        'en-US': {
            // Button
            allToTXT: 'All links -> TXT',
            selectedToTXT: 'Selected links -> TXT',
            allToEF2: 'All links -> IDM',
            selectedToEF2: 'Selected links -> IDM',
            allToARIA2: 'All -> Aria2 RPC',
            selectedToARIA2: 'Selected -> Aria2 RPC',
            aria2RpcSettings: 'Aria2 RPC Settings',
            aria2RpcReset: 'Reset RPC settings',
            // Notification
            noFileSelected: 'No file selected',
            noFileSelectedDescription: 'Please select files first',
            noFiles: 'No files can be downloaded',
            noFilesDescription: 'No files can be downloaded, folder download is not supported, yet',
            // RPC
            rpcSendSuccess: 'RPC send success',
            rpcSendFailed: 'RPC send failed',
            unknownError: 'Unknown error',
            // RPC Settings
            rpcAddress: 'RPC address',
            rpcSecret: 'RPC secret',
            rpcDir: 'RPC dir',
            // Common
            ok: 'OK',
            cancel: 'Cancel',
            success: 'Success',
            fail: 'Fail',
            reset: 'Reset',
            to: 'to',
        },
    }

    const ARIA2_RPC_CONFIG_KEY = {
        rpcAddress: 'aria2_rpc_address',
        rpcSecret: 'aria2_rpc_secret',
        rpcDir: 'aria2_rpc_dir',
    }

    const DEFAULT_CONFIG = {
        rpcSettings: [
            {
                name: ARIA2_RPC_CONFIG_KEY.rpcAddress,
                value: 'http://localhost:6800/jsonrpc',
            },
            {
                name: ARIA2_RPC_CONFIG_KEY.rpcSecret,
                value: '',
            },
            {
                name: ARIA2_RPC_CONFIG_KEY.rpcDir,
                value: '',
            },
        ],
    }

    const ICON_CLASS = {
        allToTXT: 'fas fa-file-lines',
        selectedToTXT: 'far fa-file-lines',
        allToEF2: 'fas fa-paper-plane',
        selectedToEF2: 'far fa-paper-plane',
        allToARIA2: 'fas fa-circle-down',
        selectedToARIA2: 'far fa-circle-down',
        aria2RpcSettings: 'fas fa-gear',
        aria2RpcReset: 'fas fa-rotate-left',
    }

    const utils = {
        getValue: (name) => GM_getValue(name),
        setValue(name, value) {
            GM_setValue(name, value)
        },
        initDefaultConfig() {
            DEFAULT_CONFIG.rpcSettings.forEach((item) => {
                utils.getValue(item.name) === undefined && utils.setValue(item.name, item.value)
            })
        },
        getTranslation: (key) => I18N[navigator.language || DEFAULT_LANGUAGE][key],
        getToken: () => document.cookie,
        getAria2RpcConfig() {
            return {
                address: utils.getValue(ARIA2_RPC_CONFIG_KEY.rpcAddress),
                secret: utils.getValue(ARIA2_RPC_CONFIG_KEY.rpcSecret),
                dir:
                    utils.getValue(ARIA2_RPC_CONFIG_KEY.rpcDir).trim() === ''
                        ? undefined
                        : utils.getValue(ARIA2_RPC_CONFIG_KEY.rpcDir),
            }
        },
        resetRPCConfig() {
            DEFAULT_CONFIG.rpcSettings.forEach((item) => {
                utils.setValue(item.name, item.value)
                createNotification(
                    utils.getTranslation('success'),
                    `${utils.getTranslation('reset')} ${item.name} ${utils.getTranslation('to')} "${item.value}"`
                )
            })
            // for each DEFAULT_CONFIG.rpcSettings
        },
        downloadFile(links, format = 'txt') {
            const blob = new Blob([links], { type: 'text/plain;charset=utf-8' })
            const url = URL.createObjectURL(blob)
            const link = document.createElement('a')
            link.href = url
            // generate file neme by timestamp
            link.download = `${appdata.fileManager.mainContent.data.name} - ${new Date().getTime()}.${format}`
            link.click()
            URL.revokeObjectURL(url)
        },
        getButtonTemplate(iconClass, buttonText) {
            return `<a href="javascript:void(0)" id="index_GofileEnhanced" class="hover:text-blue-500 flex items-center gap-2" aria-label="${buttonText}"><i class="${iconClass}"></i> ${buttonText} </a>`
        },
        getFormInputItemTemplate(name, i18nKey) {
            return `
            <label for="${name}">
                ${utils.getTranslation(i18nKey)}:
            </label>
            <input
                type="text"
                id="${name}"
                name="${name}"
                value="${utils.getValue(name)}"
            >
            `
        },
        getButtonDom(selectMode = false, format = 'txt') {
            const buttonText = utils.getTranslation(
                selectMode ? 'selectedTo' + format.toUpperCase() : 'allTo' + format.toUpperCase()
            )
            const iconClass = selectMode
                ? ICON_CLASS['selectedTo' + format.toUpperCase()]
                : ICON_CLASS['allTo' + format.toUpperCase()]

            return this.getButtonTemplate(iconClass, buttonText)
        },
        getRPCButtonDom(type = 'settings') {
            const buttonText = utils.getTranslation(type === 'settings' ? 'aria2RpcSettings' : 'aria2RpcReset')
            const iconClass = type === 'settings' ? ICON_CLASS.aria2RpcSettings : ICON_CLASS.aria2RpcReset

            return this.getButtonTemplate(iconClass, buttonText)
        },
        getRPCSettingsDom() {
            return `
            <form id="gofile-enhanced-form" name="gofile-enhanced-form" class="gofile-enhanced-form" action="/" method="dialog">
                ${Object.keys(ARIA2_RPC_CONFIG_KEY)
                    .map((key) => this.getFormInputItemTemplate(ARIA2_RPC_CONFIG_KEY[key], key))
                    .join('')}
            </form>
            `
        },
    }

    const operations = {
        exportToFile(selectMode = false, format = 'txt') {
            const objectKeys = Object.keys(
                selectMode ? appdata.fileManager.contentsSelected : appdata.fileManager.mainContent.data.children
            )

            const fileKeys = objectKeys.filter((key) => appdata.fileManager.mainContent.data.children[key].type === FILE_TYPE)

            if (fileKeys.length === 0) {
                return createNotification(
                    selectMode ? utils.getTranslation('noFileSelected') : utils.getTranslation('noFiles'),
                    selectMode ? utils.getTranslation('noFileSelectedDescription') : utils.getTranslation('noFilesDescription'),
                    'warning'
                )
            }

            if (format === EXPORT_FORMAT.aria2) {
                return operations.sendToRPC(fileKeys.map((key) => appdata.fileManager.mainContent.data.children[key].link))
            }

            const formatMap = {
                [EXPORT_FORMAT.ef2]: (item) => `<${CRLF}${item.link}${CRLF}cookie: ${utils.getToken()}${CRLF}>${CRLF}`,
                [EXPORT_FORMAT.txt]: (item) => `${item.link}${CRLF}`,
            }

            const links = fileKeys
                .map((key) => {
                    const item = appdata.fileManager.mainContent.data.children[key]
                    return formatMap[format](item)
                })
                .join('')

            utils.downloadFile(links, format)
        },
        sendToRPC: async (fileLinks = []) => {
            const rpcConfig = utils.getAria2RpcConfig()

            const rpcData = fileLinks.map((link) => {
                return {
                    id: new Date().getTime(),
                    jsonrpc: '2.0',
                    method: 'aria2.addUri',
                    params: [
                        `token:${rpcConfig.secret}`,
                        [link],
                        {
                            header: [`Cookie: ${utils.getToken()}`],
                            dir: rpcConfig.dir,
                        },
                    ],
                }
            })

            GM_xmlhttpRequest({
                method: 'POST',
                url: rpcConfig.address,
                data: JSON.stringify(rpcData),
                onload: (httpRes) => {
                    if (httpRes.status === 200) {
                        try {
                            const responseArray = JSON.parse(httpRes.response)

                            responseArray.forEach((item) => {
                                if (item.error) {
                                    createNotification(
                                        utils.getTranslation('fail'),
                                        `${utils.getTranslation('rpcSendFailed')} / ${item.error.code} - ${item.error.message}`,
                                        'error'
                                    )
                                } else {
                                    createNotification(
                                        utils.getTranslation('success'),
                                        `${utils.getTranslation('rpcSendSuccess')} / ${item.result}`
                                    )
                                }
                            })
                        } catch (error) {
                            createNotification(utils.getTranslation('fail'), error.toString(), 'error')
                        }
                    } else {
                        createNotification(
                            utils.getTranslation('fail'),
                            `${utils.getTranslation('rpcSendFailed')} / ${httpRes.status} - ${httpRes.statusText}`,
                            'error'
                        )
                    }
                },
                onerror: (error) => {
                    createNotification(utils.getTranslation('fail'), JSON.stringify(error), 'error')
                },
                onabort: () => {
                    createNotification(
                        utils.getTranslation('fail'),
                        utils.getTranslation('unknownError') + ' / (abort)',
                        'error'
                    )
                },
            })
        },
        addButtonsToSidebar() {
            // boeder line
            const hrLine = document.createElement('li')
            hrLine.classList.add('border-b', 'border-gray-700')

            const buttonConfigs = [
                // txt buttons
                { selectMode: false, format: EXPORT_FORMAT.txt },
                { selectMode: true, format: EXPORT_FORMAT.txt },
                // ef2 buttons
                { selectMode: false, format: EXPORT_FORMAT.ef2 },
                { selectMode: true, format: EXPORT_FORMAT.ef2 },
                // aria2 buttons
                { selectMode: false, format: EXPORT_FORMAT.aria2 },
                { selectMode: true, format: EXPORT_FORMAT.aria2 },
            ]

            // map buttons (except aria2) to get button dom element
            const buttons = buttonConfigs.map((config) => {
                const button = document.createElement('li')
                button.innerHTML = utils.getButtonDom(config.selectMode, config.format)
                // add click event for each button
                button.addEventListener('click', operations.exportToFile.bind(null, config.selectMode, config.format))
                return button
            })

            const rpcSettingsButton = document.createElement('div')
            rpcSettingsButton.innerHTML = utils.getRPCButtonDom()
            // click rpc settings button to open modal
            rpcSettingsButton.addEventListener('click', () => {
                createModal({
                    modalTitle: utils.getTranslation('aria2RpcSettings'),
                    modalBody: utils.getRPCSettingsDom(),
                    modalYesLabel: utils.getTranslation('ok'),
                    modalCallback: () => {
                        // if click ok button
                        const form = document.forms['gofile-enhanced-form']

                        // TODO
                        Object.keys(ARIA2_RPC_CONFIG_KEY).forEach((key) => {
                            utils.setValue(ARIA2_RPC_CONFIG_KEY[key], form.elements[ARIA2_RPC_CONFIG_KEY[key]].value)
                        })
                    },
                })
            })

            const rpcResetButton = document.createElement('div')
            rpcResetButton.innerHTML = utils.getRPCButtonDom('reset')
            // click aria2 rpc reset button to reset rpc config
            rpcResetButton.addEventListener('click', () => {
                utils.resetRPCConfig()
            })

            buttons.push(hrLine.cloneNode(true))
            buttons.push(rpcSettingsButton)
            buttons.push(rpcResetButton)

            const container = document.createElement('ul')
            // add class to container
            container.classList.add('pt-4', 'space-y-4', 'border-gray-700')
            // add hr line
            container.appendChild(hrLine.cloneNode(true))
            // append buttons to container
            buttons.forEach((button) => container.appendChild(button))
            // add container to sidebar
            document.querySelector('#index_sidebar').appendChild(container)
        },
    }

    const main = {
        init() {
            // init RPC config
            utils.initDefaultConfig()

            // add buttons to sidebar, watch if appdata.fileManager.mainContent.data is ready
            let interval = setInterval(() => {
                if (appdata.fileManager.mainContent.data.children) {
                    operations.addButtonsToSidebar()
                    clearInterval(interval)
                }
            }, 640)
        },
    }

    main.init()
})()