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 @@