Skip to content

Commit 2a9499d

Browse files
committed
Refactored media upload, download api.
(Convenience methods like sendMedia, sendMultiMedia were not affected.) Removed talk implementation reference from OpenLinkService. Update version to 4.1.0
1 parent bfafa4f commit 2a9499d

25 files changed

+566
-510
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-kakao",
3-
"version": "4.0.6",
3+
"version": "4.1.0",
44
"description": "Loco protocol compatible library",
55
"main": "./dist/index.js",
66
"exports": {

src/channel/channel-session.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import { Long } from '..';
1111
import { ChannelUser, NormalChannelUserInfo } from '../user';
1212
import { ChannelMeta, NormalChannelInfo, SetChannelMeta } from './channel-info';
1313
import { ChatOnRoomRes } from '../packet/chat';
14-
import { MediaDownloader, MediaUploader, MultiMediaUploader, MediaUploadTemplate } from '../talk';
15-
import { MediaKeyComponent } from '../media';
1614
import { ChannelMetaType } from './meta';
15+
import { MediaKeyComponent, MediaMultiPost, MediaPost, MediaUploadForm } from '../media';
16+
import { FixedReadStream } from '../stream';
1717

1818
export interface ChannelTemplate {
1919

@@ -95,28 +95,38 @@ export interface ChannelSession {
9595
getChatListFrom(startLogId?: Long): AsyncCommandResult<Chatlog[]>;
9696

9797
/**
98-
* Create media downloader
98+
* Create media download stream
9999
*
100100
* @param media
101101
* @param type
102+
* @param {number} [offset=0] Download start position
102103
*/
103-
downloadMedia(media: MediaKeyComponent, type: ChatType): AsyncCommandResult<MediaDownloader>;
104+
downloadMedia(media: MediaKeyComponent, type: ChatType, offset?: number): AsyncCommandResult<FixedReadStream>;
104105

105106
/**
106-
* Create media uploader.
107+
* Create media thumbnail download stream
108+
*
109+
* @param media
110+
* @param type
111+
* @param {number} [offset=0] Download start position
112+
*/
113+
downloadMediaThumb(media: MediaKeyComponent, type: ChatType, offset?: number): AsyncCommandResult<FixedReadStream>;
114+
115+
/**
116+
* Upload media.
107117
*
108118
* @param type Media type. Supports PHOTO, VIDEO, TEXT, FILE type.
109-
* @param template
119+
* @param form
110120
*/
111-
uploadMedia(type: ChatType, template: MediaUploadTemplate): AsyncCommandResult<MediaUploader>;
121+
uploadMedia(type: ChatType, form: MediaUploadForm): AsyncCommandResult<MediaPost>;
112122

113123
/**
114-
* Create multi media uploader.
124+
* Upload multi media.
115125
*
116126
* @param type Media type. Currently works only with MULTIPHOTO.
117-
* @param templates
127+
* @param forms
118128
*/
119-
uploadMultiMedia(type: ChatType, templates: MediaUploadTemplate[]): AsyncCommandResult<MultiMediaUploader[]>;
129+
uploadMultiMedia(type: ChatType, forms: MediaUploadForm[]): AsyncCommandResult<MediaMultiPost>;
120130

121131
}
122132

src/channel/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export interface UpdatableChannelDataStore<T, U> extends ChannelDataStore<T, U>,
5454

5555
}
5656

57-
export interface ChannelListStore<T> {
57+
export interface ChannelStore<T> {
5858

5959
/**
6060
* Try to get channel instance with channel id

src/chat/store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import { Chatlog } from './chat';
99
import { ChatListUpdater } from './updater';
1010

1111
/**
12-
* Store chat list
12+
* Store chats
1313
*/
14-
export interface ChatListStore {
14+
export interface ChatStore {
1515

1616
/**
1717
* Get latest chatlog
@@ -44,6 +44,6 @@ export interface ChatListStore {
4444

4545
}
4646

47-
export interface UpdatableChatListStore extends ChatListStore, ChatListUpdater {
47+
export interface UpdatableChatListStore extends ChatStore, ChatListUpdater {
4848

4949
}

src/media/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
* Copyright (c) storycraft. Licensed under the MIT Licence.
55
*/
66

7+
export * from './upload';
8+
79
export interface MediaKeyComponent {
810

911
key: string;

src/media/upload.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Created on Sat Mar 13 2021
3+
*
4+
* Copyright (c) storycraft. Licensed under the MIT Licence.
5+
*/
6+
7+
import { Chatlog } from '../chat';
8+
import { AsyncCommandResult, CommandResult } from '../request';
9+
import { FixedWriteStream } from '../stream';
10+
11+
export interface MediaMetadata {
12+
13+
/**
14+
* Media name
15+
*/
16+
readonly name: string;
17+
18+
/**
19+
* Media width (only photo, video)
20+
*/
21+
readonly width?: number;
22+
23+
/**
24+
* Media height (only photo, video)
25+
*/
26+
readonly height?: number;
27+
28+
/**
29+
* File extension (Optional. Required when sending file)
30+
*/
31+
readonly ext?: string;
32+
33+
}
34+
35+
export interface MediaUploadForm {
36+
37+
/**
38+
* Size of media data
39+
*/
40+
readonly size: number;
41+
42+
/**
43+
* Checksum of media data (sha1)
44+
*/
45+
readonly checksum: string;
46+
47+
/**
48+
* Media metadata
49+
*/
50+
readonly metadata: MediaMetadata;
51+
52+
}
53+
54+
export interface MediaPostEntry {
55+
56+
/**
57+
* Start offset of media data.
58+
*/
59+
offset: number;
60+
61+
/**
62+
* Write stream
63+
*/
64+
stream: FixedWriteStream;
65+
66+
}
67+
68+
export interface MediaMultiPostEntry extends MediaPostEntry {
69+
70+
/**
71+
* Finish current media upload and close media stream.
72+
*/
73+
finish(): AsyncCommandResult;
74+
75+
}
76+
77+
export interface MediaPost extends MediaPostEntry {
78+
79+
/**
80+
* Finish media upload and close media stream.
81+
*/
82+
finish(): AsyncCommandResult<Chatlog>;
83+
84+
}
85+
86+
export interface MediaMultiPost {
87+
88+
/**
89+
* Media post entries. Ordered same as given UploadForm list.
90+
* Each loop will create connection for n-th UploadForm.
91+
*/
92+
entries: AsyncIterableIterator<CommandResult<MediaMultiPostEntry>>;
93+
94+
/**
95+
* Finish all media uploads and send to channel.
96+
*/
97+
finish(): AsyncCommandResult<Chatlog>;
98+
99+
}

src/openlink/open-link-service.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@
66

77
import { Long } from 'bson';
88
import { InformedOpenLink } from '..';
9-
import { TypedEmitter } from '../event/typed';
10-
import { OpenLinkEvent } from '../talk/event';
119

12-
export interface OpenLinkService extends TypedEmitter<OpenLinkEvent> {
10+
export interface OpenLinkService {
1311

1412
/**
1513
* Get all client link as iterator

src/stream/fixed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export class FixedReadStream implements ReadStream, FixedStream {
5151

5252
let view: Uint8Array = buffer;
5353
if (this._read + view.byteLength > this._size) {
54-
view = buffer.subarray(0, Math.max(this._size - this._read, 0));
54+
view = buffer.subarray(0, this._size - this._read);
5555
}
5656

5757
const read = await this._stream.read(view);

src/stream/index.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,24 @@ export namespace ReadStreamUtil {
115115
return data;
116116
}
117117

118+
/**
119+
* Write every read data from ReadStream to WriteStream.
120+
*
121+
* @param {ReadStream} read Stream to be read
122+
* @param {WriteStream} write Stream to be written
123+
* @return {Promise<number>} Bytes written.
124+
*/
125+
export async function copy(read: ReadStream, write: WriteStream): Promise<number> {
126+
let written = 0;
127+
128+
for await (const data of iter(read)) {
129+
if (write.ended) break;
130+
written += await write.write(data);
131+
}
132+
133+
return written;
134+
}
135+
118136
}
119137

120138
/**

src/talk/channel/common.ts

Lines changed: 56 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,45 +5,74 @@
55
*/
66

77
import { Long } from 'bson';
8+
import { sha1 } from 'hash-wasm';
89
import { TalkChannel } from '.';
910
import { ChannelDataUpdater, ChannelSession, NormalChannelSession, UpdatableChannelDataStore } from '../../channel';
1011
import { Chatlog, ChatLogged, ChatType } from '../../chat';
11-
import { MediaKeyComponent } from '../../media';
12+
import { MediaUploadForm } from '../../media';
1213
import { OpenChannelSession } from '../../openlink';
13-
import { AsyncCommandResult, CommandResultDone, KnownDataStatusCode } from '../../request';
14+
import { AsyncCommandResult, KnownDataStatusCode } from '../../request';
1415
import { ChannelUser, NormalChannelUserInfo, OpenChannelUserInfo } from '../../user';
1516
import { MediaUploadTemplate } from '../media/upload';
1617

1718
/*
1819
* Common complex channel methods
1920
*/
2021

22+
export async function mediaTemplateToForm(template: MediaUploadTemplate): Promise<MediaUploadForm> {
23+
return {
24+
size: template.data.byteLength,
25+
checksum: await sha1(template.data),
26+
metadata: {
27+
name: template.name,
28+
width: template.width,
29+
height: template.height,
30+
ext: template.ext
31+
}
32+
};
33+
}
34+
2135
export async function sendMultiMedia(
22-
channelSession: ChannelSession,
23-
type: ChatType,
24-
templates: MediaUploadTemplate[],
36+
channelSession: ChannelSession,
37+
type: ChatType,
38+
templates: MediaUploadTemplate[],
2539
): AsyncCommandResult<Chatlog> {
26-
const res = await channelSession.uploadMultiMedia(type, templates);
40+
const res = await channelSession.uploadMultiMedia(
41+
type,
42+
await Promise.all(templates.map(mediaTemplateToForm))
43+
);
2744
if (!res.success) return res;
2845

29-
const keyResList = await Promise.all(res.result.map((uploader) => uploader.upload()));
30-
const failed = keyResList.find((uploadRes) => !uploadRes.success);
31-
if (failed && !failed.success) return failed;
32-
const keyList = keyResList as CommandResultDone<MediaKeyComponent>[];
46+
let i = 0;
47+
for await (const entryRes of res.result.entries) {
48+
if (!entryRes.success) return entryRes;
49+
const entry = entryRes.result;
50+
const data = templates[i].data;
3351

34-
return channelSession.forwardChat({
35-
text: '',
36-
type,
37-
attachment: {
38-
kl: keyList.map((uploadRes) => uploadRes.result.key),
39-
wl: templates.map((template) => template.width || 0),
40-
hl: templates.map((template) => template.height || 0),
41-
mtl: templates.map((template) => template.ext || ''),
42-
sl: templates.map((template) => template.data.byteLength),
43-
imageUrls: [], thumbnailUrls: [],
44-
thumbnailWidths: [], thumbnailHeights: [],
45-
},
46-
});
52+
await entry.stream.write(data.subarray(Math.min(entry.offset, data.byteLength)));
53+
54+
const finishRes = await entry.finish();
55+
if (!finishRes.success) return finishRes;
56+
57+
i++;
58+
}
59+
60+
return res.result.finish();
61+
}
62+
63+
export async function sendMedia(
64+
channelSession: ChannelSession,
65+
type: ChatType,
66+
template: MediaUploadTemplate
67+
): AsyncCommandResult<Chatlog> {
68+
const res = await channelSession.uploadMedia(type, await mediaTemplateToForm(template));
69+
if (!res.success) return res;
70+
71+
const data = template.data;
72+
73+
await res.result.stream.write(data);
74+
75+
return res.result.finish();
4776
}
4877

4978
export function initWatermark(
@@ -128,7 +157,7 @@ export async function updateChatList(
128157
* Store channel data in memory
129158
*/
130159
export class TalkMemoryChannelDataStore<T, U>
131-
implements UpdatableChannelDataStore<T, U> {
160+
implements UpdatableChannelDataStore<T, U> {
132161

133162
constructor(
134163
private _info: T,
@@ -137,7 +166,7 @@ implements UpdatableChannelDataStore<T, U> {
137166
) {
138167

139168
}
140-
169+
141170
get info(): Readonly<T> {
142171
return this._info;
143172
}
@@ -210,13 +239,13 @@ implements UpdatableChannelDataStore<T, U> {
210239

211240
return userInfoRes || watermarkRes;
212241
}
213-
242+
214243
updateWatermark(readerId: Long, watermark: Long): void {
215244
this._watermarkMap.set(readerId.toString(), watermark);
216245
}
217246

218247
clearWatermark(): void {
219248
this._watermarkMap.clear();
220249
}
221-
250+
222251
}

0 commit comments

Comments
 (0)