@kongyo2/nicotag-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/dist/errors.js +69 -0
  4. package/dist/errors.js.map +1 -0
  5. package/dist/extractors/cheerio.js +17 -0
  6. package/dist/extractors/cheerio.js.map +1 -0
  7. package/dist/extractors/index.js +24 -0
  8. package/dist/extractors/index.js.map +1 -0
  9. package/dist/extractors/manual-decode.js +41 -0
  10. package/dist/extractors/manual-decode.js.map +1 -0
  11. package/dist/extractors/node-html-parser.js +19 -0
  12. package/dist/extractors/node-html-parser.js.map +1 -0
  13. package/dist/extractors/regex-he.js +20 -0
  14. package/dist/extractors/regex-he.js.map +1 -0
  15. package/dist/extractors/regex-manual.js +20 -0
  16. package/dist/extractors/regex-manual.js.map +1 -0
  17. package/dist/extractors/types.js +2 -0
  18. package/dist/extractors/types.js.map +1 -0
  19. package/dist/fetcher.js +145 -0
  20. package/dist/fetcher.js.map +1 -0
  21. package/dist/index.js +10 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/parsers/server-response.js +28 -0
  24. package/dist/parsers/server-response.js.map +1 -0
  25. package/dist/types.js +68 -0
  26. package/dist/types.js.map +1 -0
  27. package/dist/url.js +9 -0
  28. package/dist/url.js.map +1 -0
  29. package/dist/video-id.js +22 -0
  30. package/dist/video-id.js.map +1 -0
  31. package/dist/watch.js +59 -0
  32. package/dist/watch.js.map +1 -0
  33. package/dist-types/errors.d.ts +55 -0
  34. package/dist-types/errors.d.ts.map +1 -0
  35. package/dist-types/extractors/cheerio.d.ts +3 -0
  36. package/dist-types/extractors/cheerio.d.ts.map +1 -0
  37. package/dist-types/extractors/index.d.ts +10 -0
  38. package/dist-types/extractors/index.d.ts.map +1 -0
  39. package/dist-types/extractors/manual-decode.d.ts +2 -0
  40. package/dist-types/extractors/manual-decode.d.ts.map +1 -0
  41. package/dist-types/extractors/node-html-parser.d.ts +3 -0
  42. package/dist-types/extractors/node-html-parser.d.ts.map +1 -0
  43. package/dist-types/extractors/regex-he.d.ts +3 -0
  44. package/dist-types/extractors/regex-he.d.ts.map +1 -0
  45. package/dist-types/extractors/regex-manual.d.ts +3 -0
  46. package/dist-types/extractors/regex-manual.d.ts.map +1 -0
  47. package/dist-types/extractors/types.d.ts +6 -0
  48. package/dist-types/extractors/types.d.ts.map +1 -0
  49. package/dist-types/fetcher.d.ts +26 -0
  50. package/dist-types/fetcher.d.ts.map +1 -0
  51. package/dist-types/index.d.ts +10 -0
  52. package/dist-types/index.d.ts.map +1 -0
  53. package/dist-types/parsers/server-response.d.ts +3 -0
  54. package/dist-types/parsers/server-response.d.ts.map +1 -0
  55. package/dist-types/types.d.ts +239 -0
  56. package/dist-types/types.d.ts.map +1 -0
  57. package/dist-types/url.d.ts +7 -0
  58. package/dist-types/url.d.ts.map +1 -0
  59. package/dist-types/video-id.d.ts +7 -0
  60. package/dist-types/video-id.d.ts.map +1 -0
  61. package/dist-types/watch.d.ts +40 -0
  62. package/dist-types/watch.d.ts.map +1 -0
  63. package/package.json +67 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kongyo2
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @kongyo2/nicotag-api
2
+
3
+ ニコニコ動画(nicovideo.jp)の動画 ID からタグ一覧を取得する非公式クライアントです。
4
+
5
+ ## 特徴
6
+
7
+ - 動画 ID(`sm` / `nm` / `so` プレフィックス)の入力からタグ配列を取得
8
+ - 4 種類の抽出器を順番に試行する多段フォールバック(cheerio → node-html-parser → 正規表現 2 種)
9
+ - リトライ(指数バックオフ + ジッター)、タイムアウト、`AbortSignal` 対応
10
+ - Zod による型安全なレスポンス検証
11
+ - ランタイム依存は `zod` / `cheerio` / `node-html-parser` / `he` のみ
12
+ - TypeScript ファースト・ESM 専用
13
+
14
+ ## 要件
15
+
16
+ - Node.js >= 20
17
+
18
+ ## インストール
19
+
20
+ ```bash
21
+ npm install @kongyo2/nicotag-api
22
+ ```
23
+
24
+ ## 使い方
25
+
26
+ ### タグ名一覧だけ欲しい
27
+
28
+ ```ts
29
+ import { fetchVideoTags } from '@kongyo2/nicotag-api';
30
+
31
+ const result = await fetchVideoTags({ videoId: 'sm9' });
32
+
33
+ console.log(result.tagNames);
34
+ // 例: ['陰陽師', 'レッツゴー!陰陽師', 'ニコニコ動画', ...]
35
+
36
+ for (const tag of result.tags) {
37
+ console.log(tag.name, tag.isCategory, tag.isLocked);
38
+ }
39
+ ```
40
+
41
+ ### 動画情報も含めて取得
42
+
43
+ ```ts
44
+ import { fetchVideo } from '@kongyo2/nicotag-api';
45
+
46
+ const result = await fetchVideo({ videoId: 'sm9' });
47
+
48
+ console.log(result.video.title); // 動画タイトル
49
+ console.log(result.video.count?.view); // 再生数
50
+ console.log(result.tag.items.length); // タグ数
51
+ ```
52
+
53
+ ### HTTP オプション(タイムアウト・リトライ・中断)
54
+
55
+ ```ts
56
+ const ac = new AbortController();
57
+ setTimeout(() => ac.abort(), 5_000);
58
+
59
+ await fetchVideoTags({
60
+ videoId: 'sm9',
61
+ http: {
62
+ timeoutMs: 10_000,
63
+ signal: ac.signal,
64
+ retry: { maxRetries: 3, initialDelayMs: 500 },
65
+ userAgent: 'my-app/1.0',
66
+ },
67
+ });
68
+ ```
69
+
70
+ ### HTML のみをパース(取得は自前で行う場合)
71
+
72
+ ```ts
73
+ import { extractAndParse } from '@kongyo2/nicotag-api';
74
+
75
+ const html = await (await fetch('https://www.nicovideo.jp/watch/sm9')).text();
76
+ const { parsed, extractorUsed } = extractAndParse(html);
77
+
78
+ console.log(parsed.data.response.tag.items);
79
+ ```
80
+
81
+ ## 動画 ID
82
+
83
+ `sm` / `nm` / `so` で始まり、後ろが数字のもののみ受け付けます(例: `sm9`, `nm12345`, `so9876`)。
84
+ 不正な ID を渡した場合は `InvalidVideoIdError` が送出されます。
85
+
86
+ ## エラー
87
+
88
+ すべての例外は `NicoTagError` のサブクラスとして送出されます。
89
+
90
+ | クラス | 用途 |
91
+ | --- | --- |
92
+ | `InvalidVideoIdError` | 動画 ID が `/^(sm\|nm\|so)\d+$/` に一致しない |
93
+ | `FetchError` | HTTP エラー / ネットワーク失敗 |
94
+ | `TimeoutError` | タイムアウト |
95
+ | `AbortError` | ユーザー側からの中断 |
96
+ | `ExtractError` | 全抽出器が失敗(各試行内容は `attempts` に格納) |
97
+ | `ParseError` | JSON のパース失敗 |
98
+ | `ValidationError` | Zod スキーマ検証失敗(`issues` を含む) |
99
+
100
+ ## 開発
101
+
102
+ ```bash
103
+ npm install
104
+ npm run typecheck
105
+ npm run lint
106
+ npm test
107
+ npm run build
108
+ ```
109
+
110
+ ライブテスト(実際の nicovideo.jp にアクセス):
111
+
112
+ ```bash
113
+ NICOTAG_LIVE=1 npm run test:live
114
+ ```
115
+
116
+ ## ライセンス
117
+
118
+ [MIT](./LICENSE) © kongyo2
package/dist/errors.js ADDED
@@ -0,0 +1,69 @@
1
+ export class NicoTagError extends Error {
2
+ name = 'NicoTagError';
3
+ url;
4
+ videoId;
5
+ attempt;
6
+ constructor(message, options = {}) {
7
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
8
+ if (options.url !== undefined)
9
+ this.url = options.url;
10
+ if (options.videoId !== undefined)
11
+ this.videoId = options.videoId;
12
+ if (options.attempt !== undefined)
13
+ this.attempt = options.attempt;
14
+ }
15
+ }
16
+ export class InvalidVideoIdError extends NicoTagError {
17
+ name = 'InvalidVideoIdError';
18
+ }
19
+ export class FetchError extends NicoTagError {
20
+ name = 'FetchError';
21
+ status;
22
+ statusText;
23
+ constructor(message, options = {}) {
24
+ super(message, options);
25
+ if (options.status !== undefined)
26
+ this.status = options.status;
27
+ if (options.statusText !== undefined)
28
+ this.statusText = options.statusText;
29
+ }
30
+ }
31
+ export class TimeoutError extends NicoTagError {
32
+ name = 'TimeoutError';
33
+ }
34
+ export class AbortError extends NicoTagError {
35
+ name = 'AbortError';
36
+ }
37
+ export class ExtractError extends NicoTagError {
38
+ name = 'ExtractError';
39
+ attempts;
40
+ constructor(message, attempts, options = {}) {
41
+ super(message, options);
42
+ this.attempts = attempts;
43
+ }
44
+ }
45
+ export class ParseError extends NicoTagError {
46
+ name = 'ParseError';
47
+ snippet;
48
+ constructor(message, options = {}) {
49
+ super(message, options);
50
+ if (options.snippet !== undefined)
51
+ this.snippet = options.snippet;
52
+ }
53
+ }
54
+ export class ValidationError extends NicoTagError {
55
+ name = 'ValidationError';
56
+ issues;
57
+ constructor(message, issues, options = {}) {
58
+ super(message, options);
59
+ this.issues = issues;
60
+ }
61
+ }
62
+ export function describeError(error) {
63
+ if (error instanceof Error) {
64
+ const causeMsg = error.cause instanceof Error ? `: ${error.cause.message}` : '';
65
+ return `${error.name}: ${error.message}${causeMsg}`;
66
+ }
67
+ return String(error);
68
+ }
69
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import * as cheerio from 'cheerio';
2
+ export const cheerioExtractor = {
3
+ name: 'cheerio',
4
+ extract(html) {
5
+ const $ = cheerio.load(html, { xml: false });
6
+ const tag = $('meta[name="server-response"]').first();
7
+ if (tag.length === 0) {
8
+ throw new Error('meta[name="server-response"] not found via cheerio');
9
+ }
10
+ const content = tag.attr('content');
11
+ if (content === undefined || content.length === 0) {
12
+ throw new Error('content attribute of server-response meta is empty (cheerio)');
13
+ }
14
+ return content;
15
+ },
16
+ };
17
+ //# sourceMappingURL=cheerio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cheerio.js","sourceRoot":"","sources":["../../src/extractors/cheerio.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AAGnC,MAAM,CAAC,MAAM,gBAAgB,GAAc;IACzC,IAAI,EAAE,SAAS;IACf,OAAO,CAAC,IAAY;QAClB,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,CAAC,CAAC,8BAA8B,CAAC,CAAC,KAAK,EAAE,CAAC;QACtD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAClD,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAClF,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { cheerioExtractor } from './cheerio.js';
2
+ import { nodeHtmlParserExtractor } from './node-html-parser.js';
3
+ import { regexHeExtractor } from './regex-he.js';
4
+ import { regexManualExtractor } from './regex-manual.js';
5
+ export { cheerioExtractor, nodeHtmlParserExtractor, regexHeExtractor, regexManualExtractor };
6
+ export const DEFAULT_EXTRACTOR_CHAIN = Object.freeze([
7
+ cheerioExtractor,
8
+ nodeHtmlParserExtractor,
9
+ regexHeExtractor,
10
+ regexManualExtractor,
11
+ ]);
12
+ export function getExtractor(name) {
13
+ switch (name) {
14
+ case 'cheerio':
15
+ return cheerioExtractor;
16
+ case 'node-html-parser':
17
+ return nodeHtmlParserExtractor;
18
+ case 'regex-he':
19
+ return regexHeExtractor;
20
+ case 'regex-manual':
21
+ return regexManualExtractor;
22
+ }
23
+ }
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/extractors/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,CAAC;AAG7F,MAAM,CAAC,MAAM,uBAAuB,GAAyB,MAAM,CAAC,MAAM,CAAC;IACzE,gBAAgB;IAChB,uBAAuB;IACvB,gBAAgB;IAChB,oBAAoB;CACrB,CAAC,CAAC;AAEH,MAAM,UAAU,YAAY,CAAC,IAAmB;IAC9C,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,gBAAgB,CAAC;QAC1B,KAAK,kBAAkB;YACrB,OAAO,uBAAuB,CAAC;QACjC,KAAK,UAAU;YACb,OAAO,gBAAgB,CAAC;QAC1B,KAAK,cAAc;YACjB,OAAO,oBAAoB,CAAC;IAChC,CAAC;AACH,CAAC"}
@@ -0,0 +1,41 @@
1
+ const NAMED = Object.freeze({
2
+ amp: '&',
3
+ lt: '<',
4
+ gt: '>',
5
+ quot: '"',
6
+ apos: "'",
7
+ nbsp: ' ',
8
+ });
9
+ export function decodeHtmlEntitiesManual(input) {
10
+ return input.replace(/&(#[xX][0-9a-fA-F]+|#\d+|[a-zA-Z][a-zA-Z0-9]+);/g, (match, body) => {
11
+ if (body.startsWith('#x') || body.startsWith('#X')) {
12
+ const code = Number.parseInt(body.slice(2), 16);
13
+ if (Number.isFinite(code) && code >= 0 && code <= 0x10ffff) {
14
+ try {
15
+ return String.fromCodePoint(code);
16
+ }
17
+ catch {
18
+ return match;
19
+ }
20
+ }
21
+ return match;
22
+ }
23
+ if (body.startsWith('#')) {
24
+ const code = Number.parseInt(body.slice(1), 10);
25
+ if (Number.isFinite(code) && code >= 0 && code <= 0x10ffff) {
26
+ try {
27
+ return String.fromCodePoint(code);
28
+ }
29
+ catch {
30
+ return match;
31
+ }
32
+ }
33
+ return match;
34
+ }
35
+ const named = NAMED[body];
36
+ if (named !== undefined)
37
+ return named;
38
+ return match;
39
+ });
40
+ }
41
+ //# sourceMappingURL=manual-decode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-decode.js","sourceRoot":"","sources":["../../src/extractors/manual-decode.ts"],"names":[],"mappings":"AAAA,MAAM,KAAK,GAAqC,MAAM,CAAC,MAAM,CAAC;IAC5D,GAAG,EAAE,GAAG;IACR,EAAE,EAAE,GAAG;IACP,EAAE,EAAE,GAAG;IACP,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;CACV,CAAC,CAAC;AAEH,MAAM,UAAU,wBAAwB,CAAC,KAAa;IACpD,OAAO,KAAK,CAAC,OAAO,CAClB,kDAAkD,EAClD,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QACtB,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,EAAE,CAAC;gBAC3D,IAAI,CAAC;oBACH,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBACpC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAChD,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,QAAQ,EAAE,CAAC;gBAC3D,IAAI,CAAC;oBACH,OAAO,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;gBACpC,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACtC,OAAO,KAAK,CAAC;IACf,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { parse } from 'node-html-parser';
2
+ import { decodeHtmlEntitiesManual } from './manual-decode.js';
3
+ export const nodeHtmlParserExtractor = {
4
+ name: 'node-html-parser',
5
+ extract(html) {
6
+ const root = parse(html, { lowerCaseTagName: false, comment: false });
7
+ const tag = root.querySelector('meta[name="server-response"]') ??
8
+ root.querySelector("meta[name='server-response']");
9
+ if (!tag) {
10
+ throw new Error('meta[name="server-response"] not found via node-html-parser');
11
+ }
12
+ const raw = tag.getAttribute('content');
13
+ if (raw === undefined || raw === null || raw.length === 0) {
14
+ throw new Error('content attribute is empty (node-html-parser)');
15
+ }
16
+ return decodeHtmlEntitiesManual(raw);
17
+ },
18
+ };
19
+ //# sourceMappingURL=node-html-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-html-parser.js","sourceRoot":"","sources":["../../src/extractors/node-html-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,MAAM,CAAC,MAAM,uBAAuB,GAAc;IAChD,IAAI,EAAE,kBAAkB;IACxB,OAAO,CAAC,IAAY;QAClB,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACtE,MAAM,GAAG,GACP,IAAI,CAAC,aAAa,CAAC,8BAA8B,CAAC;YAClD,IAAI,CAAC,aAAa,CAAC,8BAA8B,CAAC,CAAC;QACrD,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QACxC,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1D,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,OAAO,wBAAwB,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import he from 'he';
2
+ const META_RE_NAME_FIRST = /<meta\s+name=["']server-response["']\s+content=(?:"([^"]*)"|'([^']*)')/i;
3
+ const META_RE_CONTENT_FIRST = /<meta\s+content=(?:"([^"]*)"|'([^']*)')\s+name=["']server-response["']/i;
4
+ export const regexHeExtractor = {
5
+ name: 'regex-he',
6
+ extract(html) {
7
+ const m1 = META_RE_NAME_FIRST.exec(html);
8
+ const captured = m1
9
+ ? (m1[1] ?? m1[2])
10
+ : (() => {
11
+ const m2 = META_RE_CONTENT_FIRST.exec(html);
12
+ return m2 ? (m2[1] ?? m2[2]) : undefined;
13
+ })();
14
+ if (captured === undefined || captured.length === 0) {
15
+ throw new Error('meta[name="server-response"] not found via regex-he');
16
+ }
17
+ return he.decode(captured);
18
+ },
19
+ };
20
+ //# sourceMappingURL=regex-he.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex-he.js","sourceRoot":"","sources":["../../src/extractors/regex-he.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAGpB,MAAM,kBAAkB,GACtB,yEAAyE,CAAC;AAC5E,MAAM,qBAAqB,GACzB,yEAAyE,CAAC;AAE5E,MAAM,CAAC,MAAM,gBAAgB,GAAc;IACzC,IAAI,EAAE,UAAU;IAChB,OAAO,CAAC,IAAY;QAClB,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,EAAE;YACjB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5C,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC3C,CAAC,CAAC,EAAE,CAAC;QACT,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;CACF,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { decodeHtmlEntitiesManual } from './manual-decode.js';
2
+ const META_RE_NAME_FIRST = /<meta\s+name=["']server-response["']\s+content=(?:"([^"]*)"|'([^']*)')/i;
3
+ const META_RE_CONTENT_FIRST = /<meta\s+content=(?:"([^"]*)"|'([^']*)')\s+name=["']server-response["']/i;
4
+ export const regexManualExtractor = {
5
+ name: 'regex-manual',
6
+ extract(html) {
7
+ const m1 = META_RE_NAME_FIRST.exec(html);
8
+ const captured = m1
9
+ ? (m1[1] ?? m1[2])
10
+ : (() => {
11
+ const m2 = META_RE_CONTENT_FIRST.exec(html);
12
+ return m2 ? (m2[1] ?? m2[2]) : undefined;
13
+ })();
14
+ if (captured === undefined || captured.length === 0) {
15
+ throw new Error('meta[name="server-response"] not found via regex-manual');
16
+ }
17
+ return decodeHtmlEntitiesManual(captured);
18
+ },
19
+ };
20
+ //# sourceMappingURL=regex-manual.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regex-manual.js","sourceRoot":"","sources":["../../src/extractors/regex-manual.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,MAAM,kBAAkB,GACtB,yEAAyE,CAAC;AAC5E,MAAM,qBAAqB,GACzB,yEAAyE,CAAC;AAE5E,MAAM,CAAC,MAAM,oBAAoB,GAAc;IAC7C,IAAI,EAAE,cAAc;IACpB,OAAO,CAAC,IAAY;QAClB,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,QAAQ,GAAG,EAAE;YACjB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,EAAE,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5C,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC3C,CAAC,CAAC,EAAE,CAAC;QACT,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;QACD,OAAO,wBAAwB,CAAC,QAAQ,CAAC,CAAC;IAC5C,CAAC;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/extractors/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,145 @@
1
+ import { AbortError, FetchError, TimeoutError, describeError } from './errors.js';
2
+ export const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36';
3
+ const DEFAULT_RETRY = {
4
+ maxRetries: 4,
5
+ initialDelayMs: 300,
6
+ maxDelayMs: 5_000,
7
+ jitterRatio: 0.3,
8
+ };
9
+ const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
10
+ export async function fetchTextWithRetry(url, options = {}) {
11
+ const retry = { ...DEFAULT_RETRY, ...options.retry };
12
+ const timeoutMs = options.timeoutMs ?? 15_000;
13
+ const userAgent = options.userAgent ?? DEFAULT_USER_AGENT;
14
+ const acceptLanguage = options.acceptLanguage ?? 'ja,en;q=0.9';
15
+ const fetchImpl = options.fetchImpl ?? fetch;
16
+ const sleep = options.sleep ?? defaultSleep;
17
+ if (options.signal?.aborted) {
18
+ throw new AbortError('Fetch aborted before start', { url });
19
+ }
20
+ const headers = {
21
+ 'User-Agent': userAgent,
22
+ 'Accept-Language': acceptLanguage,
23
+ Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
24
+ ...options.headers,
25
+ };
26
+ let lastError;
27
+ for (let attempt = 0; attempt <= retry.maxRetries; attempt += 1) {
28
+ if (options.signal?.aborted) {
29
+ throw new AbortError('Fetch aborted', { url, attempt });
30
+ }
31
+ const timeoutCtl = new AbortController();
32
+ const userSignal = options.signal;
33
+ const onUserAbort = () => {
34
+ timeoutCtl.abort(new AbortError('User aborted', { url, attempt }));
35
+ };
36
+ if (userSignal)
37
+ userSignal.addEventListener('abort', onUserAbort, { once: true });
38
+ const timer = setTimeout(() => {
39
+ timeoutCtl.abort(new TimeoutError(`Fetch timed out after ${timeoutMs}ms`, { url, attempt }));
40
+ }, timeoutMs);
41
+ try {
42
+ const response = await fetchImpl(url, {
43
+ method: 'GET',
44
+ headers,
45
+ signal: timeoutCtl.signal,
46
+ redirect: options.redirect ?? 'follow',
47
+ });
48
+ if (response.ok) {
49
+ const text = await response.text();
50
+ return { text, url: response.url || url, status: response.status, attempts: attempt + 1 };
51
+ }
52
+ const retryable = RETRYABLE_STATUS.has(response.status);
53
+ const err = new FetchError(`HTTP ${response.status} ${response.statusText} for ${url}`, {
54
+ url,
55
+ attempt,
56
+ status: response.status,
57
+ statusText: response.statusText,
58
+ });
59
+ if (!retryable || attempt === retry.maxRetries) {
60
+ throw err;
61
+ }
62
+ lastError = err;
63
+ }
64
+ catch (error) {
65
+ if (error instanceof FetchError && !RETRYABLE_STATUS.has(error.status ?? 0)) {
66
+ throw error;
67
+ }
68
+ if (isAbortFromUser(error, userSignal)) {
69
+ throw error instanceof AbortError
70
+ ? error
71
+ : new AbortError('Fetch aborted', { url, attempt, cause: error });
72
+ }
73
+ if (isTimeoutAbort(error, timeoutCtl)) {
74
+ lastError = new TimeoutError(`Fetch timed out after ${timeoutMs}ms`, {
75
+ url,
76
+ attempt,
77
+ cause: error,
78
+ });
79
+ }
80
+ else if (error instanceof FetchError) {
81
+ lastError = error;
82
+ }
83
+ else {
84
+ lastError = new FetchError(`Network error: ${describeError(error)}`, {
85
+ url,
86
+ attempt,
87
+ cause: error,
88
+ });
89
+ }
90
+ if (attempt === retry.maxRetries) {
91
+ throw lastError;
92
+ }
93
+ }
94
+ finally {
95
+ clearTimeout(timer);
96
+ if (userSignal)
97
+ userSignal.removeEventListener('abort', onUserAbort);
98
+ }
99
+ const delay = computeBackoff(attempt, retry);
100
+ await sleep(delay, options.signal);
101
+ }
102
+ throw lastError instanceof Error
103
+ ? lastError
104
+ : new FetchError('Unknown fetch failure', { url, cause: lastError });
105
+ }
106
+ function computeBackoff(attempt, retry) {
107
+ const exp = retry.initialDelayMs * 2 ** attempt;
108
+ const capped = Math.min(exp, retry.maxDelayMs);
109
+ const jitter = capped * retry.jitterRatio * (Math.random() * 2 - 1);
110
+ return Math.max(0, Math.floor(capped + jitter));
111
+ }
112
+ function defaultSleep(ms, signal) {
113
+ return new Promise((resolve, reject) => {
114
+ if (signal?.aborted) {
115
+ reject(new AbortError('Sleep aborted'));
116
+ return;
117
+ }
118
+ const timer = setTimeout(() => {
119
+ if (signal)
120
+ signal.removeEventListener('abort', onAbort);
121
+ resolve();
122
+ }, ms);
123
+ const onAbort = () => {
124
+ clearTimeout(timer);
125
+ reject(new AbortError('Sleep aborted'));
126
+ };
127
+ if (signal)
128
+ signal.addEventListener('abort', onAbort, { once: true });
129
+ });
130
+ }
131
+ function isAbortFromUser(error, userSignal) {
132
+ if (!userSignal?.aborted)
133
+ return false;
134
+ if (error instanceof AbortError)
135
+ return true;
136
+ return error instanceof DOMException && error.name === 'AbortError';
137
+ }
138
+ function isTimeoutAbort(error, timeoutCtl) {
139
+ if (!timeoutCtl.signal.aborted)
140
+ return false;
141
+ if (error instanceof TimeoutError)
142
+ return true;
143
+ return error instanceof DOMException && error.name === 'AbortError';
144
+ }
145
+ //# sourceMappingURL=fetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetcher.js","sourceRoot":"","sources":["../src/fetcher.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { fetchVideoTags, fetchVideo, extractAndParse, } from './watch.js';
2
+ export { buildWatchUrl, WATCH_BASE_URL } from './url.js';
3
+ export { VideoIdSchema, isValidVideoId, assertVideoId, normalizeVideoId, } from './video-id.js';
4
+ export { fetchTextWithRetry, DEFAULT_USER_AGENT, } from './fetcher.js';
5
+ export { TagItemSchema, TagSectionSchema, VideoCountSchema, VideoSectionSchema, ServerResponseSchema, } from './types.js';
6
+ export { NicoTagError, InvalidVideoIdError, FetchError, TimeoutError, AbortError, ExtractError, ParseError, ValidationError, describeError, } from './errors.js';
7
+ export { cheerioExtractor, nodeHtmlParserExtractor, regexHeExtractor, regexManualExtractor, DEFAULT_EXTRACTOR_CHAIN, getExtractor, } from './extractors/index.js';
8
+ export { parseServerResponseJson } from './parsers/server-response.js';
9
+ export { decodeHtmlEntitiesManual } from './extractors/manual-decode.js';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,UAAU,EACV,eAAe,GAKhB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,aAAa,EAAE,cAAc,EAA6B,MAAM,UAAU,CAAC;AAEpF,OAAO,EACL,aAAa,EACb,cAAc,EACd,aAAa,EACb,gBAAgB,GAEjB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,GAInB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,GAKrB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,YAAY,EACZ,mBAAmB,EACnB,UAAU,EACV,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,UAAU,EACV,eAAe,EACf,aAAa,GAGd,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,EACvB,YAAY,GAGb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAEvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { ZodError } from 'zod';
2
+ import { ParseError, ValidationError } from '../errors.js';
3
+ import { ServerResponseSchema } from '../types.js';
4
+ export function parseServerResponseJson(jsonText) {
5
+ let raw;
6
+ try {
7
+ raw = JSON.parse(jsonText);
8
+ }
9
+ catch (error) {
10
+ const snippet = jsonText.length > 200 ? `${jsonText.slice(0, 200)}…` : jsonText;
11
+ throw new ParseError('Failed to JSON.parse server-response content', {
12
+ cause: error,
13
+ snippet,
14
+ });
15
+ }
16
+ try {
17
+ return ServerResponseSchema.parse(raw);
18
+ }
19
+ catch (error) {
20
+ if (error instanceof ZodError) {
21
+ throw new ValidationError('server-response payload failed schema validation', error.issues, {
22
+ cause: error,
23
+ });
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+ //# sourceMappingURL=server-response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-response.js","sourceRoot":"","sources":["../../src/parsers/server-response.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,KAAK,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAuB,MAAM,aAAa,CAAC;AAExE,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;QAChF,MAAM,IAAI,UAAU,CAAC,8CAA8C,EAAE;YACnE,KAAK,EAAE,KAAK;YACZ,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC;QACH,OAAO,oBAAoB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,eAAe,CAAC,kDAAkD,EAAE,KAAK,CAAC,MAAM,EAAE;gBAC1F,KAAK,EAAE,KAAK;aACb,CAAC,CAAC;QACL,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}