Skip to content

Commit 51a2d4b

Browse files
author
Hyunje Jun
authored
Merge pull request #34 from musou1500/support-richmenu-api
WIP: Support richmenu api
2 parents be100e0 + d7fb792 commit 51a2d4b

File tree

11 files changed

+366
-75
lines changed

11 files changed

+366
-75
lines changed

lib/client.ts

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import { get, post, stream } from "./http";
1+
import { Readable } from "stream";
2+
import { get, post, stream, del, postBinary } from "./http";
23
import * as Types from "./types";
34
import * as URL from "./urls";
4-
import { toArray } from "./util";
5+
import { toArray, detectContentType } from "./util";
6+
import { JSONParseError } from "./exceptions";
57

68
export default class Client {
79
public config: Types.ClientConfig;
@@ -45,52 +47,58 @@ export default class Client {
4547
}
4648

4749
public getProfile(userId: string): Promise<Types.Profile> {
48-
return this.get(URL.profile(userId));
50+
return this.get(URL.profile(userId)).then(this.checkJSON);
4951
}
5052

5153
public getGroupMemberProfile(
5254
groupId: string,
5355
userId: string,
5456
): Promise<Types.Profile> {
55-
return this.get(URL.groupMemberProfile(groupId, userId));
57+
return this.get(URL.groupMemberProfile(groupId, userId)).then(
58+
this.checkJSON,
59+
);
5660
}
5761

5862
public getRoomMemberProfile(
5963
roomId: string,
6064
userId: string,
6165
): Promise<Types.Profile> {
62-
return this.get(URL.roomMemberProfile(roomId, userId));
66+
return this.get(URL.roomMemberProfile(roomId, userId)).then(this.checkJSON);
6367
}
6468

6569
public getGroupMemberIds(groupId: string): Promise<string[]> {
6670
const load = (start?: string): Promise<string[]> =>
67-
this.get(
68-
URL.groupMemberIds(groupId, start),
69-
).then((res: { memberIds: string[]; next?: string }) => {
70-
if (!res.next) {
71-
return res.memberIds;
72-
}
73-
74-
return load(res.next).then(extraIds => res.memberIds.concat(extraIds));
75-
});
71+
this.get(URL.groupMemberIds(groupId, start))
72+
.then(this.checkJSON)
73+
.then((res: { memberIds: string[]; next?: string }) => {
74+
if (!res.next) {
75+
return res.memberIds;
76+
}
77+
78+
return load(res.next).then(extraIds =>
79+
res.memberIds.concat(extraIds),
80+
);
81+
});
7682
return load();
7783
}
7884

7985
public getRoomMemberIds(roomId: string): Promise<string[]> {
8086
const load = (start?: string): Promise<string[]> =>
81-
this.get(
82-
URL.roomMemberIds(roomId, start),
83-
).then((res: { memberIds: string[]; next?: string }) => {
84-
if (!res.next) {
85-
return res.memberIds;
86-
}
87-
88-
return load(res.next).then(extraIds => res.memberIds.concat(extraIds));
89-
});
87+
this.get(URL.roomMemberIds(roomId, start))
88+
.then(this.checkJSON)
89+
.then((res: { memberIds: string[]; next?: string }) => {
90+
if (!res.next) {
91+
return res.memberIds;
92+
}
93+
94+
return load(res.next).then(extraIds =>
95+
res.memberIds.concat(extraIds),
96+
);
97+
});
9098
return load();
9199
}
92100

93-
public getMessageContent(messageId: string): Promise<NodeJS.ReadableStream> {
101+
public getMessageContent(messageId: string): Promise<Readable> {
94102
return this.stream(URL.content(messageId));
95103
}
96104

@@ -102,10 +110,59 @@ export default class Client {
102110
return this.post(URL.leaveRoom(roomId));
103111
}
104112

113+
public getRichMenu(
114+
richMenuId: string,
115+
): Promise<Types.RichMenuId & Types.RichMenu> {
116+
return this.get(URL.richMenu(richMenuId)).then(this.checkJSON);
117+
}
118+
119+
public createRichMenu(richMenu: Types.RichMenu): Promise<Types.RichMenuId> {
120+
return this.post(URL.richMenu(), richMenu).then(this.checkJSON);
121+
}
122+
123+
public deleteRichMenu(richMenuId: string): Promise<any> {
124+
return this.delete(URL.richMenu(richMenuId));
125+
}
126+
127+
public getUserRichMenuIds(userId: string): Promise<Types.RichMenuId> {
128+
return this.get(URL.userRichMenu(userId)).then(this.checkJSON);
129+
}
130+
131+
public linkRichMenuToUser(userId: string, richMenuId: string): Promise<any> {
132+
return this.post(URL.userRichMenu(userId, richMenuId));
133+
}
134+
135+
public unlinkRichMenuFromUser(
136+
userId: string,
137+
richMenuId: string,
138+
): Promise<any> {
139+
return this.delete(URL.userRichMenu(userId, richMenuId));
140+
}
141+
142+
public getRichMenuImage(richMenuId: string): Promise<Readable> {
143+
return this.stream(URL.richMenuContent(richMenuId));
144+
}
145+
146+
public setRichMenuImage(
147+
richMenuId: string,
148+
data: Buffer | Readable,
149+
contentType?: string,
150+
): Promise<any> {
151+
return this.postBinary(URL.richMenuContent(richMenuId), data, contentType);
152+
}
153+
154+
public getRichMenuList(): Promise<Array<Types.RichMenuId & Types.RichMenu>> {
155+
return this.get(URL.richMenuList()).then(this.checkJSON);
156+
}
157+
105158
private authHeader(): { [key: string]: string } {
106159
return { Authorization: "Bearer " + this.config.channelAccessToken };
107160
}
108161

162+
private delete(url: string): Promise<any> {
163+
return del(url, this.authHeader());
164+
}
165+
109166
private get(url: string): Promise<any> {
110167
return get(url, this.authHeader());
111168
}
@@ -114,7 +171,23 @@ export default class Client {
114171
return post(url, this.authHeader(), body);
115172
}
116173

117-
private stream(url: string): Promise<NodeJS.ReadableStream> {
174+
private postBinary(
175+
url: string,
176+
data: Buffer | Readable,
177+
contentType?: string,
178+
) {
179+
return postBinary(url, this.authHeader(), data, contentType);
180+
}
181+
182+
private stream(url: string): Promise<Readable> {
118183
return stream(url, this.authHeader());
119184
}
185+
186+
private checkJSON(raw: any): any {
187+
if (typeof raw === "object") {
188+
return raw;
189+
} else {
190+
throw new JSONParseError("Failed to parse response body as JSON", raw);
191+
}
192+
}
120193
}

lib/http.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
import axios, { AxiosError } from "axios";
2-
import {
3-
HTTPError,
4-
JSONParseError,
5-
ReadError,
6-
RequestError,
7-
} from "./exceptions";
2+
import { detectContentType } from "./util";
3+
import { Readable } from "stream";
4+
import { HTTPError, ReadError, RequestError } from "./exceptions";
85

96
const pkg = require("../package.json");
107

11-
function checkJSON(raw: any): any {
12-
if (typeof raw === "object") {
13-
return raw;
14-
} else {
15-
throw new JSONParseError("Failed to parse response body as JSON", raw);
16-
}
17-
}
18-
198
function wrapError(err: AxiosError) {
209
if (err.response) {
2110
throw new HTTPError(
@@ -37,22 +26,19 @@ function wrapError(err: AxiosError) {
3726

3827
const userAgent = `${pkg.name}/${pkg.version}`;
3928

40-
export function stream(
41-
url: string,
42-
headers: any,
43-
): Promise<NodeJS.ReadableStream> {
29+
export function stream(url: string, headers: any): Promise<Readable> {
4430
headers["User-Agent"] = userAgent;
4531
return axios
4632
.get(url, { headers, responseType: "stream" })
47-
.then(res => res.data as NodeJS.ReadableStream);
33+
.then(res => res.data as Readable);
4834
}
4935

5036
export function get(url: string, headers: any): Promise<any> {
5137
headers["User-Agent"] = userAgent;
5238

5339
return axios
5440
.get(url, { headers })
55-
.then(res => checkJSON(res.data))
41+
.then(res => res.data)
5642
.catch(wrapError);
5743
}
5844

@@ -61,6 +47,33 @@ export function post(url: string, headers: any, data?: any): Promise<any> {
6147
headers["User-Agent"] = userAgent;
6248
return axios
6349
.post(url, data, { headers })
64-
.then(res => checkJSON(res.data))
50+
.then(res => res.data)
51+
.catch(wrapError);
52+
}
53+
54+
export function postBinary(
55+
url: string,
56+
headers: any,
57+
data: Buffer | Readable,
58+
contentType?: string,
59+
): Promise<any> {
60+
return new Promise(resolve => {
61+
return resolve(contentType ? contentType : detectContentType(data));
62+
}).then((contentType: string) => {
63+
headers["Content-Type"] = contentType;
64+
headers["User-Agent"] = userAgent;
65+
return axios
66+
.post(url, data, { headers })
67+
.then(res => res.data)
68+
.catch(wrapError);
69+
});
70+
}
71+
72+
export function del(url: string, headers: any): Promise<any> {
73+
headers["User-Agent"] = userAgent;
74+
75+
return axios
76+
.delete(url, { headers })
77+
.then(res => res.data)
6578
.catch(wrapError);
6679
}

lib/types.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export type ImageMapMessage = {
170170
type: "image";
171171
baseUrl: string;
172172
altText: string;
173-
baseSize: { width: number; height: number };
173+
baseSize: Size;
174174
actions: ImageMapAction[];
175175
};
176176

@@ -182,7 +182,7 @@ export type TemplateMessage = {
182182

183183
export type ImageMapAction = ImageMapURIAction | ImageMapMessageAction;
184184

185-
export type ImageMapActionBase = { area: ImageMapArea };
185+
export type ImageMapActionBase = { area: Area };
186186

187187
export type ImageMapURIAction = {
188188
type: "uri";
@@ -194,7 +194,7 @@ export type ImageMapMessageAction = {
194194
text: string;
195195
} & ImageMapActionBase;
196196

197-
export type ImageMapArea = {
197+
export type Area = {
198198
x: number;
199199
y: number;
200200
width: number;
@@ -212,13 +212,13 @@ export type TemplateButtons = {
212212
thumbnailImageUrl?: string;
213213
title?: string;
214214
text: string;
215-
actions: TemplateAction<{ label: string }>[];
215+
actions: Action<{ label: string }>[];
216216
};
217217

218218
export type TemplateConfirm = {
219219
type: "confirm";
220220
text: string;
221-
actions: TemplateAction<{ label: string }>[];
221+
actions: Action<{ label: string }>[];
222222
};
223223

224224
export type TemplateCarousel = { type: "carousel"; columns: TemplateColumn[] };
@@ -227,7 +227,7 @@ export type TemplateColumn = {
227227
thumbnailImageUrl?: string;
228228
title?: string;
229229
text: string;
230-
actions: TemplateAction<{ label: string }>[];
230+
actions: Action<{ label: string }>[];
231231
};
232232

233233
export type TemplateImageCarousel = {
@@ -237,36 +237,51 @@ export type TemplateImageCarousel = {
237237

238238
export type TemplateImageColumn = {
239239
imageUrl: string;
240-
action: TemplateAction<{ label?: string }>;
240+
action: Action<{ label?: string }>;
241241
};
242242

243-
export type TemplateAction<Label> =
244-
| TemplatePostbackAction & Label
245-
| TemplateMessageAction & Label
246-
| TemplateURIAction & Label
247-
| TemplateDatetimePickerAction & Label;
243+
export type Action<Label> =
244+
| PostbackAction & Label
245+
| MessageAction & Label
246+
| URIAction & Label
247+
| DatetimePickerAction & Label;
248248

249-
export type TemplatePostbackAction = {
249+
export type PostbackAction = {
250250
type: "postback";
251251
data: string;
252252
text?: string;
253253
};
254254

255-
export type TemplateMessageAction = {
255+
export type MessageAction = {
256256
type: "message";
257257
text: string;
258258
};
259259

260-
export type TemplateURIAction = {
260+
export type URIAction = {
261261
type: "uri";
262262
uri: string;
263263
};
264264

265-
export type TemplateDatetimePickerAction = {
265+
export type DatetimePickerAction = {
266266
type: "datetimepicker";
267267
data: string;
268268
mode: "date" | "time" | "datetime";
269269
initial?: string;
270270
max?: string;
271271
min?: string;
272272
};
273+
274+
export type Size = {
275+
width: number;
276+
height: number;
277+
};
278+
279+
export type RichMenuId = { richMenuId: string };
280+
281+
export type RichMenu = {
282+
size: Size;
283+
selected: boolean;
284+
name: string;
285+
chatBarText: string;
286+
areas: Array<{ bounds: Area; action: Action<{}> }>;
287+
};

lib/urls.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,14 @@ export const roomMemberIds = (roomId: string, start?: string) =>
3232
export const leaveGroup = (groupId: string) => apiURL(`group/${groupId}/leave`);
3333

3434
export const leaveRoom = (roomId: string) => apiURL(`room/${roomId}/leave`);
35+
36+
export const richMenu = (richMenuId?: string) =>
37+
apiURL("richmenu" + (richMenuId ? `/${richMenuId}` : ""));
38+
39+
export const richMenuList = () => apiURL(`richmenu/list`);
40+
41+
export const userRichMenu = (userId: string, richMenuId?: string) =>
42+
apiURL(`user/${userId}/richmenu` + (richMenuId ? `/${richMenuId}` : ""));
43+
44+
export const richMenuContent = (richMenuId: string) =>
45+
apiURL(`richmenu/${richMenuId}/content`);

0 commit comments

Comments
 (0)