@book000/pixivts 0.60.0 → 0.60.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/dist/index.js CHANGED
@@ -1,1520 +1,1604 @@
1
- // src/result.ts
2
- var OkResultImpl = class _OkResultImpl {
3
- constructor(value) {
4
- this.value = value;
5
- }
6
- value;
7
- isOk = true;
8
- isErr = false;
9
- map(fn) {
10
- return new _OkResultImpl(fn(this.value));
11
- }
12
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters, @typescript-eslint/no-unused-vars -- F is part of the public API contract; _fn is intentionally unused (OkResult.mapErr is a no-op)
13
- mapErr(_fn) {
14
- return this;
15
- }
16
- andThen(fn) {
17
- return fn(this.value);
18
- }
19
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _onErr is intentionally unused: OkResult.match always calls onOk
20
- match(onOk, _onErr) {
21
- return onOk(this.value);
22
- }
23
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _fallback is intentionally unused: OkResult.unwrapOr always returns value
24
- unwrapOr(_fallback) {
25
- return this.value;
26
- }
1
+ //#region src/result.ts
2
+ var OkResultImpl = class OkResultImpl {
3
+ value;
4
+ isOk = true;
5
+ isErr = false;
6
+ constructor(value) {
7
+ this.value = value;
8
+ }
9
+ map(fn) {
10
+ return new OkResultImpl(fn(this.value));
11
+ }
12
+ mapErr(_fn) {
13
+ return this;
14
+ }
15
+ andThen(fn) {
16
+ return fn(this.value);
17
+ }
18
+ match(onOk, _onErr) {
19
+ return onOk(this.value);
20
+ }
21
+ unwrapOr(_fallback) {
22
+ return this.value;
23
+ }
27
24
  };
28
- var ErrResultImpl = class _ErrResultImpl {
29
- // eslint-disable-next-line n/handle-callback-err -- 'error' is a stored value, not a Node.js callback error parameter
30
- constructor(error) {
31
- this.error = error;
32
- }
33
- error;
34
- isOk = false;
35
- isErr = true;
36
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters, @typescript-eslint/no-unused-vars -- U is part of the public API contract; _fn is intentionally unused (ErrResult.map is a no-op)
37
- map(_fn) {
38
- return this;
39
- }
40
- mapErr(fn) {
41
- return new _ErrResultImpl(fn(this.error));
42
- }
43
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _fn is intentionally unused: ErrResult.andThen is a no-op (the success path does not apply)
44
- andThen(_fn) {
45
- return this;
46
- }
47
- match(_onOk, onErr) {
48
- return onErr(this.error);
49
- }
50
- unwrapOr(fallback) {
51
- return fallback;
52
- }
25
+ var ErrResultImpl = class ErrResultImpl {
26
+ error;
27
+ isOk = false;
28
+ isErr = true;
29
+ constructor(error) {
30
+ this.error = error;
31
+ }
32
+ map(_fn) {
33
+ return this;
34
+ }
35
+ mapErr(fn) {
36
+ return new ErrResultImpl(fn(this.error));
37
+ }
38
+ andThen(_fn) {
39
+ return this;
40
+ }
41
+ match(_onOk, onErr) {
42
+ return onErr(this.error);
43
+ }
44
+ unwrapOr(fallback) {
45
+ return fallback;
46
+ }
53
47
  };
48
+ /**
49
+ * Creates a successful `Result<T, never>`.
50
+ *
51
+ * @param value - The success value
52
+ */
54
53
  function ok(value) {
55
- return new OkResultImpl(value);
54
+ return new OkResultImpl(value);
56
55
  }
56
+ /**
57
+ * Creates a failed `Result<never, E>`.
58
+ *
59
+ * @param error - The error value
60
+ */
57
61
  function err(error) {
58
- return new ErrResultImpl(error);
62
+ return new ErrResultImpl(error);
59
63
  }
60
- var ResultAsync = class _ResultAsync {
61
- _promise;
62
- constructor(promise) {
63
- this._promise = promise;
64
- }
65
- // PromiseLike contract — makes `await resultAsync` work
66
- // eslint-disable-next-line unicorn/no-thenable -- ResultAsync intentionally implements PromiseLike to be directly awaitable
67
- then(onfulfilled, onrejected) {
68
- return this._promise.then(onfulfilled, onrejected);
69
- }
70
- /**
71
- * Wraps a `Promise<T>` into a `ResultAsync<T, E>`.
72
- *
73
- * If the promise rejects, `onError` maps the rejection reason to `E`.
74
- *
75
- * @param promise - The promise to wrap
76
- * @param onError - Error mapper
77
- */
78
- static fromPromise(promise, onError) {
79
- return new _ResultAsync(
80
- promise.then(
81
- (v) => ok(v),
82
- (error) => err(onError(error))
83
- )
84
- );
85
- }
86
- /**
87
- * Wraps an already-resolved `Result<T, E>` into a `ResultAsync<T, E>`.
88
- *
89
- * @param result - The result to wrap
90
- */
91
- static fromResult(result) {
92
- return new _ResultAsync(Promise.resolve(result));
93
- }
94
- /**
95
- * Transforms the success value.
96
- *
97
- * If the inner result is `Err`, `fn` is not called.
98
- *
99
- * @param fn - Synchronous mapper
100
- */
101
- map(fn) {
102
- return new _ResultAsync(
103
- // eslint-disable-next-line unicorn/no-array-callback-reference -- r.map(fn) is safe here; fn is a user-supplied mapper, not a DOM/Array method reference
104
- this._promise.then((r) => r.map(fn))
105
- );
106
- }
107
- /**
108
- * Transforms the error value.
109
- *
110
- * If the inner result is `Ok`, `fn` is not called.
111
- *
112
- * @param fn - Synchronous error mapper
113
- */
114
- mapErr(fn) {
115
- return new _ResultAsync(
116
- this._promise.then((r) => r.mapErr(fn))
117
- );
118
- }
119
- /**
120
- * Chains another async operation that may fail.
121
- *
122
- * If the inner result is `Err`, `fn` is not called.
123
- *
124
- * @param fn - Async mapper that returns a `ResultAsync<U, F>`
125
- */
126
- andThen(fn) {
127
- return new _ResultAsync(
128
- this._promise.then(async (r) => {
129
- if (r.isErr) return r;
130
- const next = fn(r.value);
131
- if (next instanceof _ResultAsync) {
132
- return next._promise;
133
- }
134
- return next;
135
- })
136
- );
137
- }
138
- /**
139
- * Pattern-matches on success / failure.
140
- *
141
- * @param onOk - Called with the success value
142
- * @param onErr - Called with the error value
143
- * @returns A `Promise<U>`
144
- */
145
- async match(onOk, onErr) {
146
- const r = await this._promise;
147
- if (r.isOk) return onOk(r.value);
148
- return onErr(r.error);
149
- }
150
- /**
151
- * Returns the success value, or `fallback` if the result is `Err`.
152
- *
153
- * @param fallback - The fallback value
154
- */
155
- async unwrapOr(fallback) {
156
- const r = await this._promise;
157
- if (r.isOk) return r.value;
158
- return fallback;
159
- }
64
+ /**
65
+ * A `PromiseLike<Result<T, E>>` that is directly `await`-able and supports
66
+ * chainable `map / mapErr / andThen` operators.
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * const result = await ResultAsync.fromPromise(fetch('/api'), networkError)
71
+ * .andThen((res) =>
72
+ * ResultAsync.fromPromise(res.json() as Promise<unknown>, networkError)
73
+ * )
74
+ * ```
75
+ */
76
+ var ResultAsync = class ResultAsync {
77
+ _promise;
78
+ constructor(promise) {
79
+ this._promise = promise;
80
+ }
81
+ then(onfulfilled, onrejected) {
82
+ return this._promise.then(onfulfilled, onrejected);
83
+ }
84
+ /**
85
+ * Wraps a `Promise<T>` into a `ResultAsync<T, E>`.
86
+ *
87
+ * If the promise rejects, `onError` maps the rejection reason to `E`.
88
+ *
89
+ * @param promise - The promise to wrap
90
+ * @param onError - Error mapper
91
+ */
92
+ static fromPromise(promise, onError) {
93
+ return new ResultAsync(promise.then((v) => ok(v), (error) => err(onError(error))));
94
+ }
95
+ /**
96
+ * Wraps an already-resolved `Result<T, E>` into a `ResultAsync<T, E>`.
97
+ *
98
+ * @param result - The result to wrap
99
+ */
100
+ static fromResult(result) {
101
+ return new ResultAsync(Promise.resolve(result));
102
+ }
103
+ /**
104
+ * Transforms the success value.
105
+ *
106
+ * If the inner result is `Err`, `fn` is not called.
107
+ *
108
+ * @param fn - Synchronous mapper
109
+ */
110
+ map(fn) {
111
+ return new ResultAsync(this._promise.then((r) => r.map(fn)));
112
+ }
113
+ /**
114
+ * Transforms the error value.
115
+ *
116
+ * If the inner result is `Ok`, `fn` is not called.
117
+ *
118
+ * @param fn - Synchronous error mapper
119
+ */
120
+ mapErr(fn) {
121
+ return new ResultAsync(this._promise.then((r) => r.mapErr(fn)));
122
+ }
123
+ /**
124
+ * Chains another async operation that may fail.
125
+ *
126
+ * If the inner result is `Err`, `fn` is not called.
127
+ *
128
+ * @param fn - Async mapper that returns a `ResultAsync<U, F>`
129
+ */
130
+ andThen(fn) {
131
+ return new ResultAsync(this._promise.then(async (r) => {
132
+ if (r.isErr) return r;
133
+ const next = fn(r.value);
134
+ if (next instanceof ResultAsync) return next._promise;
135
+ return next;
136
+ }));
137
+ }
138
+ /**
139
+ * Pattern-matches on success / failure.
140
+ *
141
+ * @param onOk - Called with the success value
142
+ * @param onErr - Called with the error value
143
+ * @returns A `Promise<U>`
144
+ */
145
+ async match(onOk, onErr) {
146
+ const r = await this._promise;
147
+ if (r.isOk) return onOk(r.value);
148
+ return onErr(r.error);
149
+ }
150
+ /**
151
+ * Returns the success value, or `fallback` if the result is `Err`.
152
+ *
153
+ * @param fallback - The fallback value
154
+ */
155
+ async unwrapOr(fallback) {
156
+ const r = await this._promise;
157
+ if (r.isOk) return r.value;
158
+ return fallback;
159
+ }
160
160
  };
161
-
162
- // src/errors.ts
161
+ //#endregion
162
+ //#region src/errors.ts
163
+ /**
164
+ * An `Error` subclass that wraps a `PixivError` for use in thrown contexts
165
+ * (e.g. async generators that must throw proper `Error` objects).
166
+ *
167
+ * All `PixivError` properties are spread directly onto this instance so that
168
+ * callers can use `instanceof PixivFetchError` or access `error.type` etc.
169
+ *
170
+ * @example
171
+ * ```ts
172
+ * try {
173
+ * for await (const page of result.pages()) { ... }
174
+ * } catch (e) {
175
+ * if (e instanceof PixivFetchError) {
176
+ * console.error(e.pixivError.type)
177
+ * }
178
+ * }
179
+ * ```
180
+ */
163
181
  var PixivFetchError = class extends Error {
164
- /** The underlying structured `PixivError`. */
165
- pixivError;
166
- constructor(pixivError) {
167
- super(`pixiv API error: ${pixivError.type}`);
168
- this.name = "PixivFetchError";
169
- this.pixivError = pixivError;
170
- Object.assign(this, pixivError);
171
- }
182
+ /** The underlying structured `PixivError`. */
183
+ pixivError;
184
+ constructor(pixivError) {
185
+ super(`pixiv API error: ${pixivError.type}`);
186
+ this.name = "PixivFetchError";
187
+ this.pixivError = pixivError;
188
+ Object.assign(this, pixivError);
189
+ }
172
190
  };
191
+ /** Creates a rate-limit error. */
173
192
  function rateLimitError(retryAfter) {
174
- return { type: "rate_limit", retryAfter };
193
+ return {
194
+ type: "rate_limit",
195
+ retryAfter
196
+ };
175
197
  }
198
+ /** Creates an auth-failed error. */
176
199
  function authFailedError(status) {
177
- return { type: "auth_failed", status };
200
+ return {
201
+ type: "auth_failed",
202
+ status
203
+ };
178
204
  }
205
+ /** Creates a network error. */
179
206
  function networkError(cause) {
180
- return { type: "network", cause };
207
+ return {
208
+ type: "network",
209
+ cause
210
+ };
181
211
  }
212
+ /** Creates an API error. */
182
213
  function apiError(status, body) {
183
- return { type: "api_error", status, body };
214
+ return {
215
+ type: "api_error",
216
+ status,
217
+ body
218
+ };
184
219
  }
185
-
186
- // src/paginated.ts
187
- var PaginatedResultAsync = class _PaginatedResultAsync extends ResultAsync {
188
- #http;
189
- #getItems;
190
- constructor(promise, http, getItems) {
191
- super(promise);
192
- this.#http = http;
193
- this.#getItems = getItems;
194
- }
195
- /**
196
- * Creates a `PaginatedResultAsync` from a `ResultAsync`.
197
- *
198
- * @param inner - The first-page result
199
- * @param http - HTTP client for fetching subsequent pages
200
- * @param getItems - Extracts item array from a page
201
- */
202
- static fromResultAsync(inner, http, getItems) {
203
- const promise = Promise.resolve(inner);
204
- return new _PaginatedResultAsync(promise, http, getItems);
205
- }
206
- /**
207
- * Async generator that yields each page starting from the first.
208
- *
209
- * If any page fetch fails, the generator throws a `PixivFetchError`.
210
- *
211
- * @example
212
- * ```ts
213
- * for await (const page of client.illusts.search({ word: 'cat' }).pages()) {
214
- * console.log(page.illusts.length)
215
- * }
216
- * ```
217
- */
218
- async *pages() {
219
- const first = await Promise.resolve(this);
220
- if (first.isErr) throw new PixivFetchError(first.error);
221
- yield first.value;
222
- let nextUrl = first.value.nextUrl;
223
- while (nextUrl !== null) {
224
- const pageResult = await this.#http.getAbsolute(nextUrl);
225
- if (pageResult.isErr) throw new PixivFetchError(pageResult.error);
226
- yield pageResult.value;
227
- nextUrl = pageResult.value.nextUrl;
228
- }
229
- }
230
- /**
231
- * Async generator that yields individual items across all pages.
232
- *
233
- * If any page fetch fails, the generator throws a `PixivFetchError`.
234
- *
235
- * @example
236
- * ```ts
237
- * for await (const illust of client.illusts.search({ word: 'cat' }).items()) {
238
- * console.log(illust.title)
239
- * }
240
- * ```
241
- */
242
- async *items() {
243
- for await (const page of this.pages()) {
244
- for (const item of this.#getItems(page)) {
245
- yield item;
246
- }
247
- }
248
- }
220
+ //#endregion
221
+ //#region src/paginated.ts
222
+ /**
223
+ * A `ResultAsync<TPage, PixivError>` with additional `.pages()` / `.items()`
224
+ * async generators for consuming paginated pixiv list responses.
225
+ *
226
+ * Returned by all resource methods that produce a `nextUrl` field.
227
+ */
228
+ var PaginatedResultAsync = class PaginatedResultAsync extends ResultAsync {
229
+ #http;
230
+ #getItems;
231
+ constructor(promise, http, getItems) {
232
+ super(promise);
233
+ this.#http = http;
234
+ this.#getItems = getItems;
235
+ }
236
+ /**
237
+ * Creates a `PaginatedResultAsync` from a `ResultAsync`.
238
+ *
239
+ * @param inner - The first-page result
240
+ * @param http - HTTP client for fetching subsequent pages
241
+ * @param getItems - Extracts item array from a page
242
+ */
243
+ static fromResultAsync(inner, http, getItems) {
244
+ return new PaginatedResultAsync(Promise.resolve(inner), http, getItems);
245
+ }
246
+ /**
247
+ * Async generator that yields each page starting from the first.
248
+ *
249
+ * If any page fetch fails, the generator throws a `PixivFetchError`.
250
+ *
251
+ * @example
252
+ * ```ts
253
+ * for await (const page of client.illusts.search({ word: 'cat' }).pages()) {
254
+ * console.log(page.illusts.length)
255
+ * }
256
+ * ```
257
+ */
258
+ async *pages() {
259
+ const first = await Promise.resolve(this);
260
+ if (first.isErr) throw new PixivFetchError(first.error);
261
+ yield first.value;
262
+ let nextUrl = first.value.nextUrl;
263
+ while (nextUrl !== null) {
264
+ const pageResult = await this.#http.getAbsolute(nextUrl);
265
+ if (pageResult.isErr) throw new PixivFetchError(pageResult.error);
266
+ yield pageResult.value;
267
+ nextUrl = pageResult.value.nextUrl;
268
+ }
269
+ }
270
+ /**
271
+ * Async generator that yields individual items across all pages.
272
+ *
273
+ * If any page fetch fails, the generator throws a `PixivFetchError`.
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * for await (const illust of client.illusts.search({ word: 'cat' }).items()) {
278
+ * console.log(illust.title)
279
+ * }
280
+ * ```
281
+ */
282
+ async *items() {
283
+ for await (const page of this.pages()) for (const item of this.#getItems(page)) yield item;
284
+ }
249
285
  };
286
+ /**
287
+ * Creates an immediately-failed `PaginatedResultAsync`.
288
+ *
289
+ * Useful when validation or auth fails before any HTTP request is made.
290
+ *
291
+ * @param error - The error to return
292
+ * @param http - HTTP client (used for signature compatibility)
293
+ * @param getItems - Item extractor (used for signature compatibility)
294
+ */
250
295
  function failedPaginated(error, http, getItems) {
251
- return new PaginatedResultAsync(
252
- Promise.resolve(err(error)),
253
- http,
254
- getItems
255
- );
296
+ return new PaginatedResultAsync(Promise.resolve(err(error)), http, getItems);
256
297
  }
257
-
258
- // src/params.ts
298
+ //#endregion
299
+ //#region src/params.ts
300
+ /**
301
+ * Parameter building utilities.
302
+ *
303
+ * Provides camelCase → snake_case conversion and a URLSearchParams builder
304
+ * that replicates the behaviour of the `qs` library used in the legacy code
305
+ * (without the `qs` runtime dependency).
306
+ */
307
+ /**
308
+ * Converts a camelCase string to snake_case.
309
+ *
310
+ * @example
311
+ * camelToSnake('illustId') // 'illust_id'
312
+ * camelToSnake('searchAiType') // 'search_ai_type'
313
+ *
314
+ * @param key - camelCase string
315
+ * @returns snake_case string
316
+ */
259
317
  function camelToSnake(key) {
260
- return key.replaceAll(/([A-Z])/g, (m) => `_${m.toLowerCase()}`);
318
+ return key.replaceAll(/([A-Z])/g, (m) => `_${m.toLowerCase()}`);
261
319
  }
320
+ /**
321
+ * Converts all keys of a plain object from camelCase to snake_case, shallow.
322
+ *
323
+ * Values are preserved as-is; nested objects are NOT recursed into.
324
+ *
325
+ * @param obj - Object with camelCase keys
326
+ * @returns New object with snake_case keys
327
+ */
262
328
  function toSnakeKeys(obj) {
263
- const out = {};
264
- for (const key of Object.keys(obj)) {
265
- out[camelToSnake(key)] = obj[key];
266
- }
267
- return out;
329
+ const out = {};
330
+ for (const key of Object.keys(obj)) out[camelToSnake(key)] = obj[key];
331
+ return out;
268
332
  }
333
+ /**
334
+ * Serialises a record of query parameters into a `URLSearchParams` instance.
335
+ *
336
+ * Rules:
337
+ * - `null` / `undefined` values are skipped.
338
+ * - Arrays are appended with a `[]` suffix: `foo[]=1&foo[]=2` (Rails/pixiv convention).
339
+ * - Booleans are serialised as `'true'` / `'false'`.
340
+ * - Numbers are serialised via `.toString()`.
341
+ *
342
+ * @param params - Key/value pairs to serialise
343
+ * @returns Populated `URLSearchParams`
344
+ */
269
345
  function buildSearchParams(params) {
270
- const usp = new URLSearchParams();
271
- for (const [key, value] of Object.entries(params)) {
272
- if (value === null || value === void 0) continue;
273
- if (Array.isArray(value)) {
274
- for (const item of value) usp.append(`${key}[]`, String(item));
275
- } else {
276
- usp.set(key, String(value));
277
- }
278
- }
279
- return usp;
346
+ const usp = new URLSearchParams();
347
+ for (const [key, value] of Object.entries(params)) {
348
+ if (value === null || value === void 0) continue;
349
+ if (Array.isArray(value)) for (const item of value) usp.append(`${key}[]`, String(item));
350
+ else usp.set(key, String(value));
351
+ }
352
+ return usp;
280
353
  }
354
+ /**
355
+ * Merges camelCase→snake_case conversion with URLSearchParams building.
356
+ *
357
+ * Convenience wrapper used by resource methods.
358
+ *
359
+ * @param params - camelCase record
360
+ * @returns Populated `URLSearchParams` with snake_case keys
361
+ */
281
362
  function buildParams(params) {
282
- return buildSearchParams(toSnakeKeys(params));
363
+ return buildSearchParams(toSnakeKeys(params));
283
364
  }
365
+ /**
366
+ * Converts a snake_case string to lowerCamelCase.
367
+ *
368
+ * Already-camelCase keys pass through unchanged (idempotent).
369
+ *
370
+ * @example
371
+ * snakeToCamel('image_urls') // 'imageUrls'
372
+ * snakeToCamel('x_restrict') // 'xRestrict'
373
+ * snakeToCamel('imageUrls') // 'imageUrls' (no-op)
374
+ *
375
+ * @param key - snake_case string
376
+ * @returns lowerCamelCase string
377
+ */
284
378
  function snakeToCamel(key) {
285
- return key.replaceAll(/_([a-z0-9])/g, (_m, c) => c.toUpperCase());
379
+ return key.replaceAll(/_([a-z0-9])/g, (_m, c) => c.toUpperCase());
286
380
  }
381
+ /**
382
+ * Recursively converts all object keys from snake_case to lowerCamelCase.
383
+ *
384
+ * - Plain objects: keys are converted, values are recursed.
385
+ * - Arrays: each element is recursed.
386
+ * - Primitives / null: returned as-is.
387
+ *
388
+ * All keys are converted uniformly — no path-based exclusions.
389
+ *
390
+ * @param value - Any JSON-compatible value
391
+ * @returns Deep copy with all object keys in lowerCamelCase
392
+ */
287
393
  function camelizeKeys(value) {
288
- if (value === null || typeof value !== "object") return value;
289
- if (Array.isArray(value)) return value.map((v) => camelizeKeys(v));
290
- const out = {};
291
- for (const [k, v] of Object.entries(value)) {
292
- out[snakeToCamel(k)] = camelizeKeys(v);
293
- }
294
- return out;
394
+ if (value === null || typeof value !== "object") return value;
395
+ if (Array.isArray(value)) return value.map((v) => camelizeKeys(v));
396
+ const out = {};
397
+ for (const [k, v] of Object.entries(value)) out[snakeToCamel(k)] = camelizeKeys(v);
398
+ return out;
295
399
  }
400
+ /**
401
+ * Parses a pixiv `next_url` into a typed cursor object.
402
+ *
403
+ * Pass the `next_url` field from any paginated response to extract the
404
+ * cursor parameters needed to resume pagination from a saved position.
405
+ *
406
+ * @example
407
+ * ```ts
408
+ * const page = await client.users.bookmarks.illusts({ userId: client.userId })
409
+ * if (page.isOk && page.value.nextUrl) {
410
+ * const cursor = parseNextUrl(page.value.nextUrl)
411
+ * // Resume later:
412
+ * const next = await client.users.bookmarks.illusts({
413
+ * userId: client.userId,
414
+ * maxBookmarkId: cursor.maxBookmarkId,
415
+ * })
416
+ * }
417
+ * ```
418
+ *
419
+ * @param url - The `next_url` string returned by a pixiv list endpoint
420
+ * @returns Typed cursor parameters; fields absent in the URL are `undefined`
421
+ */
296
422
  function parseNextUrl(url) {
297
- const usp = new URL(url).searchParams;
298
- const result = {};
299
- const toNum = (key) => {
300
- const v = usp.get(key);
301
- if (v === null || v === "") return void 0;
302
- const n = Number(v);
303
- return Number.isNaN(n) ? void 0 : n;
304
- };
305
- const maxBookmarkId = toNum("max_bookmark_id");
306
- if (maxBookmarkId !== void 0) result.maxBookmarkId = maxBookmarkId;
307
- const maxBookmarkIdForRecommend = toNum("max_bookmark_id_for_recommend");
308
- if (maxBookmarkIdForRecommend !== void 0)
309
- result.maxBookmarkIdForRecommend = maxBookmarkIdForRecommend;
310
- const minBookmarkIdForRecentIllust = toNum("min_bookmark_id_for_recent_illust");
311
- if (minBookmarkIdForRecentIllust !== void 0)
312
- result.minBookmarkIdForRecentIllust = minBookmarkIdForRecentIllust;
313
- const offset = toNum("offset");
314
- if (offset !== void 0) result.offset = offset;
315
- const lastOrder = toNum("last_order");
316
- if (lastOrder !== void 0) result.lastOrder = lastOrder;
317
- return result;
423
+ const usp = new URL(url).searchParams;
424
+ const result = {};
425
+ const toNum = (key) => {
426
+ const v = usp.get(key);
427
+ if (v === null || v === "") return void 0;
428
+ const n = Number(v);
429
+ return Number.isNaN(n) ? void 0 : n;
430
+ };
431
+ const maxBookmarkId = toNum("max_bookmark_id");
432
+ if (maxBookmarkId !== void 0) result.maxBookmarkId = maxBookmarkId;
433
+ const maxBookmarkIdForRecommend = toNum("max_bookmark_id_for_recommend");
434
+ if (maxBookmarkIdForRecommend !== void 0) result.maxBookmarkIdForRecommend = maxBookmarkIdForRecommend;
435
+ const minBookmarkIdForRecentIllust = toNum("min_bookmark_id_for_recent_illust");
436
+ if (minBookmarkIdForRecentIllust !== void 0) result.minBookmarkIdForRecentIllust = minBookmarkIdForRecentIllust;
437
+ const offset = toNum("offset");
438
+ if (offset !== void 0) result.offset = offset;
439
+ const lastOrder = toNum("last_order");
440
+ if (lastOrder !== void 0) result.lastOrder = lastOrder;
441
+ return result;
318
442
  }
319
-
320
- // src/options.ts
321
- var SearchTarget = {
322
- PARTIAL_MATCH_FOR_TAGS: "partial_match_for_tags",
323
- EXACT_MATCH_FOR_TAGS: "exact_match_for_tags",
324
- TITLE_AND_CAPTION: "title_and_caption",
325
- KEYWORD: "keyword"
443
+ //#endregion
444
+ //#region src/options.ts
445
+ /**
446
+ * Public option constants for @book000/pixivts.
447
+ *
448
+ * Each option is exported as a runtime `const` object for enum-like access
449
+ * (e.g. `BookmarkRestrict.PUBLIC`). Plain string literals are also accepted
450
+ * wherever these values are used as parameters.
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * // Enum-like usage
455
+ * await client.illusts.bookmarkAdd({ illustId: 123, restrict: BookmarkRestrict.PUBLIC })
456
+ *
457
+ * // Plain string literal — also valid
458
+ * await client.illusts.bookmarkAdd({ illustId: 123, restrict: 'public' })
459
+ * ```
460
+ */
461
+ /**
462
+ * Search match target for illust / novel searches.
463
+ *
464
+ * - `partial_match_for_tags` — tags contain the word (default)
465
+ * - `exact_match_for_tags` — tags exactly equal the word
466
+ * - `title_and_caption` — title or caption contains the word
467
+ * - `keyword` — general keyword search (novel only)
468
+ */
469
+ const SearchTarget = {
470
+ PARTIAL_MATCH_FOR_TAGS: "partial_match_for_tags",
471
+ EXACT_MATCH_FOR_TAGS: "exact_match_for_tags",
472
+ TITLE_AND_CAPTION: "title_and_caption",
473
+ KEYWORD: "keyword"
326
474
  };
327
- var SearchSort = {
328
- DATE_DESC: "date_desc",
329
- DATE_ASC: "date_asc",
330
- POPULAR_DESC: "popular_desc"
475
+ /**
476
+ * Sort order for search results.
477
+ *
478
+ * - `date_desc` — newest first (default)
479
+ * - `date_asc` — oldest first
480
+ * - `popular_desc` — most bookmarks first (premium only)
481
+ */
482
+ const SearchSort = {
483
+ DATE_DESC: "date_desc",
484
+ DATE_ASC: "date_asc",
485
+ POPULAR_DESC: "popular_desc"
331
486
  };
332
- var SearchDuration = {
333
- WITHIN_LAST_DAY: "within_last_day",
334
- WITHIN_LAST_WEEK: "within_last_week",
335
- WITHIN_LAST_MONTH: "within_last_month"
487
+ /**
488
+ * Date range filter for search results.
489
+ *
490
+ * - `within_last_day` — past 24 hours
491
+ * - `within_last_week` — past 7 days
492
+ * - `within_last_month` — past 30 days
493
+ */
494
+ const SearchDuration = {
495
+ WITHIN_LAST_DAY: "within_last_day",
496
+ WITHIN_LAST_WEEK: "within_last_week",
497
+ WITHIN_LAST_MONTH: "within_last_month"
336
498
  };
337
- var RankingMode = {
338
- DAY: "day",
339
- DAY_MALE: "day_male",
340
- DAY_FEMALE: "day_female",
341
- WEEK_ORIGINAL: "week_original",
342
- WEEK_ROOKIE: "week_rookie",
343
- WEEK: "week",
344
- MONTH: "month",
345
- DAY_AI: "day_ai",
346
- DAY_R18: "day_r18",
347
- WEEK_R18: "week_r18",
348
- DAY_MALE_R18: "day_male_r18",
349
- DAY_FEMALE_R18: "day_female_r18",
350
- DAY_R18_AI: "day_r18_ai"
499
+ /**
500
+ * Ranking mode for illust rankings.
501
+ *
502
+ * R-18 modes require a premium account with R-18 content enabled.
503
+ */
504
+ const RankingMode = {
505
+ DAY: "day",
506
+ DAY_MALE: "day_male",
507
+ DAY_FEMALE: "day_female",
508
+ WEEK_ORIGINAL: "week_original",
509
+ WEEK_ROOKIE: "week_rookie",
510
+ WEEK: "week",
511
+ MONTH: "month",
512
+ DAY_AI: "day_ai",
513
+ DAY_R18: "day_r18",
514
+ WEEK_R18: "week_r18",
515
+ DAY_MALE_R18: "day_male_r18",
516
+ DAY_FEMALE_R18: "day_female_r18",
517
+ DAY_R18_AI: "day_r18_ai"
351
518
  };
352
- var NovelRankingMode = {
353
- DAY: "day",
354
- WEEK: "week",
355
- DAY_MALE: "day_male",
356
- DAY_FEMALE: "day_female",
357
- WEEK_ROOKIE: "week_rookie",
358
- DAY_R18: "day_r18",
359
- WEEK_R18: "week_r18",
360
- DAY_R18_AI: "day_r18_ai"
519
+ /**
520
+ * Ranking mode for novel rankings.
521
+ *
522
+ * R-18 modes require a premium account with R-18 content enabled.
523
+ */
524
+ const NovelRankingMode = {
525
+ DAY: "day",
526
+ WEEK: "week",
527
+ DAY_MALE: "day_male",
528
+ DAY_FEMALE: "day_female",
529
+ WEEK_ROOKIE: "week_rookie",
530
+ DAY_R18: "day_r18",
531
+ WEEK_R18: "week_r18",
532
+ DAY_R18_AI: "day_r18_ai"
361
533
  };
362
- var BookmarkRestrict = {
363
- PUBLIC: "public",
364
- PRIVATE: "private"
534
+ /**
535
+ * Visibility restriction for bookmarks.
536
+ *
537
+ * - `public` — publicly visible (default)
538
+ * - `private` — visible only to the owner
539
+ */
540
+ const BookmarkRestrict = {
541
+ PUBLIC: "public",
542
+ PRIVATE: "private"
365
543
  };
366
- var FollowRestrict = {
367
- PUBLIC: "public",
368
- PRIVATE: "private"
544
+ /**
545
+ * Visibility restriction for follows.
546
+ *
547
+ * - `public` — publicly visible (default)
548
+ * - `private` — visible only to the owner
549
+ */
550
+ const FollowRestrict = {
551
+ PUBLIC: "public",
552
+ PRIVATE: "private"
369
553
  };
370
- var OSFilter = {
371
- FOR_IOS: "for_ios",
372
- FOR_ANDROID: "for_android"
554
+ /**
555
+ * OS filter used to request works compatible with the given platform.
556
+ *
557
+ * - `for_ios` — iOS-compatible works (default)
558
+ * - `for_android` — Android-compatible works
559
+ */
560
+ const OSFilter = {
561
+ FOR_IOS: "for_ios",
562
+ FOR_ANDROID: "for_android"
373
563
  };
374
- var UserIllustType = {
375
- ILLUST: "illust",
376
- MANGA: "manga"
564
+ /**
565
+ * Work type filter for user illust listings.
566
+ *
567
+ * - `illust` — illustrations only
568
+ * - `manga` — manga only
569
+ */
570
+ const UserIllustType = {
571
+ ILLUST: "illust",
572
+ MANGA: "manga"
377
573
  };
378
-
379
- // src/auth.ts
380
- var T = Array.from(
381
- { length: 64 },
382
- (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32)
383
- );
384
- var S = [
385
- 7,
386
- 12,
387
- 17,
388
- 22,
389
- 7,
390
- 12,
391
- 17,
392
- 22,
393
- 7,
394
- 12,
395
- 17,
396
- 22,
397
- 7,
398
- 12,
399
- 17,
400
- 22,
401
- 5,
402
- 9,
403
- 14,
404
- 20,
405
- 5,
406
- 9,
407
- 14,
408
- 20,
409
- 5,
410
- 9,
411
- 14,
412
- 20,
413
- 5,
414
- 9,
415
- 14,
416
- 20,
417
- 4,
418
- 11,
419
- 16,
420
- 23,
421
- 4,
422
- 11,
423
- 16,
424
- 23,
425
- 4,
426
- 11,
427
- 16,
428
- 23,
429
- 4,
430
- 11,
431
- 16,
432
- 23,
433
- 6,
434
- 10,
435
- 15,
436
- 21,
437
- 6,
438
- 10,
439
- 15,
440
- 21,
441
- 6,
442
- 10,
443
- 15,
444
- 21,
445
- 6,
446
- 10,
447
- 15,
448
- 21
574
+ //#endregion
575
+ //#region src/auth.ts
576
+ const T = Array.from({ length: 64 }, (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32));
577
+ const S = [
578
+ 7,
579
+ 12,
580
+ 17,
581
+ 22,
582
+ 7,
583
+ 12,
584
+ 17,
585
+ 22,
586
+ 7,
587
+ 12,
588
+ 17,
589
+ 22,
590
+ 7,
591
+ 12,
592
+ 17,
593
+ 22,
594
+ 5,
595
+ 9,
596
+ 14,
597
+ 20,
598
+ 5,
599
+ 9,
600
+ 14,
601
+ 20,
602
+ 5,
603
+ 9,
604
+ 14,
605
+ 20,
606
+ 5,
607
+ 9,
608
+ 14,
609
+ 20,
610
+ 4,
611
+ 11,
612
+ 16,
613
+ 23,
614
+ 4,
615
+ 11,
616
+ 16,
617
+ 23,
618
+ 4,
619
+ 11,
620
+ 16,
621
+ 23,
622
+ 4,
623
+ 11,
624
+ 16,
625
+ 23,
626
+ 6,
627
+ 10,
628
+ 15,
629
+ 21,
630
+ 6,
631
+ 10,
632
+ 15,
633
+ 21,
634
+ 6,
635
+ 10,
636
+ 15,
637
+ 21,
638
+ 6,
639
+ 10,
640
+ 15,
641
+ 21
449
642
  ];
450
643
  function md5Bytes(bytes) {
451
- const len = bytes.length;
452
- bytes.push(128);
453
- while (bytes.length % 64 !== 56) bytes.push(0);
454
- const bitLen = len * 8;
455
- for (let i = 0; i < 8; i++) {
456
- bytes.push(i < 4 ? bitLen >>> i * 8 & 255 : 0);
457
- }
458
- let a = 1732584193;
459
- let b = 4023233417;
460
- let c = 2562383102;
461
- let d = 271733878;
462
- for (let chunk = 0; chunk < bytes.length; chunk += 64) {
463
- const M = [];
464
- for (let w = 0; w < 16; w++) {
465
- const off = chunk + w * 4;
466
- M.push(
467
- bytes[off] | bytes[off + 1] << 8 | bytes[off + 2] << 16 | bytes[off + 3] << 24
468
- );
469
- }
470
- let aa = a;
471
- let bb = b;
472
- let cc = c;
473
- let dd = d;
474
- for (let i = 0; i < 64; i++) {
475
- let f;
476
- let g;
477
- if (i < 16) {
478
- f = bb & cc | ~bb & dd;
479
- g = i;
480
- } else if (i < 32) {
481
- f = dd & bb | ~dd & cc;
482
- g = (5 * i + 1) % 16;
483
- } else if (i < 48) {
484
- f = bb ^ cc ^ dd;
485
- g = (3 * i + 5) % 16;
486
- } else {
487
- f = cc ^ (bb | ~dd);
488
- g = 7 * i % 16;
489
- }
490
- const tmp = dd;
491
- dd = cc;
492
- cc = bb;
493
- const sum = Math.trunc(aa + f + M[g] + T[i]);
494
- const rotated = sum << S[i] | sum >>> 32 - S[i];
495
- bb = Math.trunc(bb + rotated);
496
- aa = tmp;
497
- }
498
- a = Math.trunc(a + aa);
499
- b = Math.trunc(b + bb);
500
- c = Math.trunc(c + cc);
501
- d = Math.trunc(d + dd);
502
- }
503
- return [a, b, c, d].map(
504
- (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255].map((byte) => byte.toString(16).padStart(2, "0")).join("")
505
- ).join("");
644
+ const len = bytes.length;
645
+ bytes.push(128);
646
+ while (bytes.length % 64 !== 56) bytes.push(0);
647
+ const bitLen = len * 8;
648
+ for (let i = 0; i < 8; i++) bytes.push(i < 4 ? bitLen >>> i * 8 & 255 : 0);
649
+ let a = 1732584193;
650
+ let b = 4023233417;
651
+ let c = 2562383102;
652
+ let d = 271733878;
653
+ for (let chunk = 0; chunk < bytes.length; chunk += 64) {
654
+ const M = [];
655
+ for (let w = 0; w < 16; w++) {
656
+ const off = chunk + w * 4;
657
+ M.push(bytes[off] | bytes[off + 1] << 8 | bytes[off + 2] << 16 | bytes[off + 3] << 24);
658
+ }
659
+ let aa = a;
660
+ let bb = b;
661
+ let cc = c;
662
+ let dd = d;
663
+ for (let i = 0; i < 64; i++) {
664
+ let f;
665
+ let g;
666
+ if (i < 16) {
667
+ f = bb & cc | ~bb & dd;
668
+ g = i;
669
+ } else if (i < 32) {
670
+ f = dd & bb | ~dd & cc;
671
+ g = (5 * i + 1) % 16;
672
+ } else if (i < 48) {
673
+ f = bb ^ cc ^ dd;
674
+ g = (3 * i + 5) % 16;
675
+ } else {
676
+ f = cc ^ (bb | ~dd);
677
+ g = 7 * i % 16;
678
+ }
679
+ const tmp = dd;
680
+ dd = cc;
681
+ cc = bb;
682
+ const sum = Math.trunc(aa + f + M[g] + T[i]);
683
+ const rotated = sum << S[i] | sum >>> 32 - S[i];
684
+ bb = Math.trunc(bb + rotated);
685
+ aa = tmp;
686
+ }
687
+ a = Math.trunc(a + aa);
688
+ b = Math.trunc(b + bb);
689
+ c = Math.trunc(c + cc);
690
+ d = Math.trunc(d + dd);
691
+ }
692
+ return [
693
+ a,
694
+ b,
695
+ c,
696
+ d
697
+ ].map((n) => [
698
+ n & 255,
699
+ n >>> 8 & 255,
700
+ n >>> 16 & 255,
701
+ n >>> 24 & 255
702
+ ].map((byte) => byte.toString(16).padStart(2, "0")).join("")).join("");
506
703
  }
704
+ /**
705
+ * Produces a hex-encoded MD5 digest of `input`.
706
+ *
707
+ * This is a minimal, spec-compliant implementation (RFC 1321) that avoids
708
+ * any runtime platform dependency.
709
+ *
710
+ * @param input - UTF-8 string to hash
711
+ * @returns Lowercase hex-encoded MD5 digest
712
+ */
507
713
  function md5(input) {
508
- const bytes = [];
509
- for (let i = 0; i < input.length; i++) {
510
- const code = input.codePointAt(i) ?? 0;
511
- if (code < 128) {
512
- bytes.push(code);
513
- } else if (code < 2048) {
514
- bytes.push(192 | code >> 6, 128 | code & 63);
515
- } else {
516
- bytes.push(
517
- 224 | code >> 12,
518
- 128 | code >> 6 & 63,
519
- 128 | code & 63
520
- );
521
- }
522
- }
523
- return md5Bytes(bytes);
714
+ const bytes = [];
715
+ for (let i = 0; i < input.length; i++) {
716
+ const code = input.codePointAt(i) ?? 0;
717
+ if (code < 128) bytes.push(code);
718
+ else if (code < 2048) bytes.push(192 | code >> 6, 128 | code & 63);
719
+ else bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
720
+ }
721
+ return md5Bytes(bytes);
524
722
  }
525
- var CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT";
526
- var CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj";
527
- var HASH_SECRET = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c";
528
- var AUTH_URL = "https://oauth.secure.pixiv.net/auth/token";
723
+ const CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT";
724
+ const CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj";
725
+ const HASH_SECRET = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c";
726
+ const AUTH_URL = "https://oauth.secure.pixiv.net/auth/token";
727
+ /** Builds the x-client-hash header value for a given UTC timestamp string. */
529
728
  function buildClientHash(localTime) {
530
- return md5(localTime + HASH_SECRET);
729
+ return md5(localTime + HASH_SECRET);
531
730
  }
532
- var AuthManager = class _AuthManager {
533
- #accessToken;
534
- #refreshToken;
535
- userId;
536
- constructor(credentials) {
537
- this.#accessToken = credentials.accessToken;
538
- this.#refreshToken = credentials.refreshToken;
539
- this.userId = credentials.userId;
540
- }
541
- /** Returns the current access token. */
542
- get accessToken() {
543
- return this.#accessToken;
544
- }
545
- /** Returns the current refresh token. */
546
- get refreshToken() {
547
- return this.#refreshToken;
548
- }
549
- /**
550
- * Exchanges the stored refresh token for a fresh access token.
551
- *
552
- * Updates the internal credentials on success.
553
- * Throws if the token endpoint returns a non-200 response.
554
- */
555
- async refresh() {
556
- const localTime = (/* @__PURE__ */ new Date()).toISOString().replace(/Z$/, "+00:00");
557
- const headers = {
558
- "x-client-time": localTime,
559
- "x-client-hash": buildClientHash(localTime),
560
- "app-os": "ios",
561
- "app-os-version": "16.4.1",
562
- "user-agent": "PixivIOSApp/7.16.9 (iOS 16.4.1; iPad13,4)",
563
- "Content-Type": "application/x-www-form-urlencoded"
564
- };
565
- const body = new URLSearchParams({
566
- client_id: CLIENT_ID,
567
- client_secret: CLIENT_SECRET,
568
- get_secure_url: "1",
569
- grant_type: "refresh_token",
570
- refresh_token: this.#refreshToken
571
- }).toString();
572
- const response = await fetch(AUTH_URL, {
573
- method: "POST",
574
- headers,
575
- body
576
- });
577
- if (response.status !== 200) {
578
- throw new Error(
579
- `Failed to refresh pixiv token: HTTP ${response.status}`
580
- );
581
- }
582
- const data = await response.json();
583
- this.#accessToken = data.response.access_token;
584
- this.#refreshToken = data.response.refresh_token;
585
- this.userId = data.user.id;
586
- }
587
- /**
588
- * Creates an `AuthManager` by performing the initial token refresh.
589
- *
590
- * @param refreshToken - Pixiv refresh token
591
- * @returns Initialized `AuthManager`
592
- */
593
- static async login(refreshToken) {
594
- const manager = new _AuthManager({
595
- userId: "",
596
- accessToken: "",
597
- refreshToken
598
- });
599
- await manager.refresh();
600
- return manager;
601
- }
731
+ /**
732
+ * Manages access tokens for the pixiv API.
733
+ *
734
+ * Holds the current access token and refresh token in memory.
735
+ * The refresh() method exchanges the refresh token for a new access token
736
+ * via the pixiv OAuth endpoint.
737
+ */
738
+ var AuthManager = class AuthManager {
739
+ #accessToken;
740
+ #refreshToken;
741
+ userId;
742
+ constructor(credentials) {
743
+ this.#accessToken = credentials.accessToken;
744
+ this.#refreshToken = credentials.refreshToken;
745
+ this.userId = credentials.userId;
746
+ }
747
+ /** Returns the current access token. */
748
+ get accessToken() {
749
+ return this.#accessToken;
750
+ }
751
+ /** Returns the current refresh token. */
752
+ get refreshToken() {
753
+ return this.#refreshToken;
754
+ }
755
+ /**
756
+ * Exchanges the stored refresh token for a fresh access token.
757
+ *
758
+ * Updates the internal credentials on success.
759
+ * Throws if the token endpoint returns a non-200 response.
760
+ */
761
+ async refresh() {
762
+ const localTime = (/* @__PURE__ */ new Date()).toISOString().replace(/Z$/, "+00:00");
763
+ const headers = {
764
+ "x-client-time": localTime,
765
+ "x-client-hash": buildClientHash(localTime),
766
+ "app-os": "ios",
767
+ "app-os-version": "16.4.1",
768
+ "user-agent": "PixivIOSApp/7.16.9 (iOS 16.4.1; iPad13,4)",
769
+ "Content-Type": "application/x-www-form-urlencoded"
770
+ };
771
+ const body = new URLSearchParams({
772
+ client_id: CLIENT_ID,
773
+ client_secret: CLIENT_SECRET,
774
+ get_secure_url: "1",
775
+ grant_type: "refresh_token",
776
+ refresh_token: this.#refreshToken
777
+ }).toString();
778
+ const response = await fetch(AUTH_URL, {
779
+ method: "POST",
780
+ headers,
781
+ body
782
+ });
783
+ if (response.status !== 200) throw new Error(`Failed to refresh pixiv token: HTTP ${response.status}`);
784
+ const data = await response.json();
785
+ this.#accessToken = data.response.access_token;
786
+ this.#refreshToken = data.response.refresh_token;
787
+ this.userId = data.user.id;
788
+ }
789
+ /**
790
+ * Creates an `AuthManager` by performing the initial token refresh.
791
+ *
792
+ * @param refreshToken - Pixiv refresh token
793
+ * @returns Initialized `AuthManager`
794
+ */
795
+ static async login(refreshToken) {
796
+ const manager = new AuthManager({
797
+ userId: "",
798
+ accessToken: "",
799
+ refreshToken
800
+ });
801
+ await manager.refresh();
802
+ return manager;
803
+ }
602
804
  };
603
-
604
- // src/http.ts
605
- var DEFAULT_RETRY = { maxRetries: 3, waitMs: 1e4 };
606
- var BASE_URL = "https://app-api.pixiv.net";
607
- var DEFAULT_HEADERS = {
608
- Host: "app-api.pixiv.net",
609
- "App-OS": "ios",
610
- "App-OS-Version": "14.6",
611
- "User-Agent": "PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)",
612
- "Accept-Language": "ja"
805
+ //#endregion
806
+ //#region src/http.ts
807
+ const DEFAULT_RETRY = {
808
+ maxRetries: 3,
809
+ waitMs: 1e4
613
810
  };
811
+ const BASE_URL = "https://app-api.pixiv.net";
812
+ const DEFAULT_HEADERS = {
813
+ Host: "app-api.pixiv.net",
814
+ "App-OS": "ios",
815
+ "App-OS-Version": "14.6",
816
+ "User-Agent": "PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)",
817
+ "Accept-Language": "ja"
818
+ };
819
+ /**
820
+ * Parses the `Retry-After` header value into milliseconds.
821
+ *
822
+ * Supports delay-seconds format and HTTP-date format.
823
+ * Falls back to `defaultMs` if the header is absent or unparseable.
824
+ *
825
+ * @param retryAfter - Header value (null if not present)
826
+ * @param defaultMs - Fallback wait time in milliseconds
827
+ * @returns Wait time in milliseconds (clamped to ≥ 0)
828
+ */
614
829
  function parseRetryAfter(retryAfter, defaultMs) {
615
- if (!retryAfter) return defaultMs;
616
- if (/^\d+$/.test(retryAfter.trim())) {
617
- return Number.parseInt(retryAfter, 10) * 1e3;
618
- }
619
- const retryDate = Date.parse(retryAfter);
620
- if (!Number.isNaN(retryDate)) {
621
- return Math.max(0, retryDate - Date.now());
622
- }
623
- return defaultMs;
830
+ if (!retryAfter) return defaultMs;
831
+ if (/^\d+$/.test(retryAfter.trim())) return Number.parseInt(retryAfter, 10) * 1e3;
832
+ const retryDate = Date.parse(retryAfter);
833
+ if (!Number.isNaN(retryDate)) return Math.max(0, retryDate - Date.now());
834
+ return defaultMs;
624
835
  }
625
836
  function headersToRecord(headers) {
626
- const result = {};
627
- for (const [key, value] of headers) {
628
- result[key] = value;
629
- }
630
- return result;
837
+ const result = {};
838
+ for (const [key, value] of headers) result[key] = value;
839
+ return result;
631
840
  }
841
+ /**
842
+ * HTTP client for the pixiv API.
843
+ *
844
+ * All methods return `ResultAsync<T, PixivError>` — no throws.
845
+ * A 429 → retry loop and a 401 → refresh → retry are handled internally.
846
+ */
632
847
  var HttpClient = class {
633
- #auth;
634
- #retry;
635
- #interceptor;
636
- constructor(auth, options) {
637
- this.#auth = auth;
638
- this.#retry = {
639
- maxRetries: options?.retry?.maxRetries ?? DEFAULT_RETRY.maxRetries,
640
- waitMs: options?.retry?.waitMs ?? DEFAULT_RETRY.waitMs
641
- };
642
- this.#interceptor = options?.onResponse;
643
- }
644
- /**
645
- * Sends a GET request to the pixiv API.
646
- *
647
- * @param path - API endpoint path (e.g. "/v1/illust/detail")
648
- * @param params - Query parameters as a URLSearchParams instance
649
- * @returns `ResultAsync<T, PixivError>`
650
- */
651
- get(path, params) {
652
- const qs = params ? `?${params.toString()}` : "";
653
- const url = `${BASE_URL}${path}${qs}`;
654
- return this.#send(url, "GET", path, void 0);
655
- }
656
- /**
657
- * Sends a POST request to the pixiv API.
658
- *
659
- * @param path - API endpoint path (e.g. "/v2/illust/bookmark/add")
660
- * @param body - URL-encoded request body string
661
- * @returns `ResultAsync<T, PixivError>`
662
- */
663
- post(path, body) {
664
- const url = `${BASE_URL}${path}`;
665
- return this.#send(url, "POST", path, body);
666
- }
667
- /**
668
- * Fetches a pixiv image URL without an Authorization header.
669
- *
670
- * Uses a browser User-Agent and the pixiv Referer, which are required for
671
- * image CDN access. Retry and interceptor are not applied here.
672
- *
673
- * @param imageUrl - Full image URL
674
- * @returns `ResultAsync<Response, PixivError>`
675
- */
676
- fetchImage(imageUrl) {
677
- return ResultAsync.fromPromise(
678
- fetch(imageUrl, {
679
- headers: {
680
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
681
- Referer: "https://www.pixiv.net/"
682
- }
683
- }),
684
- networkError
685
- ).andThen((response) => {
686
- if (!response.ok) {
687
- return ResultAsync.fromResult(
688
- err(apiError(response.status, null))
689
- );
690
- }
691
- return ResultAsync.fromResult(ok(response));
692
- });
693
- }
694
- /**
695
- * Sends a request to an absolute URL returned in a `next_url` field.
696
- *
697
- * Applies the same retry / interceptor / auth logic as `get()`.
698
- *
699
- * @param absoluteUrl - Full URL including query string
700
- * @returns `ResultAsync<T, PixivError>`
701
- */
702
- getAbsolute(absoluteUrl) {
703
- let endpoint;
704
- try {
705
- endpoint = new URL(absoluteUrl).pathname;
706
- } catch {
707
- endpoint = absoluteUrl;
708
- }
709
- return this.#send(absoluteUrl, "GET", endpoint, void 0);
710
- }
711
- // ---------------------------------------------------------------------------
712
- // Private helpers
713
- // ---------------------------------------------------------------------------
714
- #send(url, method, endpoint, body) {
715
- return new ResultAsync(this.#sendWithRetry(url, method, endpoint, body));
716
- }
717
- async #sendWithRetry(url, method, endpoint, body, allowRefresh = true) {
718
- const maxRetries = Math.max(0, this.#retry.maxRetries);
719
- const waitMs = Math.max(0, this.#retry.waitMs);
720
- let lastRetryAfterMs = 0;
721
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
722
- const requestHeaders = {
723
- ...DEFAULT_HEADERS,
724
- Authorization: `Bearer ${this.#auth.accessToken}`,
725
- ...method === "POST" ? { "Content-Type": "application/x-www-form-urlencoded" } : {}
726
- };
727
- let response;
728
- try {
729
- response = await fetch(url, {
730
- method,
731
- headers: requestHeaders,
732
- body: method === "POST" ? body : void 0
733
- });
734
- } catch (fetchError) {
735
- return err(networkError(fetchError));
736
- }
737
- if (response.status === 429) {
738
- await response.body?.cancel();
739
- const retryAfterMs = parseRetryAfter(
740
- response.headers.get("Retry-After"),
741
- waitMs
742
- );
743
- lastRetryAfterMs = retryAfterMs;
744
- if (attempt < maxRetries) {
745
- await new Promise((resolve) => setTimeout(resolve, retryAfterMs));
746
- continue;
747
- }
748
- return err(rateLimitError(lastRetryAfterMs));
749
- }
750
- if (response.status === 401) {
751
- await response.body?.cancel();
752
- if (allowRefresh) {
753
- try {
754
- await this.#auth.refresh();
755
- } catch {
756
- return err(authFailedError(401));
757
- }
758
- return this.#sendWithRetry(url, method, endpoint, body, false);
759
- }
760
- return err(authFailedError(401));
761
- }
762
- const contentType = response.headers.get("content-type") ?? "";
763
- const text = await response.text();
764
- let data;
765
- const isJson = contentType.includes("application/json");
766
- if (isJson) {
767
- try {
768
- data = camelizeKeys(JSON.parse(text));
769
- } catch {
770
- data = text;
771
- }
772
- } else {
773
- data = text;
774
- }
775
- const responseHeaders = headersToRecord(response.headers);
776
- if (!response.ok) {
777
- return err(apiError(response.status, data));
778
- }
779
- const httpResponse = {
780
- data,
781
- status: response.status,
782
- responseUrl: response.url || void 0};
783
- if (this.#interceptor) {
784
- const record = {
785
- method,
786
- endpoint,
787
- url: response.url || url,
788
- requestHeaders: JSON.stringify(requestHeaders),
789
- requestBody: body ?? null,
790
- responseType: isJson ? "JSON" : "TEXT",
791
- statusCode: response.status,
792
- responseHeaders: JSON.stringify(responseHeaders),
793
- responseBody: text
794
- };
795
- Promise.resolve(this.#interceptor(record)).catch(() => void 0);
796
- }
797
- return ok(httpResponse.data);
798
- }
799
- return err(rateLimitError(lastRetryAfterMs));
800
- }
848
+ #auth;
849
+ #retry;
850
+ #interceptor;
851
+ constructor(auth, options) {
852
+ this.#auth = auth;
853
+ this.#retry = {
854
+ maxRetries: options?.retry?.maxRetries ?? DEFAULT_RETRY.maxRetries,
855
+ waitMs: options?.retry?.waitMs ?? DEFAULT_RETRY.waitMs
856
+ };
857
+ this.#interceptor = options?.onResponse;
858
+ }
859
+ /**
860
+ * Sends a GET request to the pixiv API.
861
+ *
862
+ * @param path - API endpoint path (e.g. "/v1/illust/detail")
863
+ * @param params - Query parameters as a URLSearchParams instance
864
+ * @returns `ResultAsync<T, PixivError>`
865
+ */
866
+ get(path, params) {
867
+ const url = `${BASE_URL}${path}${params ? `?${params.toString()}` : ""}`;
868
+ return this.#send(url, "GET", path, void 0);
869
+ }
870
+ /**
871
+ * Sends a POST request to the pixiv API.
872
+ *
873
+ * @param path - API endpoint path (e.g. "/v2/illust/bookmark/add")
874
+ * @param body - URL-encoded request body string
875
+ * @returns `ResultAsync<T, PixivError>`
876
+ */
877
+ post(path, body) {
878
+ const url = `${BASE_URL}${path}`;
879
+ return this.#send(url, "POST", path, body);
880
+ }
881
+ /**
882
+ * Fetches a pixiv image URL without an Authorization header.
883
+ *
884
+ * Uses a browser User-Agent and the pixiv Referer, which are required for
885
+ * image CDN access. Retry and interceptor are not applied here.
886
+ *
887
+ * @param imageUrl - Full image URL
888
+ * @returns `ResultAsync<Response, PixivError>`
889
+ */
890
+ fetchImage(imageUrl) {
891
+ return ResultAsync.fromPromise(fetch(imageUrl, { headers: {
892
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36",
893
+ Referer: "https://www.pixiv.net/"
894
+ } }), networkError).andThen((response) => {
895
+ if (!response.ok) return ResultAsync.fromResult(err(apiError(response.status, null)));
896
+ return ResultAsync.fromResult(ok(response));
897
+ });
898
+ }
899
+ /**
900
+ * Sends a request to an absolute URL returned in a `next_url` field.
901
+ *
902
+ * Applies the same retry / interceptor / auth logic as `get()`.
903
+ *
904
+ * @param absoluteUrl - Full URL including query string
905
+ * @returns `ResultAsync<T, PixivError>`
906
+ */
907
+ getAbsolute(absoluteUrl) {
908
+ let endpoint;
909
+ try {
910
+ endpoint = new URL(absoluteUrl).pathname;
911
+ } catch {
912
+ endpoint = absoluteUrl;
913
+ }
914
+ return this.#send(absoluteUrl, "GET", endpoint, void 0);
915
+ }
916
+ #send(url, method, endpoint, body) {
917
+ return new ResultAsync(this.#sendWithRetry(url, method, endpoint, body));
918
+ }
919
+ async #sendWithRetry(url, method, endpoint, body, allowRefresh = true) {
920
+ const maxRetries = Math.max(0, this.#retry.maxRetries);
921
+ const waitMs = Math.max(0, this.#retry.waitMs);
922
+ let lastRetryAfterMs = 0;
923
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
924
+ const requestHeaders = {
925
+ ...DEFAULT_HEADERS,
926
+ Authorization: `Bearer ${this.#auth.accessToken}`,
927
+ ...method === "POST" ? { "Content-Type": "application/x-www-form-urlencoded" } : {}
928
+ };
929
+ let response;
930
+ try {
931
+ response = await fetch(url, {
932
+ method,
933
+ headers: requestHeaders,
934
+ body: method === "POST" ? body : void 0
935
+ });
936
+ } catch (fetchError) {
937
+ return err(networkError(fetchError));
938
+ }
939
+ if (response.status === 429) {
940
+ await response.body?.cancel();
941
+ const retryAfterMs = parseRetryAfter(response.headers.get("Retry-After"), waitMs);
942
+ lastRetryAfterMs = retryAfterMs;
943
+ if (attempt < maxRetries) {
944
+ await new Promise((resolve) => setTimeout(resolve, retryAfterMs));
945
+ continue;
946
+ }
947
+ return err(rateLimitError(lastRetryAfterMs));
948
+ }
949
+ if (response.status === 401) {
950
+ await response.body?.cancel();
951
+ if (allowRefresh) {
952
+ try {
953
+ await this.#auth.refresh();
954
+ } catch {
955
+ return err(authFailedError(401));
956
+ }
957
+ return this.#sendWithRetry(url, method, endpoint, body, false);
958
+ }
959
+ return err(authFailedError(401));
960
+ }
961
+ const contentType = response.headers.get("content-type") ?? "";
962
+ const text = await response.text();
963
+ let data;
964
+ const isJson = contentType.includes("application/json");
965
+ if (isJson) try {
966
+ data = camelizeKeys(JSON.parse(text));
967
+ } catch {
968
+ data = text;
969
+ }
970
+ else data = text;
971
+ const responseHeaders = headersToRecord(response.headers);
972
+ if (!response.ok) return err(apiError(response.status, data));
973
+ const httpResponse = {
974
+ data,
975
+ status: response.status,
976
+ headers: responseHeaders,
977
+ requestHeaders,
978
+ requestBody: body ?? null,
979
+ responseUrl: response.url || void 0,
980
+ endpoint
981
+ };
982
+ if (this.#interceptor) {
983
+ const record = {
984
+ method,
985
+ endpoint,
986
+ url: response.url || url,
987
+ requestHeaders: JSON.stringify(requestHeaders),
988
+ requestBody: body ?? null,
989
+ responseType: isJson ? "JSON" : "TEXT",
990
+ statusCode: response.status,
991
+ responseHeaders: JSON.stringify(responseHeaders),
992
+ responseBody: text
993
+ };
994
+ Promise.resolve(this.#interceptor(record)).catch(() => void 0);
995
+ }
996
+ return ok(httpResponse.data);
997
+ }
998
+ return err(rateLimitError(lastRetryAfterMs));
999
+ }
801
1000
  };
802
-
803
- // src/resources/illusts.ts
1001
+ //#endregion
1002
+ //#region src/resources/illusts.ts
1003
+ /** Methods for the illust API namespace. */
804
1004
  var IllustResource = class {
805
- #http;
806
- constructor(http) {
807
- this.#http = http;
808
- }
809
- /**
810
- * Fetches a single illust by ID.
811
- * GET /v1/illust/detail
812
- *
813
- * @param params - Request parameters
814
- *
815
- * @example
816
- * ```ts
817
- * const result = await client.illusts.detail({ illustId: 12345 })
818
- * if (result.isOk) {
819
- * console.log(result.value.illust.title)
820
- * } else {
821
- * console.error(result.error)
822
- * }
823
- * ```
824
- */
825
- detail(params) {
826
- return this.#http.get(
827
- "/v1/illust/detail",
828
- buildParams({ illustId: params.illustId, filter: params.filter ?? "for_ios" })
829
- );
830
- }
831
- /**
832
- * Fetches related illusts for a given illust.
833
- * GET /v2/illust/related
834
- *
835
- * @param params - Request parameters
836
- */
837
- related(params) {
838
- return PaginatedResultAsync.fromResultAsync(
839
- this.#http.get(
840
- "/v2/illust/related",
841
- buildParams({
842
- illustId: params.illustId,
843
- filter: params.filter ?? "for_ios",
844
- ...params.seedIllustIds ? { seedIllustIds: params.seedIllustIds } : {}
845
- })
846
- ),
847
- this.#http,
848
- (page) => page.illusts
849
- );
850
- }
851
- /**
852
- * Searches for illusts.
853
- * GET /v1/search/illust
854
- *
855
- * @param params - Request parameters
856
- *
857
- * @example
858
- * ```ts
859
- * // Iterate all results across pages
860
- * for await (const illust of client.illusts.search({ word: 'cat' }).items()) {
861
- * console.log(illust.title)
862
- * }
863
- *
864
- * // Fetch only the first page
865
- * const page = await client.illusts.search({ word: 'cat' })
866
- * if (page.isOk) {
867
- * console.log(page.value.illusts.length)
868
- * }
869
- * ```
870
- */
871
- search(params) {
872
- return PaginatedResultAsync.fromResultAsync(
873
- this.#http.get(
874
- "/v1/search/illust",
875
- buildParams({
876
- word: params.word,
877
- searchTarget: params.searchTarget ?? "partial_match_for_tags",
878
- sort: params.sort ?? "date_desc",
879
- filter: params.filter ?? "for_ios",
880
- duration: params.duration,
881
- startDate: params.startDate,
882
- endDate: params.endDate,
883
- searchAiType: params.searchAiType,
884
- offset: params.offset
885
- })
886
- ),
887
- this.#http,
888
- (page) => page.illusts
889
- );
890
- }
891
- /**
892
- * Fetches the illust ranking.
893
- * GET /v1/illust/ranking
894
- *
895
- * @param params - Request parameters
896
- */
897
- ranking(params = {}) {
898
- return PaginatedResultAsync.fromResultAsync(
899
- this.#http.get(
900
- "/v1/illust/ranking",
901
- buildParams({
902
- mode: params.mode ?? "day",
903
- filter: params.filter ?? "for_ios",
904
- date: params.date,
905
- offset: params.offset
906
- })
907
- ),
908
- this.#http,
909
- (page) => page.illusts
910
- );
911
- }
912
- /**
913
- * Fetches recommended illusts.
914
- * GET /v1/illust/recommended
915
- *
916
- * @param params - Request parameters
917
- */
918
- recommended(params = {}) {
919
- return PaginatedResultAsync.fromResultAsync(
920
- this.#http.get(
921
- "/v1/illust/recommended",
922
- buildParams({
923
- filter: params.filter ?? "for_ios",
924
- includeRankingLabel: true,
925
- includeRankingIllusts: true,
926
- includePrivacyPolicy: true,
927
- offset: params.offset,
928
- maxBookmarkIdForRecommend: params.maxBookmarkIdForRecommend,
929
- minBookmarkIdForRecentIllust: params.minBookmarkIdForRecentIllust
930
- })
931
- ),
932
- this.#http,
933
- (page) => page.illusts
934
- );
935
- }
936
- /**
937
- * Fetches an illust series.
938
- * GET /v1/illust/series
939
- *
940
- * @param params - Request parameters
941
- */
942
- series(params) {
943
- return PaginatedResultAsync.fromResultAsync(
944
- this.#http.get(
945
- "/v1/illust/series",
946
- buildParams({
947
- illustSeriesId: params.illustSeriesId,
948
- filter: params.filter ?? "for_ios"
949
- })
950
- ),
951
- this.#http,
952
- (page) => page.illusts
953
- );
954
- }
955
- /**
956
- * Adds an illust bookmark.
957
- * POST /v2/illust/bookmark/add
958
- *
959
- * @param params - Request parameters
960
- */
961
- bookmarkAdd(params) {
962
- const body = buildParams({
963
- illustId: params.illustId,
964
- restrict: params.restrict ?? "public",
965
- ...params.tags ? { tags: params.tags } : {}
966
- });
967
- return this.#http.post(
968
- "/v2/illust/bookmark/add",
969
- body.toString()
970
- );
971
- }
972
- /**
973
- * Removes an illust bookmark.
974
- * POST /v1/illust/bookmark/delete
975
- *
976
- * @param params - Request parameters
977
- */
978
- bookmarkDelete(params) {
979
- const body = buildParams({ illustId: String(params.illustId) });
980
- return this.#http.post(
981
- "/v1/illust/bookmark/delete",
982
- body.toString()
983
- );
984
- }
1005
+ #http;
1006
+ constructor(http) {
1007
+ this.#http = http;
1008
+ }
1009
+ /**
1010
+ * Fetches a single illust by ID.
1011
+ * GET /v1/illust/detail
1012
+ *
1013
+ * @param params - Request parameters
1014
+ *
1015
+ * @example
1016
+ * ```ts
1017
+ * const result = await client.illusts.detail({ illustId: 12345 })
1018
+ * if (result.isOk) {
1019
+ * console.log(result.value.illust.title)
1020
+ * } else {
1021
+ * console.error(result.error)
1022
+ * }
1023
+ * ```
1024
+ */
1025
+ detail(params) {
1026
+ return this.#http.get("/v1/illust/detail", buildParams({
1027
+ illustId: params.illustId,
1028
+ filter: params.filter ?? "for_ios"
1029
+ }));
1030
+ }
1031
+ /**
1032
+ * Fetches related illusts for a given illust.
1033
+ * GET /v2/illust/related
1034
+ *
1035
+ * @param params - Request parameters
1036
+ */
1037
+ related(params) {
1038
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v2/illust/related", buildParams({
1039
+ illustId: params.illustId,
1040
+ filter: params.filter ?? "for_ios",
1041
+ ...params.seedIllustIds ? { seedIllustIds: params.seedIllustIds } : {}
1042
+ })), this.#http, (page) => page.illusts);
1043
+ }
1044
+ /**
1045
+ * Searches for illusts.
1046
+ * GET /v1/search/illust
1047
+ *
1048
+ * @param params - Request parameters
1049
+ *
1050
+ * @example
1051
+ * ```ts
1052
+ * // Iterate all results across pages
1053
+ * for await (const illust of client.illusts.search({ word: 'cat' }).items()) {
1054
+ * console.log(illust.title)
1055
+ * }
1056
+ *
1057
+ * // Fetch only the first page
1058
+ * const page = await client.illusts.search({ word: 'cat' })
1059
+ * if (page.isOk) {
1060
+ * console.log(page.value.illusts.length)
1061
+ * }
1062
+ * ```
1063
+ */
1064
+ search(params) {
1065
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/search/illust", buildParams({
1066
+ word: params.word,
1067
+ searchTarget: params.searchTarget ?? "partial_match_for_tags",
1068
+ sort: params.sort ?? "date_desc",
1069
+ filter: params.filter ?? "for_ios",
1070
+ duration: params.duration,
1071
+ startDate: params.startDate,
1072
+ endDate: params.endDate,
1073
+ searchAiType: params.searchAiType,
1074
+ offset: params.offset
1075
+ })), this.#http, (page) => page.illusts);
1076
+ }
1077
+ /**
1078
+ * Fetches the illust ranking.
1079
+ * GET /v1/illust/ranking
1080
+ *
1081
+ * @param params - Request parameters
1082
+ */
1083
+ ranking(params = {}) {
1084
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/illust/ranking", buildParams({
1085
+ mode: params.mode ?? "day",
1086
+ filter: params.filter ?? "for_ios",
1087
+ date: params.date,
1088
+ offset: params.offset
1089
+ })), this.#http, (page) => page.illusts);
1090
+ }
1091
+ /**
1092
+ * Fetches recommended illusts.
1093
+ * GET /v1/illust/recommended
1094
+ *
1095
+ * @param params - Request parameters
1096
+ */
1097
+ recommended(params = {}) {
1098
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/illust/recommended", buildParams({
1099
+ filter: params.filter ?? "for_ios",
1100
+ includeRankingLabel: true,
1101
+ includeRankingIllusts: true,
1102
+ includePrivacyPolicy: true,
1103
+ offset: params.offset,
1104
+ maxBookmarkIdForRecommend: params.maxBookmarkIdForRecommend,
1105
+ minBookmarkIdForRecentIllust: params.minBookmarkIdForRecentIllust
1106
+ })), this.#http, (page) => page.illusts);
1107
+ }
1108
+ /**
1109
+ * Fetches an illust series.
1110
+ * GET /v1/illust/series
1111
+ *
1112
+ * @param params - Request parameters
1113
+ */
1114
+ series(params) {
1115
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/illust/series", buildParams({
1116
+ illustSeriesId: params.illustSeriesId,
1117
+ filter: params.filter ?? "for_ios"
1118
+ })), this.#http, (page) => page.illusts);
1119
+ }
1120
+ /**
1121
+ * Adds an illust bookmark.
1122
+ * POST /v2/illust/bookmark/add
1123
+ *
1124
+ * @param params - Request parameters
1125
+ */
1126
+ bookmarkAdd(params) {
1127
+ const body = buildParams({
1128
+ illustId: params.illustId,
1129
+ restrict: params.restrict ?? "public",
1130
+ ...params.tags ? { tags: params.tags } : {}
1131
+ });
1132
+ return this.#http.post("/v2/illust/bookmark/add", body.toString());
1133
+ }
1134
+ /**
1135
+ * Removes an illust bookmark.
1136
+ * POST /v1/illust/bookmark/delete
1137
+ *
1138
+ * @param params - Request parameters
1139
+ */
1140
+ bookmarkDelete(params) {
1141
+ const body = buildParams({ illustId: String(params.illustId) });
1142
+ return this.#http.post("/v1/illust/bookmark/delete", body.toString());
1143
+ }
985
1144
  };
986
-
987
- // src/resources/novels.ts
1145
+ //#endregion
1146
+ //#region src/resources/novels.ts
1147
+ /** Methods for the novel API namespace. */
988
1148
  var NovelResource = class {
989
- #http;
990
- constructor(http) {
991
- this.#http = http;
992
- }
993
- /**
994
- * Fetches a single novel by ID.
995
- * GET /v2/novel/detail
996
- *
997
- * @param params - Request parameters
998
- *
999
- * @example
1000
- * ```ts
1001
- * const result = await client.novels.detail({ novelId: 67890 })
1002
- * if (result.isOk) {
1003
- * console.log(result.value.novel.title)
1004
- * } else {
1005
- * console.error(result.error)
1006
- * }
1007
- * ```
1008
- */
1009
- detail(params) {
1010
- return this.#http.get(
1011
- "/v2/novel/detail",
1012
- buildParams({ novelId: params.novelId })
1013
- );
1014
- }
1015
- /**
1016
- * Fetches the WebView HTML for a novel.
1017
- * GET /webview/v2/novel
1018
- *
1019
- * Returns the raw HTML page that the pixiv app renders in a WebView.
1020
- * To extract the plain text, parse the returned HTML (e.g. strip tags).
1021
- *
1022
- * @param params - Request parameters
1023
- */
1024
- text(params) {
1025
- return this.#http.get(
1026
- "/webview/v2/novel",
1027
- // The webview endpoint uses the query parameter 'id', not 'novel_id'
1028
- buildParams({ id: params.novelId })
1029
- );
1030
- }
1031
- /**
1032
- * Fetches related novels for a given novel.
1033
- * GET /v1/novel/related
1034
- *
1035
- * @param params - Request parameters
1036
- */
1037
- related(params) {
1038
- return PaginatedResultAsync.fromResultAsync(
1039
- this.#http.get(
1040
- "/v1/novel/related",
1041
- buildParams({ novelId: params.novelId })
1042
- ),
1043
- this.#http,
1044
- (page) => page.novels
1045
- );
1046
- }
1047
- /**
1048
- * Searches for novels.
1049
- * GET /v1/search/novel
1050
- *
1051
- * @param params - Request parameters
1052
- *
1053
- * @example
1054
- * ```ts
1055
- * // Iterate all results across pages
1056
- * for await (const novel of client.novels.search({ word: 'fantasy' }).items()) {
1057
- * console.log(novel.title)
1058
- * }
1059
- *
1060
- * // Fetch only the first page
1061
- * const page = await client.novels.search({ word: 'fantasy' })
1062
- * if (page.isOk) {
1063
- * console.log(page.value.novels.length)
1064
- * }
1065
- * ```
1066
- */
1067
- search(params) {
1068
- return PaginatedResultAsync.fromResultAsync(
1069
- this.#http.get(
1070
- "/v1/search/novel",
1071
- buildParams({
1072
- word: params.word,
1073
- searchTarget: params.searchTarget ?? "partial_match_for_tags",
1074
- sort: params.sort ?? "date_desc",
1075
- filter: params.filter ?? "for_ios",
1076
- duration: params.duration,
1077
- startDate: params.startDate,
1078
- endDate: params.endDate,
1079
- searchAiType: params.searchAiType,
1080
- offset: params.offset
1081
- })
1082
- ),
1083
- this.#http,
1084
- (page) => page.novels
1085
- );
1086
- }
1087
- /**
1088
- * Fetches the novel ranking.
1089
- * GET /v1/novel/ranking
1090
- *
1091
- * @param params - Request parameters
1092
- */
1093
- ranking(params = {}) {
1094
- return PaginatedResultAsync.fromResultAsync(
1095
- this.#http.get(
1096
- "/v1/novel/ranking",
1097
- buildParams({
1098
- mode: params.mode ?? "day",
1099
- filter: params.filter ?? "for_ios",
1100
- date: params.date,
1101
- offset: params.offset
1102
- })
1103
- ),
1104
- this.#http,
1105
- (page) => page.novels
1106
- );
1107
- }
1108
- /**
1109
- * Fetches recommended novels.
1110
- * GET /v1/novel/recommended
1111
- *
1112
- * @param params - Request parameters
1113
- */
1114
- recommended(params = {}) {
1115
- return PaginatedResultAsync.fromResultAsync(
1116
- this.#http.get(
1117
- "/v1/novel/recommended",
1118
- buildParams({
1119
- filter: params.filter ?? "for_ios",
1120
- includeRankingNovels: true,
1121
- includePrivacyPolicy: true,
1122
- offset: params.offset,
1123
- maxBookmarkIdForRecommend: params.maxBookmarkIdForRecommend
1124
- })
1125
- ),
1126
- this.#http,
1127
- (page) => page.novels
1128
- );
1129
- }
1130
- /**
1131
- * Fetches a novel series.
1132
- * GET /v2/novel/series
1133
- *
1134
- * @param params - Request parameters
1135
- */
1136
- series(params) {
1137
- return PaginatedResultAsync.fromResultAsync(
1138
- this.#http.get(
1139
- "/v2/novel/series",
1140
- buildParams({ seriesId: params.seriesId, lastOrder: params.lastOrder })
1141
- ),
1142
- this.#http,
1143
- (page) => page.novels
1144
- );
1145
- }
1146
- /**
1147
- * Adds a novel bookmark.
1148
- * POST /v2/novel/bookmark/add
1149
- *
1150
- * @param params - Request parameters
1151
- */
1152
- bookmarkAdd(params) {
1153
- const body = buildParams({
1154
- novelId: params.novelId,
1155
- restrict: params.restrict ?? "public",
1156
- ...params.tags ? { tags: params.tags } : {}
1157
- });
1158
- return this.#http.post(
1159
- "/v2/novel/bookmark/add",
1160
- body.toString()
1161
- );
1162
- }
1163
- /**
1164
- * Removes a novel bookmark.
1165
- * POST /v1/novel/bookmark/delete
1166
- *
1167
- * @param params - Request parameters
1168
- */
1169
- bookmarkDelete(params) {
1170
- const body = buildParams({ novelId: String(params.novelId) });
1171
- return this.#http.post(
1172
- "/v1/novel/bookmark/delete",
1173
- body.toString()
1174
- );
1175
- }
1149
+ #http;
1150
+ constructor(http) {
1151
+ this.#http = http;
1152
+ }
1153
+ /**
1154
+ * Fetches a single novel by ID.
1155
+ * GET /v2/novel/detail
1156
+ *
1157
+ * @param params - Request parameters
1158
+ *
1159
+ * @example
1160
+ * ```ts
1161
+ * const result = await client.novels.detail({ novelId: 67890 })
1162
+ * if (result.isOk) {
1163
+ * console.log(result.value.novel.title)
1164
+ * } else {
1165
+ * console.error(result.error)
1166
+ * }
1167
+ * ```
1168
+ */
1169
+ detail(params) {
1170
+ return this.#http.get("/v2/novel/detail", buildParams({ novelId: params.novelId }));
1171
+ }
1172
+ /**
1173
+ * Fetches the WebView HTML for a novel.
1174
+ * GET /webview/v2/novel
1175
+ *
1176
+ * Returns the raw HTML page that the pixiv app renders in a WebView.
1177
+ * To extract the plain text, parse the returned HTML (e.g. strip tags).
1178
+ *
1179
+ * @param params - Request parameters
1180
+ */
1181
+ text(params) {
1182
+ return this.#http.get("/webview/v2/novel", buildParams({ id: params.novelId }));
1183
+ }
1184
+ /**
1185
+ * Fetches related novels for a given novel.
1186
+ * GET /v1/novel/related
1187
+ *
1188
+ * @param params - Request parameters
1189
+ */
1190
+ related(params) {
1191
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/novel/related", buildParams({ novelId: params.novelId })), this.#http, (page) => page.novels);
1192
+ }
1193
+ /**
1194
+ * Searches for novels.
1195
+ * GET /v1/search/novel
1196
+ *
1197
+ * @param params - Request parameters
1198
+ *
1199
+ * @example
1200
+ * ```ts
1201
+ * // Iterate all results across pages
1202
+ * for await (const novel of client.novels.search({ word: 'fantasy' }).items()) {
1203
+ * console.log(novel.title)
1204
+ * }
1205
+ *
1206
+ * // Fetch only the first page
1207
+ * const page = await client.novels.search({ word: 'fantasy' })
1208
+ * if (page.isOk) {
1209
+ * console.log(page.value.novels.length)
1210
+ * }
1211
+ * ```
1212
+ */
1213
+ search(params) {
1214
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/search/novel", buildParams({
1215
+ word: params.word,
1216
+ searchTarget: params.searchTarget ?? "partial_match_for_tags",
1217
+ sort: params.sort ?? "date_desc",
1218
+ filter: params.filter ?? "for_ios",
1219
+ duration: params.duration,
1220
+ startDate: params.startDate,
1221
+ endDate: params.endDate,
1222
+ searchAiType: params.searchAiType,
1223
+ offset: params.offset
1224
+ })), this.#http, (page) => page.novels);
1225
+ }
1226
+ /**
1227
+ * Fetches the novel ranking.
1228
+ * GET /v1/novel/ranking
1229
+ *
1230
+ * @param params - Request parameters
1231
+ */
1232
+ ranking(params = {}) {
1233
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/novel/ranking", buildParams({
1234
+ mode: params.mode ?? "day",
1235
+ filter: params.filter ?? "for_ios",
1236
+ date: params.date,
1237
+ offset: params.offset
1238
+ })), this.#http, (page) => page.novels);
1239
+ }
1240
+ /**
1241
+ * Fetches recommended novels.
1242
+ * GET /v1/novel/recommended
1243
+ *
1244
+ * @param params - Request parameters
1245
+ */
1246
+ recommended(params = {}) {
1247
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/novel/recommended", buildParams({
1248
+ filter: params.filter ?? "for_ios",
1249
+ includeRankingNovels: true,
1250
+ includePrivacyPolicy: true,
1251
+ offset: params.offset,
1252
+ maxBookmarkIdForRecommend: params.maxBookmarkIdForRecommend
1253
+ })), this.#http, (page) => page.novels);
1254
+ }
1255
+ /**
1256
+ * Fetches a novel series.
1257
+ * GET /v2/novel/series
1258
+ *
1259
+ * @param params - Request parameters
1260
+ */
1261
+ series(params) {
1262
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v2/novel/series", buildParams({
1263
+ seriesId: params.seriesId,
1264
+ lastOrder: params.lastOrder
1265
+ })), this.#http, (page) => page.novels);
1266
+ }
1267
+ /**
1268
+ * Adds a novel bookmark.
1269
+ * POST /v2/novel/bookmark/add
1270
+ *
1271
+ * @param params - Request parameters
1272
+ */
1273
+ bookmarkAdd(params) {
1274
+ const body = buildParams({
1275
+ novelId: params.novelId,
1276
+ restrict: params.restrict ?? "public",
1277
+ ...params.tags ? { tags: params.tags } : {}
1278
+ });
1279
+ return this.#http.post("/v2/novel/bookmark/add", body.toString());
1280
+ }
1281
+ /**
1282
+ * Removes a novel bookmark.
1283
+ * POST /v1/novel/bookmark/delete
1284
+ *
1285
+ * @param params - Request parameters
1286
+ */
1287
+ bookmarkDelete(params) {
1288
+ const body = buildParams({ novelId: String(params.novelId) });
1289
+ return this.#http.post("/v1/novel/bookmark/delete", body.toString());
1290
+ }
1176
1291
  };
1177
-
1178
- // src/resources/users.ts
1292
+ //#endregion
1293
+ //#region src/resources/users.ts
1294
+ /** Methods for the user bookmarks sub-namespace. */
1179
1295
  var UserBookmarksResource = class {
1180
- #http;
1181
- constructor(http) {
1182
- this.#http = http;
1183
- }
1184
- /**
1185
- * Fetches a user's bookmarked illusts.
1186
- * GET /v1/user/bookmarks/illust
1187
- *
1188
- * @param params - Request parameters
1189
- *
1190
- * @example
1191
- * ```ts
1192
- * // Iterate all bookmarked illusts across pages
1193
- * for await (const illust of client.users.bookmarks.illusts({ userId: client.userId }).items()) {
1194
- * console.log(illust.title)
1195
- * }
1196
- *
1197
- * // Resume from a saved cursor
1198
- * import { parseNextUrl } from '@book000/pixivts'
1199
- * const page = await client.users.bookmarks.illusts({ userId: client.userId })
1200
- * if (page.isOk && page.value.nextUrl) {
1201
- * const cursor = parseNextUrl(page.value.nextUrl)
1202
- * const next = await client.users.bookmarks.illusts({
1203
- * userId: client.userId,
1204
- * maxBookmarkId: cursor.maxBookmarkId,
1205
- * })
1206
- * }
1207
- * ```
1208
- */
1209
- illusts(params) {
1210
- return PaginatedResultAsync.fromResultAsync(
1211
- this.#http.get(
1212
- "/v1/user/bookmarks/illust",
1213
- buildParams({
1214
- userId: params.userId,
1215
- restrict: params.restrict ?? "public",
1216
- filter: params.filter ?? "for_ios",
1217
- tag: params.tag,
1218
- maxBookmarkId: params.maxBookmarkId,
1219
- offset: params.offset
1220
- })
1221
- ),
1222
- this.#http,
1223
- (page) => page.illusts
1224
- );
1225
- }
1226
- /**
1227
- * Fetches a user's bookmarked novels.
1228
- * GET /v1/user/bookmarks/novel
1229
- *
1230
- * @param params - Request parameters
1231
- *
1232
- * @example
1233
- * ```ts
1234
- * // Iterate all bookmarked novels across pages
1235
- * for await (const novel of client.users.bookmarks.novels({ userId: client.userId }).items()) {
1236
- * console.log(novel.title)
1237
- * }
1238
- * ```
1239
- */
1240
- novels(params) {
1241
- return PaginatedResultAsync.fromResultAsync(
1242
- this.#http.get(
1243
- "/v1/user/bookmarks/novel",
1244
- buildParams({
1245
- userId: params.userId,
1246
- restrict: params.restrict ?? "public",
1247
- filter: params.filter ?? "for_ios",
1248
- tag: params.tag,
1249
- maxBookmarkId: params.maxBookmarkId,
1250
- offset: params.offset
1251
- })
1252
- ),
1253
- this.#http,
1254
- (page) => page.novels
1255
- );
1256
- }
1296
+ #http;
1297
+ constructor(http) {
1298
+ this.#http = http;
1299
+ }
1300
+ /**
1301
+ * Fetches a user's bookmarked illusts.
1302
+ * GET /v1/user/bookmarks/illust
1303
+ *
1304
+ * @param params - Request parameters
1305
+ *
1306
+ * @example
1307
+ * ```ts
1308
+ * // Iterate all bookmarked illusts across pages
1309
+ * for await (const illust of client.users.bookmarks.illusts({ userId: client.userId }).items()) {
1310
+ * console.log(illust.title)
1311
+ * }
1312
+ *
1313
+ * // Resume from a saved cursor
1314
+ * import { parseNextUrl } from '@book000/pixivts'
1315
+ * const page = await client.users.bookmarks.illusts({ userId: client.userId })
1316
+ * if (page.isOk && page.value.nextUrl) {
1317
+ * const cursor = parseNextUrl(page.value.nextUrl)
1318
+ * const next = await client.users.bookmarks.illusts({
1319
+ * userId: client.userId,
1320
+ * maxBookmarkId: cursor.maxBookmarkId,
1321
+ * })
1322
+ * }
1323
+ * ```
1324
+ */
1325
+ illusts(params) {
1326
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/user/bookmarks/illust", buildParams({
1327
+ userId: params.userId,
1328
+ restrict: params.restrict ?? "public",
1329
+ filter: params.filter ?? "for_ios",
1330
+ tag: params.tag,
1331
+ maxBookmarkId: params.maxBookmarkId,
1332
+ offset: params.offset
1333
+ })), this.#http, (page) => page.illusts);
1334
+ }
1335
+ /**
1336
+ * Fetches a user's bookmarked novels.
1337
+ * GET /v1/user/bookmarks/novel
1338
+ *
1339
+ * @param params - Request parameters
1340
+ *
1341
+ * @example
1342
+ * ```ts
1343
+ * // Iterate all bookmarked novels across pages
1344
+ * for await (const novel of client.users.bookmarks.novels({ userId: client.userId }).items()) {
1345
+ * console.log(novel.title)
1346
+ * }
1347
+ * ```
1348
+ */
1349
+ novels(params) {
1350
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/user/bookmarks/novel", buildParams({
1351
+ userId: params.userId,
1352
+ restrict: params.restrict ?? "public",
1353
+ filter: params.filter ?? "for_ios",
1354
+ tag: params.tag,
1355
+ maxBookmarkId: params.maxBookmarkId,
1356
+ offset: params.offset
1357
+ })), this.#http, (page) => page.novels);
1358
+ }
1257
1359
  };
1360
+ /** Methods for the user API namespace. */
1258
1361
  var UserResource = class {
1259
- /** User bookmarks sub-namespace. */
1260
- bookmarks;
1261
- #http;
1262
- constructor(http) {
1263
- this.#http = http;
1264
- this.bookmarks = new UserBookmarksResource(http);
1265
- }
1266
- /**
1267
- * Fetches detailed profile information for a user.
1268
- * GET /v1/user/detail
1269
- *
1270
- * @param params - Request parameters
1271
- */
1272
- detail(params) {
1273
- return this.#http.get(
1274
- "/v1/user/detail",
1275
- buildParams({ userId: params.userId, filter: params.filter ?? "for_ios" })
1276
- );
1277
- }
1278
- /**
1279
- * Fetches illusts posted by a user.
1280
- * GET /v1/user/illusts
1281
- *
1282
- * @param params - Request parameters
1283
- */
1284
- illusts(params) {
1285
- return PaginatedResultAsync.fromResultAsync(
1286
- this.#http.get(
1287
- "/v1/user/illusts",
1288
- buildParams({
1289
- userId: params.userId,
1290
- type: params.type,
1291
- filter: params.filter ?? "for_ios",
1292
- offset: params.offset
1293
- })
1294
- ),
1295
- this.#http,
1296
- (page) => page.illusts
1297
- );
1298
- }
1299
- /**
1300
- * Fetches novels posted by a user.
1301
- * GET /v1/user/novels
1302
- *
1303
- * @param params - Request parameters
1304
- */
1305
- novels(params) {
1306
- return PaginatedResultAsync.fromResultAsync(
1307
- this.#http.get(
1308
- "/v1/user/novels",
1309
- buildParams({
1310
- userId: params.userId,
1311
- filter: params.filter ?? "for_ios",
1312
- offset: params.offset
1313
- })
1314
- ),
1315
- this.#http,
1316
- (page) => page.novels
1317
- );
1318
- }
1319
- /**
1320
- * Fetches the list of users that a user is following.
1321
- * GET /v1/user/following
1322
- *
1323
- * @param params - Request parameters
1324
- */
1325
- following(params) {
1326
- return PaginatedResultAsync.fromResultAsync(
1327
- this.#http.get(
1328
- "/v1/user/following",
1329
- buildParams({
1330
- userId: params.userId,
1331
- restrict: params.restrict ?? "public",
1332
- offset: params.offset
1333
- })
1334
- ),
1335
- this.#http,
1336
- (page) => page.userPreviews
1337
- );
1338
- }
1339
- /**
1340
- * Follows a user.
1341
- * POST /v1/user/follow/add
1342
- *
1343
- * @param params - Request parameters
1344
- */
1345
- followAdd(params) {
1346
- const body = buildParams({
1347
- userId: params.userId,
1348
- restrict: params.restrict ?? "public"
1349
- });
1350
- return this.#http.post(
1351
- "/v1/user/follow/add",
1352
- body.toString()
1353
- );
1354
- }
1355
- /**
1356
- * Unfollows a user.
1357
- * POST /v1/user/follow/delete
1358
- *
1359
- * @param params - Request parameters
1360
- */
1361
- followDelete(params) {
1362
- const body = buildParams({ userId: String(params.userId) });
1363
- return this.#http.post(
1364
- "/v1/user/follow/delete",
1365
- body.toString()
1366
- );
1367
- }
1362
+ /** User bookmarks sub-namespace. */
1363
+ bookmarks;
1364
+ #http;
1365
+ constructor(http) {
1366
+ this.#http = http;
1367
+ this.bookmarks = new UserBookmarksResource(http);
1368
+ }
1369
+ /**
1370
+ * Fetches detailed profile information for a user.
1371
+ * GET /v1/user/detail
1372
+ *
1373
+ * @param params - Request parameters
1374
+ */
1375
+ detail(params) {
1376
+ return this.#http.get("/v1/user/detail", buildParams({
1377
+ userId: params.userId,
1378
+ filter: params.filter ?? "for_ios"
1379
+ }));
1380
+ }
1381
+ /**
1382
+ * Fetches illusts posted by a user.
1383
+ * GET /v1/user/illusts
1384
+ *
1385
+ * @param params - Request parameters
1386
+ */
1387
+ illusts(params) {
1388
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/user/illusts", buildParams({
1389
+ userId: params.userId,
1390
+ type: params.type,
1391
+ filter: params.filter ?? "for_ios",
1392
+ offset: params.offset
1393
+ })), this.#http, (page) => page.illusts);
1394
+ }
1395
+ /**
1396
+ * Fetches novels posted by a user.
1397
+ * GET /v1/user/novels
1398
+ *
1399
+ * @param params - Request parameters
1400
+ */
1401
+ novels(params) {
1402
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/user/novels", buildParams({
1403
+ userId: params.userId,
1404
+ filter: params.filter ?? "for_ios",
1405
+ offset: params.offset
1406
+ })), this.#http, (page) => page.novels);
1407
+ }
1408
+ /**
1409
+ * Fetches the list of users that a user is following.
1410
+ * GET /v1/user/following
1411
+ *
1412
+ * @param params - Request parameters
1413
+ */
1414
+ following(params) {
1415
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/user/following", buildParams({
1416
+ userId: params.userId,
1417
+ restrict: params.restrict ?? "public",
1418
+ offset: params.offset
1419
+ })), this.#http, (page) => page.userPreviews);
1420
+ }
1421
+ /**
1422
+ * Follows a user.
1423
+ * POST /v1/user/follow/add
1424
+ *
1425
+ * @param params - Request parameters
1426
+ */
1427
+ followAdd(params) {
1428
+ const body = buildParams({
1429
+ userId: params.userId,
1430
+ restrict: params.restrict ?? "public"
1431
+ });
1432
+ return this.#http.post("/v1/user/follow/add", body.toString());
1433
+ }
1434
+ /**
1435
+ * Unfollows a user.
1436
+ * POST /v1/user/follow/delete
1437
+ *
1438
+ * @param params - Request parameters
1439
+ */
1440
+ followDelete(params) {
1441
+ const body = buildParams({ userId: String(params.userId) });
1442
+ return this.#http.post("/v1/user/follow/delete", body.toString());
1443
+ }
1368
1444
  };
1369
-
1370
- // src/resources/manga.ts
1445
+ //#endregion
1446
+ //#region src/resources/manga.ts
1447
+ /** Methods for the manga API namespace. */
1371
1448
  var MangaResource = class {
1372
- #http;
1373
- constructor(http) {
1374
- this.#http = http;
1375
- }
1376
- /**
1377
- * Fetches recommended manga.
1378
- * GET /v1/manga/recommended
1379
- *
1380
- * @param params - Request parameters
1381
- */
1382
- recommended(params = {}) {
1383
- return PaginatedResultAsync.fromResultAsync(
1384
- this.#http.get(
1385
- "/v1/manga/recommended",
1386
- buildParams({
1387
- filter: params.filter ?? "for_ios",
1388
- includeRankingIllusts: true,
1389
- includePrivacyPolicy: true,
1390
- offset: params.offset
1391
- })
1392
- ),
1393
- this.#http,
1394
- (page) => page.illusts
1395
- );
1396
- }
1449
+ #http;
1450
+ constructor(http) {
1451
+ this.#http = http;
1452
+ }
1453
+ /**
1454
+ * Fetches recommended manga.
1455
+ * GET /v1/manga/recommended
1456
+ *
1457
+ * @param params - Request parameters
1458
+ */
1459
+ recommended(params = {}) {
1460
+ return PaginatedResultAsync.fromResultAsync(this.#http.get("/v1/manga/recommended", buildParams({
1461
+ filter: params.filter ?? "for_ios",
1462
+ includeRankingIllusts: true,
1463
+ includePrivacyPolicy: true,
1464
+ offset: params.offset
1465
+ })), this.#http, (page) => page.illusts);
1466
+ }
1397
1467
  };
1398
-
1399
- // src/resources/ugoira.ts
1468
+ //#endregion
1469
+ //#region src/resources/ugoira.ts
1470
+ /** Methods for the ugoira API namespace. */
1400
1471
  var UgoiraResource = class {
1401
- #http;
1402
- constructor(http) {
1403
- this.#http = http;
1404
- }
1405
- /**
1406
- * Fetches ugoira metadata (ZIP URL and per-frame timings).
1407
- * GET /v1/ugoira/metadata
1408
- *
1409
- * @param params - Request parameters
1410
- */
1411
- metadata(params) {
1412
- return this.#http.get(
1413
- "/v1/ugoira/metadata",
1414
- buildParams({ illustId: params.illustId })
1415
- );
1416
- }
1472
+ #http;
1473
+ constructor(http) {
1474
+ this.#http = http;
1475
+ }
1476
+ /**
1477
+ * Fetches ugoira metadata (ZIP URL and per-frame timings).
1478
+ * GET /v1/ugoira/metadata
1479
+ *
1480
+ * @param params - Request parameters
1481
+ */
1482
+ metadata(params) {
1483
+ return this.#http.get("/v1/ugoira/metadata", buildParams({ illustId: params.illustId }));
1484
+ }
1417
1485
  };
1418
-
1419
- // src/resources/images.ts
1486
+ //#endregion
1487
+ //#region src/resources/images.ts
1488
+ /** Methods for fetching pixiv images. */
1420
1489
  var ImageResource = class {
1421
- #http;
1422
- constructor(http) {
1423
- this.#http = http;
1424
- }
1425
- /**
1426
- * Fetches a pixiv image.
1427
- *
1428
- * Uses a browser User-Agent and Referer (required for pixiv CDN).
1429
- * No Authorization header is sent.
1430
- *
1431
- * @param imageUrl - Full CDN image URL
1432
- */
1433
- fetch(imageUrl) {
1434
- return this.#http.fetchImage(imageUrl);
1435
- }
1490
+ #http;
1491
+ constructor(http) {
1492
+ this.#http = http;
1493
+ }
1494
+ /**
1495
+ * Fetches a pixiv image.
1496
+ *
1497
+ * Uses a browser User-Agent and Referer (required for pixiv CDN).
1498
+ * No Authorization header is sent.
1499
+ *
1500
+ * @param imageUrl - Full CDN image URL
1501
+ */
1502
+ fetch(imageUrl) {
1503
+ return this.#http.fetchImage(imageUrl);
1504
+ }
1436
1505
  };
1437
-
1438
- // src/client.ts
1439
- var PixivClient = class _PixivClient {
1440
- /** Illust API namespace. */
1441
- illusts;
1442
- /** Novel API namespace. */
1443
- novels;
1444
- /** User API namespace. */
1445
- users;
1446
- /** Manga API namespace. */
1447
- manga;
1448
- /** Ugoira API namespace. */
1449
- ugoira;
1450
- /** Image fetch helpers. */
1451
- images;
1452
- #auth;
1453
- constructor(auth, http) {
1454
- this.#auth = auth;
1455
- this.illusts = new IllustResource(http);
1456
- this.novels = new NovelResource(http);
1457
- this.users = new UserResource(http);
1458
- this.manga = new MangaResource(http);
1459
- this.ugoira = new UgoiraResource(http);
1460
- this.images = new ImageResource(http);
1461
- }
1462
- /**
1463
- * Numeric user ID of the authenticated account.
1464
- *
1465
- * Available immediately after {@link PixivClient.of} resolves.
1466
- * The pixiv OAuth endpoint returns the ID as a string; this getter
1467
- * normalises it to `number` for consistency with resource method params
1468
- * (e.g. `UserBookmarksIllustParams.userId`).
1469
- *
1470
- * @example
1471
- * ```ts
1472
- * const client = await PixivClient.of(refreshToken)
1473
- * const bookmarks = await client.users.bookmarks.illusts({ userId: client.userId })
1474
- * ```
1475
- */
1476
- get userId() {
1477
- const id = Number(this.#auth.userId);
1478
- if (Number.isNaN(id)) {
1479
- throw new TypeError(`Invalid userId: "${this.#auth.userId}"`);
1480
- }
1481
- return id;
1482
- }
1483
- /**
1484
- * Returns the current OAuth access token.
1485
- *
1486
- * The access token is short-lived and changes after each call to
1487
- * {@link PixivClient.of} and after each automatic token refresh triggered
1488
- * by a 401 response.
1489
- *
1490
- * @returns The current bearer access token string
1491
- */
1492
- getAccessToken() {
1493
- return this.#auth.accessToken;
1494
- }
1495
- /**
1496
- * Returns the current OAuth refresh token.
1497
- *
1498
- * The refresh token is long-lived and is used to obtain new access tokens.
1499
- * It may rotate after a successful token refresh.
1500
- *
1501
- * @returns The current refresh token string
1502
- */
1503
- getRefreshToken() {
1504
- return this.#auth.refreshToken;
1505
- }
1506
- /**
1507
- * Creates a PixivClient by refreshing the given token.
1508
- *
1509
- * @param refreshToken - Pixiv refresh token
1510
- * @param options - Optional retry and response interceptor configuration
1511
- * @returns A fully initialised {@link PixivClient}
1512
- */
1513
- static async of(refreshToken, options) {
1514
- const auth = await AuthManager.login(refreshToken);
1515
- const http = new HttpClient(auth, options);
1516
- return new _PixivClient(auth, http);
1517
- }
1506
+ //#endregion
1507
+ //#region src/client.ts
1508
+ /**
1509
+ * PixivClient main entry point for the @book000/pixivts library.
1510
+ *
1511
+ * @example
1512
+ * ```ts
1513
+ * const client = await PixivClient.of(process.env.PIXIV_REFRESH_TOKEN!)
1514
+ * const result = await client.illusts.detail({ illustId: 12345 })
1515
+ * if (result.isOk) console.log(result.value.illust.title)
1516
+ * ```
1517
+ */
1518
+ /**
1519
+ * Main client for the pixiv API.
1520
+ *
1521
+ * Create an instance via {@link PixivClient.of} — the constructor is private
1522
+ * because initialisation requires an async token refresh.
1523
+ */
1524
+ var PixivClient = class PixivClient {
1525
+ /** Illust API namespace. */
1526
+ illusts;
1527
+ /** Novel API namespace. */
1528
+ novels;
1529
+ /** User API namespace. */
1530
+ users;
1531
+ /** Manga API namespace. */
1532
+ manga;
1533
+ /** Ugoira API namespace. */
1534
+ ugoira;
1535
+ /** Image fetch helpers. */
1536
+ images;
1537
+ #auth;
1538
+ constructor(auth, http) {
1539
+ this.#auth = auth;
1540
+ this.illusts = new IllustResource(http);
1541
+ this.novels = new NovelResource(http);
1542
+ this.users = new UserResource(http);
1543
+ this.manga = new MangaResource(http);
1544
+ this.ugoira = new UgoiraResource(http);
1545
+ this.images = new ImageResource(http);
1546
+ }
1547
+ /**
1548
+ * Numeric user ID of the authenticated account.
1549
+ *
1550
+ * Available immediately after {@link PixivClient.of} resolves.
1551
+ * The pixiv OAuth endpoint returns the ID as a string; this getter
1552
+ * normalises it to `number` for consistency with resource method params
1553
+ * (e.g. `UserBookmarksIllustParams.userId`).
1554
+ *
1555
+ * @example
1556
+ * ```ts
1557
+ * const client = await PixivClient.of(refreshToken)
1558
+ * const bookmarks = await client.users.bookmarks.illusts({ userId: client.userId })
1559
+ * ```
1560
+ */
1561
+ get userId() {
1562
+ const id = Number(this.#auth.userId);
1563
+ if (Number.isNaN(id)) throw new TypeError(`Invalid userId: "${this.#auth.userId}"`);
1564
+ return id;
1565
+ }
1566
+ /**
1567
+ * Returns the current OAuth access token.
1568
+ *
1569
+ * The access token is short-lived and changes after each call to
1570
+ * {@link PixivClient.of} and after each automatic token refresh triggered
1571
+ * by a 401 response.
1572
+ *
1573
+ * @returns The current bearer access token string
1574
+ */
1575
+ getAccessToken() {
1576
+ return this.#auth.accessToken;
1577
+ }
1578
+ /**
1579
+ * Returns the current OAuth refresh token.
1580
+ *
1581
+ * The refresh token is long-lived and is used to obtain new access tokens.
1582
+ * It may rotate after a successful token refresh.
1583
+ *
1584
+ * @returns The current refresh token string
1585
+ */
1586
+ getRefreshToken() {
1587
+ return this.#auth.refreshToken;
1588
+ }
1589
+ /**
1590
+ * Creates a PixivClient by refreshing the given token.
1591
+ *
1592
+ * @param refreshToken - Pixiv refresh token
1593
+ * @param options - Optional retry and response interceptor configuration
1594
+ * @returns A fully initialised {@link PixivClient}
1595
+ */
1596
+ static async of(refreshToken, options) {
1597
+ const auth = await AuthManager.login(refreshToken);
1598
+ return new PixivClient(auth, new HttpClient(auth, options));
1599
+ }
1518
1600
  };
1519
-
1601
+ //#endregion
1520
1602
  export { BookmarkRestrict, FollowRestrict, NovelRankingMode, OSFilter, PaginatedResultAsync, PixivClient, PixivFetchError, RankingMode, ResultAsync, SearchDuration, SearchSort, SearchTarget, UserIllustType, apiError, authFailedError, err, failedPaginated, networkError, ok, parseNextUrl, rateLimitError };
1603
+
1604
+ //# sourceMappingURL=index.js.map