narou
Version:
Narou API Wrapper
1,346 lines (1,334 loc) • 37.6 kB
JavaScript
//#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