@book000/pixivts 0.55.1 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +115 -19
  2. package/dist/index.cjs +1321 -0
  3. package/dist/index.d.cts +1662 -0
  4. package/dist/index.d.ts +1662 -45
  5. package/dist/index.js +1308 -65
  6. package/package.json +35 -72
  7. package/dist/checks.d.ts +0 -19
  8. package/dist/checks.d.ts.map +0 -1
  9. package/dist/checks.js +0 -85
  10. package/dist/checks.js.map +0 -1
  11. package/dist/checks.test.d.ts +0 -2
  12. package/dist/checks.test.d.ts.map +0 -1
  13. package/dist/checks.test.js +0 -306
  14. package/dist/checks.test.js.map +0 -1
  15. package/dist/http-client.d.ts +0 -66
  16. package/dist/http-client.d.ts.map +0 -1
  17. package/dist/http-client.js +0 -138
  18. package/dist/http-client.js.map +0 -1
  19. package/dist/http-client.test.d.ts +0 -2
  20. package/dist/http-client.test.d.ts.map +0 -1
  21. package/dist/http-client.test.js +0 -149
  22. package/dist/http-client.test.js.map +0 -1
  23. package/dist/index.d.ts.map +0 -1
  24. package/dist/index.js.map +0 -1
  25. package/dist/options.d.ts +0 -317
  26. package/dist/options.d.ts.map +0 -1
  27. package/dist/options.js +0 -222
  28. package/dist/options.js.map +0 -1
  29. package/dist/options.test.d.ts +0 -2
  30. package/dist/options.test.d.ts.map +0 -1
  31. package/dist/options.test.js +0 -117
  32. package/dist/options.test.js.map +0 -1
  33. package/dist/pixiv.d.ts +0 -317
  34. package/dist/pixiv.d.ts.map +0 -1
  35. package/dist/pixiv.js +0 -810
  36. package/dist/pixiv.js.map +0 -1
  37. package/dist/pixiv.test.d.ts +0 -2
  38. package/dist/pixiv.test.d.ts.map +0 -1
  39. package/dist/pixiv.test.js +0 -883
  40. package/dist/pixiv.test.js.map +0 -1
  41. package/dist/saving-responses/index.d.ts +0 -130
  42. package/dist/saving-responses/index.d.ts.map +0 -1
  43. package/dist/saving-responses/index.js +0 -263
  44. package/dist/saving-responses/index.js.map +0 -1
  45. package/dist/saving-responses/index.test.d.ts +0 -2
  46. package/dist/saving-responses/index.test.d.ts.map +0 -1
  47. package/dist/saving-responses/index.test.js +0 -468
  48. package/dist/saving-responses/index.test.js.map +0 -1
  49. package/dist/saving-responses/response-entity.d.ts +0 -16
  50. package/dist/saving-responses/response-entity.d.ts.map +0 -1
  51. package/dist/saving-responses/response-entity.js +0 -101
  52. package/dist/saving-responses/response-entity.js.map +0 -1
  53. package/dist/types/endpoints/v1/illust/bookmark/delete.d.ts +0 -14
  54. package/dist/types/endpoints/v1/illust/bookmark/delete.d.ts.map +0 -1
  55. package/dist/types/endpoints/v1/illust/bookmark/delete.js +0 -3
  56. package/dist/types/endpoints/v1/illust/bookmark/delete.js.map +0 -1
  57. package/dist/types/endpoints/v1/illust/detail.d.ts +0 -25
  58. package/dist/types/endpoints/v1/illust/detail.d.ts.map +0 -1
  59. package/dist/types/endpoints/v1/illust/detail.js +0 -20
  60. package/dist/types/endpoints/v1/illust/detail.js.map +0 -1
  61. package/dist/types/endpoints/v1/illust/ranking.d.ts +0 -51
  62. package/dist/types/endpoints/v1/illust/ranking.d.ts.map +0 -1
  63. package/dist/types/endpoints/v1/illust/ranking.js +0 -40
  64. package/dist/types/endpoints/v1/illust/ranking.js.map +0 -1
  65. package/dist/types/endpoints/v1/illust/recommended.d.ts +0 -104
  66. package/dist/types/endpoints/v1/illust/recommended.d.ts.map +0 -1
  67. package/dist/types/endpoints/v1/illust/recommended.js +0 -44
  68. package/dist/types/endpoints/v1/illust/recommended.js.map +0 -1
  69. package/dist/types/endpoints/v1/illust/series.d.ts +0 -45
  70. package/dist/types/endpoints/v1/illust/series.d.ts.map +0 -1
  71. package/dist/types/endpoints/v1/illust/series.js +0 -31
  72. package/dist/types/endpoints/v1/illust/series.js.map +0 -1
  73. package/dist/types/endpoints/v1/illust/ugoira/metadata.d.ts +0 -25
  74. package/dist/types/endpoints/v1/illust/ugoira/metadata.d.ts.map +0 -1
  75. package/dist/types/endpoints/v1/illust/ugoira/metadata.js +0 -20
  76. package/dist/types/endpoints/v1/illust/ugoira/metadata.js.map +0 -1
  77. package/dist/types/endpoints/v1/manga/recommended.d.ts +0 -72
  78. package/dist/types/endpoints/v1/manga/recommended.d.ts.map +0 -1
  79. package/dist/types/endpoints/v1/manga/recommended.js +0 -33
  80. package/dist/types/endpoints/v1/manga/recommended.js.map +0 -1
  81. package/dist/types/endpoints/v1/novel/bookmark/delete.d.ts +0 -14
  82. package/dist/types/endpoints/v1/novel/bookmark/delete.d.ts.map +0 -1
  83. package/dist/types/endpoints/v1/novel/bookmark/delete.js +0 -3
  84. package/dist/types/endpoints/v1/novel/bookmark/delete.js.map +0 -1
  85. package/dist/types/endpoints/v1/novel/ranking.d.ts +0 -47
  86. package/dist/types/endpoints/v1/novel/ranking.d.ts.map +0 -1
  87. package/dist/types/endpoints/v1/novel/ranking.js +0 -39
  88. package/dist/types/endpoints/v1/novel/ranking.js.map +0 -1
  89. package/dist/types/endpoints/v1/novel/recommended.d.ts +0 -72
  90. package/dist/types/endpoints/v1/novel/recommended.d.ts.map +0 -1
  91. package/dist/types/endpoints/v1/novel/recommended.js +0 -32
  92. package/dist/types/endpoints/v1/novel/recommended.js.map +0 -1
  93. package/dist/types/endpoints/v1/novel/related.d.ts +0 -37
  94. package/dist/types/endpoints/v1/novel/related.d.ts.map +0 -1
  95. package/dist/types/endpoints/v1/novel/related.js +0 -24
  96. package/dist/types/endpoints/v1/novel/related.js.map +0 -1
  97. package/dist/types/endpoints/v1/search/illust.d.ts +0 -109
  98. package/dist/types/endpoints/v1/search/illust.d.ts.map +0 -1
  99. package/dist/types/endpoints/v1/search/illust.js +0 -40
  100. package/dist/types/endpoints/v1/search/illust.js.map +0 -1
  101. package/dist/types/endpoints/v1/search/novel.d.ts +0 -103
  102. package/dist/types/endpoints/v1/search/novel.d.ts.map +0 -1
  103. package/dist/types/endpoints/v1/search/novel.js +0 -38
  104. package/dist/types/endpoints/v1/search/novel.js.map +0 -1
  105. package/dist/types/endpoints/v1/user/bookmarks/illust.d.ts +0 -48
  106. package/dist/types/endpoints/v1/user/bookmarks/illust.d.ts.map +0 -1
  107. package/dist/types/endpoints/v1/user/bookmarks/illust.js +0 -31
  108. package/dist/types/endpoints/v1/user/bookmarks/illust.js.map +0 -1
  109. package/dist/types/endpoints/v1/user/bookmarks/novel.d.ts +0 -44
  110. package/dist/types/endpoints/v1/user/bookmarks/novel.d.ts.map +0 -1
  111. package/dist/types/endpoints/v1/user/bookmarks/novel.js +0 -28
  112. package/dist/types/endpoints/v1/user/bookmarks/novel.js.map +0 -1
  113. package/dist/types/endpoints/v1/user/detail.d.ts +0 -44
  114. package/dist/types/endpoints/v1/user/detail.d.ts.map +0 -1
  115. package/dist/types/endpoints/v1/user/detail.js +0 -26
  116. package/dist/types/endpoints/v1/user/detail.js.map +0 -1
  117. package/dist/types/endpoints/v1/user/follow/add.d.ts +0 -24
  118. package/dist/types/endpoints/v1/user/follow/add.d.ts.map +0 -1
  119. package/dist/types/endpoints/v1/user/follow/add.js +0 -3
  120. package/dist/types/endpoints/v1/user/follow/add.js.map +0 -1
  121. package/dist/types/endpoints/v1/user/follow/delete.d.ts +0 -14
  122. package/dist/types/endpoints/v1/user/follow/delete.d.ts.map +0 -1
  123. package/dist/types/endpoints/v1/user/follow/delete.js +0 -3
  124. package/dist/types/endpoints/v1/user/follow/delete.js.map +0 -1
  125. package/dist/types/endpoints/v1/user/following.d.ts +0 -38
  126. package/dist/types/endpoints/v1/user/following.d.ts.map +0 -1
  127. package/dist/types/endpoints/v1/user/following.js +0 -26
  128. package/dist/types/endpoints/v1/user/following.js.map +0 -1
  129. package/dist/types/endpoints/v1/user/illusts.d.ts +0 -51
  130. package/dist/types/endpoints/v1/user/illusts.d.ts.map +0 -1
  131. package/dist/types/endpoints/v1/user/illusts.js +0 -31
  132. package/dist/types/endpoints/v1/user/illusts.js.map +0 -1
  133. package/dist/types/endpoints/v1/user/novels.d.ts +0 -43
  134. package/dist/types/endpoints/v1/user/novels.d.ts.map +0 -1
  135. package/dist/types/endpoints/v1/user/novels.js +0 -29
  136. package/dist/types/endpoints/v1/user/novels.js.map +0 -1
  137. package/dist/types/endpoints/v2/illust/bookmark/add.d.ts +0 -28
  138. package/dist/types/endpoints/v2/illust/bookmark/add.d.ts.map +0 -1
  139. package/dist/types/endpoints/v2/illust/bookmark/add.js +0 -3
  140. package/dist/types/endpoints/v2/illust/bookmark/add.js.map +0 -1
  141. package/dist/types/endpoints/v2/illust/related.d.ts +0 -48
  142. package/dist/types/endpoints/v2/illust/related.d.ts.map +0 -1
  143. package/dist/types/endpoints/v2/illust/related.js +0 -32
  144. package/dist/types/endpoints/v2/illust/related.js.map +0 -1
  145. package/dist/types/endpoints/v2/novel/bookmark/add.d.ts +0 -27
  146. package/dist/types/endpoints/v2/novel/bookmark/add.d.ts.map +0 -1
  147. package/dist/types/endpoints/v2/novel/bookmark/add.js +0 -3
  148. package/dist/types/endpoints/v2/novel/bookmark/add.js.map +0 -1
  149. package/dist/types/endpoints/v2/novel/detail.d.ts +0 -25
  150. package/dist/types/endpoints/v2/novel/detail.d.ts.map +0 -1
  151. package/dist/types/endpoints/v2/novel/detail.js +0 -20
  152. package/dist/types/endpoints/v2/novel/detail.js.map +0 -1
  153. package/dist/types/endpoints/v2/novel/series.d.ts +0 -49
  154. package/dist/types/endpoints/v2/novel/series.d.ts.map +0 -1
  155. package/dist/types/endpoints/v2/novel/series.js +0 -31
  156. package/dist/types/endpoints/v2/novel/series.js.map +0 -1
  157. package/dist/types/endpoints/webview/v2/novel.d.ts +0 -21
  158. package/dist/types/endpoints/webview/v2/novel.d.ts.map +0 -1
  159. package/dist/types/endpoints/webview/v2/novel.js +0 -18
  160. package/dist/types/endpoints/webview/v2/novel.js.map +0 -1
  161. package/dist/types/error-response.d.ts +0 -31
  162. package/dist/types/error-response.d.ts.map +0 -1
  163. package/dist/types/error-response.js +0 -3
  164. package/dist/types/error-response.js.map +0 -1
  165. package/dist/types/errors.d.ts +0 -7
  166. package/dist/types/errors.d.ts.map +0 -1
  167. package/dist/types/errors.js +0 -14
  168. package/dist/types/errors.js.map +0 -1
  169. package/dist/types/pixiv-common.d.ts +0 -103
  170. package/dist/types/pixiv-common.d.ts.map +0 -1
  171. package/dist/types/pixiv-common.js +0 -68
  172. package/dist/types/pixiv-common.js.map +0 -1
  173. package/dist/types/pixiv-illust-series.d.ts +0 -56
  174. package/dist/types/pixiv-illust-series.d.ts.map +0 -1
  175. package/dist/types/pixiv-illust-series.js +0 -26
  176. package/dist/types/pixiv-illust-series.js.map +0 -1
  177. package/dist/types/pixiv-illust.d.ts +0 -180
  178. package/dist/types/pixiv-illust.d.ts.map +0 -1
  179. package/dist/types/pixiv-illust.js +0 -56
  180. package/dist/types/pixiv-illust.js.map +0 -1
  181. package/dist/types/pixiv-novel-series.d.ts +0 -84
  182. package/dist/types/pixiv-novel-series.d.ts.map +0 -1
  183. package/dist/types/pixiv-novel-series.js +0 -43
  184. package/dist/types/pixiv-novel-series.js.map +0 -1
  185. package/dist/types/pixiv-novel.d.ts +0 -131
  186. package/dist/types/pixiv-novel.d.ts.map +0 -1
  187. package/dist/types/pixiv-novel.js +0 -44
  188. package/dist/types/pixiv-novel.js.map +0 -1
  189. package/dist/types/pixiv-ugoira.d.ts +0 -50
  190. package/dist/types/pixiv-ugoira.d.ts.map +0 -1
  191. package/dist/types/pixiv-ugoira.js +0 -34
  192. package/dist/types/pixiv-ugoira.js.map +0 -1
  193. package/dist/types/pixiv-user.d.ts +0 -212
  194. package/dist/types/pixiv-user.d.ts.map +0 -1
  195. package/dist/types/pixiv-user.js +0 -118
  196. package/dist/types/pixiv-user.js.map +0 -1
  197. package/dist/utils.d.ts +0 -3
  198. package/dist/utils.d.ts.map +0 -1
  199. package/dist/utils.js +0 -15
  200. package/dist/utils.js.map +0 -1
  201. package/dist/utils.test.d.ts +0 -2
  202. package/dist/utils.test.d.ts.map +0 -1
  203. package/dist/utils.test.js +0 -15
  204. package/dist/utils.test.js.map +0 -1
package/dist/index.cjs ADDED
@@ -0,0 +1,1321 @@
1
+ 'use strict';
2
+
3
+ // src/result.ts
4
+ var OkResultImpl = class _OkResultImpl {
5
+ constructor(value) {
6
+ this.value = value;
7
+ }
8
+ isOk = true;
9
+ isErr = false;
10
+ map(fn) {
11
+ return new _OkResultImpl(fn(this.value));
12
+ }
13
+ // 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)
14
+ mapErr(_fn) {
15
+ return this;
16
+ }
17
+ andThen(fn) {
18
+ return fn(this.value);
19
+ }
20
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _onErr is intentionally unused: OkResult.match always calls onOk
21
+ match(onOk, _onErr) {
22
+ return onOk(this.value);
23
+ }
24
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- _fallback is intentionally unused: OkResult.unwrapOr always returns value
25
+ unwrapOr(_fallback) {
26
+ return this.value;
27
+ }
28
+ };
29
+ var ErrResultImpl = class _ErrResultImpl {
30
+ // eslint-disable-next-line n/handle-callback-err -- 'error' is a stored value, not a Node.js callback error parameter
31
+ constructor(error) {
32
+ this.error = error;
33
+ }
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
+ }
53
+ };
54
+ function ok(value) {
55
+ return new OkResultImpl(value);
56
+ }
57
+ function err(error) {
58
+ return new ErrResultImpl(error);
59
+ }
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
+ }
160
+ };
161
+
162
+ // src/errors.ts
163
+ 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
+ }
172
+ };
173
+ function rateLimitError(retryAfter) {
174
+ return { type: "rate_limit", retryAfter };
175
+ }
176
+ function authFailedError(status) {
177
+ return { type: "auth_failed", status };
178
+ }
179
+ function networkError(cause) {
180
+ return { type: "network", cause };
181
+ }
182
+ function apiError(status, body) {
183
+ return { type: "api_error", status, body };
184
+ }
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.next_url;
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.next_url;
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
+ }
249
+ };
250
+ function failedPaginated(error, http, getItems) {
251
+ return new PaginatedResultAsync(
252
+ Promise.resolve(err(error)),
253
+ http,
254
+ getItems
255
+ );
256
+ }
257
+
258
+ // src/auth.ts
259
+ var T = Array.from(
260
+ { length: 64 },
261
+ (_, i) => Math.floor(Math.abs(Math.sin(i + 1)) * 2 ** 32)
262
+ );
263
+ var S = [
264
+ 7,
265
+ 12,
266
+ 17,
267
+ 22,
268
+ 7,
269
+ 12,
270
+ 17,
271
+ 22,
272
+ 7,
273
+ 12,
274
+ 17,
275
+ 22,
276
+ 7,
277
+ 12,
278
+ 17,
279
+ 22,
280
+ 5,
281
+ 9,
282
+ 14,
283
+ 20,
284
+ 5,
285
+ 9,
286
+ 14,
287
+ 20,
288
+ 5,
289
+ 9,
290
+ 14,
291
+ 20,
292
+ 5,
293
+ 9,
294
+ 14,
295
+ 20,
296
+ 4,
297
+ 11,
298
+ 16,
299
+ 23,
300
+ 4,
301
+ 11,
302
+ 16,
303
+ 23,
304
+ 4,
305
+ 11,
306
+ 16,
307
+ 23,
308
+ 4,
309
+ 11,
310
+ 16,
311
+ 23,
312
+ 6,
313
+ 10,
314
+ 15,
315
+ 21,
316
+ 6,
317
+ 10,
318
+ 15,
319
+ 21,
320
+ 6,
321
+ 10,
322
+ 15,
323
+ 21,
324
+ 6,
325
+ 10,
326
+ 15,
327
+ 21
328
+ ];
329
+ function md5Bytes(bytes) {
330
+ const len = bytes.length;
331
+ bytes.push(128);
332
+ while (bytes.length % 64 !== 56) bytes.push(0);
333
+ const bitLen = len * 8;
334
+ for (let i = 0; i < 8; i++) {
335
+ bytes.push(i < 4 ? bitLen >>> i * 8 & 255 : 0);
336
+ }
337
+ let a = 1732584193;
338
+ let b = 4023233417;
339
+ let c = 2562383102;
340
+ let d = 271733878;
341
+ for (let chunk = 0; chunk < bytes.length; chunk += 64) {
342
+ const M = [];
343
+ for (let w = 0; w < 16; w++) {
344
+ const off = chunk + w * 4;
345
+ M.push(
346
+ bytes[off] | bytes[off + 1] << 8 | bytes[off + 2] << 16 | bytes[off + 3] << 24
347
+ );
348
+ }
349
+ let aa = a;
350
+ let bb = b;
351
+ let cc = c;
352
+ let dd = d;
353
+ for (let i = 0; i < 64; i++) {
354
+ let f;
355
+ let g;
356
+ if (i < 16) {
357
+ f = bb & cc | ~bb & dd;
358
+ g = i;
359
+ } else if (i < 32) {
360
+ f = dd & bb | ~dd & cc;
361
+ g = (5 * i + 1) % 16;
362
+ } else if (i < 48) {
363
+ f = bb ^ cc ^ dd;
364
+ g = (3 * i + 5) % 16;
365
+ } else {
366
+ f = cc ^ (bb | ~dd);
367
+ g = 7 * i % 16;
368
+ }
369
+ const tmp = dd;
370
+ dd = cc;
371
+ cc = bb;
372
+ const sum = Math.trunc(aa + f + M[g] + T[i]);
373
+ const rotated = sum << S[i] | sum >>> 32 - S[i];
374
+ bb = Math.trunc(bb + rotated);
375
+ aa = tmp;
376
+ }
377
+ a = Math.trunc(a + aa);
378
+ b = Math.trunc(b + bb);
379
+ c = Math.trunc(c + cc);
380
+ d = Math.trunc(d + dd);
381
+ }
382
+ return [a, b, c, d].map(
383
+ (n) => [n & 255, n >>> 8 & 255, n >>> 16 & 255, n >>> 24 & 255].map((byte) => byte.toString(16).padStart(2, "0")).join("")
384
+ ).join("");
385
+ }
386
+ function md5(input) {
387
+ const bytes = [];
388
+ for (let i = 0; i < input.length; i++) {
389
+ const code = input.codePointAt(i) ?? 0;
390
+ if (code < 128) {
391
+ bytes.push(code);
392
+ } else if (code < 2048) {
393
+ bytes.push(192 | code >> 6, 128 | code & 63);
394
+ } else {
395
+ bytes.push(
396
+ 224 | code >> 12,
397
+ 128 | code >> 6 & 63,
398
+ 128 | code & 63
399
+ );
400
+ }
401
+ }
402
+ return md5Bytes(bytes);
403
+ }
404
+ var CLIENT_ID = "MOBrBDS8blbauoSck0ZfDbtuzpyT";
405
+ var CLIENT_SECRET = "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj";
406
+ var HASH_SECRET = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c";
407
+ var AUTH_URL = "https://oauth.secure.pixiv.net/auth/token";
408
+ function buildClientHash(localTime) {
409
+ return md5(localTime + HASH_SECRET);
410
+ }
411
+ var AuthManager = class _AuthManager {
412
+ #accessToken;
413
+ #refreshToken;
414
+ userId;
415
+ constructor(credentials) {
416
+ this.#accessToken = credentials.accessToken;
417
+ this.#refreshToken = credentials.refreshToken;
418
+ this.userId = credentials.userId;
419
+ }
420
+ /** Returns the current access token. */
421
+ get accessToken() {
422
+ return this.#accessToken;
423
+ }
424
+ /** Returns the current refresh token. */
425
+ get refreshToken() {
426
+ return this.#refreshToken;
427
+ }
428
+ /**
429
+ * Exchanges the stored refresh token for a fresh access token.
430
+ *
431
+ * Updates the internal credentials on success.
432
+ * Throws if the token endpoint returns a non-200 response.
433
+ */
434
+ async refresh() {
435
+ const localTime = (/* @__PURE__ */ new Date()).toISOString().replace(/Z$/, "+00:00");
436
+ const headers = {
437
+ "x-client-time": localTime,
438
+ "x-client-hash": buildClientHash(localTime),
439
+ "app-os": "ios",
440
+ "app-os-version": "16.4.1",
441
+ "user-agent": "PixivIOSApp/7.16.9 (iOS 16.4.1; iPad13,4)",
442
+ "Content-Type": "application/x-www-form-urlencoded"
443
+ };
444
+ const body = new URLSearchParams({
445
+ client_id: CLIENT_ID,
446
+ client_secret: CLIENT_SECRET,
447
+ get_secure_url: "1",
448
+ grant_type: "refresh_token",
449
+ refresh_token: this.#refreshToken
450
+ }).toString();
451
+ const response = await fetch(AUTH_URL, {
452
+ method: "POST",
453
+ headers,
454
+ body
455
+ });
456
+ if (response.status !== 200) {
457
+ throw new Error(
458
+ `Failed to refresh pixiv token: HTTP ${response.status}`
459
+ );
460
+ }
461
+ const data = await response.json();
462
+ this.#accessToken = data.response.access_token;
463
+ this.#refreshToken = data.response.refresh_token;
464
+ this.userId = data.user.id;
465
+ }
466
+ /**
467
+ * Creates an `AuthManager` by performing the initial token refresh.
468
+ *
469
+ * @param refreshToken - Pixiv refresh token
470
+ * @returns Initialized `AuthManager`
471
+ */
472
+ static async login(refreshToken) {
473
+ const manager = new _AuthManager({
474
+ userId: "",
475
+ accessToken: "",
476
+ refreshToken
477
+ });
478
+ await manager.refresh();
479
+ return manager;
480
+ }
481
+ };
482
+
483
+ // src/http.ts
484
+ var DEFAULT_RETRY = { maxRetries: 3, waitMs: 1e4 };
485
+ var BASE_URL = "https://app-api.pixiv.net";
486
+ var DEFAULT_HEADERS = {
487
+ Host: "app-api.pixiv.net",
488
+ "App-OS": "ios",
489
+ "App-OS-Version": "14.6",
490
+ "User-Agent": "PixivIOSApp/7.13.3 (iOS 14.6; iPhone13,2)",
491
+ "Accept-Language": "ja"
492
+ };
493
+ function parseRetryAfter(retryAfter, defaultMs) {
494
+ if (!retryAfter) return defaultMs;
495
+ if (/^\d+$/.test(retryAfter.trim())) {
496
+ return Number.parseInt(retryAfter, 10) * 1e3;
497
+ }
498
+ const retryDate = Date.parse(retryAfter);
499
+ if (!Number.isNaN(retryDate)) {
500
+ return Math.max(0, retryDate - Date.now());
501
+ }
502
+ return defaultMs;
503
+ }
504
+ function headersToRecord(headers) {
505
+ const result = {};
506
+ for (const [key, value] of headers) {
507
+ result[key] = value;
508
+ }
509
+ return result;
510
+ }
511
+ var HttpClient = class {
512
+ #auth;
513
+ #retry;
514
+ #interceptor;
515
+ constructor(auth, options) {
516
+ this.#auth = auth;
517
+ this.#retry = {
518
+ maxRetries: options?.retry?.maxRetries ?? DEFAULT_RETRY.maxRetries,
519
+ waitMs: options?.retry?.waitMs ?? DEFAULT_RETRY.waitMs
520
+ };
521
+ this.#interceptor = options?.onResponse;
522
+ }
523
+ /**
524
+ * Sends a GET request to the pixiv API.
525
+ *
526
+ * @param path - API endpoint path (e.g. "/v1/illust/detail")
527
+ * @param params - Query parameters as a URLSearchParams instance
528
+ * @returns `ResultAsync<T, PixivError>`
529
+ */
530
+ get(path, params) {
531
+ const qs = params ? `?${params.toString()}` : "";
532
+ const url = `${BASE_URL}${path}${qs}`;
533
+ return this.#send(url, "GET", path, void 0);
534
+ }
535
+ /**
536
+ * Sends a POST request to the pixiv API.
537
+ *
538
+ * @param path - API endpoint path (e.g. "/v2/illust/bookmark/add")
539
+ * @param body - URL-encoded request body string
540
+ * @returns `ResultAsync<T, PixivError>`
541
+ */
542
+ post(path, body) {
543
+ const url = `${BASE_URL}${path}`;
544
+ return this.#send(url, "POST", path, body);
545
+ }
546
+ /**
547
+ * Fetches a pixiv image URL without an Authorization header.
548
+ *
549
+ * Uses a browser User-Agent and the pixiv Referer, which are required for
550
+ * image CDN access. Retry and interceptor are not applied here.
551
+ *
552
+ * @param imageUrl - Full image URL
553
+ * @returns `ResultAsync<Response, PixivError>`
554
+ */
555
+ fetchImage(imageUrl) {
556
+ return ResultAsync.fromPromise(
557
+ fetch(imageUrl, {
558
+ headers: {
559
+ "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",
560
+ Referer: "https://www.pixiv.net/"
561
+ }
562
+ }),
563
+ networkError
564
+ ).andThen((response) => {
565
+ if (!response.ok) {
566
+ return ResultAsync.fromResult(
567
+ err(apiError(response.status, null))
568
+ );
569
+ }
570
+ return ResultAsync.fromResult(ok(response));
571
+ });
572
+ }
573
+ /**
574
+ * Sends a request to an absolute URL returned in a `next_url` field.
575
+ *
576
+ * Applies the same retry / interceptor / auth logic as `get()`.
577
+ *
578
+ * @param absoluteUrl - Full URL including query string
579
+ * @returns `ResultAsync<T, PixivError>`
580
+ */
581
+ getAbsolute(absoluteUrl) {
582
+ let endpoint;
583
+ try {
584
+ endpoint = new URL(absoluteUrl).pathname;
585
+ } catch {
586
+ endpoint = absoluteUrl;
587
+ }
588
+ return this.#send(absoluteUrl, "GET", endpoint, void 0);
589
+ }
590
+ // ---------------------------------------------------------------------------
591
+ // Private helpers
592
+ // ---------------------------------------------------------------------------
593
+ #send(url, method, endpoint, body) {
594
+ return new ResultAsync(this.#sendWithRetry(url, method, endpoint, body));
595
+ }
596
+ async #sendWithRetry(url, method, endpoint, body, allowRefresh = true) {
597
+ const maxRetries = Math.max(0, this.#retry.maxRetries);
598
+ const waitMs = Math.max(0, this.#retry.waitMs);
599
+ let lastRetryAfterMs = 0;
600
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
601
+ const requestHeaders = {
602
+ ...DEFAULT_HEADERS,
603
+ Authorization: `Bearer ${this.#auth.accessToken}`,
604
+ ...method === "POST" ? { "Content-Type": "application/x-www-form-urlencoded" } : {}
605
+ };
606
+ let response;
607
+ try {
608
+ response = await fetch(url, {
609
+ method,
610
+ headers: requestHeaders,
611
+ body: method === "POST" ? body : void 0
612
+ });
613
+ } catch (fetchError) {
614
+ return err(networkError(fetchError));
615
+ }
616
+ if (response.status === 429) {
617
+ await response.body?.cancel();
618
+ const retryAfterMs = parseRetryAfter(
619
+ response.headers.get("Retry-After"),
620
+ waitMs
621
+ );
622
+ lastRetryAfterMs = retryAfterMs;
623
+ if (attempt < maxRetries) {
624
+ await new Promise((resolve) => setTimeout(resolve, retryAfterMs));
625
+ continue;
626
+ }
627
+ return err(rateLimitError(lastRetryAfterMs));
628
+ }
629
+ if (response.status === 401) {
630
+ await response.body?.cancel();
631
+ if (allowRefresh) {
632
+ try {
633
+ await this.#auth.refresh();
634
+ } catch {
635
+ return err(authFailedError(401));
636
+ }
637
+ return this.#sendWithRetry(url, method, endpoint, body, false);
638
+ }
639
+ return err(authFailedError(401));
640
+ }
641
+ const contentType = response.headers.get("content-type") ?? "";
642
+ const text = await response.text();
643
+ let data;
644
+ const isJson = contentType.includes("application/json");
645
+ if (isJson) {
646
+ try {
647
+ data = JSON.parse(text);
648
+ } catch {
649
+ data = text;
650
+ }
651
+ } else {
652
+ data = text;
653
+ }
654
+ const responseHeaders = headersToRecord(response.headers);
655
+ if (!response.ok) {
656
+ return err(apiError(response.status, data));
657
+ }
658
+ const httpResponse = {
659
+ data,
660
+ status: response.status,
661
+ responseUrl: response.url || void 0};
662
+ if (this.#interceptor) {
663
+ const record = {
664
+ method,
665
+ endpoint,
666
+ url: response.url || url,
667
+ requestHeaders: JSON.stringify(requestHeaders),
668
+ requestBody: body ?? null,
669
+ responseType: isJson ? "JSON" : "TEXT",
670
+ statusCode: response.status,
671
+ responseHeaders: JSON.stringify(responseHeaders),
672
+ responseBody: isJson ? JSON.stringify(data) : text
673
+ };
674
+ Promise.resolve(this.#interceptor(record)).catch(() => void 0);
675
+ }
676
+ return ok(httpResponse.data);
677
+ }
678
+ return err(rateLimitError(lastRetryAfterMs));
679
+ }
680
+ };
681
+
682
+ // src/params.ts
683
+ function camelToSnake(key) {
684
+ return key.replaceAll(/([A-Z])/g, (m) => `_${m.toLowerCase()}`);
685
+ }
686
+ function toSnakeKeys(obj) {
687
+ const out = {};
688
+ for (const key of Object.keys(obj)) {
689
+ out[camelToSnake(key)] = obj[key];
690
+ }
691
+ return out;
692
+ }
693
+ function buildSearchParams(params) {
694
+ const usp = new URLSearchParams();
695
+ for (const [key, value] of Object.entries(params)) {
696
+ if (value === null || value === void 0) continue;
697
+ if (Array.isArray(value)) {
698
+ for (const item of value) usp.append(key, String(item));
699
+ } else {
700
+ usp.set(key, String(value));
701
+ }
702
+ }
703
+ return usp;
704
+ }
705
+ function buildParams(params) {
706
+ return buildSearchParams(toSnakeKeys(params));
707
+ }
708
+
709
+ // src/resources/illusts.ts
710
+ var IllustResource = class {
711
+ #http;
712
+ constructor(http) {
713
+ this.#http = http;
714
+ }
715
+ /**
716
+ * Fetches a single illust by ID.
717
+ * GET /v1/illust/detail
718
+ *
719
+ * @param params - Request parameters
720
+ */
721
+ detail(params) {
722
+ return this.#http.get(
723
+ "/v1/illust/detail",
724
+ buildParams({ illustId: params.illustId, filter: params.filter ?? "for_ios" })
725
+ );
726
+ }
727
+ /**
728
+ * Fetches related illusts for a given illust.
729
+ * GET /v2/illust/related
730
+ *
731
+ * @param params - Request parameters
732
+ */
733
+ related(params) {
734
+ return PaginatedResultAsync.fromResultAsync(
735
+ this.#http.get(
736
+ "/v2/illust/related",
737
+ buildParams({
738
+ illustId: params.illustId,
739
+ filter: params.filter ?? "for_ios",
740
+ ...params.seedIllustIds ? { seedIllustIds: params.seedIllustIds } : {}
741
+ })
742
+ ),
743
+ this.#http,
744
+ (page) => page.illusts
745
+ );
746
+ }
747
+ /**
748
+ * Searches for illusts.
749
+ * GET /v1/search/illust
750
+ *
751
+ * @param params - Request parameters
752
+ */
753
+ search(params) {
754
+ return PaginatedResultAsync.fromResultAsync(
755
+ this.#http.get(
756
+ "/v1/search/illust",
757
+ buildParams({
758
+ word: params.word,
759
+ searchTarget: params.searchTarget ?? "partial_match_for_tags",
760
+ sort: params.sort ?? "date_desc",
761
+ filter: params.filter ?? "for_ios",
762
+ duration: params.duration,
763
+ startDate: params.startDate,
764
+ endDate: params.endDate,
765
+ searchAiType: params.searchAiType,
766
+ offset: params.offset
767
+ })
768
+ ),
769
+ this.#http,
770
+ (page) => page.illusts
771
+ );
772
+ }
773
+ /**
774
+ * Fetches the illust ranking.
775
+ * GET /v1/illust/ranking
776
+ *
777
+ * @param params - Request parameters
778
+ */
779
+ ranking(params = {}) {
780
+ return PaginatedResultAsync.fromResultAsync(
781
+ this.#http.get(
782
+ "/v1/illust/ranking",
783
+ buildParams({
784
+ mode: params.mode ?? "day",
785
+ filter: params.filter ?? "for_ios",
786
+ date: params.date,
787
+ offset: params.offset
788
+ })
789
+ ),
790
+ this.#http,
791
+ (page) => page.illusts
792
+ );
793
+ }
794
+ /**
795
+ * Fetches recommended illusts.
796
+ * GET /v1/illust/recommended
797
+ *
798
+ * @param params - Request parameters
799
+ */
800
+ recommended(params = {}) {
801
+ return PaginatedResultAsync.fromResultAsync(
802
+ this.#http.get(
803
+ "/v1/illust/recommended",
804
+ buildParams({
805
+ filter: params.filter ?? "for_ios",
806
+ includeRankingLabel: true,
807
+ includeRankingIllusts: true,
808
+ includePrivacyPolicy: true,
809
+ offset: params.offset
810
+ })
811
+ ),
812
+ this.#http,
813
+ (page) => page.illusts
814
+ );
815
+ }
816
+ /**
817
+ * Fetches an illust series.
818
+ * GET /v1/illust/series
819
+ *
820
+ * @param params - Request parameters
821
+ */
822
+ series(params) {
823
+ return PaginatedResultAsync.fromResultAsync(
824
+ this.#http.get(
825
+ "/v1/illust/series",
826
+ buildParams({
827
+ illustSeriesId: params.illustSeriesId,
828
+ filter: params.filter ?? "for_ios"
829
+ })
830
+ ),
831
+ this.#http,
832
+ (page) => page.illusts
833
+ );
834
+ }
835
+ /**
836
+ * Adds an illust bookmark.
837
+ * POST /v2/illust/bookmark/add
838
+ *
839
+ * @param params - Request parameters
840
+ */
841
+ bookmarkAdd(params) {
842
+ const body = buildParams({
843
+ illustId: params.illustId,
844
+ restrict: params.restrict ?? "public",
845
+ ...params.tags ? { tags: params.tags } : {}
846
+ });
847
+ return this.#http.post(
848
+ "/v2/illust/bookmark/add",
849
+ body.toString()
850
+ );
851
+ }
852
+ /**
853
+ * Removes an illust bookmark.
854
+ * POST /v1/illust/bookmark/delete
855
+ *
856
+ * @param params - Request parameters
857
+ */
858
+ bookmarkDelete(params) {
859
+ const body = buildParams({ illustId: String(params.illustId) });
860
+ return this.#http.post(
861
+ "/v1/illust/bookmark/delete",
862
+ body.toString()
863
+ );
864
+ }
865
+ };
866
+
867
+ // src/resources/novels.ts
868
+ var NovelResource = class {
869
+ #http;
870
+ constructor(http) {
871
+ this.#http = http;
872
+ }
873
+ /**
874
+ * Fetches a single novel by ID.
875
+ * GET /v2/novel/detail
876
+ *
877
+ * @param params - Request parameters
878
+ */
879
+ detail(params) {
880
+ return this.#http.get(
881
+ "/v2/novel/detail",
882
+ buildParams({ novelId: params.novelId })
883
+ );
884
+ }
885
+ /**
886
+ * Fetches the WebView HTML for a novel.
887
+ * GET /webview/v2/novel
888
+ *
889
+ * Returns the raw HTML page that the pixiv app renders in a WebView.
890
+ * To extract the plain text, parse the returned HTML (e.g. strip tags).
891
+ *
892
+ * @param params - Request parameters
893
+ */
894
+ text(params) {
895
+ return this.#http.get(
896
+ "/webview/v2/novel",
897
+ // The webview endpoint uses the query parameter 'id', not 'novel_id'
898
+ buildParams({ id: params.novelId })
899
+ );
900
+ }
901
+ /**
902
+ * Fetches related novels for a given novel.
903
+ * GET /v1/novel/related
904
+ *
905
+ * @param params - Request parameters
906
+ */
907
+ related(params) {
908
+ return PaginatedResultAsync.fromResultAsync(
909
+ this.#http.get(
910
+ "/v1/novel/related",
911
+ buildParams({ novelId: params.novelId })
912
+ ),
913
+ this.#http,
914
+ (page) => page.novels
915
+ );
916
+ }
917
+ /**
918
+ * Searches for novels.
919
+ * GET /v1/search/novel
920
+ *
921
+ * @param params - Request parameters
922
+ */
923
+ search(params) {
924
+ return PaginatedResultAsync.fromResultAsync(
925
+ this.#http.get(
926
+ "/v1/search/novel",
927
+ buildParams({
928
+ word: params.word,
929
+ searchTarget: params.searchTarget ?? "partial_match_for_tags",
930
+ sort: params.sort ?? "date_desc",
931
+ filter: params.filter ?? "for_ios",
932
+ duration: params.duration,
933
+ startDate: params.startDate,
934
+ endDate: params.endDate,
935
+ searchAiType: params.searchAiType,
936
+ offset: params.offset
937
+ })
938
+ ),
939
+ this.#http,
940
+ (page) => page.novels
941
+ );
942
+ }
943
+ /**
944
+ * Fetches the novel ranking.
945
+ * GET /v1/novel/ranking
946
+ *
947
+ * @param params - Request parameters
948
+ */
949
+ ranking(params = {}) {
950
+ return PaginatedResultAsync.fromResultAsync(
951
+ this.#http.get(
952
+ "/v1/novel/ranking",
953
+ buildParams({
954
+ mode: params.mode ?? "day",
955
+ filter: params.filter ?? "for_ios",
956
+ date: params.date,
957
+ offset: params.offset
958
+ })
959
+ ),
960
+ this.#http,
961
+ (page) => page.novels
962
+ );
963
+ }
964
+ /**
965
+ * Fetches recommended novels.
966
+ * GET /v1/novel/recommended
967
+ *
968
+ * @param params - Request parameters
969
+ */
970
+ recommended(params = {}) {
971
+ return PaginatedResultAsync.fromResultAsync(
972
+ this.#http.get(
973
+ "/v1/novel/recommended",
974
+ buildParams({
975
+ filter: params.filter ?? "for_ios",
976
+ includeRankingNovels: true,
977
+ includePrivacyPolicy: true,
978
+ offset: params.offset
979
+ })
980
+ ),
981
+ this.#http,
982
+ (page) => page.novels
983
+ );
984
+ }
985
+ /**
986
+ * Fetches a novel series.
987
+ * GET /v2/novel/series
988
+ *
989
+ * @param params - Request parameters
990
+ */
991
+ series(params) {
992
+ return PaginatedResultAsync.fromResultAsync(
993
+ this.#http.get(
994
+ "/v2/novel/series",
995
+ buildParams({ seriesId: params.seriesId, lastOrder: params.lastOrder })
996
+ ),
997
+ this.#http,
998
+ (page) => page.novels
999
+ );
1000
+ }
1001
+ /**
1002
+ * Adds a novel bookmark.
1003
+ * POST /v2/novel/bookmark/add
1004
+ *
1005
+ * @param params - Request parameters
1006
+ */
1007
+ bookmarkAdd(params) {
1008
+ const body = buildParams({
1009
+ novelId: params.novelId,
1010
+ restrict: params.restrict ?? "public",
1011
+ ...params.tags ? { tags: params.tags } : {}
1012
+ });
1013
+ return this.#http.post(
1014
+ "/v2/novel/bookmark/add",
1015
+ body.toString()
1016
+ );
1017
+ }
1018
+ /**
1019
+ * Removes a novel bookmark.
1020
+ * POST /v1/novel/bookmark/delete
1021
+ *
1022
+ * @param params - Request parameters
1023
+ */
1024
+ bookmarkDelete(params) {
1025
+ const body = buildParams({ novelId: String(params.novelId) });
1026
+ return this.#http.post(
1027
+ "/v1/novel/bookmark/delete",
1028
+ body.toString()
1029
+ );
1030
+ }
1031
+ };
1032
+
1033
+ // src/resources/users.ts
1034
+ var UserBookmarksResource = class {
1035
+ #http;
1036
+ constructor(http) {
1037
+ this.#http = http;
1038
+ }
1039
+ /**
1040
+ * Fetches a user's bookmarked illusts.
1041
+ * GET /v1/user/bookmarks/illust
1042
+ *
1043
+ * @param params - Request parameters
1044
+ */
1045
+ illusts(params) {
1046
+ return PaginatedResultAsync.fromResultAsync(
1047
+ this.#http.get(
1048
+ "/v1/user/bookmarks/illust",
1049
+ buildParams({
1050
+ userId: params.userId,
1051
+ restrict: params.restrict ?? "public",
1052
+ filter: params.filter ?? "for_ios",
1053
+ tag: params.tag,
1054
+ maxBookmarkId: params.maxBookmarkId,
1055
+ offset: params.offset
1056
+ })
1057
+ ),
1058
+ this.#http,
1059
+ (page) => page.illusts
1060
+ );
1061
+ }
1062
+ /**
1063
+ * Fetches a user's bookmarked novels.
1064
+ * GET /v1/user/bookmarks/novel
1065
+ *
1066
+ * @param params - Request parameters
1067
+ */
1068
+ novels(params) {
1069
+ return PaginatedResultAsync.fromResultAsync(
1070
+ this.#http.get(
1071
+ "/v1/user/bookmarks/novel",
1072
+ buildParams({
1073
+ userId: params.userId,
1074
+ restrict: params.restrict ?? "public",
1075
+ filter: params.filter ?? "for_ios",
1076
+ tag: params.tag,
1077
+ offset: params.offset
1078
+ })
1079
+ ),
1080
+ this.#http,
1081
+ (page) => page.novels
1082
+ );
1083
+ }
1084
+ };
1085
+ var UserResource = class {
1086
+ /** User bookmarks sub-namespace. */
1087
+ bookmarks;
1088
+ #http;
1089
+ constructor(http) {
1090
+ this.#http = http;
1091
+ this.bookmarks = new UserBookmarksResource(http);
1092
+ }
1093
+ /**
1094
+ * Fetches detailed profile information for a user.
1095
+ * GET /v1/user/detail
1096
+ *
1097
+ * @param params - Request parameters
1098
+ */
1099
+ detail(params) {
1100
+ return this.#http.get(
1101
+ "/v1/user/detail",
1102
+ buildParams({ userId: params.userId, filter: params.filter ?? "for_ios" })
1103
+ );
1104
+ }
1105
+ /**
1106
+ * Fetches illusts posted by a user.
1107
+ * GET /v1/user/illusts
1108
+ *
1109
+ * @param params - Request parameters
1110
+ */
1111
+ illusts(params) {
1112
+ return PaginatedResultAsync.fromResultAsync(
1113
+ this.#http.get(
1114
+ "/v1/user/illusts",
1115
+ buildParams({
1116
+ userId: params.userId,
1117
+ type: params.type,
1118
+ filter: params.filter ?? "for_ios",
1119
+ offset: params.offset
1120
+ })
1121
+ ),
1122
+ this.#http,
1123
+ (page) => page.illusts
1124
+ );
1125
+ }
1126
+ /**
1127
+ * Fetches novels posted by a user.
1128
+ * GET /v1/user/novels
1129
+ *
1130
+ * @param params - Request parameters
1131
+ */
1132
+ novels(params) {
1133
+ return PaginatedResultAsync.fromResultAsync(
1134
+ this.#http.get(
1135
+ "/v1/user/novels",
1136
+ buildParams({
1137
+ userId: params.userId,
1138
+ filter: params.filter ?? "for_ios",
1139
+ offset: params.offset
1140
+ })
1141
+ ),
1142
+ this.#http,
1143
+ (page) => page.novels
1144
+ );
1145
+ }
1146
+ /**
1147
+ * Fetches the list of users that a user is following.
1148
+ * GET /v1/user/following
1149
+ *
1150
+ * @param params - Request parameters
1151
+ */
1152
+ following(params) {
1153
+ return PaginatedResultAsync.fromResultAsync(
1154
+ this.#http.get(
1155
+ "/v1/user/following",
1156
+ buildParams({
1157
+ userId: params.userId,
1158
+ restrict: params.restrict ?? "public",
1159
+ offset: params.offset
1160
+ })
1161
+ ),
1162
+ this.#http,
1163
+ (page) => page.user_previews
1164
+ );
1165
+ }
1166
+ /**
1167
+ * Follows a user.
1168
+ * POST /v1/user/follow/add
1169
+ *
1170
+ * @param params - Request parameters
1171
+ */
1172
+ followAdd(params) {
1173
+ const body = buildParams({
1174
+ userId: params.userId,
1175
+ restrict: params.restrict ?? "public"
1176
+ });
1177
+ return this.#http.post(
1178
+ "/v1/user/follow/add",
1179
+ body.toString()
1180
+ );
1181
+ }
1182
+ /**
1183
+ * Unfollows a user.
1184
+ * POST /v1/user/follow/delete
1185
+ *
1186
+ * @param params - Request parameters
1187
+ */
1188
+ followDelete(params) {
1189
+ const body = buildParams({ userId: String(params.userId) });
1190
+ return this.#http.post(
1191
+ "/v1/user/follow/delete",
1192
+ body.toString()
1193
+ );
1194
+ }
1195
+ };
1196
+
1197
+ // src/resources/manga.ts
1198
+ var MangaResource = class {
1199
+ #http;
1200
+ constructor(http) {
1201
+ this.#http = http;
1202
+ }
1203
+ /**
1204
+ * Fetches recommended manga.
1205
+ * GET /v1/manga/recommended
1206
+ *
1207
+ * @param params - Request parameters
1208
+ */
1209
+ recommended(params = {}) {
1210
+ return PaginatedResultAsync.fromResultAsync(
1211
+ this.#http.get(
1212
+ "/v1/manga/recommended",
1213
+ buildParams({
1214
+ filter: params.filter ?? "for_ios",
1215
+ includeRankingIllusts: true,
1216
+ includePrivacyPolicy: true,
1217
+ offset: params.offset
1218
+ })
1219
+ ),
1220
+ this.#http,
1221
+ (page) => page.illusts
1222
+ );
1223
+ }
1224
+ };
1225
+
1226
+ // src/resources/ugoira.ts
1227
+ var UgoiraResource = class {
1228
+ #http;
1229
+ constructor(http) {
1230
+ this.#http = http;
1231
+ }
1232
+ /**
1233
+ * Fetches ugoira metadata (ZIP URL and per-frame timings).
1234
+ * GET /v1/ugoira/metadata
1235
+ *
1236
+ * @param params - Request parameters
1237
+ */
1238
+ metadata(params) {
1239
+ return this.#http.get(
1240
+ "/v1/ugoira/metadata",
1241
+ buildParams({ illustId: params.illustId })
1242
+ );
1243
+ }
1244
+ };
1245
+
1246
+ // src/resources/images.ts
1247
+ var ImageResource = class {
1248
+ #http;
1249
+ constructor(http) {
1250
+ this.#http = http;
1251
+ }
1252
+ /**
1253
+ * Fetches a pixiv image.
1254
+ *
1255
+ * Uses a browser User-Agent and Referer (required for pixiv CDN).
1256
+ * No Authorization header is sent.
1257
+ *
1258
+ * @param imageUrl - Full CDN image URL
1259
+ */
1260
+ fetch(imageUrl) {
1261
+ return this.#http.fetchImage(imageUrl);
1262
+ }
1263
+ };
1264
+
1265
+ // src/client.ts
1266
+ var PixivClient = class _PixivClient {
1267
+ /** Illust API namespace. */
1268
+ illusts;
1269
+ /** Novel API namespace. */
1270
+ novels;
1271
+ /** User API namespace. */
1272
+ users;
1273
+ /** Manga API namespace. */
1274
+ manga;
1275
+ /** Ugoira API namespace. */
1276
+ ugoira;
1277
+ /** Image fetch helpers. */
1278
+ images;
1279
+ #auth;
1280
+ constructor(auth, http) {
1281
+ this.#auth = auth;
1282
+ this.illusts = new IllustResource(http);
1283
+ this.novels = new NovelResource(http);
1284
+ this.users = new UserResource(http);
1285
+ this.manga = new MangaResource(http);
1286
+ this.ugoira = new UgoiraResource(http);
1287
+ this.images = new ImageResource(http);
1288
+ }
1289
+ /**
1290
+ * Numeric user ID of the authenticated account, as a string.
1291
+ *
1292
+ * Available immediately after {@link PixivClient.of} resolves.
1293
+ */
1294
+ get userId() {
1295
+ return this.#auth.userId;
1296
+ }
1297
+ /**
1298
+ * Creates a PixivClient by refreshing the given token.
1299
+ *
1300
+ * @param refreshToken - Pixiv refresh token
1301
+ * @param options - Optional retry and response interceptor configuration
1302
+ * @returns A fully initialised {@link PixivClient}
1303
+ */
1304
+ static async of(refreshToken, options) {
1305
+ const auth = await AuthManager.login(refreshToken);
1306
+ const http = new HttpClient(auth, options);
1307
+ return new _PixivClient(auth, http);
1308
+ }
1309
+ };
1310
+
1311
+ exports.PaginatedResultAsync = PaginatedResultAsync;
1312
+ exports.PixivClient = PixivClient;
1313
+ exports.PixivFetchError = PixivFetchError;
1314
+ exports.ResultAsync = ResultAsync;
1315
+ exports.apiError = apiError;
1316
+ exports.authFailedError = authFailedError;
1317
+ exports.err = err;
1318
+ exports.failedPaginated = failedPaginated;
1319
+ exports.networkError = networkError;
1320
+ exports.ok = ok;
1321
+ exports.rateLimitError = rateLimitError;