import { Readable } from "node:stream"; import HTTPClient from "./http-axios.js"; import * as Types from "./types.js"; import { AxiosRequestConfig, AxiosResponse } from "axios"; import { createMultipartFormData, ensureJSON, toArray } from "./utils.js"; import { DATA_API_PREFIX, MESSAGING_API_PREFIX, OAUTH_BASE_PREFIX, OAUTH_BASE_PREFIX_V2_1, } from "./endpoints.js"; type ChatType = "group" | "room"; type RequestOption = { retryKey: string; }; /** * @deprecated Use clients generated by openapi spec instead. */ export default class Client { public config: Types.ClientConfig; private http: HTTPClient; private requestOption: Partial = {}; constructor(config: Types.ClientConfig) { if (!config.channelAccessToken) { throw new Error("no channel access token"); } this.config = config; this.http = new HTTPClient({ defaultHeaders: { Authorization: "Bearer " + this.config.channelAccessToken, }, responseParser: this.parseHTTPResponse.bind(this), ...config.httpConfig, }); } public setRequestOptionOnce(option: Partial) { this.requestOption = option; } private generateRequestConfig(): Partial { const config: Partial = { headers: {} }; if (this.requestOption.retryKey) { config.headers["X-Line-Retry-Key"] = this.requestOption.retryKey; } // clear requestOption this.requestOption = {}; return config; } private parseHTTPResponse(response: AxiosResponse) { const { LINE_REQUEST_ID_HTTP_HEADER_NAME } = Types; let resBody = { ...response.data, }; if (response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]) { resBody[LINE_REQUEST_ID_HTTP_HEADER_NAME] = response.headers[LINE_REQUEST_ID_HTTP_HEADER_NAME]; } return resBody; } public pushMessage( to: string, messages: Types.Message | Types.Message[], notificationDisabled: boolean = false, customAggregationUnits?: string[], ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/push`, { messages: toArray(messages), to, notificationDisabled, customAggregationUnits, }, this.generateRequestConfig(), ); } public replyMessage( replyToken: string, messages: Types.Message | Types.Message[], notificationDisabled: boolean = false, ): Promise { return this.http.post(`${MESSAGING_API_PREFIX}/message/reply`, { messages: toArray(messages), replyToken, notificationDisabled, }); } public async multicast( to: string[], messages: Types.Message | Types.Message[], notificationDisabled: boolean = false, customAggregationUnits?: string[], ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/multicast`, { messages: toArray(messages), to, notificationDisabled, customAggregationUnits, }, this.generateRequestConfig(), ); } public async narrowcast( messages: Types.Message | Types.Message[], recipient?: Types.ReceieptObject, filter?: { demographic: Types.DemographicFilterObject }, limit?: { max?: number; upToRemainingQuota?: boolean }, notificationDisabled?: boolean, ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/narrowcast`, { messages: toArray(messages), recipient, filter, limit, notificationDisabled, }, this.generateRequestConfig(), ); } public async broadcast( messages: Types.Message | Types.Message[], notificationDisabled: boolean = false, ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/broadcast`, { messages: toArray(messages), notificationDisabled, }, this.generateRequestConfig(), ); } public validatePushMessageObjects( messages: Types.Message | Types.Message[], ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/validate/push`, { messages: toArray(messages), }, this.generateRequestConfig(), ); } public validateReplyMessageObjects( messages: Types.Message | Types.Message[], ): Promise { return this.http.post(`${MESSAGING_API_PREFIX}/message/validate/reply`, { messages: toArray(messages), }); } public async validateMulticastMessageObjects( messages: Types.Message | Types.Message[], ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/validate/multicast`, { messages: toArray(messages), }, this.generateRequestConfig(), ); } public async validateNarrowcastMessageObjects( messages: Types.Message | Types.Message[], ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/validate/narrowcast`, { messages: toArray(messages), }, this.generateRequestConfig(), ); } public async validateBroadcastMessageObjects( messages: Types.Message | Types.Message[], ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/message/validate/broadcast`, { messages: toArray(messages), }, this.generateRequestConfig(), ); } public validateCustomAggregationUnits(units: string[]): { messages: string[]; valid: boolean; } { const messages: string[] = []; if (units.length > 1) { messages.push("customAggregationUnits can only contain one unit"); } units.forEach((unit, index) => { if (unit.length > 30) { messages.push( `customAggregationUnits[${index}] must be less than or equal to 30 characters`, ); } if (!/^[a-zA-Z0-9_]+$/.test(unit)) { messages.push( `customAggregationUnits[${index}] must be alphanumeric characters or underscores`, ); } }); return { messages, valid: messages.length === 0, }; } public async getProfile(userId: string): Promise { const profile = await this.http.get( `${MESSAGING_API_PREFIX}/profile/${userId}`, ); return ensureJSON(profile); } private async getChatMemberProfile( chatType: ChatType, chatId: string, userId: string, ): Promise { const profile = await this.http.get( `${MESSAGING_API_PREFIX}/${chatType}/${chatId}/member/${userId}`, ); return ensureJSON(profile); } public async getGroupMemberProfile( groupId: string, userId: string, ): Promise { return this.getChatMemberProfile("group", groupId, userId); } public async getRoomMemberProfile( roomId: string, userId: string, ): Promise { return this.getChatMemberProfile("room", roomId, userId); } private async getChatMemberIds( chatType: ChatType, chatId: string, ): Promise { let memberIds: string[] = []; let start: string; do { const res = await this.http.get<{ memberIds: string[]; next?: string }>( `${MESSAGING_API_PREFIX}/${chatType}/${chatId}/members/ids`, start ? { start } : null, ); ensureJSON(res); memberIds = memberIds.concat(res.memberIds); start = res.next; } while (start); return memberIds; } public async getGroupMemberIds(groupId: string): Promise { return this.getChatMemberIds("group", groupId); } public async getRoomMemberIds(roomId: string): Promise { return this.getChatMemberIds("room", roomId); } public async getBotFollowersIds(): Promise { let userIds: string[] = []; let start: string; do { const res = await this.http.get<{ userIds: string[]; next?: string }>( `${MESSAGING_API_PREFIX}/followers/ids`, start ? { start, limit: 1000 } : { limit: 1000 }, ); ensureJSON(res); userIds = userIds.concat(res.userIds); start = res.next; } while (start); return userIds; } public async getGroupMembersCount( groupId: string, ): Promise { const groupMemberCount = await this.http.get( `${MESSAGING_API_PREFIX}/group/${groupId}/members/count`, ); return ensureJSON(groupMemberCount); } public async getRoomMembersCount( roomId: string, ): Promise { const roomMemberCount = await this.http.get( `${MESSAGING_API_PREFIX}/room/${roomId}/members/count`, ); return ensureJSON(roomMemberCount); } public async getGroupSummary( groupId: string, ): Promise { const groupSummary = await this.http.get( `${MESSAGING_API_PREFIX}/group/${groupId}/summary`, ); return ensureJSON(groupSummary); } public async getMessageContent(messageId: string): Promise { return this.http.getStream( `${DATA_API_PREFIX}/message/${messageId}/content`, ); } private leaveChat(chatType: ChatType, chatId: string): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/${chatType}/${chatId}/leave`, ); } public async leaveGroup(groupId: string): Promise { return this.leaveChat("group", groupId); } public async leaveRoom(roomId: string): Promise { return this.leaveChat("room", roomId); } public async getRichMenu( richMenuId: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/richmenu/${richMenuId}`, ); return ensureJSON(res); } public async createRichMenu(richMenu: Types.RichMenu): Promise { const res = await this.http.post( `${MESSAGING_API_PREFIX}/richmenu`, richMenu, ); return ensureJSON(res).richMenuId; } public async deleteRichMenu(richMenuId: string): Promise { return this.http.delete(`${MESSAGING_API_PREFIX}/richmenu/${richMenuId}`); } public async getRichMenuAliasList(): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/richmenu/alias/list`, ); return ensureJSON(res); } public async getRichMenuAlias( richMenuAliasId: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/richmenu/alias/${richMenuAliasId}`, ); return ensureJSON(res); } public async createRichMenuAlias( richMenuId: string, richMenuAliasId: string, ): Promise<{}> { const res = await this.http.post<{}>( `${MESSAGING_API_PREFIX}/richmenu/alias`, { richMenuId, richMenuAliasId, }, ); return ensureJSON(res); } public async deleteRichMenuAlias(richMenuAliasId: string): Promise<{}> { const res = this.http.delete<{}>( `${MESSAGING_API_PREFIX}/richmenu/alias/${richMenuAliasId}`, ); return ensureJSON(res); } public async updateRichMenuAlias( richMenuAliasId: string, richMenuId: string, ): Promise<{}> { const res = await this.http.post<{}>( `${MESSAGING_API_PREFIX}/richmenu/alias/${richMenuAliasId}`, { richMenuId, }, ); return ensureJSON(res); } public async getRichMenuIdOfUser(userId: string): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/user/${userId}/richmenu`, ); return ensureJSON(res).richMenuId; } public async linkRichMenuToUser( userId: string, richMenuId: string, ): Promise { return this.http.post( `${MESSAGING_API_PREFIX}/user/${userId}/richmenu/${richMenuId}`, ); } public async unlinkRichMenuFromUser(userId: string): Promise { return this.http.delete(`${MESSAGING_API_PREFIX}/user/${userId}/richmenu`); } public async linkRichMenuToMultipleUsers( richMenuId: string, userIds: string[], ): Promise { return this.http.post(`${MESSAGING_API_PREFIX}/richmenu/bulk/link`, { richMenuId, userIds, }); } public async unlinkRichMenusFromMultipleUsers( userIds: string[], ): Promise { return this.http.post(`${MESSAGING_API_PREFIX}/richmenu/bulk/unlink`, { userIds, }); } public async getRichMenuImage(richMenuId: string): Promise { return this.http.getStream( `${DATA_API_PREFIX}/richmenu/${richMenuId}/content`, ); } public async setRichMenuImage( richMenuId: string, data: Buffer | Readable, contentType?: string, ): Promise { return this.http.postBinary( `${DATA_API_PREFIX}/richmenu/${richMenuId}/content`, data, contentType, ); } public async getRichMenuList(): Promise> { const res = await this.http.get( `${MESSAGING_API_PREFIX}/richmenu/list`, ); return ensureJSON(res).richmenus; } public async setDefaultRichMenu(richMenuId: string): Promise<{}> { return this.http.post( `${MESSAGING_API_PREFIX}/user/all/richmenu/${richMenuId}`, ); } public async getDefaultRichMenuId(): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/user/all/richmenu`, ); return ensureJSON(res).richMenuId; } public async deleteDefaultRichMenu(): Promise<{}> { return this.http.delete(`${MESSAGING_API_PREFIX}/user/all/richmenu`); } public async getLinkToken(userId: string): Promise { const res = await this.http.post( `${MESSAGING_API_PREFIX}/user/${userId}/linkToken`, ); return ensureJSON(res).linkToken; } public async getNumberOfSentReplyMessages( date: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/delivery/reply?date=${date}`, ); return ensureJSON(res); } public async getNumberOfSentPushMessages( date: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/delivery/push?date=${date}`, ); return ensureJSON(res); } public async getNumberOfSentMulticastMessages( date: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/delivery/multicast?date=${date}`, ); return ensureJSON(res); } public async getNarrowcastProgress( requestId: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/progress/narrowcast?requestId=${requestId}`, ); return ensureJSON(res); } public async getTargetLimitForAdditionalMessages(): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/quota`, ); return ensureJSON(res); } public async getNumberOfMessagesSentThisMonth(): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/quota/consumption`, ); return ensureJSON(res); } public async getNumberOfSentBroadcastMessages( date: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/message/delivery/broadcast?date=${date}`, ); return ensureJSON(res); } public async getNumberOfMessageDeliveries( date: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/insight/message/delivery?date=${date}`, ); return ensureJSON(res); } public async getNumberOfFollowers( date: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/insight/followers?date=${date}`, ); return ensureJSON(res); } public async getFriendDemographics(): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/insight/demographic`, ); return ensureJSON(res); } public async getUserInteractionStatistics( requestId: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/insight/message/event?requestId=${requestId}`, ); return ensureJSON(res); } public async getStatisticsPerUnit( customAggregationUnit: string, from: string, to: string, ): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/insight/message/event/aggregation?customAggregationUnit=${customAggregationUnit}&from=${from}&to=${to}`, ); return ensureJSON(res); } public async createUploadAudienceGroup(uploadAudienceGroup: { description: string; isIfaAudience?: boolean; audiences?: { id: string }[]; uploadDescription?: string; }) { const res = await this.http.post<{ audienceGroupId: number; type: string; description: string; created: number; }>(`${MESSAGING_API_PREFIX}/audienceGroup/upload`, { ...uploadAudienceGroup, }); return ensureJSON(res); } public async createUploadAudienceGroupByFile(uploadAudienceGroup: { description: string; isIfaAudience?: boolean; uploadDescription?: string; file: Buffer | Readable; }) { const file = await this.http.toBuffer(uploadAudienceGroup.file); const body = createMultipartFormData({ ...uploadAudienceGroup, file }); const res = await this.http.postFormMultipart<{ audienceGroupId: number; type: "UPLOAD"; description: string; created: number; }>(`${DATA_API_PREFIX}/audienceGroup/upload/byFile`, body); return ensureJSON(res); } public async updateUploadAudienceGroup( uploadAudienceGroup: { audienceGroupId: number; description?: string; uploadDescription?: string; audiences: { id: string }[]; }, // for set request timeout httpConfig?: Partial, ) { const res = await this.http.put<{}>( `${MESSAGING_API_PREFIX}/audienceGroup/upload`, { ...uploadAudienceGroup, }, httpConfig, ); return ensureJSON(res); } public async updateUploadAudienceGroupByFile( uploadAudienceGroup: { audienceGroupId: number; uploadDescription?: string; file: Buffer | Readable; }, // for set request timeout httpConfig?: Partial, ) { const file = await this.http.toBuffer(uploadAudienceGroup.file); const body = createMultipartFormData({ ...uploadAudienceGroup, file }); const res = await this.http.putFormMultipart<{}>( `${DATA_API_PREFIX}/audienceGroup/upload/byFile`, body, httpConfig, ); return ensureJSON(res); } public async createClickAudienceGroup(clickAudienceGroup: { description: string; requestId: string; clickUrl?: string; }) { const res = await this.http.post< { audienceGroupId: number; type: string; created: number; } & typeof clickAudienceGroup >(`${MESSAGING_API_PREFIX}/audienceGroup/click`, { ...clickAudienceGroup, }); return ensureJSON(res); } public async createImpAudienceGroup(impAudienceGroup: { requestId: string; description: string; }) { const res = await this.http.post< { audienceGroupId: number; type: string; created: number; } & typeof impAudienceGroup >(`${MESSAGING_API_PREFIX}/audienceGroup/imp`, { ...impAudienceGroup, }); return ensureJSON(res); } public async setDescriptionAudienceGroup( description: string, audienceGroupId: string, ) { const res = await this.http.put<{}>( `${MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}/updateDescription`, { description, }, ); return ensureJSON(res); } public async deleteAudienceGroup(audienceGroupId: string) { const res = await this.http.delete<{}>( `${MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}`, ); return ensureJSON(res); } public async getAudienceGroup(audienceGroupId: string) { const res = await this.http.get( `${MESSAGING_API_PREFIX}/audienceGroup/${audienceGroupId}`, ); return ensureJSON(res); } public async getAudienceGroups( page: number, description?: string, status?: Types.AudienceGroupStatus, size?: number, createRoute?: Types.AudienceGroupCreateRoute, includesExternalPublicGroups?: boolean, ) { const res = await this.http.get<{ audienceGroups: Types.AudienceGroups; hasNextPage: boolean; totalCount: number; readWriteAudienceGroupTotalCount: number; page: number; size: number; }>(`${MESSAGING_API_PREFIX}/audienceGroup/list`, { page, description, status, size, createRoute, includesExternalPublicGroups, }); return ensureJSON(res); } public async getAudienceGroupAuthorityLevel() { const res = await this.http.get<{ authorityLevel: Types.AudienceGroupAuthorityLevel; }>(`${MESSAGING_API_PREFIX}/audienceGroup/authorityLevel`); return ensureJSON(res); } public async changeAudienceGroupAuthorityLevel( authorityLevel: Types.AudienceGroupAuthorityLevel, ) { const res = await this.http.put<{}>( `${MESSAGING_API_PREFIX}/audienceGroup/authorityLevel`, { authorityLevel }, ); return ensureJSON(res); } public async getBotInfo(): Promise { const res = await this.http.get( `${MESSAGING_API_PREFIX}/info`, ); return ensureJSON(res); } public async setWebhookEndpointUrl(endpoint: string) { return this.http.put<{}>( `${MESSAGING_API_PREFIX}/channel/webhook/endpoint`, { endpoint }, ); } public async getWebhookEndpointInfo() { const res = await this.http.get( `${MESSAGING_API_PREFIX}/channel/webhook/endpoint`, ); return ensureJSON(res); } public async testWebhookEndpoint(endpoint?: string) { const res = await this.http.post( `${MESSAGING_API_PREFIX}/channel/webhook/test`, { endpoint }, ); return ensureJSON(res); } public async validateRichMenu(richMenu: Types.RichMenu): Promise<{}> { return await this.http.post<{}>( `${MESSAGING_API_PREFIX}/richmenu/validate`, richMenu, ); } } export class OAuth { private http: HTTPClient; constructor() { this.http = new HTTPClient(); } public issueAccessToken( client_id: string, client_secret: string, ): Promise { return this.http.postForm(`${OAUTH_BASE_PREFIX}/accessToken`, { grant_type: "client_credentials", client_id, client_secret, }); } public revokeAccessToken(access_token: string): Promise<{}> { return this.http.postForm(`${OAUTH_BASE_PREFIX}/revoke`, { access_token }); } public verifyAccessToken( access_token: string, ): Promise { return this.http.get(`${OAUTH_BASE_PREFIX_V2_1}/verify`, { access_token }); } public verifyIdToken( id_token: string, client_id: string, nonce?: string, user_id?: string, ): Promise { const body: { [key: string]: string } = { id_token, client_id, }; if (nonce) { body.nonce = nonce; } if (user_id) { body.user_id = user_id; } return this.http.postForm(`${OAUTH_BASE_PREFIX_V2_1}/verify`, body); } public issueChannelAccessTokenV2_1( client_assertion: string, ): Promise { return this.http.postForm(`${OAUTH_BASE_PREFIX_V2_1}/token`, { grant_type: "client_credentials", client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", client_assertion, }); } public getChannelAccessTokenKeyIdsV2_1( client_assertion: string, ): Promise<{ key_ids: string[] }> { return this.http.get(`${OAUTH_BASE_PREFIX_V2_1}/tokens/kid`, { client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", client_assertion, }); } public revokeChannelAccessTokenV2_1( client_id: string, client_secret: string, access_token: string, ): Promise<{}> { return this.http.postForm(`${OAUTH_BASE_PREFIX_V2_1}/revoke`, { client_id, client_secret, access_token, }); } }