diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 8548e77..3873b18 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -244,6 +244,61 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "base64" version = "0.21.7" @@ -1686,6 +1741,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "hyper" version = "1.10.1" @@ -1700,6 +1761,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2216,6 +2278,12 @@ dependencies = [ "web_atoms", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.8.3" @@ -3414,6 +3482,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_repr" version = "0.1.20" @@ -4169,6 +4248,7 @@ dependencies = [ name = "thing" version = "0.1.0" dependencies = [ + "axum", "chrono", "dirs 5.0.1", "futures-util", @@ -4182,6 +4262,7 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-opener", "tokio", + "url", "windows-sys 0.52.0", "winreg 0.52.0", "zip", @@ -4293,9 +4374,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" @@ -4462,6 +4555,7 @@ dependencies = [ "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4500,6 +4594,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index cac6cdb..40aec49 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -27,7 +27,9 @@ serde_yaml = "0.9" chrono = "0.4" reqwest = { version = "0.12", features = ["json", "stream"] } futures-util = "0.3" -tokio = { version = "1", features = ["io-util", "time"] } +tokio = { version = "1", features = ["io-util", "time", "rt-multi-thread", "macros", "sync", "fs", "net"] } +axum = "0.7" +url = "2" zip = "2" dirs = "5" diff --git a/src-tauri/resources/thing-extension/background.js b/src-tauri/resources/thing-extension/background.js index 1241701..c3b0f6e 100644 --- a/src-tauri/resources/thing-extension/background.js +++ b/src-tauri/resources/thing-extension/background.js @@ -2,17 +2,17 @@ * Thing Extension - 后台 Service Worker * * 职责: - * 1. 接管浏览器下载,转发到 aria2 + * 1. 接管浏览器下载,转发到 Thing 下载引擎 * 2. 资源嗅探:识别视频/音频/图片/压缩包/安装包等,存储到内存供 popup 查看 - * 3. 右键菜单"使用 aria2 下载" + * 3. 右键菜单"使用 Thing 下载" * * 注:Service Worker 是短生命周期的,配置需持久化到 chrome.storage */ // ===== 默认配置 ===== const DEFAULT_CONFIG = { - rpcUrl: 'http://127.0.0.1:6800/jsonrpc', - rpcSecret: '', + serverUrl: 'http://127.0.0.1:16800', + secret: '', interceptDownload: true, minSize: 0, excludeDomains: [], @@ -301,48 +301,61 @@ async function saveConfig(config) { await chrome.storage.local.set({ config }) } -// ===== aria2 JSON-RPC 调用 ===== -async function aria2Call(method, params = []) { +// ===== Thing 下载引擎 REST API 调用 ===== +async function apiRequest(path, options = {}) { const config = await getConfig() - const rpcParams = [] - if (config.rpcSecret) { - rpcParams.push(`token:${config.rpcSecret}`) + const url = config.serverUrl.replace(/\/$/, '') + path + const headers = { 'Content-Type': 'application/json' } + if (config.secret) { + headers['Authorization'] = `Bearer ${config.secret}` } - rpcParams.push(...params) - - const resp = await fetch(config.rpcUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - jsonrpc: '2.0', - id: Date.now().toString(), - method, - params: rpcParams - }) + const resp = await fetch(url, { + ...options, + headers: { ...headers, ...options.headers } }) if (!resp.ok) { - throw new Error(`aria2 RPC HTTP ${resp.status}`) + const text = await resp.text().catch(() => '') + throw new Error(`API HTTP ${resp.status}: ${text || resp.statusText}`) } - const data = await resp.json() - if (data.error) { - throw new Error(`aria2 RPC error: ${data.error.message} (${data.error.code})`) - } - return data.result + return resp.json() } /** - * 添加 URI 下载任务到 aria2 + * 测试连接(GET /health) */ -async function aria2AddUri(url, filename, referer, cookies, headers) { - const options = {} - if (filename) options.out = filename - if (referer) options.referer = referer - const headerList = [] - if (cookies) headerList.push(`Cookie: ${cookies}`) - if (headers && headers.length) headerList.push(...headers) - if (headerList.length) options.header = headerList - options['user-agent'] = navigator.userAgent - return aria2Call('aria2.addUri', [[url], options]) +async function testConnection() { + const config = await getConfig() + const url = config.serverUrl.replace(/\/$/, '') + '/health' + const resp = await fetch(url) + if (!resp.ok) throw new Error(`HTTP ${resp.status}`) + return true +} + +/** + * 添加下载任务(POST /api/downloads) + */ +async function addDownload(url, filename, referer, cookies, headers) { + const body = { url } + if (filename) body.filename = filename + // 构造请求头 + const headerMap = {} + headerMap['User-Agent'] = navigator.userAgent + if (referer) headerMap['Referer'] = referer + if (cookies) headerMap['Cookie'] = cookies + if (headers && headers.length) { + for (const h of headers) { + const idx = h.indexOf(':') + if (idx > 0) { + headerMap[h.slice(0, idx).trim()] = h.slice(idx + 1).trim() + } + } + } + body.headers = headerMap + const result = await apiRequest('/api/downloads', { + method: 'POST', + body: JSON.stringify(body) + }) + return result.id } // ===== 下载拦截 ===== @@ -373,10 +386,10 @@ async function handleDownloadCreated(downloadItem) { const filename = downloadItem.filename || '' try { - const gid = await aria2AddUri(url, filename, downloadItem.referrer, '') - await notify('已添加到 aria2', `${filename || url}\nGID: ${gid}`) + const id = await addDownload(url, filename, downloadItem.referrer, '') + await notify('已添加到 Thing', `${filename || url}`) } catch (e) { - await notify('aria2 添加失败', `${filename || url}\n${e.message}`) + await notify('Thing 添加失败', `${filename || url}\n${e.message}`) try { await chrome.downloads.download({ url }) } catch { /* ignore */ } } } @@ -399,7 +412,7 @@ async function notify(title, message) { chrome.runtime.onInstalled.addListener(() => { chrome.contextMenus.create({ id: 'thing-download-link', - title: '使用 aria2 下载此链接', + title: '使用 Thing 下载此链接', contexts: ['link'] }) chrome.contextMenus.create({ @@ -414,10 +427,10 @@ chrome.contextMenus.onClicked.addListener(async (info, tab) => { const url = info.linkUrl const filename = url.split('/').pop()?.split('?')[0] || '' try { - const gid = await aria2AddUri(url, filename, info.pageUrl, '') - await notify('已添加到 aria2', `${filename || url}\nGID: ${gid}`) + await addDownload(url, filename, info.pageUrl, '') + await notify('已添加到 Thing', `${filename || url}`) } catch (e) { - await notify('aria2 添加失败', `${e.message}`) + await notify('Thing 添加失败', `${e.message}`) } } }) @@ -603,8 +616,8 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => { return true } if (msg.type === 'testConnection') { - aria2Call('aria2.getVersion', []) - .then(res => sendResponse({ ok: true, version: res.version })) + testConnection() + .then(() => sendResponse({ ok: true })) .catch(e => sendResponse({ ok: false, error: e.message })) return true } @@ -629,8 +642,8 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => { } if (msg.type === 'downloadSniffed') { const { url, filename, referer } = msg - aria2AddUri(url, filename, referer || '', '') - .then(gid => sendResponse({ ok: true, gid })) + addDownload(url, filename, referer || '', '') + .then(id => sendResponse({ ok: true, id })) .catch(e => sendResponse({ ok: false, error: e.message })) return true } diff --git a/src-tauri/resources/thing-extension/manifest.json b/src-tauri/resources/thing-extension/manifest.json index 4d8dde1..38483f3 100644 --- a/src-tauri/resources/thing-extension/manifest.json +++ b/src-tauri/resources/thing-extension/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Thing Extension", "version": "0.2.0", - "description": "发送浏览器下载到Thing Downloader(aria2),嗅探网页资源。", + "description": "发送浏览器下载到 Thing 下载引擎,嗅探网页资源。", "icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", diff --git a/src-tauri/resources/thing-extension/popup.html b/src-tauri/resources/thing-extension/popup.html index fbb0e7c..b04af8c 100644 --- a/src-tauri/resources/thing-extension/popup.html +++ b/src-tauri/resources/thing-extension/popup.html @@ -12,7 +12,7 @@

Thing Extension

- aria2 下载接管 · 资源嗅探 + Thing 下载引擎 · 资源嗅探
@@ -68,13 +68,13 @@