-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathranking.ts
More file actions
224 lines (211 loc) · 7.68 KB
/
ranking.ts
File metadata and controls
224 lines (211 loc) · 7.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
import type { NarouRankingResult, RankingResult } from "./narou-ranking-results.js";
import SearchBuilder from "./search-builder.js";
import type { DefaultSearchResultFields } from "./search-builder.js";
import type {
GzipLevel,
OptionalFields,
} from "./params.js";
import {
RankingParams,
RankingType,
Fields,
} from "./params.js";
import type NarouNovel from "./narou.js";
import type { SearchResultFields } from "./narou-search-results.js";
import { addDays, formatDate } from "./util/date.js";
function isRequestInit(value: unknown): value is RequestInit {
return typeof value == "object" && value !== null && !Array.isArray(value);
}
/**
* なろう小説ランキングAPIのヘルパークラス。
*
* ランキング種別や日付を指定してランキングデータを取得します。
* また、取得したランキングデータに含まれるNコードを元に、
* なろう小説APIを利用して詳細な小説情報を取得することも可能です。
*
* @class RankingBuilder
* @see https://dev.syosetu.com/man/rankapi/ なろう小説ランキングAPI仕様
*/
export default class RankingBuilder {
/**
* ランキング集計対象の日付
* @protected
*/
protected date$: Date;
/**
* ランキング種別
* @protected
*/
protected type$: RankingType;
/**
* constructor
* @param params - 初期クエリパラメータ
* @param api - API実行クラスのインスタンス
* @private
*/
constructor(
protected params: Partial<RankingParams> = {},
protected api: NarouNovel
) {
/**
* クエリパラメータ
* @protected
*/
this.date$ = addDays(new Date(), -1);
this.type$ = RankingType.Daily;
}
/**
* ランキング集計対象の日付を指定します。
*
* - 日間: 任意の日付
* - 週間: 火曜日の日付
* - 月間・四半期: 1日の日付
*
* @param date 集計対象の日付
* @returns {RankingBuilder} this
* @see https://dev.syosetu.com/man/rankapi/
*/
date(date: Date) {
this.date$ = date;
return this;
}
/**
* ランキング種別を指定します。
* @param type ランキング種別
* @returns {RankingBuilder} this
* @see https://dev.syosetu.com/man/rankapi/
*/
type(type: RankingType) {
this.type$ = type;
return this;
}
/**
* gzip圧縮する。
*
* 転送量上限を減らすためにも推奨
* @param {GzipLevel} level gzip圧縮レベル(1~5)
* @return {RankingBuilder} this
*/
gzip(level: GzipLevel) {
this.set({ gzip: level });
return this;
}
/**
* クエリパラメータを内部的にセットします。
* @param obj - セットするパラメータオブジェクト
* @returns {RankingBuilder} this
* @private
*/
protected set(obj: Partial<RankingParams>) {
Object.assign(this.params, obj);
return this;
}
/**
* 設定されたパラメータに基づき、なろう小説ランキングAPIへのリクエストを実行します。
*
* 返される結果には、Nコード、ポイント、順位が含まれます。
* @param {RequestInit} [fetchOptions] fetch のオプション
* @returns {Promise<NarouRankingResult[]>} ランキング結果の配列
* @see https://dev.syosetu.com/man/rankapi/#output
*/
execute(fetchOptions?: RequestInit): Promise<NarouRankingResult[]> {
const date = formatDate(this.date$);
this.set({ rtype: `${date}-${this.type$}` });
return this.api.executeRanking(this.params as RankingParams, fetchOptions);
}
/**
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
*/
async executeWithFields(
fetchOptions?: RequestInit
): Promise<RankingResult<DefaultSearchResultFields>[]>;
/**
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
*
* @template TFields - 取得する小説情報のフィールド型
* @param fields - 取得するフィールドの配列
* @returns {Promise<RankingResult<SearchResultFields<TFields>>[]>} 詳細情報を含むランキング結果の配列
*/
async executeWithFields<TFields extends Fields>(
fields: TFields | TFields[],
fetchOptions?: RequestInit
): Promise<RankingResult<SearchResultFields<TFields>>[]>;
/**
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
*
* @param opt - オプショナルな取得フィールド (`weekly` など)
* @returns {Promise<RankingResult<DefaultSearchResultFields | "weekly_unique">[]>} 詳細情報を含むランキング結果の配列
*/
async executeWithFields(
fields: never[],
opt: OptionalFields | OptionalFields[],
fetchOptions?: RequestInit
): Promise<RankingResult<DefaultSearchResultFields | "weekly_unique">[]>;
/**
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
*
* @template TFields - 取得する小説情報のフィールド型
* @param fields - 取得するフィールドの配列
* @param opt - オプショナルな取得フィールド (`weekly` など)
* @returns {Promise<RankingResult<SearchResultFields<TFields> | "weekly_unique">[]>} 詳細情報を含むランキング結果の配列
*/
async executeWithFields<TFields extends Fields>(
fields: TFields | TFields[],
opt: OptionalFields | OptionalFields[],
fetchOptions?: RequestInit
): Promise<RankingResult<SearchResultFields<TFields> | "weekly_unique">[]>;
/**
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
*
* @template TFields - 取得する小説情報のフィールド型
* @template TOpt - オプショナルな取得フィールドの型
* @param fields - 取得するフィールドの配列 (省略時はデフォルトフィールド)
* @param opt - オプショナルな取得フィールド (`weekly` など)
* @param fetchOptions fetch のオプション
* @returns {Promise<RankingResult<SearchResultFields<TFields>>[]>} 詳細情報を含むランキング結果の配列
*/
async executeWithFields<
TFields extends Fields,
TOpt extends OptionalFields | undefined = undefined
>(
fields: TFields | TFields[] | RequestInit = [],
opt?: TOpt | RequestInit,
fetchOptions?: RequestInit
): Promise<RankingResult<SearchResultFields<TFields>>[]> {
let opt$ = opt;
let fetchOptions$ = fetchOptions;
if (isRequestInit(fields)) {
fetchOptions$ = fields;
fields = [] as TFields[];
opt$ = undefined;
} else if (isRequestInit(opt)) {
fetchOptions$ = opt;
opt$ = undefined;
}
const ranking = await this.execute(fetchOptions$);
const fields$ = Array.isArray(fields)
? fields.length == 0
? []
: ([...fields, Fields.ncode] as const)
: ([fields, Fields.ncode] as const);
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(fetchOptions$);
return ranking.map<
RankingResult<
| SearchResultFields<TFields>
| (TOpt extends "weekly" ? "weekly_unique" : never)
>
>((r) => ({
...r,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...(result.values.find((novel) => novel.ncode == r.ncode) as any),
}));
}
}