UNPKG

narou

Version:
1 lines 15.5 kB
{"version":3,"sources":["../src/narou-fetch.ts","../src/util/unzipp.ts","../src/narou-search-results.ts","../src/narou.ts"],"sourcesContent":["import { unzipp } from \"./util/unzipp.js\";\nimport NarouNovel from \"./narou.js\";\nimport type { NarouParams } from \"./narou.js\";\n\ntype Fetch = typeof fetch;\n\n/**\n * なろう小説APIへのリクエストを実行する\n */\nexport default class NarouNovelFetch extends NarouNovel {\n /**\n * コンストラクタ\n * @param fetch fetch関数(デフォルトはネイティブのfetch)\n */\n constructor(private fetch?: Fetch) {\n super();\n }\n\n protected async execute<T>(\n params: NarouParams,\n endpoint: string\n ): Promise<T> {\n const query = { ...params, out: \"json\" };\n\n if (query.gzip === undefined) {\n query.gzip = 5;\n }\n if (query.gzip === 0) {\n delete query.gzip;\n }\n const url = new URL(endpoint);\n\n Object.entries(query).forEach(([key, value]) => {\n if (value !== undefined) {\n url.searchParams.append(key, value.toString());\n }\n });\n\n const res = await (this.fetch ?? fetch)(url);\n\n if (!query.gzip) {\n return (await res.json()) as T;\n }\n\n const buffer = await res.arrayBuffer();\n return await unzipp(buffer);\n }\n}\n","import { gunzip, InputType } from \"zlib\";\nimport { promisify } from \"util\";\n\nconst gunzipAsync = promisify<InputType, Buffer>(gunzip);\n\nconst decoder = new TextDecoder()\n/**\n * 圧縮されたJSONデータを解凍して解析します。\n * \n * @param data - ArrayBuffer形式の圧縮データ\n * @returns 解凍されたデータからパースされたJSONオブジェクト\n * @throws {string} データが解凍できない、または解凍されたデータが有効なJSONでない場合、\n * 解凍されたデータの文字列表現をスローします。\n * @throws {string} 解凍中にエラーが発生した場合、元のデータの文字列表現をスローします。\n */\nexport async function unzipp(data: ArrayBuffer) {\n try {\n const buffer = await gunzipAsync(data);\n try {\n return JSON.parse(decoder.decode(buffer));\n } catch {\n throw decoder.decode(buffer);\n }\n } catch (e) {\n if (typeof e === \"string\") throw e;\n throw decoder.decode(data);\n }\n}\n","import type {\n BooleanNumber as BooleanNumber,\n Genre,\n R18Site,\n SearchParams,\n Fields,\n BigGenre,\n R18Fields,\n OptionalFields,\n UserFields,\n UserSearchParams,\n} from \"./params.js\";\n\n/**\n * なろう小説API検索結果\n */\nexport default class NarouSearchResults<T, TKey extends keyof T> {\n /**\n * 検索結果数\n */\n allcount: number;\n /**\n * 結果表示上限数\n */\n limit: number;\n /**\n * 結果表示開始数\n */\n start: number;\n /**\n * 結果表示の現在ページ(=start/limit)\n */\n page: number;\n /**\n * 今回取得できた検索結果の数\n */\n length: number;\n /**\n * 検索結果\n */\n values: readonly Pick<T, TKey>[];\n\n /**\n * @constractor\n * @private\n */\n constructor(\n [header, ...result]: [{ allcount: number }, ...Pick<T, TKey>[]],\n params: SearchParams | UserSearchParams\n ) {\n const count = header.allcount;\n const limit = params.lim ?? 20;\n const start = params.st ?? 0;\n\n this.allcount = count;\n this.limit = limit;\n this.start = start;\n this.page = start / limit;\n this.length = result.length;\n this.values = result;\n }\n}\n\n/**\n * 小説情報\n * @see https://dev.syosetu.com/man/api/#output\n * @see https://dev.syosetu.com/xman/api/#output\n */\nexport interface NarouSearchResult {\n /** 小説名 */\n title: string;\n /** Nコード */\n ncode: string;\n /** 作者のユーザID(数値) */\n userid: number;\n /** 作者名 */\n writer: string;\n /** 小説のあらすじ */\n story: string;\n /** 掲載サイト */\n nocgenre: R18Site;\n /** 大ジャンル */\n biggenre: BigGenre;\n /** ジャンル */\n genre: Genre;\n /** キーワード */\n keyword: string;\n /** 初回掲載日 YYYY-MM-DD HH:MM:SSの形式 */\n general_firstup: string;\n /** 最終掲載日 YYYY-MM-DD HH:MM:SSの形式 */\n general_lastup: string;\n /** 連載の場合は1、短編の場合は2 */\n novel_type: NovelType;\n /** 連載の場合は1、短編の場合は2 */\n noveltype: NovelType;\n /** 短編小説と完結済小説は0となっています。連載中は1です。 */\n end: End;\n /** 全掲載話数です。短編の場合は1です。 */\n general_all_no: number;\n /** 小説文字数です。スペースや改行は文字数としてカウントしません。 */\n length: number;\n /** 読了時間(分単位)です。読了時間は小説文字数÷500を切り上げした数値です。 */\n time: number;\n /** 長期連載中は1、それ以外は0です。 */\n isstop: BooleanNumber;\n /** 登録必須キーワードに「R15」が含まれる場合は1、それ以外は0です。 */\n isr15: BooleanNumber;\n /** 登録必須キーワードに「ボーイズラブ」が含まれる場合は1、それ以外は0です。 */\n isbl: BooleanNumber;\n /** 登録必須キーワードに「ガールズラブ」が含まれる場合は1、それ以外は0です。 */\n isgl: BooleanNumber;\n /** 登録必須キーワードに「残酷な描写あり」が含まれる場合は1、それ以外は0です。 */\n iszankoku: BooleanNumber;\n /** 登録必須キーワードに「異世界転生」が含まれる場合は1、それ以外は0です。 */\n istensei: BooleanNumber;\n /** 登録必須キーワードに「異世界転移」が含まれる場合は1、それ以外は0です。 */\n istenni: BooleanNumber;\n /** 総合得点(=(ブックマーク数×2)+評価点) */\n global_point: number;\n /**\n * 日間ポイント\n * ランキング集計時点から過去24時間以内で新たに登録されたブックマークや評価が対象\n */\n daily_point: number;\n /**\n * 週間ポイント\n * ランキング集計時点から過去7日以内で新たに登録されたブックマークや評価が対象\n */\n weekly_point: number;\n /**\n * 月間ポイント\n * ランキング集計時点から過去30日以内で新たに登録されたブックマークや評価が対象\n */\n monthly_point: number;\n /**\n * 四半期ポイント\n * ランキング集計時点から過去90日以内で新たに登録されたブックマークや評価が対象\n */\n quarter_point: number;\n /**\n * 年間ポイント\n * ランキング集計時点から過去365日以内で新たに登録されたブックマークや評価が対象\n */\n yearly_point: number;\n /** ブックマーク数 */\n fav_novel_cnt: number;\n /** 感想数 */\n impression_cnt: number;\n /** レビュー数 */\n review_cnt: number;\n /** 評価ポイント */\n all_point: number;\n /** 評価者数 */\n all_hyoka_cnt: number;\n /** 挿絵の数 */\n sasie_cnt: number;\n /**\n * 会話率\n * @see https://dev.syosetu.com/man/kaiwa/\n */\n kaiwaritu: number;\n /**\n * 小説の更新日時\n */\n novelupdated_at: string;\n /**\n * 最終更新日時\n * システム用で小説更新時とは関係ありません\n */\n updated_at: string;\n /** 週間ユニークユーザー数 */\n weekly_unique: number;\n}\n\n/**\n * ユーザ情報\n * @see https://dev.syosetu.com/man/userapi/#output\n */\nexport interface UserSearchResult {\n /** ユーザID */\n userid: number;\n /** ユーザ名 */\n name: string;\n /** ユーザ名のフリガナ */\n yomikata: string;\n /**\n * ユーザ名のフリガナの頭文字\n * ひらがな以外の場合はnullまたは空文字となります。\n */\n name1st: string;\n /** 小説投稿数 */\n novel_cnt: number;\n /** レビュー投稿数 */\n review_cnt: number;\n /**\n * 小説累計文字数\n * スペースや改行は文字数としてカウントしません。\n */\n novel_length: number;\n /**\n * 総合評価ポイントの合計\n * 投稿済小説でそれぞれ獲得した総合評価ポイントの合計です。\n */\n sum_global_point: number;\n}\n\n/**\n * noveltype/novel_typeの値ヘルパー\n */\nexport const NovelType = {\n /** 連載 */\n Rensai: 1,\n /** 短編 */\n Tanpen: 2,\n} as const;\nexport type NovelType = typeof NovelType[keyof typeof NovelType];\n\n/**\n * endの値ヘルパー\n */\nexport const End = {\n /** 短編小説と完結済小説 */\n KanketsuOrTanpen: 0,\n /** 連載中 */\n Rensai: 1,\n} as const;\nexport type End = typeof End[keyof typeof End];\n\nexport type SearchResultFields<T extends Fields> = {\n [K in keyof typeof Fields]: typeof Fields[K] extends T ? K : never;\n}[keyof typeof Fields];\n\nexport type SearchResultOptionalFields<T extends OptionalFields> = {\n [K in keyof typeof OptionalFields]: typeof OptionalFields[K] extends T\n ? K\n : never;\n}[keyof typeof OptionalFields];\n\nexport type SearchResultR18Fields<T extends R18Fields> = {\n [K in keyof typeof R18Fields]: typeof R18Fields[K] extends T ? K : never;\n}[keyof typeof R18Fields];\n\nexport type UserSearchResultFields<T extends UserFields> = {\n [K in keyof typeof UserFields]: typeof UserFields[K] extends T ? K : never;\n}[keyof typeof UserFields];\n\nexport type PickedNarouSearchResult<T extends keyof NarouSearchResult> = Pick<\n NarouSearchResult,\n T\n>;\n","import type { NarouRankingResult } from \"./narou-ranking-results.js\";\nimport NarouSearchResults from \"./narou-search-results.js\";\nimport type {\n NarouSearchResult,\n UserSearchResult,\n} from \"./narou-search-results.js\";\nimport type {\n RankingHistoryParams,\n RankingParams,\n SearchParams,\n UserSearchParams,\n} from \"./params.js\";\nimport type { RankingHistoryRawResult } from \"./ranking-history.js\";\n\n/**\n * なろう小説APIへのリクエストパラメータ\n */\nexport type NarouParams =\n | SearchParams\n | RankingParams\n | RankingHistoryParams\n | UserSearchParams;\n\n/**\n * なろう小説APIへのリクエストを実行する\n * @class NarouNovel\n * @private\n */\nexport default abstract class NarouNovel {\n /**\n * なろうAPIへのAPIリクエストを実行する\n * @param params クエリパラメータ\n * @param endpoint APIエンドポイント\n * @returns 実行結果\n */\n protected abstract execute<T>(\n params: NarouParams,\n endpoint: string\n ): Promise<T>;\n\n /**\n * APIへの検索リクエストを実行する\n * @param params クエリパラメータ\n * @param endpoint APIエンドポイント\n * @returns 検索結果\n */\n protected async executeSearch<T extends keyof NarouSearchResult>(\n params: SearchParams,\n endpoint = \"https://api.syosetu.com/novelapi/api/\"\n ): Promise<NarouSearchResults<NarouSearchResult, T>> {\n return new NarouSearchResults(await this.execute(params, endpoint), params);\n }\n\n /**\n * 小説APIへの検索リクエストを実行する\n * @param params クエリパラメータ\n * @returns 検索結果\n * @see https://dev.syosetu.com/man/api/\n */\n async executeNovel<T extends keyof NarouSearchResult>(\n params: SearchParams\n ): Promise<NarouSearchResults<NarouSearchResult, T>> {\n return await this.executeSearch(\n params,\n \"https://api.syosetu.com/novelapi/api/\"\n );\n }\n\n /**\n * R18小説APIへの検索リクエストを実行する\n * @param params クエリパラメータ\n * @returns 検索結果\n * @see https://dev.syosetu.com/xman/api/\n */\n async executeNovel18<T extends keyof NarouSearchResult>(\n params: SearchParams\n ): Promise<NarouSearchResults<NarouSearchResult, T>> {\n return await this.executeSearch(\n params,\n \"https://api.syosetu.com/novel18api/api/\"\n );\n }\n\n /**\n * ランキングAPIへのリクエストを実行する\n * @param params クエリパラメータ\n * @returns ランキング結果\n * @see https://dev.syosetu.com/man/rankapi/\n */\n async executeRanking(params: RankingParams): Promise<NarouRankingResult[]> {\n return await this.execute(params, \"https://api.syosetu.com/rank/rankget/\");\n }\n\n /**\n * 殿堂入りAPiへのリクエストを実行する\n * @param params クエリパラメータ\n * @returns ランキング履歴結果\n * @see https://dev.syosetu.com/man/rankinapi/\n */\n async executeRankingHistory(\n params: RankingHistoryParams\n ): Promise<RankingHistoryRawResult[]> {\n return await this.execute(params, \"https://api.syosetu.com/rank/rankin/\");\n }\n\n /**\n * ユーザー検索APIへのリクエストを実行する\n * @param params クエリパラメータ\n * @returns 検索結果\n * @see https://dev.syosetu.com/man/userapi/\n */\n async executeUserSearch<T extends keyof UserSearchResult>(\n params: UserSearchParams\n ): Promise<NarouSearchResults<UserSearchResult, T>> {\n return new NarouSearchResults<UserSearchResult, T>(\n await this.execute(params, \"https://api.syosetu.com/userapi/api/\"),\n params\n );\n }\n}\n"],"mappings":"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","names":["fetch"]}