An ID on the command line is decimal digits only (closes #185) #189

Merged
clawbot merged 2 commits from issue-185-decimal-ids into next 2026-10-14 03:06:35 +02:00
3 changed files with 44 additions and 19 deletions
Showing only changes of commit 6a6b3ef478 - Show all commits

No files matched your search

+7
View File
@@ -626,6 +626,13 @@ exits 1, or 3 when the server no longer accepts the saved session (see "Session
handling"). `--cache-dir` overrides where the cache lives; without it each handling"). `--cache-dir` overrides where the cache lives; without it each
account gets its own directory under the per-user cache path. account gets its own directory under the per-user cache path.
An ID given to `files --collection`, `get`, `get-thumb` or
`helper fix-missing-thumbnails --file` is written in decimal digits, no larger
than 9007199254740991. Anything else, such as `0x10`, `1e3` or `-1`, is refused
before any request with one line, `Invalid file ID: <arg>` or
`Invalid collection ID: <arg>`, and exit status 1. One bad `--file` ID refuses
the whole command, so nothing is fixed.
`get` and `get-thumb` resolve the file by ID directly, so `--collection` is `get` and `get-thumb` resolve the file by ID directly, so `--collection` is
accepted for backward compatibility but ignored. For a live photo, `get` writes accepted for backward compatibility but ignored. For a live photo, `get` writes
its image and its video, each named after the title with its own extension, as its image and its video, each named after the title with its own extension, as
+9
View File
@@ -25,6 +25,15 @@ declares one.
# Completed Steps # Completed Steps
- 2026-10-14: `files --collection`, `get`, `get-thumb` and
`helper fix-missing-thumbnails --file` take an ID only when it is written in
decimal digits and is a safe integer (issue 185). Before, `Number()` read
`0x10` as 16, `1e3` as 1000 and an empty string as 0, and `--file` dropped an
ID it could not read without a word. Anything else now prints
`Invalid file ID: <arg>` or `Invalid collection ID: <arg>` and exits 1 before
any request; one bad `--file` ID fails the whole command. `parseID` in
`src/cli-commands.ts` does the check for all four.
- 2026-10-06: The README says how to run `quak backup` from cron (issue 170): - 2026-10-06: The README says how to run `quak backup` from cron (issue 170):
log in once as the job's user, a crontab with a backup every night and log in once as the job's user, a crontab with a backup every night and
`--verify` on Sundays appending to a log file, and the `HOME` and `--verify` on Sundays appending to a log file, and the `HOME` and
+28 -19
View File
@@ -97,6 +97,20 @@ const requireSession = (ctx: CliContext): Client | undefined => {
return client; return client;
}; };
// `text` as an ID, or undefined after telling the user it is not one. An ID is
// written in decimal digits and is a safe integer; `Number()` alone would read
// `0x10` as 16, `1e3` as 1000 and "" as 0.
const parseID = (
ctx: CliContext,
kind: "file" | "collection",
text: string,
): number | undefined => {
const id = Number(text);
if (/^[0-9]+$/.test(text) && Number.isSafeInteger(id)) return id;
ctx.stderr.write(`Invalid ${kind} ID: ${text}\n`);
return undefined;
};
// A library client that omits `fetchMLData`, so the point commands below do not // A library client that omits `fetchMLData`, so the point commands below do not
// kick the library's background ML backfill: they read metadata, or fetch one // kick the library's background ML backfill: they read metadata, or fetch one
// file's content, and exit. `backup` and `backup-metadata` handle ML on their // file's content, and exit. `backup` and `backup-metadata` handle ML on their
@@ -247,11 +261,8 @@ export const filesCommand = async (
await init(); await init();
const client = requireSession(ctx); const client = requireSession(ctx);
if (!client) return 3; if (!client) return 3;
const collectionID = Number(opts.collection); const collectionID = parseID(ctx, "collection", opts.collection);
if (!Number.isFinite(collectionID)) { if (collectionID === undefined) return 1;
ctx.stderr.write("Invalid collection ID\n");
return 1;
}
const lib = await openReadLibrary(ctx, client); const lib = await openReadLibrary(ctx, client);
try { try {
@@ -289,11 +300,8 @@ export const getCommand = async (
await init(); await init();
const client = requireSession(ctx); const client = requireSession(ctx);
if (!client) return 3; if (!client) return 3;
const fileID = Number(fileIDStr); const fileID = parseID(ctx, "file", fileIDStr);
if (!Number.isFinite(fileID)) { if (fileID === undefined) return 1;
ctx.stderr.write("Invalid file ID\n");
return 1;
}
const lib = await openReadLibrary(ctx, client); const lib = await openReadLibrary(ctx, client);
try { try {
@@ -347,11 +355,8 @@ export const getThumbCommand = async (
await init(); await init();
const client = requireSession(ctx); const client = requireSession(ctx);
if (!client) return 3; if (!client) return 3;
const fileID = Number(fileIDStr); const fileID = parseID(ctx, "file", fileIDStr);
if (!Number.isFinite(fileID)) { if (fileID === undefined) return 1;
ctx.stderr.write("Invalid file ID\n");
return 1;
}
const lib = await openReadLibrary(ctx, client); const lib = await openReadLibrary(ctx, client);
try { try {
@@ -516,15 +521,19 @@ export const fixMissingThumbnailsCommand = async (
await init(); await init();
const client = requireSession(ctx); const client = requireSession(ctx);
if (!client) return 3; if (!client) return 3;
// One bad --file ID fails the whole command, before anything is fetched.
let fileIDs: number[] = [];
for (const text of opts.file ?? []) {
const fileID = parseID(ctx, "file", text);
if (fileID === undefined) return 1;
fileIDs.push(fileID);
}
const lib = await openReadLibrary(ctx, client); const lib = await openReadLibrary(ctx, client);
try { try {
// Refresh first so files added since the cache was written are found; // Refresh first so files added since the cache was written are found;
// a failed refresh throws. // a failed refresh throws.
await lib.fresh(); await lib.fresh();
let fileIDs: number[]; if (fileIDs.length === 0) {
if (opts.file && opts.file.length > 0) {
fileIDs = opts.file.map(Number).filter(Number.isFinite);
} else {
ctx.stderr.write("Scanning for missing thumbnails...\n"); ctx.stderr.write("Scanning for missing thumbnails...\n");
const missing = await listMissingThumbnails(lib, client, (msg) => { const missing = await listMissingThumbnails(lib, client, (msg) => {
if (!opts.json) ctx.stderr.write(msg + "\n"); if (!opts.json) ctx.stderr.write(msg + "\n");