Aria2 Rpc 使用

Aria2 Rpc 使用
Aria2 Rpc 使用

本文將演示如何使用 aria2 rpc 開發一個下載模塊,注意,這不是一個完整的應用,僅僅是爲了給你一些啓發。

techversion
electron30.0.6
webpack5.91.0
nodejsv20.14.0
aria21.37.0
React18.2.0
react-use-websocket4.8.1
@mui/x-charts/SparkLineChart7.3.2

aria2 文檔 react-use-websocket 文檔

成品演示

加載並啓動 aria2

怎麼將 aria2 集成到你的項目中

你可以要求你的用戶自行安裝 aria2c.exe,或者將 aria2c.exe 直接打包到你的項目中。

如果你採用後者,下面是一些示例。

假設你的工程目錄是:

1src
2build
3  |-- aria2c.exe
4package.json

打包

下面是一個使用 Electron Builder 的示例,它將 build/aria2c.exe 拷貝到安裝後的根目錄。

 1"scripts": {
 2    "dev": "xxxxxxx"
 3},
 4"build": {
 5    "extraFiles": [
 6        {
 7            "from": "build/aria2c.exe",
 8            "to": ""
 9        },
10}

將它放到 package.json 中。

調用

我們希望開發和生產的時候,都能調用到這個 aria2c.exe.

1// prod
2let downloadBin = path.join(path.dirname(process.execPath), 'aria2c.exe');
3if (dev) {
4  // dev
5  downloadBin = path.join(process.cwd(), 'build', 'aria2c.exe');
6}

如何啓動

給它一些必要的參數,啓動!

 1function buildAargs(pid: number) {
 2  const mustOptions = [`--enable-rpc`, `--stop-with-process=${pid}`];
 3  // ... 比如支持從程序啓動參數中自由添加配置,可以隨意定製
 4  return [...mustOptions];
 5}
 6
 7const start = async () => new Promise < number | undefined > ((resolve, reject) => {
 8  const mainPid = process.pid;
 9  const args = buildAargs(mainPid);
10
11  if (!fs.existsSync(downloadBin)) {
12    logger.error(downloadBin + " not exists.");
13    reject(something);
14    return;
15  }
16
17  logger.log(`aria2c.exe started`);
18  const downloadClient = spawn(downloadBin, args);
19
20  downloadClient.stdout.on('data', (data) => {
21    const str = JSON.stringify(data);
22    if (str.indexOf("RPC: listening on TCP port") > 0) {
23      resolve(downloadClient.pid);
24      // ...
25    }
26  });
27
28  downloadClient.stderr.on('data', (data) => {
29    logger.error(`aria2c.exe error: ${data}`);
30    // ...
31  });
32
33  downloadClient.on('close', (code) => {
34    logger.log(`aria2c.exe: child process exited with code ${code || ""}`);
35    // ...
36  });
37});

aria2c默認會監聽到 ws://127.0.0.1:6800/jsonrpc,實際上該端口不會和 http 端口衝突,所以你暫且可以不用做一些端口衝突的處理。

使用react-use-websocket連接

以下是一個簡單的示例,最終使用的示例在下一小節,你應該先使用這個小示例,確保能夠從 rpc server 中讀取到消息。

 1import useWebSocket from "react-use-websocket";
 2import { Options } from "react-use-websocket/src/lib/types";
 3
 4export function useMyAria(options?: Options) {
 5  const rpcServer = "ws://127.0.0.1:6800/jsonrpc";
 6  // options was shared
 7  return useWebSocket<Partial<AriaResponse>>(rpcServer, {
 8    share: true,
 9    shouldReconnect: () => true,
10    onOpen: () => {
11    },
12    onMessage: (e) => {
13    },
14    onError: (e) => {
15      nconsole.log("rpc listener: error occurred" + JSON.stringify(e));
16    },
17    onClose: () => {
18    },
19    ...options,
20  });
21}

假設你在另一個模塊中,使用這個 hook:

 1const { sendJsonMessage, readyState } = useMyAria({
 2  onMessage(e: MessageEvent<any>) {
 3    if (!e.data) {
 4      return;
 5    }
 6    console.log(e.data);
 7  }
 8});
 9
10// 顯示當前的活躍下載數量
11const onButtonClick = (e) => {
12  sendJsonMessage([{
13    jsonrpc: "2.0",
14    method: "aria2.tellActive",
15    id: "rpc_timer_tell_active"
16  }]);
17};
18
19// 一次下載兩個需要 cookie 認證的文件
20const onDownloadClick = (e) => {
21  const jsonRpcMsg = [
22    {
23      "id": "e46bc4e56b7a6e33",
24      "jsonrpc": "2.0",
25      "method": "aria2.addUri",
26      "params": [
27        ["https://zzzz.xxxx.com/tttt/myfile.7z.003?fname=myfile.7z.003"],
28        {
29          "dir": "D:\\games",
30          "gid": "e46bc4e56b7a6e33",
31          "header": [
32            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
33            "Accept-Encoding: gzip, deflate, br, zstd",
34            "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5",
35            "Connection: keep-alive",
36            "Cookie: your cookie",
37            "Host: yyyy.xxxx.com",
38            "Referer: https://www.xxxx.com/",
39            "Sec-Ch-Ua: \"Chromium\";v=\"124\", \"Microsoft Edge\";v=\"124\", \"Not-A.Brand\";v=\"99\"",
40            "Sec-Ch-Ua-Mobile: ?0",
41            "Sec-Ch-Ua-Platform: \"Windows\"",
42            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
43          ],
44          "max-connection-per-server": 16,
45          "min-split-size": "1M",
46          "split": 16
47        }
48      ]
49    },
50    {
51      "jsonrpc": "2.0",
52      "method": "aria2.addUri",
53      "params": [
54        [
55          "https://zzzz.xxxx.com/tttt/myfile.7z.001?fname=myfile.7z.001\u0026from=30111\u0026version=3.3.3.3"
56        ],
57        {
58          "dir": "D:\\games",
59          "gid": "fcfc5dd991923b96",
60          "header": [
61            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
62            "Accept-Encoding: gzip, deflate, br, zstd",
63            "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,hu;q=0.5",
64            "Connection: keep-alive",
65            "Cookie: your cookie",
66            "Host: yyyy.xxxx.com",
67            "Referer: https://www.xxxx.com/",
68            "Sec-Ch-Ua: \"Chromium\";v=\"124\", \"Microsoft Edge\";v=\"124\", \"Not-A.Brand\";v=\"99\"",
69            "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0"
70          ],
71          "max-connection-per-server": 16,
72          "min-split-size": "1M",
73          "split": 16
74        }
75      ],
76      "id": "fcfc5dd991923b96"
77    }
78  ];
79  sendJsonMessage(jsonRpcMsg);
80};
81
82return (
83  <div>
84    <button onClick={onButtonClick}>顯示當前活躍的下載信息</button>
85    <button onClick={onDownloadClick}>下載</button>
86  </div>
87);

jsonRpc 的格式非常奇怪,你必須適應。一般來說,它是這種格式:

1interface JsonRpcMessage {
2  id: string,
3  method: string,
4  jsonrpc: string,
5  params: [string[], { [key: string]: any }],
6}

它的 params 數組,params[0] 是一系列下載地址,params[1] 是一個 object,裏面有很多的配置項。例如,你可以配置下載的文件夾,分割多少份下載,還有 cookie。

必須 在代碼中組裝上面這樣的 json 字符串,然後發送給 aria2c。

aria2 rpc react-use-websocket 文檔

定時輪詢與監聽

一般人到這已經放棄了。

如果你希望顯示實時的下載進度,你可以主動向 aria 發起 rpc 調用,然後接收它的結果,再次聲明,這個代碼無法直接運行,只是爲了給你一些啓發。

 1interface AriaResponse {
 2  id: string;
 3  error: {
 4    code: number;
 5    message: string;
 6  };
 7  jsonrpc: string;
 8  method: string,
 9  params: any[],
10  result: any,
11}
12
13interface AriaGidReport {
14  gid: string;
15  status: string;
16  downloadSpeed: string;
17  errorCode: string;
18  errorMessage: string;
19  completedLength: string;
20  followedBy: string[];
21  following: string;
22  totalLength: string;
23  verifyIntegrityPending: string;
24  files: { path: string }[];
25}
26
27const { sendJsonMessage, readyState } = useMyAria({
28  onMessage(e: MessageEvent<any>) {
29    if (!e.data) {
30      return;
31    }
32    const informs = JSON.parse(e.data) as AriaResponse[];
33    if (!informs || informs.length === 0) {
34      return;
35    }
36    let totalResults: AriaGidReport[] = [];
37    for (let i = 0; i < informs.length; i++) {
38      const msg = informs[i];
39      if (msg.id === "rpc_timer_tell_active" || msg.id === "rpc_timer_tell_stop" || msg.id === "rpc_timer_tell_wait") {
40        totalResults = totalResults.concat(msg.result as AriaGidReport[]);
41      }
42    }
43    if (totalResults.length === 0) {
44      return;
45    }
46    memStore.addAllAriaStats(totalResults);// 這裏就是所有的結果,你可以在另外一個模塊中消費它
47  },
48});
49
50useEffect(() => {
51  const queryStatus = () => {
52    // nconsole.log("rpc timer query status");
53    sendJsonMessage([{
54      jsonrpc: "2.0",
55      method: "aria2.tellActive",
56      id: 'rpc_timer_tell_active',
57    },
58      {
59        jsonrpc: "2.0",
60        method: "aria2.tellWaiting",
61        id: 'rpc_timer_tell_wait',
62        params: [0, 1000],
63      },
64      {
65        jsonrpc: "2.0",
66        method: "aria2.tellStopped",
67        id: 'rpc_timer_tell_stop',
68        params: [0, 1000],
69      },
70    ]);
71  };
72
73  const queryId = setInterval(queryStatus, queryAndHandleInterval);
74  return () => {
75    nconsole.debug("rpc timer query clearing interval id queryStatus");
76    clearInterval(queryId);
77  };
78}, [readyState]);

而 aria 支持一些 rpc 的通知:

1type ariaEvent =
2  "aria2.onDownloadStart"
3  | "aria2.onDownloadPause"
4  | "aria2.onDownloadStop"
5  | "aria2.onDownloadComplete"
6  | "aria2.onDownloadError"
7  | "aria2.onBtDownloadComplete";

這些通知不能告訴你下載的進度,但是當下載達到重要節點的時候,會通知你。你可以利用它們做一些事情,例如下載失敗,重新下載等。

磁力鏈,直鏈與種子文件

磁力鏈,種子和直鏈下載的處理方式是不一樣的。

磁力鏈和種子任務,下載完成後,會做種。你查詢回的結果,雖然下載進度到了 100%,但是 status 仍然不是 complete。

所以你應該根據下載的大小,是否 active 等結合起來判斷一個任務是否已經完成。

 1const isTaskPending = (task: Task) => {
 2  // const gids = ... || [];
 3  if (gids.length === 1 && task.type === "magnet") return true;
 4  if (gids.length === 0) return true;
 5  const isPending = gids.some((x) => {
 6    const stat = memStore.status.taskBlink[x];
 7    if (stat) {
 8      nconsole.log(stat.files[0].path, stat.status, stat.verifyIntegrityPending ? "verifying" : "", stat.completedLength, stat.totalLength);
 9    }
10    return !stat
11      || (Number(stat.totalLength) === 0)
12      || (Number(stat.completedLength) < Number(stat.totalLength))
13      || stat.status === "waiting"
14      || stat.status === "active"
15      || stat.verifyIntegrityPending === "true"; // 如果開啓了文件校驗,應該等待文件校驗完畢再判定下載成功
16  });
17
18  if (!isPending) {
19    // 爲了 debug 用
20    nconsole.log("task finished");
21  }
22
23  return isPending;
24};

當然你也可以依賴 onDownloadComplete 這些事件,但是它們只發送一次,因此並不可靠。

下載完成校驗文件

optiondescription
checksum開啓文件校驗,如 checksum=sha-1=123123…
–check-integrity校驗失敗是否重新下載,checksum不開啓沒有作用

當開啓這些參數,下載完成後,會進行文件校驗,並且查詢狀態的結果中,該任務會有 verifyIntegrityPending=true 屬性。

可以藉此來判斷下載任務的進度。

完成

到此,你已經會通過 electron 發送 json rpc 消息,和 aria2c 交互,創建新的下載,查詢下載狀態,並監控下載完成。

抱歉我不能將完整的代碼發出來,但是如我再三強調,每個人的項目都不一樣,而且實現這些目標的方法有很多種。本文旨在給你啓發。

如果你希望獲得幫助,可以留下你的評論。