byr-pt-cli 0.1.0 → 0.1.2
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.
- package/LICENSE +21 -0
- package/README.md +48 -27
- package/dist/{cli-CdpxYIyd.mjs → cli-B6Pr98en.mjs} +94 -48
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +6 -6
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 onemoreproduct
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,64 +1,85 @@
|
|
|
1
1
|
# byr-pt-cli
|
|
2
2
|
|
|
3
|
-
`byr
|
|
3
|
+
CLI for `byr.pt` with stable machine-readable output (`--json`) and script-friendly commands for search, details, download, auth, and user stats.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g byr-pt-cli
|
|
9
|
+
byr --help
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
6
13
|
|
|
7
14
|
```bash
|
|
15
|
+
# Use cookie (recommended)
|
|
8
16
|
export BYR_COOKIE='uid=...; pass=...'
|
|
9
|
-
# or: export BYR_USERNAME='...' BYR_PASSWORD='...'
|
|
10
17
|
|
|
18
|
+
# Or username/password
|
|
19
|
+
# export BYR_USERNAME='...'
|
|
20
|
+
# export BYR_PASSWORD='...'
|
|
21
|
+
|
|
22
|
+
byr search --query "ubuntu" --limit 5
|
|
23
|
+
byr get --id 1001
|
|
24
|
+
byr download --id 1001 --output ./1001.torrent --dry-run
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
```bash
|
|
11
30
|
byr search --query "ubuntu" --limit 5 --category movie --spstate free
|
|
12
31
|
byr search --imdb tt0133093 --json
|
|
13
32
|
byr get --id 1001
|
|
14
|
-
byr download --id 1001 --output ./1001.torrent
|
|
33
|
+
byr download --id 1001 --output ./1001.torrent
|
|
15
34
|
byr user info --json
|
|
16
35
|
byr meta categories --json
|
|
17
36
|
byr meta levels --json
|
|
18
37
|
byr auth status --verify --json
|
|
38
|
+
byr auth import-cookie --cookie "uid=...; pass=..." --json
|
|
19
39
|
byr auth import-cookie --from-browser chrome --profile "Default" --json
|
|
20
40
|
byr auth logout --json
|
|
21
41
|
```
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
## Search filters
|
|
24
44
|
|
|
25
|
-
-
|
|
26
|
-
-
|
|
45
|
+
- `--category` repeatable / comma-separated, accepts alias or numeric ID
|
|
46
|
+
- `--incldead` `all|alive|dead` (or `0|1|2`)
|
|
47
|
+
- `--spstate` `all|normal|free|2x|2xfree|50|2x50|30` (or `0..7`)
|
|
48
|
+
- `--bookmarked` `all|only|unbookmarked` (or `0|1|2`)
|
|
49
|
+
- `--imdb` alternative to `--query`
|
|
50
|
+
- `--page` page index
|
|
27
51
|
|
|
28
|
-
|
|
52
|
+
## Auth and config
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
- `--incldead` (`all|alive|dead` or `0|1|2`)
|
|
32
|
-
- `--spstate` (`all|normal|free|2x|2xfree|50|2x50|30` or `0..7`)
|
|
33
|
-
- `--bookmarked` (`all|only|unbookmarked` or `0|1|2`)
|
|
34
|
-
- `--imdb` (alternative to `--query`)
|
|
35
|
-
- `--page`
|
|
54
|
+
Credential source priority:
|
|
36
55
|
|
|
37
|
-
|
|
56
|
+
`CLI flags > ENV > ./.byrrc.json > ~/.config/byr-cli/config.json > ~/.config/byr-cli/auth.json`
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
`auth import-cookie` supports:
|
|
59
|
+
|
|
60
|
+
- legacy BYR cookie: `uid=...; pass=...`
|
|
61
|
+
- current session cookie: `session_id=...; auth_token=...` (optional `refresh_token=...`)
|
|
40
62
|
|
|
41
63
|
## JSON contract
|
|
42
64
|
|
|
43
|
-
|
|
65
|
+
All commands support `--json`.
|
|
44
66
|
|
|
45
67
|
- success: `{ "ok": true, "data": ..., "meta": ... }`
|
|
46
68
|
- error: `{ "ok": false, "error": { "code": "...", "message": "...", "details": ... } }`
|
|
47
69
|
|
|
48
|
-
##
|
|
70
|
+
## Testing
|
|
71
|
+
|
|
72
|
+
Run package tests and checks:
|
|
49
73
|
|
|
50
74
|
```bash
|
|
51
|
-
|
|
75
|
+
pnpm --filter byr-pt-cli test
|
|
76
|
+
pnpm --filter byr-pt-cli check
|
|
52
77
|
```
|
|
53
78
|
|
|
54
|
-
|
|
55
|
-
CI should keep `test:live` disabled and only run fixture/mock tests.
|
|
56
|
-
|
|
57
|
-
## Skill bundle
|
|
58
|
-
|
|
59
|
-
OpenClaw runtime skill is in `skill-openclaw/` and can be published to ClawHub.
|
|
79
|
+
Local live smoke (requires valid BYR credentials):
|
|
60
80
|
|
|
61
81
|
```bash
|
|
62
|
-
|
|
63
|
-
pnpm --filter byr-pt-cli skill:publish
|
|
82
|
+
BYR_LIVE=1 BYR_COOKIE='uid=...; pass=...' pnpm --filter byr-pt-cli test:live
|
|
64
83
|
```
|
|
84
|
+
|
|
85
|
+
Flow: `search -> get -> download --dry-run -> user info`.
|
|
@@ -2,9 +2,9 @@ import { renderDownloadOutput, runDownloadCommand } from "./commands/download.mj
|
|
|
2
2
|
import { renderGetOutput, runGetCommand } from "./commands/get.mjs";
|
|
3
3
|
import { renderSearchOutput, runSearchCommand } from "./commands/search.mjs";
|
|
4
4
|
import { a as BYR_INCLDEAD_FACET, c as parseCategoryAliases, i as BYR_BOOKMARKED_FACET, l as parseSimpleFacetAliases, o as BYR_SPSTATE_FACET, s as getByrMetadata, t as createByrClient } from "./client-DT9oDCaE.mjs";
|
|
5
|
-
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import { CliAppError, EXIT_CODES, createCommandContext, createErrorEnvelope, createSuccessEnvelope, mapErrorCodeToExitCode, toCliAppError } from "@onemoreproduct/cli-core";
|
|
7
|
-
import { copyFileSync, existsSync, readFileSync, statSync } from "node:fs";
|
|
7
|
+
import { copyFileSync, existsSync, readFileSync, rmSync, statSync } from "node:fs";
|
|
8
8
|
import { homedir, tmpdir } from "node:os";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { createDecipheriv, pbkdf2Sync, randomUUID } from "node:crypto";
|
|
@@ -17,6 +17,13 @@ const TARGET_DOMAINS = new Set([
|
|
|
17
17
|
".bt.byr.cn",
|
|
18
18
|
"bt.byr.cn"
|
|
19
19
|
]);
|
|
20
|
+
const AUTH_COOKIE_NAMES = new Set([
|
|
21
|
+
"uid",
|
|
22
|
+
"pass",
|
|
23
|
+
"session_id",
|
|
24
|
+
"auth_token",
|
|
25
|
+
"refresh_token"
|
|
26
|
+
]);
|
|
20
27
|
async function importCookieFromBrowser(browser, profile) {
|
|
21
28
|
if (process.platform !== "darwin") throw new CliAppError({
|
|
22
29
|
code: "E_AUTH_REQUIRED",
|
|
@@ -44,43 +51,46 @@ function importCookieFromChrome(profile) {
|
|
|
44
51
|
const shm = `${dbPath}-shm`;
|
|
45
52
|
if (existsSync(wal)) copyFileSync(wal, `${tempDb}-wal`);
|
|
46
53
|
if (existsSync(shm)) copyFileSync(shm, `${tempDb}-shm`);
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
try {
|
|
55
|
+
const sqlite = spawnSync("sqlite3", [
|
|
56
|
+
"-separator",
|
|
57
|
+
" ",
|
|
58
|
+
tempDb,
|
|
59
|
+
"SELECT name, value, hex(encrypted_value) FROM cookies WHERE host_key IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass','session_id','auth_token','refresh_token')"
|
|
60
|
+
], { encoding: "utf8" });
|
|
61
|
+
if (sqlite.status !== 0) throw new CliAppError({
|
|
62
|
+
code: "E_AUTH_REQUIRED",
|
|
63
|
+
message: "Failed to read Chrome cookies with sqlite3",
|
|
64
|
+
details: { stderr: sqlite.stderr?.trim() }
|
|
65
|
+
});
|
|
66
|
+
const cookies = /* @__PURE__ */ new Map();
|
|
67
|
+
const lines = (sqlite.stdout ?? "").split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
68
|
+
let keyHex;
|
|
69
|
+
for (const line of lines) {
|
|
70
|
+
const [name, value, encryptedHex] = line.split(" ");
|
|
71
|
+
if (!AUTH_COOKIE_NAMES.has(name)) continue;
|
|
72
|
+
if (value && value.length > 0) {
|
|
73
|
+
cookies.set(name, value);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (!keyHex) keyHex = getChromeSafeStorageKeyHex();
|
|
77
|
+
if (!encryptedHex || encryptedHex.length === 0 || !keyHex) continue;
|
|
78
|
+
const resolved = decryptChromeCookieHex(encryptedHex, keyHex);
|
|
79
|
+
if (resolved) cookies.set(name, resolved);
|
|
67
80
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
return {
|
|
82
|
+
cookie: buildByrAuthCookieHeader(cookies, {
|
|
83
|
+
source: `chrome:${profileName}`,
|
|
84
|
+
profile: profileName
|
|
85
|
+
}),
|
|
86
|
+
source: `chrome:${profileName}`
|
|
87
|
+
};
|
|
88
|
+
} finally {
|
|
89
|
+
rmSync(tempDir, {
|
|
90
|
+
recursive: true,
|
|
91
|
+
force: true
|
|
92
|
+
});
|
|
72
93
|
}
|
|
73
|
-
const uid = decrypted.get("uid");
|
|
74
|
-
const pass = decrypted.get("pass");
|
|
75
|
-
if (!uid || !pass) throw new CliAppError({
|
|
76
|
-
code: "E_AUTH_REQUIRED",
|
|
77
|
-
message: "Unable to extract uid/pass from Chrome cookies",
|
|
78
|
-
details: { profile: profileName }
|
|
79
|
-
});
|
|
80
|
-
return {
|
|
81
|
-
cookie: `uid=${uid}; pass=${pass}`,
|
|
82
|
-
source: `chrome:${profileName}`
|
|
83
|
-
};
|
|
84
94
|
}
|
|
85
95
|
function getChromeSafeStorageKeyHex() {
|
|
86
96
|
for (const [service, account] of [["Chrome Safe Storage", "Chrome"], ["Chrome Safe Storage", "Google Chrome"]]) {
|
|
@@ -137,7 +147,7 @@ function tryReadSafariSqlite(path) {
|
|
|
137
147
|
"-separator",
|
|
138
148
|
" ",
|
|
139
149
|
path,
|
|
140
|
-
"SELECT name, value, host FROM cookies WHERE host IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass')"
|
|
150
|
+
"SELECT name, value, host FROM cookies WHERE host IN ('.byr.pt','byr.pt','.bt.byr.cn','bt.byr.cn') AND name IN ('uid','pass','session_id','auth_token','refresh_token')"
|
|
141
151
|
], { encoding: "utf8" });
|
|
142
152
|
if (sqlite.status !== 0) return;
|
|
143
153
|
const records = [];
|
|
@@ -168,15 +178,37 @@ function extractByrCookie(records, source) {
|
|
|
168
178
|
const map = /* @__PURE__ */ new Map();
|
|
169
179
|
for (const record of records) {
|
|
170
180
|
if (!TARGET_DOMAINS.has(record.domain)) continue;
|
|
171
|
-
if (
|
|
181
|
+
if (AUTH_COOKIE_NAMES.has(record.name)) map.set(record.name, record.value);
|
|
172
182
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
183
|
+
if (map.size === 0) return;
|
|
184
|
+
try {
|
|
185
|
+
return {
|
|
186
|
+
cookie: buildByrAuthCookieHeader(map, { source }),
|
|
187
|
+
source
|
|
188
|
+
};
|
|
189
|
+
} catch {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function buildByrAuthCookieHeader(cookieMap, context) {
|
|
194
|
+
const uid = cookieMap.get("uid");
|
|
195
|
+
const pass = cookieMap.get("pass");
|
|
196
|
+
if (uid && pass) return `uid=${uid}; pass=${pass}`;
|
|
197
|
+
const sessionId = cookieMap.get("session_id");
|
|
198
|
+
const authToken = cookieMap.get("auth_token");
|
|
199
|
+
if (sessionId && authToken) {
|
|
200
|
+
const refreshToken = cookieMap.get("refresh_token");
|
|
201
|
+
return refreshToken ? `session_id=${sessionId}; auth_token=${authToken}; refresh_token=${refreshToken}` : `session_id=${sessionId}; auth_token=${authToken}`;
|
|
202
|
+
}
|
|
203
|
+
throw new CliAppError({
|
|
204
|
+
code: "E_AUTH_REQUIRED",
|
|
205
|
+
message: "Unable to extract BYR auth cookies from browser storage",
|
|
206
|
+
details: {
|
|
207
|
+
source: context.source,
|
|
208
|
+
profile: context.profile,
|
|
209
|
+
requiredAnyOf: [["uid", "pass"], ["session_id", "auth_token"]]
|
|
210
|
+
}
|
|
211
|
+
});
|
|
180
212
|
}
|
|
181
213
|
function parseBinaryCookies(buffer) {
|
|
182
214
|
if (buffer.length < 8 || buffer.subarray(0, 4).toString("ascii") !== "cook") return [];
|
|
@@ -290,9 +322,23 @@ async function writeAuthStore(cookie, source) {
|
|
|
290
322
|
return store;
|
|
291
323
|
}
|
|
292
324
|
async function clearAuthStore() {
|
|
325
|
+
const authStorePath = getByrAuthStorePath();
|
|
326
|
+
let existed = true;
|
|
327
|
+
try {
|
|
328
|
+
await stat(authStorePath);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
const nodeError = error;
|
|
331
|
+
if (nodeError.code === "ENOENT") existed = false;
|
|
332
|
+
else throw new CliAppError({
|
|
333
|
+
code: "E_AUTH_INVALID",
|
|
334
|
+
message: "Failed to inspect BYR auth store",
|
|
335
|
+
details: { reason: nodeError.message },
|
|
336
|
+
cause: error
|
|
337
|
+
});
|
|
338
|
+
}
|
|
293
339
|
try {
|
|
294
|
-
await rm(
|
|
295
|
-
return
|
|
340
|
+
await rm(authStorePath, { force: true });
|
|
341
|
+
return existed;
|
|
296
342
|
} catch (error) {
|
|
297
343
|
throw new CliAppError({
|
|
298
344
|
code: "E_AUTH_INVALID",
|
package/dist/cli.mjs
CHANGED
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "byr-pt-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "BYR CLI with OpenClaw-friendly JSON contract",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,16 +20,16 @@
|
|
|
20
20
|
"import": "./dist/cli.mjs"
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"clawkit-cli-core": "^0.1.0"
|
|
25
|
+
},
|
|
23
26
|
"scripts": {
|
|
24
27
|
"build": "tsdown src/index.ts src/cli.ts src/domain/client.ts src/domain/types.ts src/commands/download.ts src/commands/get.ts src/commands/search.ts --format esm --dts",
|
|
25
|
-
"test": "pnpm --filter
|
|
28
|
+
"test": "pnpm --filter clawkit-cli-core build && vitest run",
|
|
26
29
|
"test:live": "tsx ./scripts/test-live.ts",
|
|
27
30
|
"typecheck": "tsc --project tsconfig.json --noEmit",
|
|
28
31
|
"check": "pnpm typecheck && pnpm test",
|
|
29
32
|
"skill:publish": "node ./scripts/skill-publish.mjs",
|
|
30
33
|
"skill:sync": "clawhub sync --root ./skill-openclaw --all"
|
|
31
|
-
},
|
|
32
|
-
"dependencies": {
|
|
33
|
-
"@onemoreproduct/cli-core": "workspace:*"
|
|
34
34
|
}
|
|
35
|
-
}
|
|
35
|
+
}
|