下载非内核

This commit is contained in:
zhongluofeng
2026-07-22 18:26:26 +08:00
parent 1e31ee8da9
commit f7d3c13f35
24 changed files with 2609 additions and 2532 deletions
@@ -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
}