Skip to content

Commit ff872be

Browse files
committed
Support different base urls
1 parent dc34acf commit ff872be

File tree

2 files changed

+41
-11
lines changed

2 files changed

+41
-11
lines changed

src/fetch-books.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ export async function fetchBooks(
2929
};
3030
}
3131

32-
export function toUrl(query: Query, filter: Filter): string {
33-
const url = new URL(Kindle.BOOKS_URL);
32+
export function toUrl(baseUrl: string, query: Query, filter: Filter): string {
33+
const url = new URL(`${baseUrl}/${Kindle.BOOKS_PATH}`);
3434
const searchParams = {
3535
...query,
3636
...filter,

src/kindle.ts

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,21 @@ export type KindleConfiguration = {
4747
cookies: KindleRequiredCookies,
4848
clientOptions: TlsClientConfig
4949
) => HttpClient;
50+
51+
/**
52+
* Base url of the kindle service.
53+
* Amazon has different regional service endpoints, for example https://lesen.amazon.de/kindle-library (DACH region) or https://read.amazon.com/kindle-library (worldwide).
54+
* Path and query parameters will be ignored.
55+
*
56+
* @default "https://read.amazon.com"
57+
*/
58+
baseUrl?: string;
5059
};
5160

5261
export type KindleOptions = {
5362
config: KindleConfiguration;
5463
sessionId: string;
64+
baseUrl: string;
5565
};
5666

5767
export type KindleFromCookieOptions = {
@@ -60,13 +70,18 @@ export type KindleFromCookieOptions = {
6070
};
6171

6272
export class Kindle {
63-
public static DEVICE_TOKEN_URL =
64-
"https://read.amazon.com/service/web/register/getDeviceToken";
65-
public static readonly BOOKS_URL =
66-
"https://read.amazon.com/kindle-library/search?query=&libraryType=BOOKS&sortType=recency&querySize=50";
73+
public static readonly BASE_URL = "https://read.amazon.com";
74+
75+
public static readonly DEVICE_TOKEN_PATH =
76+
"service/web/register/getDeviceToken";
77+
78+
public static readonly BOOKS_PATH =
79+
"kindle-library/search?query=&libraryType=BOOKS&sortType=recency&querySize=50";
80+
6781
public static readonly DEFAULT_QUERY = Object.freeze({
6882
sortType: "acquisition_desc",
6983
} satisfies Query);
84+
7085
public static readonly DEFAULT_FILTER = Object.freeze({
7186
querySize: 50,
7287
fetchAllPages: false,
@@ -81,6 +96,7 @@ export class Kindle {
8196
*/
8297
readonly defaultBooks: KindleBook[];
8398
readonly #client: HttpClient;
99+
readonly #baseUrl: string;
84100

85101
constructor(
86102
private options: KindleOptions,
@@ -91,9 +107,11 @@ export class Kindle {
91107
) {
92108
this.defaultBooks = prePopulatedBooks ?? [];
93109
this.#client = client;
110+
this.#baseUrl = options.baseUrl;
94111
}
95112

96113
static async fromConfig(config: KindleConfiguration): Promise<Kindle> {
114+
const baseUrl = new URL(config.baseUrl ?? Kindle.BASE_URL).origin;
97115
const cookies =
98116
typeof config.cookies === "string"
99117
? Kindle.deserializeCookies(config.cookies)
@@ -102,10 +120,14 @@ export class Kindle {
102120
config.clientFactory?.(cookies, config.tlsServer) ??
103121
new HttpClient(cookies, config.tlsServer);
104122

105-
const { sessionId, books } = await Kindle.baseRequest(client);
123+
const { sessionId, books } = await Kindle.baseRequest(baseUrl, client);
106124
client.updateSession(sessionId);
107125

108-
const deviceInfo = await Kindle.deviceToken(client, config.deviceToken);
126+
const deviceInfo = await Kindle.deviceToken(
127+
baseUrl,
128+
client,
129+
config.deviceToken
130+
);
109131
client.updateAdpSession(deviceInfo.deviceSessionToken);
110132

111133
return new this(
@@ -115,26 +137,29 @@ export class Kindle {
115137
cookies,
116138
},
117139
sessionId,
140+
baseUrl,
118141
},
119142
client,
120143
books
121144
);
122145
}
123146

124147
static async deviceToken(
148+
baseUrl: string,
125149
client: HttpClient,
126150
token: string
127151
): Promise<KindleDeviceInfo> {
128152
const params = new URLSearchParams({
129153
serialNumber: token,
130154
deviceType: token,
131155
});
132-
const url = `${Kindle.DEVICE_TOKEN_URL}?${params.toString()}`;
156+
const url = `${baseUrl}/${Kindle.DEVICE_TOKEN_PATH}?${params.toString()}`;
133157
const response = await client.request(url);
134158
return JSON.parse(response.body) as KindleDeviceInfo;
135159
}
136160

137161
static async baseRequest(
162+
baseUrl: string,
138163
client: HttpClient,
139164
version?: string,
140165
args?: {
@@ -159,7 +184,7 @@ export class Kindle {
159184

160185
// loop until we get less than the requested amount of books or hit the limit
161186
do {
162-
const url = toUrl(query, filter);
187+
const url = toUrl(baseUrl, query, filter);
163188
const { books, sessionId, paginationToken } = await fetchBooks(
164189
client,
165190
url,
@@ -187,7 +212,12 @@ export class Kindle {
187212
query?: Query;
188213
filter?: Filter;
189214
}): Promise<KindleBook[]> {
190-
const result = await Kindle.baseRequest(this.#client, undefined, args);
215+
const result = await Kindle.baseRequest(
216+
this.#baseUrl,
217+
this.#client,
218+
undefined,
219+
args
220+
);
191221
// refreshing the internal session every time books is called.
192222
// This doesn't prevent us from calling the books endpoint but
193223
// it does prevent requesting the metadata of individual books

0 commit comments

Comments
 (0)