Skip to content

Commit 0fef4bc

Browse files
authored
Merge pull request #213 from deflis/add-fetch-parameters
fetchのパラメータを指定できるようにする。
2 parents 3fb956e + fa4272b commit 0fef4bc

14 files changed

+338
-41
lines changed

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type NarouNovel from "./narou.js";
2+
import type { ExecuteOptions } from "./narou.js";
23
import NarouNovelFetch from "./narou-fetch.js";
34
import NarouNovelJsonp from "./narou-jsonp.js";
45
import RankingBuilder from "./ranking.js";
@@ -67,13 +68,16 @@ export function ranking(api: NarouNovel = narouNovelFetch): RankingBuilder {
6768
/**
6869
* なろう殿堂入り API でランキング履歴を取得する
6970
* @param {string} ncode 小説のNコード
71+
* @param {ExecuteOptions} [options] 実行オプション
72+
* @param {NarouNovel} [api] API実行クラスのインスタンス
7073
* @see https://dev.syosetu.com/man/rankinapi/
7174
*/
7275
export async function rankingHistory(
7376
ncode: string,
77+
options?: ExecuteOptions,
7478
api: NarouNovel = narouNovelFetch
7579
): Promise<RankingHistoryResult[]> {
76-
const result = await api.executeRankingHistory({ ncode });
80+
const result = await api.executeRankingHistory({ ncode }, options);
7781
if (Array.isArray(result)) {
7882
return result.map(formatRankingHistory);
7983
} else {

src/narou-fetch.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { unzipp } from "./util/unzipp.js";
22
import NarouNovel from "./narou.js";
3-
import type { NarouParams } from "./narou.js";
3+
import type { NarouParams, ExecuteOptions } from "./narou.js";
44

55
type Fetch = typeof fetch;
66

@@ -18,7 +18,8 @@ export default class NarouNovelFetch extends NarouNovel {
1818

1919
protected async execute<T>(
2020
params: NarouParams,
21-
endpoint: string
21+
endpoint: string,
22+
options?: ExecuteOptions
2223
): Promise<T> {
2324
const query = { ...params, out: "json" };
2425

@@ -36,7 +37,7 @@ export default class NarouNovelFetch extends NarouNovel {
3637
}
3738
});
3839

39-
const res = await (this.fetch ?? fetch)(url);
40+
const res = await (this.fetch ?? fetch)(url, options?.fetchOptions);
4041

4142
if (!query.gzip) {
4243
return (await res.json()) as T;

src/narou-jsonp.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import NarouNovel from "./narou.js";
2-
import type { NarouParams } from "./narou.js";
2+
import type { NarouParams, ExecuteOptions } from "./narou.js";
33
import { jsonp } from "./util/jsonp.js";
44

55
/**
@@ -8,7 +8,9 @@ import { jsonp } from "./util/jsonp.js";
88
export default class NarouNovelJsonp extends NarouNovel {
99
protected async execute<T>(
1010
params: NarouParams,
11-
endpoint: string
11+
endpoint: string,
12+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
13+
_options?: ExecuteOptions
1214
): Promise<T> {
1315
const query = { ...params, out: "jsonp" };
1416
query.gzip = 0;

src/narou.ts

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,16 @@ export type NarouParams =
2121
| RankingHistoryParams
2222
| UserSearchParams;
2323

24+
/**
25+
* なろう小説APIへのリクエストオプション
26+
*/
27+
export interface ExecuteOptions {
28+
/**
29+
* fetch関数のオプション
30+
*/
31+
fetchOptions?: RequestInit;
32+
}
33+
2434
/**
2535
* なろう小説APIへのリクエストを実行する
2636
* @class NarouNovel
@@ -35,7 +45,8 @@ export default abstract class NarouNovel {
3545
*/
3646
protected abstract execute<T>(
3747
params: NarouParams,
38-
endpoint: string
48+
endpoint: string,
49+
options?: ExecuteOptions
3950
): Promise<T>;
4051

4152
/**
@@ -46,9 +57,13 @@ export default abstract class NarouNovel {
4657
*/
4758
protected async executeSearch<T extends keyof NarouSearchResult>(
4859
params: SearchParams,
49-
endpoint = "https://api.syosetu.com/novelapi/api/"
60+
endpoint = "https://api.syosetu.com/novelapi/api/",
61+
options?: ExecuteOptions
5062
): Promise<NarouSearchResults<NarouSearchResult, T>> {
51-
return new NarouSearchResults(await this.execute(params, endpoint), params);
63+
return new NarouSearchResults(
64+
await this.execute(params, endpoint, options),
65+
params
66+
);
5267
}
5368

5469
/**
@@ -58,11 +73,13 @@ export default abstract class NarouNovel {
5873
* @see https://dev.syosetu.com/man/api/
5974
*/
6075
async executeNovel<T extends keyof NarouSearchResult>(
61-
params: SearchParams
76+
params: SearchParams,
77+
options?: ExecuteOptions
6278
): Promise<NarouSearchResults<NarouSearchResult, T>> {
6379
return await this.executeSearch(
6480
params,
65-
"https://api.syosetu.com/novelapi/api/"
81+
"https://api.syosetu.com/novelapi/api/",
82+
options
6683
);
6784
}
6885

@@ -73,11 +90,13 @@ export default abstract class NarouNovel {
7390
* @see https://dev.syosetu.com/xman/api/
7491
*/
7592
async executeNovel18<T extends keyof NarouSearchResult>(
76-
params: SearchParams
93+
params: SearchParams,
94+
options?: ExecuteOptions
7795
): Promise<NarouSearchResults<NarouSearchResult, T>> {
7896
return await this.executeSearch(
7997
params,
80-
"https://api.syosetu.com/novel18api/api/"
98+
"https://api.syosetu.com/novel18api/api/",
99+
options
81100
);
82101
}
83102

@@ -87,20 +106,33 @@ export default abstract class NarouNovel {
87106
* @returns ランキング結果
88107
* @see https://dev.syosetu.com/man/rankapi/
89108
*/
90-
async executeRanking(params: RankingParams): Promise<NarouRankingResult[]> {
91-
return await this.execute(params, "https://api.syosetu.com/rank/rankget/");
109+
async executeRanking(
110+
params: RankingParams,
111+
options?: ExecuteOptions
112+
): Promise<NarouRankingResult[]> {
113+
return await this.execute(
114+
params,
115+
"https://api.syosetu.com/rank/rankget/",
116+
options
117+
);
92118
}
93119

94120
/**
95121
* 殿堂入りAPiへのリクエストを実行する
96122
* @param params クエリパラメータ
123+
* @param options 実行オプション
97124
* @returns ランキング履歴結果
98125
* @see https://dev.syosetu.com/man/rankinapi/
99126
*/
100127
async executeRankingHistory(
101-
params: RankingHistoryParams
128+
params: RankingHistoryParams,
129+
options?: ExecuteOptions
102130
): Promise<RankingHistoryRawResult[]> {
103-
return await this.execute(params, "https://api.syosetu.com/rank/rankin/");
131+
return await this.execute(
132+
params,
133+
"https://api.syosetu.com/rank/rankin/",
134+
options
135+
);
104136
}
105137

106138
/**
@@ -110,10 +142,15 @@ export default abstract class NarouNovel {
110142
* @see https://dev.syosetu.com/man/userapi/
111143
*/
112144
async executeUserSearch<T extends keyof UserSearchResult>(
113-
params: UserSearchParams
145+
params: UserSearchParams,
146+
options?: ExecuteOptions
114147
): Promise<NarouSearchResults<UserSearchResult, T>> {
115148
return new NarouSearchResults<UserSearchResult, T>(
116-
await this.execute(params, "https://api.syosetu.com/userapi/api/"),
149+
await this.execute(
150+
params,
151+
"https://api.syosetu.com/userapi/api/",
152+
options
153+
),
117154
params
118155
);
119156
}

src/ranking.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Fields,
1212
} from "./params.js";
1313
import type NarouNovel from "./narou.js";
14+
import type { ExecuteOptions } from "./narou.js";
1415
import type { SearchResultFields } from "./narou-search-results.js";
1516
import { addDays, formatDate } from "./util/date.js";
1617

@@ -108,21 +109,24 @@ export default class RankingBuilder {
108109
* 設定されたパラメータに基づき、なろう小説ランキングAPIへのリクエストを実行します。
109110
*
110111
* 返される結果には、Nコード、ポイント、順位が含まれます。
112+
* @param options 実行オプション
111113
* @returns {Promise<NarouRankingResult[]>} ランキング結果の配列
112114
* @see https://dev.syosetu.com/man/rankapi/#output
113115
*/
114-
execute(): Promise<NarouRankingResult[]> {
116+
execute(options?: ExecuteOptions): Promise<NarouRankingResult[]> {
115117
const date = formatDate(this.date$);
116118
this.set({ rtype: `${date}-${this.type$}` });
117-
return this.api.executeRanking(this.params as RankingParams);
119+
return this.api.executeRanking(this.params as RankingParams, options);
118120
}
119121

120122
/**
121123
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
122124
*/
123-
async executeWithFields(): Promise<
124-
RankingResult<DefaultSearchResultFields>[]
125-
>;
125+
async executeWithFields(
126+
fields?: never[] | undefined,
127+
opt?: never[] | undefined,
128+
options?: ExecuteOptions
129+
): Promise<RankingResult<DefaultSearchResultFields>[]>;
126130
/**
127131
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
128132
*
@@ -131,7 +135,9 @@ export default class RankingBuilder {
131135
* @returns {Promise<RankingResult<SearchResultFields<TFields>>[]>} 詳細情報を含むランキング結果の配列
132136
*/
133137
async executeWithFields<TFields extends Fields>(
134-
fields: TFields | TFields[]
138+
fields: TFields | TFields[],
139+
opt?: never | never[],
140+
options?: ExecuteOptions
135141
): Promise<RankingResult<SearchResultFields<TFields>>[]>;
136142
/**
137143
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
@@ -141,7 +147,8 @@ export default class RankingBuilder {
141147
*/
142148
async executeWithFields(
143149
fields: never[],
144-
opt: OptionalFields | OptionalFields[]
150+
opt: OptionalFields | OptionalFields[],
151+
options?: ExecuteOptions
145152
): Promise<RankingResult<DefaultSearchResultFields | "weekly_unique">[]>;
146153
/**
147154
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
@@ -153,7 +160,8 @@ export default class RankingBuilder {
153160
*/
154161
async executeWithFields<TFields extends Fields>(
155162
fields: TFields | TFields[],
156-
opt: OptionalFields | OptionalFields[]
163+
opt: OptionalFields | OptionalFields[],
164+
options?: ExecuteOptions
157165
): Promise<RankingResult<SearchResultFields<TFields> | "weekly_unique">[]>;
158166
/**
159167
* ランキングAPIを実行し、取得したNコードを元になろう小説APIで詳細情報を取得して結合します。
@@ -169,9 +177,10 @@ export default class RankingBuilder {
169177
TOpt extends OptionalFields | undefined = undefined
170178
>(
171179
fields: TFields | TFields[] = [],
172-
opt?: TOpt
180+
opt?: TOpt,
181+
options?: ExecuteOptions
173182
): Promise<RankingResult<SearchResultFields<TFields>>[]> {
174-
const ranking = await this.execute();
183+
const ranking = await this.execute(options);
175184
const fields$ = Array.isArray(fields)
176185
? fields.length == 0
177186
? []
@@ -186,7 +195,7 @@ export default class RankingBuilder {
186195
}
187196
builder.ncode(rankingNcodes);
188197
builder.limit(ranking.length);
189-
const result = await builder.execute();
198+
const result = await builder.execute(options);
190199

191200
return ranking.map<
192201
RankingResult<

src/search-builder-r18.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NovelSearchBuilderBase } from "./search-builder.js";
2+
import type { ExecuteOptions } from "./narou.js";
23
import type NarouSearchResults from "./narou-search-results.js";
34
import type {
45
NarouSearchResult,
@@ -28,10 +29,13 @@ export default class SearchBuilderR18<
2829
/**
2930
* なろう小説APIへの検索リクエストを実行する
3031
* @override
32+
* @param options 実行オプション
3133
* @returns {Promise<NarouSearchResults>} 検索結果
3234
*/
33-
execute(): Promise<NarouSearchResults<NarouSearchResult, T | TOpt>> {
34-
return this.api.executeNovel18(this.params);
35+
execute(
36+
options?: ExecuteOptions
37+
): Promise<NarouSearchResults<NarouSearchResult, T | TOpt>> {
38+
return this.api.executeNovel18(this.params, options);
3539
}
3640

3741
/**

src/search-builder.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type NarouNovel from "./narou.js";
2+
import type { ExecuteOptions } from "./narou.js";
23
import type {
34
NarouSearchResult,
45
SearchResultFields,
@@ -40,7 +41,7 @@ export abstract class SearchBuilderBase<
4041
constructor(
4142
protected params: TParams = {} as TParams,
4243
protected api: NarouNovel
43-
) {}
44+
) { }
4445

4546
/**
4647
* 配列から重複を除去する
@@ -472,10 +473,13 @@ export abstract class NovelSearchBuilderBase<
472473

473474
/**
474475
* なろう小説APIへの検索リクエストを実行する
476+
* @param options 実行オプション
475477
* @returns {Promise<NarouSearchResults>} 検索結果
476478
*/
477-
execute(): Promise<NarouSearchResults<NarouSearchResult, T>> {
478-
return this.api.executeNovel(this.params);
479+
execute(
480+
options?: ExecuteOptions
481+
): Promise<NarouSearchResults<NarouSearchResult, T>> {
482+
return this.api.executeNovel(this.params, options);
479483
}
480484
}
481485

src/user-search.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
} from "./narou-search-results.js";
66
import type { UserFields, UserOrder, UserSearchParams } from "./params.js";
77
import { SearchBuilderBase } from "./search-builder.js";
8+
import type { ExecuteOptions } from "./narou.js";
89

910
/**
1011
* なろうユーザ検索API
@@ -102,9 +103,15 @@ export default class UserSearchBuilder<
102103

103104
/**
104105
* なろう小説APIへのリクエストを実行する
106+
* @param options 実行オプション
105107
* @returns ランキング
106108
*/
107-
execute(): Promise<NarouSearchResults<UserSearchResult, TField>> {
108-
return this.api.executeUserSearch(this.params as UserSearchParams);
109+
execute(
110+
options?: ExecuteOptions
111+
): Promise<NarouSearchResults<UserSearchResult, TField>> {
112+
return this.api.executeUserSearch(
113+
this.params as UserSearchParams,
114+
options
115+
);
109116
}
110117
}

test/narou-fetch.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,23 @@ describe('NarouNovelFetch', () => {
166166
// リクエストが呼ばれたことを確認
167167
expect(requestSpy).toHaveBeenCalled();
168168
});
169+
170+
it('should pass fetchOptions to fetch', async () => {
171+
// MSWでエンドポイントをモック
172+
server.use(
173+
http.get('https://api.example.com', ({ request }) => {
174+
expect(request.headers.get('user-agent')).toBe('node-narou');
175+
return responseGzipOrJson(mockData, new URL(request.url));
176+
})
177+
);
178+
179+
const narouFetch = new NarouNovelFetch();
180+
181+
// @ts-expect-error - Accessing protected method for testing
182+
await narouFetch.execute(
183+
{ gzip: 0 },
184+
'https://api.example.com',
185+
{ fetchOptions: { headers: { 'user-agent': 'node-narou' } } }
186+
);
187+
});
169188
});

0 commit comments

Comments
 (0)