@psychout98/tadaima 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/config.d.ts +23 -0
  2. package/dist/config.d.ts.map +1 -0
  3. package/dist/config.js +25 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/daemon.d.ts +8 -0
  6. package/dist/daemon.d.ts.map +1 -0
  7. package/dist/daemon.js +91 -0
  8. package/dist/daemon.js.map +1 -0
  9. package/dist/download-handler.d.ts +15 -0
  10. package/dist/download-handler.d.ts.map +1 -0
  11. package/dist/download-handler.js +203 -0
  12. package/dist/download-handler.js.map +1 -0
  13. package/dist/downloader.d.ts +11 -0
  14. package/dist/downloader.d.ts.map +1 -0
  15. package/dist/downloader.js +65 -0
  16. package/dist/downloader.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +271 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/logger.d.ts +2 -0
  22. package/dist/logger.d.ts.map +1 -0
  23. package/dist/logger.js +74 -0
  24. package/dist/logger.js.map +1 -0
  25. package/dist/organizer.d.ts +12 -0
  26. package/dist/organizer.d.ts.map +1 -0
  27. package/dist/organizer.js +42 -0
  28. package/dist/organizer.js.map +1 -0
  29. package/dist/rd-client.d.ts +25 -0
  30. package/dist/rd-client.d.ts.map +1 -0
  31. package/dist/rd-client.js +129 -0
  32. package/dist/rd-client.js.map +1 -0
  33. package/dist/service.d.ts +3 -0
  34. package/dist/service.d.ts.map +1 -0
  35. package/dist/service.js +186 -0
  36. package/dist/service.js.map +1 -0
  37. package/dist/setup.d.ts +2 -0
  38. package/dist/setup.d.ts.map +1 -0
  39. package/dist/setup.js +92 -0
  40. package/dist/setup.js.map +1 -0
  41. package/dist/status-writer.d.ts +20 -0
  42. package/dist/status-writer.d.ts.map +1 -0
  43. package/dist/status-writer.js +34 -0
  44. package/dist/status-writer.js.map +1 -0
  45. package/dist/tui.d.ts +14 -0
  46. package/dist/tui.d.ts.map +1 -0
  47. package/dist/tui.js +73 -0
  48. package/dist/tui.js.map +1 -0
  49. package/dist/updater.d.ts +27 -0
  50. package/dist/updater.d.ts.map +1 -0
  51. package/dist/updater.js +191 -0
  52. package/dist/updater.js.map +1 -0
  53. package/dist/ws-client.d.ts +26 -0
  54. package/dist/ws-client.d.ts.map +1 -0
  55. package/dist/ws-client.js +155 -0
  56. package/dist/ws-client.js.map +1 -0
  57. package/package.json +62 -0
package/dist/index.js ADDED
@@ -0,0 +1,271 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, join } from "node:path";
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
6
+ const args = process.argv.slice(2);
7
+ const command = args[0];
8
+ async function main() {
9
+ switch (command) {
10
+ case "setup": {
11
+ const { runSetup } = await import("./setup.js");
12
+ await runSetup();
13
+ break;
14
+ }
15
+ case "start": {
16
+ const isDaemon = args.includes("-d") || args.includes("--daemon");
17
+ if (isDaemon) {
18
+ const { startDaemon } = await import("./daemon.js");
19
+ startDaemon();
20
+ break;
21
+ }
22
+ console.log(`tadaima-agent v${pkg.version}`);
23
+ const { AgentWebSocket } = await import("./ws-client.js");
24
+ const { DownloadHandler } = await import("./download-handler.js");
25
+ const { TUI } = await import("./tui.js");
26
+ const { shouldCheckNow, checkForUpdate, applyUpdate, logUpdateAdvisory } = await import("./updater.js");
27
+ const { writeStatus, removeStatus } = await import("./status-writer.js");
28
+ const ws = new AgentWebSocket();
29
+ const handler = new DownloadHandler(ws);
30
+ const tui = new TUI(pkg.version);
31
+ // Track connection state for status file (ws internals are private)
32
+ let wsConnected = false;
33
+ let pendingUpdateVersion = null;
34
+ // Non-blocking update check on startup
35
+ let pendingUpdate = null;
36
+ const tryApplyUpdate = async () => {
37
+ if (!pendingUpdate)
38
+ return;
39
+ if (handler.activeCount > 0)
40
+ return; // defer until idle
41
+ try {
42
+ await applyUpdate(pendingUpdate);
43
+ }
44
+ catch (err) {
45
+ console.warn("Auto-update failed:", err instanceof Error ? err.message : err);
46
+ }
47
+ pendingUpdate = null;
48
+ pendingUpdateVersion = null;
49
+ };
50
+ const runUpdateCheck = async () => {
51
+ try {
52
+ const result = await checkForUpdate(pkg.version);
53
+ if (result) {
54
+ // For npm/Docker installs where binary replacement won't work,
55
+ // just log an advisory
56
+ const isBinaryInstall = !process.argv[1]?.includes("node_modules");
57
+ if (!isBinaryInstall) {
58
+ logUpdateAdvisory(pkg.version, result.version);
59
+ return;
60
+ }
61
+ pendingUpdate = result;
62
+ pendingUpdateVersion = result.version;
63
+ await tryApplyUpdate();
64
+ }
65
+ }
66
+ catch (err) {
67
+ console.warn("Update check failed:", err instanceof Error ? err.message : err);
68
+ }
69
+ };
70
+ if (shouldCheckNow("startup")) {
71
+ runUpdateCheck();
72
+ }
73
+ // Periodic update check every hour
74
+ const updateInterval = setInterval(async () => {
75
+ if (shouldCheckNow("periodic")) {
76
+ await runUpdateCheck();
77
+ }
78
+ }, 60 * 60 * 1000);
79
+ ws.setMessageHandler((msg) => {
80
+ wsConnected = true; // receiving messages means we're connected
81
+ const type = msg.type;
82
+ if (type === "download:request") {
83
+ handler.handleRequest(msg);
84
+ }
85
+ else if (type === "download:cancel") {
86
+ handler.handleCancel(msg);
87
+ }
88
+ else if (type === "cache:check") {
89
+ handler.handleCacheCheck(msg);
90
+ }
91
+ });
92
+ ws.connect();
93
+ // Write status file every 10 seconds for tray/menu bar apps
94
+ const { config: agentConfig } = await import("./config.js");
95
+ const statusInterval = setInterval(() => {
96
+ writeStatus({
97
+ pid: process.pid,
98
+ version: pkg.version,
99
+ connected: wsConnected,
100
+ relay: agentConfig.get("relay"),
101
+ deviceName: agentConfig.get("deviceName"),
102
+ activeDownloads: handler.activeCount,
103
+ lastHeartbeat: new Date().toISOString(),
104
+ updateAvailable: pendingUpdateVersion,
105
+ });
106
+ }, 10_000);
107
+ // Write initial status immediately
108
+ writeStatus({
109
+ pid: process.pid,
110
+ version: pkg.version,
111
+ connected: false,
112
+ relay: agentConfig.get("relay"),
113
+ deviceName: agentConfig.get("deviceName"),
114
+ activeDownloads: 0,
115
+ lastHeartbeat: new Date().toISOString(),
116
+ updateAvailable: null,
117
+ });
118
+ // Use TUI unless running as daemon
119
+ if (!process.env.TADAIMA_DAEMON) {
120
+ tui.start();
121
+ }
122
+ process.on("SIGINT", () => {
123
+ clearInterval(updateInterval);
124
+ clearInterval(statusInterval);
125
+ removeStatus();
126
+ tui.stop();
127
+ ws.stop();
128
+ process.exit(0);
129
+ });
130
+ process.on("SIGTERM", () => {
131
+ clearInterval(updateInterval);
132
+ clearInterval(statusInterval);
133
+ removeStatus();
134
+ tui.stop();
135
+ ws.stop();
136
+ process.exit(0);
137
+ });
138
+ break;
139
+ }
140
+ case "stop": {
141
+ const { stopDaemon } = await import("./daemon.js");
142
+ stopDaemon();
143
+ break;
144
+ }
145
+ case "status": {
146
+ const { getDaemonStatus } = await import("./daemon.js");
147
+ const { config } = await import("./config.js");
148
+ const st = getDaemonStatus();
149
+ console.log(`tadaima-agent v${pkg.version}`);
150
+ console.log(`Relay: ${config.get("relay") || "Not configured"}`);
151
+ console.log(`Device: ${config.get("deviceName") || "Not paired"}`);
152
+ console.log(`Status: ${st.running ? `Running (PID ${st.pid})` : "Not running"}`);
153
+ break;
154
+ }
155
+ case "config": {
156
+ const { config } = await import("./config.js");
157
+ const subcommand = args[1];
158
+ if (subcommand === "get") {
159
+ const key = args[2];
160
+ if (!key) {
161
+ console.log("Usage: tadaima config get <key>");
162
+ break;
163
+ }
164
+ console.log(config.get(key));
165
+ }
166
+ else if (subcommand === "set") {
167
+ const key = args[2];
168
+ const value = args[3];
169
+ if (!key || value === undefined) {
170
+ console.log("Usage: tadaima config set <key> <value>");
171
+ break;
172
+ }
173
+ config.set(key, value);
174
+ console.log(`Set ${key} = ${value}`);
175
+ }
176
+ else if (subcommand === "list") {
177
+ const store = config.store;
178
+ const redacted = { ...store };
179
+ if (redacted.deviceToken)
180
+ redacted.deviceToken = "****" + String(redacted.deviceToken).slice(-8);
181
+ if (redacted.realDebrid &&
182
+ typeof redacted.realDebrid === "object" &&
183
+ redacted.realDebrid.apiKey) {
184
+ redacted.realDebrid = {
185
+ ...redacted.realDebrid,
186
+ apiKey: "****",
187
+ };
188
+ }
189
+ console.log(JSON.stringify(redacted, null, 2));
190
+ }
191
+ else {
192
+ console.log("Usage: tadaima config <get|set|list>");
193
+ }
194
+ break;
195
+ }
196
+ case "logs": {
197
+ const { tailLogs } = await import("./logger.js");
198
+ const follow = args.includes("-f") || args.includes("--follow");
199
+ const nIdx = args.indexOf("-n");
200
+ const lines = nIdx >= 0 ? parseInt(args[nIdx + 1], 10) : 50;
201
+ tailLogs(lines, follow);
202
+ break;
203
+ }
204
+ case "install-service": {
205
+ const { installService } = await import("./service.js");
206
+ installService();
207
+ break;
208
+ }
209
+ case "uninstall-service": {
210
+ const { uninstallService } = await import("./service.js");
211
+ uninstallService();
212
+ break;
213
+ }
214
+ case "update": {
215
+ const { checkForUpdate, applyUpdate } = await import("./updater.js");
216
+ console.log(`Current version: v${pkg.version}`);
217
+ console.log("Checking for updates...");
218
+ try {
219
+ const result = await checkForUpdate(pkg.version);
220
+ if (!result) {
221
+ console.log("Already up to date.");
222
+ }
223
+ else {
224
+ console.log(`Update available: v${result.version}`);
225
+ await applyUpdate(result);
226
+ }
227
+ }
228
+ catch (err) {
229
+ console.error("Update failed:", err instanceof Error ? err.message : err);
230
+ process.exit(1);
231
+ }
232
+ break;
233
+ }
234
+ case "rollback": {
235
+ const { rollback } = await import("./updater.js");
236
+ rollback();
237
+ break;
238
+ }
239
+ case "version":
240
+ case "--version":
241
+ case "-v":
242
+ console.log(`tadaima-agent v${pkg.version}`);
243
+ break;
244
+ default:
245
+ console.log(`tadaima-agent v${pkg.version}`);
246
+ console.log(`Node.js ${process.version} on ${process.platform} ${process.arch}`);
247
+ console.log("");
248
+ console.log("Commands:");
249
+ console.log(" setup Pair this device with your Tadaima instance");
250
+ console.log(" start Start agent (foreground with TUI)");
251
+ console.log(" start -d Start as background daemon");
252
+ console.log(" stop Stop background daemon");
253
+ console.log(" status Show connection status");
254
+ console.log(" config get <key> Read a config value");
255
+ console.log(" config set <k> <v> Update a config value");
256
+ console.log(" config list Show all config values");
257
+ console.log(" logs Tail recent logs");
258
+ console.log(" logs -f Follow log output");
259
+ console.log(" install-service Install as system service");
260
+ console.log(" uninstall-service Remove system service");
261
+ console.log(" update Check for and apply updates");
262
+ console.log(" rollback Restore previous binary version");
263
+ console.log(" version Show version info");
264
+ break;
265
+ }
266
+ }
267
+ main().catch((err) => {
268
+ console.error(err);
269
+ process.exit(1);
270
+ });
271
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function tailLogs(lines?: number, follow?: boolean): void;
2
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAkCA,wBAAgB,QAAQ,CAAC,KAAK,GAAE,MAAW,EAAE,MAAM,GAAE,OAAe,GAAG,IAAI,CAa1E"}
package/dist/logger.js ADDED
@@ -0,0 +1,74 @@
1
+ import { createReadStream, existsSync, readFileSync, statSync, } from "node:fs";
2
+ import { createInterface } from "node:readline";
3
+ import { getLogPath } from "./daemon.js";
4
+ let _intervalId;
5
+ let _rl;
6
+ function cleanup() {
7
+ if (_intervalId !== undefined) {
8
+ clearInterval(_intervalId);
9
+ _intervalId = undefined;
10
+ }
11
+ if (_rl) {
12
+ _rl.close();
13
+ _rl = undefined;
14
+ }
15
+ }
16
+ process.on("exit", cleanup);
17
+ process.on("SIGINT", () => {
18
+ cleanup();
19
+ process.exit(0);
20
+ });
21
+ process.on("SIGTERM", () => {
22
+ cleanup();
23
+ process.exit(0);
24
+ });
25
+ export function tailLogs(lines = 50, follow = false) {
26
+ const logPath = getLogPath();
27
+ if (!existsSync(logPath)) {
28
+ console.log("No log file found. Start the agent in daemon mode first.");
29
+ return;
30
+ }
31
+ if (follow) {
32
+ followLog(logPath);
33
+ }
34
+ else {
35
+ tailFile(logPath, lines);
36
+ }
37
+ }
38
+ function tailFile(path, n) {
39
+ const content = readFileSync(path, "utf-8");
40
+ const allLines = content.split("\n");
41
+ const tail = allLines.slice(-n);
42
+ console.log(tail.join("\n"));
43
+ }
44
+ function followLog(path) {
45
+ cleanup();
46
+ let offset = statSync(path).size;
47
+ console.log(`Following ${path} (Ctrl+C to stop)\n`);
48
+ _intervalId = setInterval(() => {
49
+ try {
50
+ const currentSize = statSync(path).size;
51
+ if (currentSize > offset) {
52
+ if (_rl) {
53
+ _rl.close();
54
+ _rl = undefined;
55
+ }
56
+ const stream = createReadStream(path, {
57
+ start: offset,
58
+ encoding: "utf-8",
59
+ });
60
+ _rl = createInterface({ input: stream });
61
+ _rl.on("line", (line) => {
62
+ console.log(line);
63
+ });
64
+ _rl.on("close", () => {
65
+ offset = currentSize;
66
+ });
67
+ }
68
+ }
69
+ catch {
70
+ // File may have been rotated
71
+ }
72
+ }, 500);
73
+ }
74
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface OrganizeRequest {
2
+ title: string;
3
+ year: number;
4
+ tmdbId: number;
5
+ mediaType: "movie" | "tv";
6
+ season?: number;
7
+ episode?: number;
8
+ episodeTitle?: string;
9
+ sourcePath: string;
10
+ }
11
+ export declare function organizeFile(req: OrganizeRequest): Promise<string>;
12
+ //# sourceMappingURL=organizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organizer.d.ts","sourceRoot":"","sources":["../src/organizer.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAeD,wBAAsB,YAAY,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAgCxE"}
@@ -0,0 +1,42 @@
1
+ import { mkdir, rename } from "node:fs/promises";
2
+ import { dirname, extname, join, resolve } from "node:path";
3
+ import { config } from "./config.js";
4
+ import { buildMoviePath, buildEpisodePath } from "@tadaima/shared";
5
+ /**
6
+ * Move a downloaded file to Plex-compatible directory structure.
7
+ * Returns the final path.
8
+ */
9
+ function assertWithinBase(destPath, baseDir) {
10
+ const resolvedDest = resolve(destPath);
11
+ const resolvedBase = resolve(baseDir);
12
+ if (!resolvedDest.startsWith(resolvedBase + "/") && resolvedDest !== resolvedBase) {
13
+ throw new Error(`Path traversal detected: ${destPath} escapes base directory ${baseDir}`);
14
+ }
15
+ return resolvedDest;
16
+ }
17
+ export async function organizeFile(req) {
18
+ if (!req.sourcePath || req.sourcePath.trim() === "") {
19
+ throw new Error("Invalid organizeFile request: sourcePath is empty or missing");
20
+ }
21
+ const ext = extname(req.sourcePath).replace(".", "");
22
+ let relativePath;
23
+ if (req.mediaType === "movie") {
24
+ const moviesBase = config.get("directories.movies");
25
+ relativePath = buildMoviePath(req.title, req.year, req.tmdbId, ext);
26
+ const destPath = join(moviesBase, relativePath.replace(/^Movies\//, ""));
27
+ assertWithinBase(destPath, moviesBase);
28
+ await mkdir(dirname(destPath), { recursive: true });
29
+ await rename(req.sourcePath, destPath);
30
+ return destPath;
31
+ }
32
+ else {
33
+ const tvBase = config.get("directories.tv");
34
+ relativePath = buildEpisodePath(req.title, req.tmdbId, req.season ?? 1, req.episode ?? 1, req.episodeTitle ?? `Episode ${req.episode ?? 1}`, ext);
35
+ const destPath = join(tvBase, relativePath.replace(/^TV\//, ""));
36
+ assertWithinBase(destPath, tvBase);
37
+ await mkdir(dirname(destPath), { recursive: true });
38
+ await rename(req.sourcePath, destPath);
39
+ return destPath;
40
+ }
41
+ }
42
+ //# sourceMappingURL=organizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"organizer.js","sourceRoot":"","sources":["../src/organizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAanE;;;GAGG;AACH,SAAS,gBAAgB,CAAC,QAAgB,EAAE,OAAe;IACzD,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtC,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,YAAY,GAAG,GAAG,CAAC,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,2BAA2B,OAAO,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAoB;IACrD,IAAI,CAAC,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAClF,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACrD,IAAI,YAAoB,CAAC;IAEzB,IAAI,GAAG,CAAC,SAAS,KAAK,OAAO,EAAE,CAAC;QAC9B,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;QACpD,YAAY,GAAG,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;QACzE,gBAAgB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACvC,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvC,OAAO,QAAQ,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC5C,YAAY,GAAG,gBAAgB,CAC7B,GAAG,CAAC,KAAK,EACT,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,MAAM,IAAI,CAAC,EACf,GAAG,CAAC,OAAO,IAAI,CAAC,EAChB,GAAG,CAAC,YAAY,IAAI,WAAW,GAAG,CAAC,OAAO,IAAI,CAAC,EAAE,EACjD,GAAG,CACJ,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;QACjE,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvC,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,25 @@
1
+ export interface UnrestrictedLink {
2
+ url: string;
3
+ filename: string;
4
+ filesize: number;
5
+ }
6
+ export declare const rdClient: {
7
+ addMagnet(magnet: string): Promise<{
8
+ id: string;
9
+ uri: string;
10
+ }>;
11
+ selectFiles(torrentId: string, fileIds?: string): Promise<void>;
12
+ getTorrentInfo(torrentId: string): Promise<{
13
+ id: string;
14
+ status: string;
15
+ progress: number;
16
+ links: string[];
17
+ filename: string;
18
+ }>;
19
+ pollUntilReady(torrentId: string, pollInterval?: number, timeout?: number, onProgress?: (progress: number) => void, signal?: AbortSignal): Promise<string[]>;
20
+ unrestrictLink(link: string): Promise<UnrestrictedLink>;
21
+ unrestrictAll(links: string[]): Promise<UnrestrictedLink[]>;
22
+ checkCache(infoHashes: string[]): Promise<Record<string, boolean>>;
23
+ downloadMagnet(magnet: string, onProgress?: (progress: number) => void, signal?: AbortSignal): Promise<UnrestrictedLink[]>;
24
+ };
25
+ //# sourceMappingURL=rd-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rd-client.d.ts","sourceRoot":"","sources":["../src/rd-client.ts"],"names":[],"mappings":"AAwDA,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,QAAQ;sBAET,MAAM,GACb,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;2BAUV,MAAM,YAAY,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;8BAUxD,MAAM,GAChB,OAAO,CAAC;QACT,EAAE,EAAE,MAAM,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;8BAYW,MAAM,iBACH,MAAM,YACX,MAAM,eACF,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,WAC9B,WAAW,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC;yBAyBO,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;yBAelC,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;2BASnD,MAAM,EAAE,GACnB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;2BAiBzB,MAAM,eACD,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,WAC9B,WAAW,GACnB,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAY/B,CAAC"}
@@ -0,0 +1,129 @@
1
+ import { config } from "./config.js";
2
+ const BASE_URL = "https://api.real-debrid.com/rest/1.0";
3
+ function getHeaders() {
4
+ return {
5
+ Authorization: `Bearer ${config.get("realDebrid.apiKey")}`,
6
+ };
7
+ }
8
+ async function refreshRdKey() {
9
+ const relay = config.get("relay");
10
+ const deviceToken = config.get("deviceToken");
11
+ if (!relay || !deviceToken)
12
+ return false;
13
+ try {
14
+ const res = await fetch(`${relay}/api/agent/config`, {
15
+ headers: { Authorization: `Bearer ${deviceToken}` },
16
+ });
17
+ if (!res.ok)
18
+ return false;
19
+ const data = (await res.json());
20
+ if (data.rdApiKey) {
21
+ config.set("realDebrid.apiKey", data.rdApiKey);
22
+ return true;
23
+ }
24
+ }
25
+ catch {
26
+ // ignore
27
+ }
28
+ return false;
29
+ }
30
+ async function rdFetch(path, opts = {}, _retried = false) {
31
+ const res = await fetch(`${BASE_URL}${path}`, {
32
+ ...opts,
33
+ headers: { ...getHeaders(), ...opts.headers },
34
+ });
35
+ // RD key rotation: on 401/403, try fetching a fresh key from relay
36
+ if ((res.status === 401 || res.status === 403) && !_retried) {
37
+ const refreshed = await refreshRdKey();
38
+ if (refreshed) {
39
+ return rdFetch(path, opts, true);
40
+ }
41
+ }
42
+ if (!res.ok) {
43
+ const body = await res.text().catch(() => "");
44
+ throw new Error(`RD API error ${res.status}: ${body}`);
45
+ }
46
+ return res;
47
+ }
48
+ export const rdClient = {
49
+ async addMagnet(magnet) {
50
+ const form = new URLSearchParams({ magnet });
51
+ const res = await rdFetch("/torrents/addMagnet", {
52
+ method: "POST",
53
+ body: form,
54
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
55
+ });
56
+ return (await res.json());
57
+ },
58
+ async selectFiles(torrentId, fileIds) {
59
+ const form = new URLSearchParams({ files: fileIds ?? "all" });
60
+ await rdFetch(`/torrents/selectFiles/${torrentId}`, {
61
+ method: "POST",
62
+ body: form,
63
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
64
+ });
65
+ },
66
+ async getTorrentInfo(torrentId) {
67
+ const res = await rdFetch(`/torrents/info/${torrentId}`);
68
+ return (await res.json());
69
+ },
70
+ async pollUntilReady(torrentId, pollInterval = config.get("rdPollInterval") * 1000, timeout = 30 * 60 * 1000, onProgress, signal) {
71
+ const start = Date.now();
72
+ const errorStatuses = ["error", "virus", "dead", "magnet_error"];
73
+ while (Date.now() - start < timeout) {
74
+ if (signal?.aborted)
75
+ throw new Error("Cancelled");
76
+ const info = await this.getTorrentInfo(torrentId);
77
+ if (info.status === "downloaded") {
78
+ return info.links;
79
+ }
80
+ if (errorStatuses.includes(info.status)) {
81
+ throw new Error(`RD torrent error: ${info.status}`);
82
+ }
83
+ if (onProgress)
84
+ onProgress(info.progress);
85
+ await new Promise((r) => setTimeout(r, pollInterval));
86
+ }
87
+ throw new Error("RD poll timeout (30 minutes)");
88
+ },
89
+ async unrestrictLink(link) {
90
+ const form = new URLSearchParams({ link });
91
+ const res = await rdFetch("/unrestrict/link", {
92
+ method: "POST",
93
+ body: form,
94
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
95
+ });
96
+ const data = (await res.json());
97
+ return { url: data.download, filename: data.filename, filesize: data.filesize };
98
+ },
99
+ async unrestrictAll(links) {
100
+ const results = [];
101
+ for (const link of links) {
102
+ results.push(await this.unrestrictLink(link));
103
+ }
104
+ return results;
105
+ },
106
+ async checkCache(infoHashes) {
107
+ if (infoHashes.length === 0)
108
+ return {};
109
+ const hashStr = infoHashes.join("/");
110
+ const res = await rdFetch(`/torrents/instantAvailability/${hashStr}`);
111
+ const data = (await res.json());
112
+ const result = {};
113
+ for (const hash of infoHashes) {
114
+ const entry = data[hash.toLowerCase()];
115
+ result[hash] =
116
+ entry != null &&
117
+ typeof entry === "object" &&
118
+ Object.keys(entry).length > 0;
119
+ }
120
+ return result;
121
+ },
122
+ async downloadMagnet(magnet, onProgress, signal) {
123
+ const { id } = await this.addMagnet(magnet);
124
+ await this.selectFiles(id);
125
+ const links = await this.pollUntilReady(id, undefined, undefined, onProgress, signal);
126
+ return this.unrestrictAll(links);
127
+ },
128
+ };
129
+ //# sourceMappingURL=rd-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rd-client.js","sourceRoot":"","sources":["../src/rd-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export declare function installService(): void;
2
+ export declare function uninstallService(): void;
3
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAOA,wBAAgB,cAAc,IAAI,IAAI,CAYrC;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAYvC"}