您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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() })()