UNPKG

@licht-77/dmm-sdk

Version:

DMM Affiliate API v3 SDK for Node.js/TypeScript

211 lines 8.86 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DmmApiClient = void 0; const node_url_1 = require("node:url"); /** * DMM Affiliate API v3 Client. */ class DmmApiClient { baseUrl; static ItemListEndpoint = '/ItemList'; static FloorListEndpoint = '/FloorList'; static ActressSearchEndpoint = '/ActressSearch'; static GenreSearchEndpoint = '/GenreSearch'; static MakerSearchEndpoint = '/MakerSearch'; static SeriesSearchEndpoint = '/SeriesSearch'; static AuthorSearchEndpoint = '/AuthorSearch'; static DefaultHitsPerPageForGetAllItems = 100; apiId; affiliateId; /** * Creates an instance of DmmApiClient. * @param {DmmApiClientOptions} options - Client options. * @throws {Error} if apiId or affiliateId is missing. */ constructor(options) { if (!options.apiId || !options.affiliateId) { throw new Error('API ID and Affiliate ID are required.'); } this.apiId = options.apiId; this.affiliateId = options.affiliateId; if (options.baseUrl !== undefined) { if (typeof options.baseUrl !== 'string' || options.baseUrl.trim() === '') { throw new Error('Invalid baseUrl: must be a valid URL string or undefined.'); } try { const url = new node_url_1.URL(options.baseUrl); if (url.protocol !== 'http:' && url.protocol !== 'https:') { throw new Error('Invalid baseUrl: protocol must be http or https.'); } } catch (e) { // URL constructor throws TypeError for invalid URLs throw new Error('Invalid baseUrl: must be a valid URL string or undefined.'); } } let tempBaseUrl = options.baseUrl ?? 'https://api.dmm.com/affiliate/v3'; if (tempBaseUrl.endsWith('/')) { tempBaseUrl = tempBaseUrl.slice(0, -1); } this.baseUrl = tempBaseUrl; } /** * Sends a request to the API endpoint. * @protected * @template T The expected response type. * @param {string} endpoint - The API endpoint path (e.g., '/ItemList'). * @param {Record<string, string | number | undefined>} [params] - API parameters. * @returns {Promise<T>} The 'result' part of the API response. * @throws {Error} if the request fails, times out, or the response format is invalid. */ async request(endpoint, params) { const url = new node_url_1.URL(`${this.baseUrl}${endpoint}`); const queryParams = { api_id: this.apiId, affiliate_id: this.affiliateId, }; for (const key in params) { if (key !== 'api_id' && key !== 'affiliate_id' && params[key] !== undefined) { queryParams[key] = String(params[key]); } } const searchParams = new node_url_1.URLSearchParams(queryParams); url.search = searchParams.toString(); try { const response = await fetch(url.toString()); if (response.ok) { const data = await response.json(); if (!data || typeof data !== 'object' || !('result' in data)) { throw new Error('Invalid API response format: "result" field is missing.'); } return data.result; } // Non-retryable error or max retries exceeded const errorBody = await response.json().catch(e => response.text()); const errorMessage = errorBody?.result?.message || (typeof errorBody === 'string' ? errorBody : JSON.stringify(errorBody)) || response.statusText; throw new Error(`API request to ${endpoint} failed with status ${response.status}: ${errorMessage}`); } catch (error) { // Non-retryable error or max retries exceeded // HTTP error responses (including JSON parse failures) are handled above const originalErrorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Error during API request to ${endpoint}: ${originalErrorMessage}`); } } /** * Calls the Item List API. * @param {ItemListRequestParams} params - Search parameters. * @returns {Promise<ItemListResponse>} Item search results. */ async getItemList(params) { const apiParams = { ...params }; return this.request(DmmApiClient.ItemListEndpoint, apiParams); } /** * Calls the Floor List API. * @returns {Promise<FloorListResponse>} Floor list. */ async getFloorList() { // This API does not take additional parameters. return this.request(DmmApiClient.FloorListEndpoint); } /** * Calls the Actress Search API. * @param {ActressSearchRequestParams} params - Search parameters. * @returns {Promise<ActressSearchResponse>} Actress search results. */ async searchActress(params) { return this.request(DmmApiClient.ActressSearchEndpoint, { ...params }); } /** * Calls the Genre Search API. * @param {GenreSearchRequestParams} params - Search parameters. * @returns {Promise<GenreSearchResponse>} Genre search results. */ async searchGenre(params) { return this.request(DmmApiClient.GenreSearchEndpoint, { ...params }); } /** * Calls the Maker Search API. * @param {MakerSearchRequestParams} params - Search parameters. * @returns {Promise<MakerSearchResponse>} Maker search results. */ async searchMaker(params) { return this.request(DmmApiClient.MakerSearchEndpoint, { ...params }); } /** * Calls the Series Search API. * @param {SeriesSearchRequestParams} params - Search parameters. * @returns {Promise<SeriesSearchResponse>} Series search results. */ async searchSeries(params) { return this.request(DmmApiClient.SeriesSearchEndpoint, { ...params }); } /** * Calls the Author Search API. * @param {AuthorSearchRequestParams} params - Search parameters. * @returns {Promise<AuthorSearchResponse>} Author search results. */ async searchAuthor(params) { return this.request(DmmApiClient.AuthorSearchEndpoint, { ...params }); } /** * Retrieves all items matching the given criteria using the Item List API with an async generator. * Suitable for efficiently processing a large number of items. * @param {Omit<ItemListRequestParams, 'hits' | 'offset'>} params - Search parameters (hits and offset are managed internally and will be ignored). * @yields {Item} Matched item information. * @throws {Error} if an error occurs during API calls. */ async *getAllItems(params) { let currentOffset = 1; const hitsPerPage = DmmApiClient.DefaultHitsPerPageForGetAllItems; let totalCount = -1; const requestParams = { ...params, hits: hitsPerPage, offset: currentOffset, }; while (totalCount === -1 || currentOffset <= totalCount) { requestParams.offset = currentOffset; try { const response = await this.getItemList(requestParams); if (totalCount === -1) { totalCount = response.total_count; if (totalCount === 0) { return; } } if (response.items && response.items.length > 0) { for (const item of response.items) { yield item; } currentOffset = response.first_position + response.items.length; } else { break; } } catch (error) { const originalErrorMessage = error instanceof Error ? error.message : String(error); const enhancedError = new Error(`Error in getAllItems at offset ${currentOffset}: ${originalErrorMessage}`); if (error instanceof Error && error.stack) { enhancedError.stack = error.stack; } if (typeof Error.prototype.cause === 'undefined') { Object.defineProperty(enhancedError, 'cause', { value: error, enumerable: false, configurable: true, writable: true }); } else { enhancedError.cause = error; } throw enhancedError; } } } } exports.DmmApiClient = DmmApiClient; //# sourceMappingURL=client.js.map