UNPKG

narou

Version:
1,346 lines (1,334 loc) 37.6 kB
//#region src/narou-search-results.ts /** * なろう小説API検索結果 */ var NarouSearchResults = class { /** * @constractor * @private */ constructor([header, ...result], params) { const count$1 = header.allcount; const limit = params.lim ?? 20; const start = params.st ?? 0; this.allcount = count$1; this.limit = limit; this.start = start; this.page = start / limit; this.length = result.length; this.values = result; } }; /** * noveltype/novel_typeの値ヘルパー */ const NovelType = { Rensai: 1, Tanpen: 2 }; /** * endの値ヘルパー */ const End = { KanketsuOrTanpen: 0, Rensai: 1 }; //#endregion //#region src/narou.ts /** * なろう小説APIへのリクエストを実行する * @class NarouNovel * @private */ var NarouNovel = class { /** * APIへの検索リクエストを実行する * @param params クエリパラメータ * @param endpoint APIエンドポイント * @returns 検索結果 */ async executeSearch(params, endpoint = "https://api.syosetu.com/novelapi/api/", options) { return new NarouSearchResults(await this.execute(params, endpoint, options), params); } /** * 小説APIへの検索リクエストを実行する * @param params クエリパラメータ * @returns 検索結果 * @see https://dev.syosetu.com/man/api/ */ async executeNovel(params, options) { return await this.executeSearch(params, "https://api.syosetu.com/novelapi/api/", options); } /** * R18小説APIへの検索リクエストを実行する * @param params クエリパラメータ * @returns 検索結果 * @see https://dev.syosetu.com/xman/api/ */ async executeNovel18(params, options) { return await this.executeSearch(params, "https://api.syosetu.com/novel18api/api/", options); } /** * ランキングAPIへのリクエストを実行する * @param params クエリパラメータ * @returns ランキング結果 * @see https://dev.syosetu.com/man/rankapi/ */ async executeRanking(params, options) { return await this.execute(params, "https://api.syosetu.com/rank/rankget/", options); } /** * 殿堂入りAPiへのリクエストを実行する * @param params クエリパラメータ * @param options 実行オプション * @returns ランキング履歴結果 * @see https://dev.syosetu.com/man/rankinapi/ */ async executeRankingHistory(params, options) { return await this.execute(params, "https://api.syosetu.com/rank/rankin/", options); } /** * ユーザー検索APIへのリクエストを実行する * @param params クエリパラメータ * @returns 検索結果 * @see https://dev.syosetu.com/man/userapi/ */ async executeUserSearch(params, options) { return new NarouSearchResults(await this.execute(params, "https://api.syosetu.com/userapi/api/", options), params); } }; //#endregion //#region src/util/jsonp.ts /** * MIT license */ let count = 0; const noop = function() {}; /** * JSONPリクエストを実行してデータを取得します。 * * @param url - リクエスト先のURL * @param options - JSONP呼び出しのオプション設定 * @returns JSONPリクエストの結果をPromiseで返します * @throws {Error} タイムアウトが発生した場合、"Timeout"メッセージのエラーをスローします * * @example * ```typescript * // 基本的な使用方法 * const data = await jsonp<ResponseType>('https://example.com/api'); * * // オプション指定 * const data = await jsonp<ResponseType>('https://example.com/api', { * prefix: 'customPrefix', * param: 'callbackParam', * timeout: 10000 * }); * ``` */ function jsonp(url, { prefix = "__jp", param = "callback", timeout = 15e3 } = {}) { return new Promise(function(resolve, reject) { const targetChild = document.getElementsByTagName("script").item(0); const target = targetChild?.parentNode ?? document.head; const id = `${prefix}${count++}`; const cleanup = function() { if (script && script.parentNode) script.parentNode.removeChild(script); window[id] = noop; if (timer) clearTimeout(timer); }; const timer = timeout > 0 ? setTimeout(() => { cleanup(); reject(/* @__PURE__ */ new Error("Timeout")); }, timeout) : void 0; const callback = (data) => { cleanup(); resolve(data); }; window[id] = callback; const script = document.createElement("script"); const urlObj = new URL(url); urlObj.searchParams.set(param, id); script.setAttribute("src", urlObj.toString()); target.insertBefore(script, targetChild); }); } //#endregion //#region src/narou-jsonp.ts /** * なろう小説APIへのリクエストを実行する */ var NarouNovelJsonp = class extends NarouNovel { async execute(params, endpoint, _options) { const query = { ...params, out: "jsonp" }; query.gzip = 0; const url = new URL(endpoint); Object.entries(query).forEach(([key, value]) => { if (value !== void 0) url.searchParams.append(key, value.toString()); }); return await jsonp(url.toString()); } }; //#endregion //#region src/params.ts const RankingType = { Daily: "d", Weekly: "w", Monthly: "m", Quarterly: "q" }; const BooleanNumber = { True: 1, False: 0 }; /** * なろう小説APIのofパラメータに指定できる出力する項目 * @see https://dev.syosetu.com/man/api/#output */ const Fields = { title: "t", ncode: "n", userid: "u", writer: "w", story: "s", biggenre: "bg", genre: "g", keyword: "k", general_firstup: "gf", general_lastup: "gl", noveltype: "nt", end: "e", general_all_no: "ga", length: "l", time: "ti", isstop: "i", isr15: "isr", isbl: "ibl", isgl: "igl", iszankoku: "izk", istensei: "its", istenni: "iti", global_point: "gp", daily_point: "dp", weekly_point: "wp", monthly_point: "mp", quarter_point: "qp", yearly_point: "yp", fav_novel_cnt: "f", impression_cnt: "imp", review_cnt: "r", all_point: "a", all_hyoka_cnt: "ah", sasie_cnt: "sa", kaiwaritu: "ka", novelupdated_at: "nu", updated_at: "ua" }; /** * なろうR18小説APIのofパラメータに指定できる出力する項目 * @see https://dev.syosetu.com/xman/api/#output */ const R18Fields = { title: "t", ncode: "n", userid: "u", writer: "w", story: "s", nocgenre: "ng", keyword: "k", general_firstup: "gf", general_lastup: "gl", noveltype: "nt", end: "e", general_all_no: "ga", length: "l", time: "ti", isstop: "i", isbl: "ibl", isgl: "igl", iszankoku: "izk", istensei: "its", istenni: "iti", global_point: "gp", daily_point: "dp", weekly_point: "wp", monthly_point: "mp", quarter_point: "qp", yearly_point: "yp", fav_novel_cnt: "f", impression_cnt: "imp", review_cnt: "r", all_point: "a", all_hyoka_cnt: "ah", sasie_cnt: "sa", kaiwaritu: "ka", novelupdated_at: "nu", updated_at: "ua" }; /** * オプション項目 */ const OptionalFields = { weekly_unique: "weekly" }; /** * ユーザ検索APIのofパラメータに指定できる出力する項目 * @see https://dev.syosetu.com/man/userapi/#output */ const UserFields = { userid: "u", name: "n", yomikata: "y", name1st: "1", novel_cnt: "nc", review_cnt: "rc", novel_length: "nl", sum_global_point: "sg" }; /** * 出力順序 */ const Order = { FavoriteNovelCount: "favnovelcnt", ReviewCount: "reviewcnt", HyokaDesc: "hyoka", HyokaAsc: "hyokaasc", ImpressionCount: "impressioncnt", HyokaCountDesc: "hyokacnt", HyokaCountAsc: "hyokacntasc", Weekly: "weekly", LengthDesc: "lengthdesc", LengthAsc: "lengthasc", NCodeDesc: "ncodedesc", New: "new", Old: "old", DailyPoint: "dailypoint", WeeklyPoint: "weeklypoint", MonthlyPoint: "monthlypoint", QuarterPoint: "quarterpoint", YearlyPoint: "yearlypoint", GeneralFirstUp: "generalfirstup" }; /** R18掲載サイト */ const R18Site = { Nocturne: 1, MoonLight: 2, MoonLightBL: 3, Midnight: 4 }; /** R18掲載サイト表記ヘルパー */ const R18SiteNotation = { [R18Site.Nocturne]: "ノクターンノベルズ(男性向け)", [R18Site.MoonLight]: "ムーンライトノベルズ(女性向け)", [R18Site.MoonLightBL]: "ムーンライトノベルズ(BL)", [R18Site.Midnight]: "ミッドナイトノベルズ(大人向け)" }; /** 大ジャンル */ const BigGenre = { Renai: 1, Fantasy: 2, Bungei: 3, Sf: 4, Sonota: 99, NonGenre: 98 }; /** 大ジャンル表記ヘルパー */ const BigGenreNotation = { [BigGenre.Renai]: "恋愛", [BigGenre.Fantasy]: "ファンタジー", [BigGenre.Bungei]: "文芸", [BigGenre.Sf]: "SF", [BigGenre.Sonota]: "その他", [BigGenre.NonGenre]: "ノンジャンル" }; /** ジャンル */ const Genre = { RenaiIsekai: 101, RenaiGenjitsusekai: 102, FantasyHigh: 201, FantasyLow: 202, BungeiJyunbungei: 301, BungeiHumanDrama: 302, BungeiHistory: 303, BungeiSuiri: 304, BungeiHorror: 305, BungeiAction: 306, BungeiComedy: 307, SfVrgame: 401, SfSpace: 402, SfKuusoukagaku: 403, SfPanic: 404, SonotaDouwa: 9901, SonotaShi: 9902, SonotaEssei: 9903, SonotaReplay: 9904, SonotaSonota: 9999, NonGenre: 9801 }; /** ジャンル表記ヘルパー */ const GenreNotation = { [Genre.RenaiIsekai]: "異世界〔恋愛〕", [Genre.RenaiGenjitsusekai]: "現実世界〔恋愛〕", [Genre.FantasyHigh]: "ハイファンタジー〔ファンタジー〕", [Genre.FantasyLow]: "ローファンタジー〔ファンタジー〕", [Genre.BungeiJyunbungei]: "純文学〔文芸〕", [Genre.BungeiHumanDrama]: "ヒューマンドラマ〔文芸〕", [Genre.BungeiHistory]: "歴史〔文芸〕", [Genre.BungeiSuiri]: "推理〔文芸〕", [Genre.BungeiHorror]: "ホラー〔文芸〕", [Genre.BungeiAction]: "アクション〔文芸〕", [Genre.BungeiComedy]: "コメディー〔文芸〕", [Genre.SfVrgame]: "VRゲーム〔SF〕", [Genre.SfSpace]: "宇宙〔SF〕", [Genre.SfKuusoukagaku]: "空想科学〔SF〕", [Genre.SfPanic]: "パニック〔SF〕", [Genre.SonotaDouwa]: "童話〔その他〕", [Genre.SonotaShi]: "詩〔その他〕", [Genre.SonotaEssei]: "エッセイ〔その他〕", [Genre.SonotaReplay]: "リプレイ〔その他〕", [Genre.SonotaSonota]: "その他〔その他〕", [Genre.NonGenre]: "ノンジャンル〔ノンジャンル〕" }; /** 文体指定 */ const BuntaiParam = { NoJisageKaigyouOoi: 1, NoJisageKaigyoHutsuu: 2, JisageKaigyoOoi: 4, JisageKaigyoHutsuu: 6 }; /** 連載停止中指定 */ const StopParam = { NoStopping: 1, Stopping: 2 }; /** 小説タイプ指定 */ const NovelTypeParam = { Short: "t", RensaiNow: "r", RensaiEnd: "er", Rensai: "re", ShortAndRensai: "ter" }; /** 日付指定パラメータ */ const DateParam = { ThisWeek: "thisweek", LastWeek: "lastweek", SevenDays: "sevenday", ThisMonth: "thismonth", LastMonth: "lastmonth" }; const UserOrder = { New: "new", NovelCount: "novelcnt", ReviewCount: "reviewcnt", NovelLength: "novellength", SumGlobalPoint: "sumglobalpoint", Old: "old" }; //#endregion //#region src/search-builder.ts var SearchBuilderBase = class { /** * constructor * @private * @param params クエリパラメータ * @param api NarouNovel インスタンス */ constructor(params = {}, api) { this.params = params; this.api = api; } /** * 配列から重複を除去する * @protected * @static * @param array 配列 * @returns 重複を除去した配列 */ static distinct(array) { return Array.from(new Set(array)); } /** * 配列をハイフン区切りの文字列に変換する * @protected * @static * @param n 文字列または数値の配列、あるいは単一の文字列または数値 * @returns ハイフン区切りの文字列 */ static array2string(n) { if (Array.isArray(n)) return this.distinct(n).join("-"); else return n.toString(); } /** * 取得件数を指定する (lim) * @param num 取得件数 (1-500) * @return {this} */ limit(num) { this.set({ lim: num }); return this; } /** * 取得開始位置を指定する (st) * @param num 取得開始位置 (1-) * @return {this} */ start(num) { this.set({ st: num }); return this; } /** * ページ番号と1ページあたりの件数で取得範囲を指定する * @param no ページ番号 (0-) * @param count 1ページあたりの件数 (デフォルト: 20) * @return {this} */ page(no, count$1 = 20) { return this.limit(count$1).start(no * count$1); } /** * 出力順序を指定する (order) * 指定しない場合は新着順となります。 * @param {TOrder} order 出力順序 * @return {this} */ order(order) { this.set({ order }); return this; } /** * gzip圧縮レベルを指定する (gzip) * * 転送量上限を減らすためにも推奨 * @param {GzipLevel} level gzip圧縮レベル(1~5) * @return {this} */ gzip(level) { this.set({ gzip: level }); return this; } /** * クエリパラメータをセットする * @protected * @param obj セットするパラメータ * @return {this} */ set(obj) { this.params = { ...this.params, ...obj }; return this; } /** * クエリパラメータを削除する * @protected * @param key 削除するパラメータのキー * @returns {this} */ unset(key) { delete this.params[key]; return this; } }; var NovelSearchBuilderBase = class NovelSearchBuilderBase extends SearchBuilderBase { /** * 検索語を指定します (word)。 * 半角または全角スペースで区切るとAND抽出になります。部分一致でHITします。 * @param word 検索語 * @return {this} */ word(word) { this.set({ word }); return this; } /** * 除外したい単語を指定します (notword)。 * スペースで区切ることにより除外する単語を増やせます。部分一致で除外されます。 * @param word 除外語 * @return {this} */ notWord(word) { this.set({ notword: word }); return this; } /** * 検索対象を作品名に限定するかどうかを指定します (title)。 * @param bool trueの場合、作品名を検索対象とする (デフォルト: true) * @return {this} */ byTitle(bool = true) { this.set({ title: bool ? BooleanNumber.True : BooleanNumber.False }); return this; } /** * 検索対象をあらすじに限定するかどうかを指定します (ex)。 * @param bool trueの場合、あらすじを検索対象とする (デフォルト: true) * @return {this} */ byOutline(bool = true) { this.set({ ex: bool ? BooleanNumber.True : BooleanNumber.False }); return this; } /** * 検索対象をキーワードに限定するかどうかを指定します (keyword)。 * @param bool trueの場合、キーワードを検索対象とする (デフォルト: true) * @return {this} */ byKeyword(bool = true) { this.set({ keyword: bool ? BooleanNumber.True : BooleanNumber.False }); return this; } /** * 検索対象を作者名に限定するかどうかを指定します (wname)。 * @param bool trueの場合、作者名を検索対象とする (デフォルト: true) * @return {this} */ byAuthor(bool = true) { this.set({ wname: bool ? BooleanNumber.True : BooleanNumber.False }); return this; } /** * ボーイズラブ作品を抽出または除外します (isbl/notbl)。 * @param bool trueの場合、ボーイズラブ作品を抽出する (デフォルト: true)。falseの場合、除外する。 * @return {this} */ isBL(bool = true) { if (bool) this.set({ isbl: BooleanNumber.True }); else this.set({ notbl: BooleanNumber.True }); return this; } /** * ガールズラブ作品を抽出または除外します (isgl/notgl)。 * @param bool trueの場合、ガールズラブ作品を抽出する (デフォルト: true)。falseの場合、除外する。 * @return {this} */ isGL(bool = true) { if (bool) this.set({ isgl: BooleanNumber.True }); else this.set({ notgl: BooleanNumber.True }); return this; } /** * 残酷な描写あり作品を抽出または除外します (iszankoku/notzankoku)。 * @param bool trueの場合、残酷な描写あり作品を抽出する (デフォルト: true)。falseの場合、除外する。 * @return {this} */ isZankoku(bool = true) { if (bool) this.set({ iszankoku: BooleanNumber.True }); else this.set({ notzankoku: BooleanNumber.True }); return this; } /** * 異世界転生作品を抽出または除外します (istensei/nottensei)。 * @param bool trueの場合、異世界転生作品を抽出する (デフォルト: true)。falseの場合、除外する。 * @return {this} */ isTensei(bool = true) { if (bool) this.set({ istensei: BooleanNumber.True }); else this.set({ nottensei: BooleanNumber.True }); return this; } /** * 異世界転移作品を抽出または除外します (istenni/nottenni)。 * @param bool trueの場合、異世界転移作品を抽出する (デフォルト: true)。falseの場合、除外する。 * @return {this} */ isTenni(bool = true) { if (bool) this.set({ istenni: BooleanNumber.True }); else this.set({ nottenni: BooleanNumber.True }); return this; } /** * 異世界転生または異世界転移作品を抽出します (istt)。 * @return {this} */ isTT() { this.set({ istt: BooleanNumber.True }); return this; } /** * 抽出する作品の文字数を指定します (length)。 * 範囲指定する場合は、最小文字数と最大文字数をハイフン(-)記号で区切ってください。 * @param length 文字数、または[最小文字数, 最大文字数] * @return {this} */ length(length) { this.set({ length: NovelSearchBuilderBase.array2string(length) }); return this; } kaiwaritu(min, max) { let n; if (max != null) n = `${min}-${max}`; else n = min; this.set({ kaiwaritu: n }); return this; } /** * 抽出する作品の挿絵数を指定します (sasie)。 * @param num 挿絵数、または[最小挿絵数, 最大挿絵数] * @return {this} */ sasie(num) { this.set({ sasie: NovelSearchBuilderBase.array2string(num) }); return this; } /** * 抽出する作品の予想読了時間を分単位で指定します (time)。 * @param num 読了時間(分)、または[最小読了時間, 最大読了時間] * @return {this} */ time(num) { this.set({ time: NovelSearchBuilderBase.array2string(num) }); return this; } /** * Nコードを指定して取得します (ncode)。 * @param ncodes Nコード、またはNコードの配列 * @return {this} */ ncode(ncodes) { this.set({ ncode: NovelSearchBuilderBase.array2string(ncodes) }); return this; } /** * 抽出する小説タイプを指定します (type)。 * @param type 小説タイプ (t: 短編, r: 連載中, er: 完結済連載小説, ter: 短編と完結済連載小説, re: 連載中と完結済連載小説) * @return {this} */ type(type) { this.set({ type }); return this; } /** * 抽出する作品の文体を指定します (buntai)。 * 複数指定する場合はハイフン(-)で区切ってください。 * @param buntai 文体コード、または文体コードの配列 * @return {this} */ buntai(buntai) { this.set({ buntai: NovelSearchBuilderBase.array2string(buntai) }); return this; } /** * 連載停止中作品に関する指定をします (stop)。 * @param bool trueの場合、長期連載停止中のみ取得する (デフォルト: true)。falseの場合、長期連載停止中を除外する。 * @return {this} */ isStop(bool = true) { this.set({ stop: bool ? StopParam.Stopping : StopParam.NoStopping }); return this; } /** * ピックアップ作品のみを取得します (ispickup)。 * @return {this} */ isPickup() { this.set({ ispickup: BooleanNumber.True }); return this; } lastUpdate(x, y) { let date; if (typeof x == "string") date = x; else if (x instanceof Date && y instanceof Date) date = `${Math.floor(x.getTime() / 1e3)}-${Math.floor(y.getTime() / 1e3)}`; else date = `${x}-${y}`; this.set({ lastup: date }); return this; } lastNovelUpdate(x, y) { let date; if (typeof x == "string") date = x; else if (x instanceof Date && y instanceof Date) date = `${Math.floor(x.getTime() / 1e3)}-${Math.floor(y.getTime() / 1e3)}`; else date = `${x}-${y}`; this.set({ lastupdate: date }); return this; } /** * なろう小説APIへの検索リクエストを実行する * @param options 実行オプション * @returns {Promise<NarouSearchResults>} 検索結果 */ execute(options) { return this.api.executeNovel(this.params, options); } }; /** * 検索ヘルパー * @class SearchBuilder */ var SearchBuilder = class SearchBuilder extends NovelSearchBuilderBase { /** * 大ジャンルを指定して取得します (biggenre)。 * 複数指定する場合はハイフン(-)で区切ってください。 * @param genre 大ジャンルコード、または大ジャンルコードの配列 * @return {this} */ bigGenre(genre) { this.set({ biggenre: SearchBuilder.array2string(genre) }); return this; } /** * 除外したい大ジャンルを指定します (notbiggenre)。 * 複数指定する場合はハイフン(-)で区切ってください。 * @param genre 除外する大ジャンルコード、または大ジャンルコードの配列 * @return {this} */ notBigGenre(genre) { this.set({ notbiggenre: SearchBuilder.array2string(genre) }); return this; } /** * ジャンルを指定して取得します (genre)。 * 複数指定する場合はハイフン(-)で区切ってください。 * @param genre ジャンルコード、またはジャンルコードの配列 * @return {this} */ genre(genre) { this.set({ genre: SearchBuilder.array2string(genre) }); return this; } /** * 除外したいジャンルを指定します (notgenre)。 * 複数指定する場合はハイフン(-)で区切ってください。 * @param genre 除外するジャンルコード、またはジャンルコードの配列 * @return {this} */ notGenre(genre) { this.set({ notgenre: SearchBuilder.array2string(genre) }); return this; } /** * ユーザIDを指定して取得します (userid)。 * 複数指定する場合はハイフン(-)で区切ってください。 * @param ids ユーザID、またはユーザIDの配列 * @return {this} */ userId(ids) { this.set({ userid: SearchBuilder.array2string(ids) }); return this; } /** * R15作品を抽出または除外します (isr15/notr15)。 * @param bool trueの場合、R15作品を抽出する (デフォルト: true)。falseの場合、除外する。 * @return {this} */ isR15(bool = true) { if (bool) this.set({ isr15: 1 }); else this.set({ notr15: 1 }); return this; } /** * 出力する項目を個別に指定します (of)。 * 未指定時は全項目出力されます。転送量軽減のため、このパラメータの使用が推奨されます。 * 複数項目を出力する場合はハイフン(-)記号で区切ってください。 * @param fields 出力するフィールド名、またはフィールド名の配列 * @return {SearchBuilder<SearchResultFields<TFields>, TOpt>} 型が更新されたビルダー */ fields(fields) { this.set({ of: SearchBuilder.array2string(fields) }); return this; } /** * 出力オプション項目を指定します (opt)。 * 複数項目を出力する場合はハイフン(-)記号で区切ってください。 * @param option 出力するオプションフィールド名、またはオプションフィールド名の配列 * @return {SearchBuilder<T, SearchResultOptionalFields<TFields>>} 型が更新されたビルダー */ opt(option) { this.set({ opt: SearchBuilder.array2string(option) }); return this; } }; //#endregion //#region src/util/date.ts /** * 文字列の日付(yyyyMMdd形式)をDateオブジェクトに変換する * @param dateStr yyyyMMdd形式の日付文字列 * @returns Dateオブジェクト */ function parseDate(dateStr) { const year = parseInt(dateStr.substring(0, 4), 10); const month = parseInt(dateStr.substring(4, 6), 10) - 1; const day = parseInt(dateStr.substring(6, 8), 10); return new Date(year, month, day, 0, 0, 0, 0); } /** * 日付をyyyyMMdd形式の文字列に変換する * @param date 日付 * @returns yyyyMMdd形式の文字列 */ function formatDate(date) { return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}`; } /** * 指定された日数を加算した新しい日付を返す * @param date 元の日付 * @param days 加算する日数 * @returns 新しい日付 */ function addDays(date, days) { const result = new Date(date); result.setDate(result.getDate() + days); return result; } //#endregion //#region src/ranking.ts /** * なろう小説ランキングAPIのヘルパークラス。 * * ランキング種別や日付を指定してランキングデータを取得します。 * また、取得したランキングデータに含まれるNコードを元に、 * なろう小説APIを利用して詳細な小説情報を取得することも可能です。 * * @class RankingBuilder * @see https://dev.syosetu.com/man/rankapi/ なろう小説ランキングAPI仕様 */ var RankingBuilder = class { /** * constructor * @param params - 初期クエリパラメータ * @param api - API実行クラスのインスタンス * @private */ constructor(params = {}, api) { this.params = params; this.api = api; /** * クエリパラメータ * @protected */ this.date$ = addDays(/* @__PURE__ */ new Date(), -1); this.type$ = RankingType.Daily; } /** * ランキング集計対象の日付を指定します。 * * - 日間: 任意の日付 * - 週間: 火曜日の日付 * - 月間・四半期: 1日の日付 * * @param date 集計対象の日付 * @returns {RankingBuilder} this * @see https://dev.syosetu.com/man/rankapi/ */ date(date) { this.date$ = date; return this; } /** * ランキング種別を指定します。 * @param type ランキング種別 * @returns {RankingBuilder} this * @see https://dev.syosetu.com/man/rankapi/ */ type(type) { this.type$ = type; return this; } /** * gzip圧縮する。 * * 転送量上限を減らすためにも推奨 * @param {GzipLevel} level gzip圧縮レベル(1~5) * @return {RankingBuilder} this */ gzip(level) { this.set({ gzip: level }); return this; } /** * クエリパラメータを内部的にセットします。 * @param obj - セットするパラメータオブジェクト * @returns {RankingBuilder} this * @private */ set(obj) { Object.assign(this.params, obj); return this; } /** * 設定されたパラメータに基づき、なろう小説ランキングAPIへのリクエストを実行します。 * * 返される結果には、Nコード、ポイント、順位が含まれます。 * @param options 実行オプション * @returns {Promise<NarouRankingResult[]>} ランキング結果の配列 * @see https://dev.syosetu.com/man/rankapi/#output */ execute(options) { const date = formatDate(this.date$); this.set({ rtype: `${date}-${this.type$}` }); return this.api.executeRanking(this.params, options); } /** * ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。 * * @template TFields - 取得する小説情報のフィールド型 * @template TOpt - オプショナルな取得フィールドの型 * @param fields - 取得するフィールドの配列 (省略時はデフォルトフィールド) * @param opt - オプショナルな取得フィールド (`weekly` など) * @returns {Promise<RankingResult<SearchResultFields<TFields>>[]>} 詳細情報を含むランキング結果の配列 */ async executeWithFields(fields = [], opt, options) { const ranking = await this.execute(options); const fields$ = Array.isArray(fields) ? fields.length == 0 ? [] : [...fields, Fields.ncode] : [fields, Fields.ncode]; const rankingNcodes = ranking.map(({ ncode }) => ncode); const builder = new SearchBuilder({}, this.api); builder.fields(fields$); if (opt) builder.opt(opt); builder.ncode(rankingNcodes); builder.limit(ranking.length); const result = await builder.execute(options); return ranking.map((r) => ({ ...r, ...result.values.find((novel) => novel.ncode == r.ncode) })); } }; //#endregion //#region src/ranking-history.ts /** * 生のランキング履歴エントリを構造化された形式にフォーマットします。 * * @param rankin - フォーマットする生のランキング履歴データ * @returns 日付とタイプが解析されたフォーマット済みランキング履歴 * * @example * const rawData = { rtype: "20230101-daily", pt: 500, rank: 10 }; * const formattedData = formatRankingHistory(rawData); * // 返り値: { type: "daily", date: [Dateオブジェクト], pt: 500, rank: 10 } */ function formatRankingHistory(rankin) { const { rtype, pt, rank } = rankin; const [_date, _type] = rtype.split("-"); return { type: _type, date: parseDate(_date), pt, rank }; } //#endregion //#region src/search-builder-r18.ts /** * 18禁API検索ヘルパー * @class SearchBuilderR18 */ var SearchBuilderR18 = class extends NovelSearchBuilderBase { /** * なろう小説APIへの検索リクエストを実行する * @override * @param options 実行オプション * @returns {Promise<NarouSearchResults>} 検索結果 */ execute(options) { return this.api.executeNovel18(this.params, options); } /** * 抽出するR18サイトを指定します (nocgenre)。 * @param sites R18サイトコード、またはR18サイトコードの配列 (1: ノクターンノベルズ, 2: ムーンライトノベルズ(男性向け), 3: ムーンライトノベルズ(BL), 4: ミッドナイトノベルズ) * @return {this} */ r18Site(sites) { this.set({ nocgenre: NovelSearchBuilderBase.array2string(sites) }); return this; } /** * X-IDを指定して取得します (xid)。 * @param ids X-ID、またはX-IDの配列 * @return {this} */ xid(ids) { this.set({ xid: NovelSearchBuilderBase.array2string(ids) }); return this; } /** * 出力する項目を個別に指定します (of)。 * 未指定時は全項目出力されます。転送量軽減のため、このパラメータの使用が推奨されます。 * @param fields 出力するR18フィールド名、またはR18フィールド名の配列 * @return {SearchBuilderR18<SearchResultR18Fields<R18Fields>>} 型が更新されたビルダー */ fields(fields) { this.set({ of: NovelSearchBuilderBase.array2string(fields) }); return this; } /** * 出力オプション項目を指定します (opt)。 * @param option 出力するオプションフィールド名、またはオプションフィールド名の配列 * @return {SearchBuilderR18<T, SearchResultOptionalFields<TFields>>} 型が更新されたビルダー */ opt(option) { this.set({ opt: NovelSearchBuilderBase.array2string(option) }); return this; } }; //#endregion //#region src/user-search.ts /** * なろうユーザ検索API * @class UserSearch */ var UserSearchBuilder = class UserSearchBuilder extends SearchBuilderBase { /** * 単語を指定できます。 * 半角または全角スペースで区切るとAND抽出になります。 * 部分一致でHITします。検索の対象はユーザ名とユーザ名のフリガナです。 */ word(word) { this.set({ word }); return this; } /** * 含みたくない単語を指定できます。 * スペースで区切ることにより含ませない単語を増やせます。部分一致で除外されます。 * 除外の対象はユーザ名とユーザ名のフリガナです。 */ notWord(notword) { this.set({ notword }); return this; } /** * ユーザIDで抽出可能。 */ userId(userid) { this.set({ userid }); return this; } /** * 抽出するユーザのユーザ名のフリガナの頭文字を指定できます。 * 頭文字はユーザ名のフリガナをひらがなに変換し、最初の1文字が「ぁ」~「ん」の場合に対象となります。 * 「ぱ」や「ば」等の半濁音や濁音は清音として扱われます。 * 漢字や英数字が頭文字のユーザは対象外です。 */ name1st(name1st) { this.set({ name1st }); return this; } /** * 抽出するユーザの小説投稿数の下限を指定できます。 * 小説投稿件数が指定された数値以上のユーザを抽出します。 */ minNovel(minnovel) { this.set({ minnovel }); return this; } /** * 抽出するユーザの小説投稿数の上限を指定できます。 * 小説投稿件数が指定された数値以下のユーザを抽出します。 */ maxNovel(maxnovel) { this.set({ maxnovel }); return this; } /** * 抽出するユーザのレビュー投稿数の下限を指定できます。 * レビュー投稿件数が指定された数値以上のユーザを抽出します。 */ minReview(minreview) { this.set({ minreview }); return this; } /** * 抽出するユーザのレビュー投稿数の上限を指定できます。 * レビュー投稿件数が指定された数値以下のユーザを抽出します。 */ maxReview(maxreview) { this.set({ maxreview }); return this; } /** * 出力する項目を個別に指定できます。未指定時は全項目出力されます。転送量軽減のため、このパラメータの使用が推奨されます。 * @return {SearchBuilder} this */ fields(fields) { this.set({ of: UserSearchBuilder.array2string(fields) }); return this; } /** * なろう小説APIへのリクエストを実行する * @param options 実行オプション * @returns ランキング */ execute(options) { return this.api.executeUserSearch(this.params, options); } }; //#endregion Object.defineProperty(exports, 'BigGenre', { enumerable: true, get: function () { return BigGenre; } }); Object.defineProperty(exports, 'BigGenreNotation', { enumerable: true, get: function () { return BigGenreNotation; } }); Object.defineProperty(exports, 'BooleanNumber', { enumerable: true, get: function () { return BooleanNumber; } }); Object.defineProperty(exports, 'BuntaiParam', { enumerable: true, get: function () { return BuntaiParam; } }); Object.defineProperty(exports, 'DateParam', { enumerable: true, get: function () { return DateParam; } }); Object.defineProperty(exports, 'End', { enumerable: true, get: function () { return End; } }); Object.defineProperty(exports, 'Fields', { enumerable: true, get: function () { return Fields; } }); Object.defineProperty(exports, 'Genre', { enumerable: true, get: function () { return Genre; } }); Object.defineProperty(exports, 'GenreNotation', { enumerable: true, get: function () { return GenreNotation; } }); Object.defineProperty(exports, 'NarouNovel', { enumerable: true, get: function () { return NarouNovel; } }); Object.defineProperty(exports, 'NarouNovelJsonp', { enumerable: true, get: function () { return NarouNovelJsonp; } }); Object.defineProperty(exports, 'NarouSearchResults', { enumerable: true, get: function () { return NarouSearchResults; } }); Object.defineProperty(exports, 'NovelSearchBuilderBase', { enumerable: true, get: function () { return NovelSearchBuilderBase; } }); Object.defineProperty(exports, 'NovelType', { enumerable: true, get: function () { return NovelType; } }); Object.defineProperty(exports, 'NovelTypeParam', { enumerable: true, get: function () { return NovelTypeParam; } }); Object.defineProperty(exports, 'OptionalFields', { enumerable: true, get: function () { return OptionalFields; } }); Object.defineProperty(exports, 'Order', { enumerable: true, get: function () { return Order; } }); Object.defineProperty(exports, 'R18Fields', { enumerable: true, get: function () { return R18Fields; } }); Object.defineProperty(exports, 'R18Site', { enumerable: true, get: function () { return R18Site; } }); Object.defineProperty(exports, 'R18SiteNotation', { enumerable: true, get: function () { return R18SiteNotation; } }); Object.defineProperty(exports, 'RankingBuilder', { enumerable: true, get: function () { return RankingBuilder; } }); Object.defineProperty(exports, 'RankingType', { enumerable: true, get: function () { return RankingType; } }); Object.defineProperty(exports, 'SearchBuilder', { enumerable: true, get: function () { return SearchBuilder; } }); Object.defineProperty(exports, 'SearchBuilderBase', { enumerable: true, get: function () { return SearchBuilderBase; } }); Object.defineProperty(exports, 'SearchBuilderR18', { enumerable: true, get: function () { return SearchBuilderR18; } }); Object.defineProperty(exports, 'StopParam', { enumerable: true, get: function () { return StopParam; } }); Object.defineProperty(exports, 'UserFields', { enumerable: true, get: function () { return UserFields; } }); Object.defineProperty(exports, 'UserOrder', { enumerable: true, get: function () { return UserOrder; } }); Object.defineProperty(exports, 'UserSearchBuilder', { enumerable: true, get: function () { return UserSearchBuilder; } }); Object.defineProperty(exports, 'formatRankingHistory', { enumerable: true, get: function () { return formatRankingHistory; } }); //# sourceMappingURL=index.common-CYj0n0aU.cjs.map