@ascegu/teamily 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/accounts.ts +51 -0
- package/src/channel.ts +411 -0
- package/src/config-schema.ts +67 -0
- package/src/monitor.ts +392 -0
- package/src/normalize.ts +90 -0
- package/src/probe.ts +86 -0
- package/src/runtime.ts +14 -0
- package/src/send.ts +273 -0
- package/src/types.ts +116 -0
package/src/send.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { ResolvedTeamilyAccount, TeamilyMessageTarget } from "./types.js";
|
|
2
|
+
import { CONTENT_TYPES, SESSION_TYPES } from "./types.js";
|
|
3
|
+
import { generateOperationID } from "./probe.js";
|
|
4
|
+
|
|
5
|
+
export interface SendTeamilyMessageParams {
|
|
6
|
+
account: ResolvedTeamilyAccount;
|
|
7
|
+
target: TeamilyMessageTarget;
|
|
8
|
+
text: string;
|
|
9
|
+
replyToId?: string;
|
|
10
|
+
fetchImpl?: typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SendTeamilyMediaParams {
|
|
14
|
+
account: ResolvedTeamilyAccount;
|
|
15
|
+
target: TeamilyMessageTarget;
|
|
16
|
+
mediaUrl: string;
|
|
17
|
+
mediaType: "image" | "video" | "audio" | "file";
|
|
18
|
+
caption?: string;
|
|
19
|
+
fetchImpl?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TeamilySendResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
messageId?: string;
|
|
25
|
+
serverMsgID?: string;
|
|
26
|
+
clientMsgID?: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Send a text message via Teamily REST API.
|
|
32
|
+
*
|
|
33
|
+
* @param params - Send parameters
|
|
34
|
+
* @returns Send result with message ID or error
|
|
35
|
+
*/
|
|
36
|
+
export async function sendMessageTeamily(
|
|
37
|
+
params: SendTeamilyMessageParams
|
|
38
|
+
): Promise<TeamilySendResult> {
|
|
39
|
+
const { account, target, text, replyToId, fetchImpl = fetch } = params;
|
|
40
|
+
|
|
41
|
+
const url = `${account.apiURL}/msg/send_msg`;
|
|
42
|
+
|
|
43
|
+
const payload = buildSendMessagePayload({
|
|
44
|
+
sendID: account.userID,
|
|
45
|
+
target,
|
|
46
|
+
content: { content: text },
|
|
47
|
+
contentType: CONTENT_TYPES.TEXT,
|
|
48
|
+
replyToId,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const response = await fetchImpl(url, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"operationID": generateOperationID(),
|
|
57
|
+
"token": account.token,
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify(payload),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
const errorText = await response.text();
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: `HTTP ${response.status}: ${errorText}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const data = await response.json() as {
|
|
71
|
+
errCode: number;
|
|
72
|
+
errMsg: string;
|
|
73
|
+
data?: {
|
|
74
|
+
serverMsgID: string;
|
|
75
|
+
clientMsgID: string;
|
|
76
|
+
sendTime: number;
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (data.errCode !== 0) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: data.errMsg || "Send failed",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
messageId: data.data?.serverMsgID,
|
|
90
|
+
serverMsgID: data.data?.serverMsgID,
|
|
91
|
+
clientMsgID: data.data?.clientMsgID,
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
success: false,
|
|
96
|
+
error: error instanceof Error ? error.message : String(error),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Send a media message via Teamily REST API.
|
|
103
|
+
*
|
|
104
|
+
* @param params - Send parameters
|
|
105
|
+
* @returns Send result with message ID or error
|
|
106
|
+
*/
|
|
107
|
+
export async function sendMediaTeamily(
|
|
108
|
+
params: SendTeamilyMediaParams
|
|
109
|
+
): Promise<TeamilySendResult> {
|
|
110
|
+
const { account, target, mediaUrl, mediaType, caption, fetchImpl = fetch } = params;
|
|
111
|
+
|
|
112
|
+
const url = `${account.apiURL}/msg/send_msg`;
|
|
113
|
+
|
|
114
|
+
let content: Record<string, unknown>;
|
|
115
|
+
let contentType: number;
|
|
116
|
+
|
|
117
|
+
switch (mediaType) {
|
|
118
|
+
case "image":
|
|
119
|
+
contentType = CONTENT_TYPES.PICTURE;
|
|
120
|
+
content = {
|
|
121
|
+
sourcePicture: {
|
|
122
|
+
uuid: generateOperationID(),
|
|
123
|
+
type: "public",
|
|
124
|
+
width: 0,
|
|
125
|
+
height: 0,
|
|
126
|
+
url: mediaUrl,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case "video":
|
|
132
|
+
contentType = CONTENT_TYPES.VIDEO;
|
|
133
|
+
content = {
|
|
134
|
+
videoPath: mediaUrl,
|
|
135
|
+
videoUUID: generateOperationID(),
|
|
136
|
+
videoUrl: mediaUrl,
|
|
137
|
+
videoType: "mp4",
|
|
138
|
+
videoSize: 0,
|
|
139
|
+
duration: 0,
|
|
140
|
+
snapshotUUID: `${generateOperationID()}_snap`,
|
|
141
|
+
snapshotUrl: "",
|
|
142
|
+
snapshotSize: 0,
|
|
143
|
+
snapshotWidth: 0,
|
|
144
|
+
snapshotHeight: 0,
|
|
145
|
+
};
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case "audio":
|
|
149
|
+
contentType = CONTENT_TYPES.VOICE;
|
|
150
|
+
content = {
|
|
151
|
+
uuid: generateOperationID(),
|
|
152
|
+
soundPath: mediaUrl,
|
|
153
|
+
sourceUrl: mediaUrl,
|
|
154
|
+
dataSize: 0,
|
|
155
|
+
duration: 0,
|
|
156
|
+
soundType: "mp3",
|
|
157
|
+
};
|
|
158
|
+
break;
|
|
159
|
+
|
|
160
|
+
case "file":
|
|
161
|
+
contentType = CONTENT_TYPES.FILE;
|
|
162
|
+
content = {
|
|
163
|
+
uuid: generateOperationID(),
|
|
164
|
+
fileName: mediaUrl.split("/").pop() || "file",
|
|
165
|
+
fileSize: 0,
|
|
166
|
+
sourceUrl: mediaUrl,
|
|
167
|
+
fileType: "application/octet-stream",
|
|
168
|
+
};
|
|
169
|
+
break;
|
|
170
|
+
|
|
171
|
+
default:
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: `Unsupported media type: ${mediaType}`,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Add caption to text content for media messages
|
|
179
|
+
if (caption) {
|
|
180
|
+
content.text = caption;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const payload = buildSendMessagePayload({
|
|
184
|
+
sendID: account.userID,
|
|
185
|
+
target,
|
|
186
|
+
content,
|
|
187
|
+
contentType,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetchImpl(url, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"operationID": generateOperationID(),
|
|
196
|
+
"token": account.token,
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify(payload),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
const errorText = await response.text();
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: `HTTP ${response.status}: ${errorText}`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const data = await response.json() as {
|
|
210
|
+
errCode: number;
|
|
211
|
+
errMsg: string;
|
|
212
|
+
data?: {
|
|
213
|
+
serverMsgID: string;
|
|
214
|
+
clientMsgID: string;
|
|
215
|
+
sendTime: number;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (data.errCode !== 0) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
error: data.errMsg || "Send failed",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
success: true,
|
|
228
|
+
messageId: data.data?.serverMsgID,
|
|
229
|
+
serverMsgID: data.data?.serverMsgID,
|
|
230
|
+
clientMsgID: data.data?.clientMsgID,
|
|
231
|
+
};
|
|
232
|
+
} catch (error) {
|
|
233
|
+
return {
|
|
234
|
+
success: false,
|
|
235
|
+
error: error instanceof Error ? error.message : String(error),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Build the send message payload for Teamily API.
|
|
242
|
+
*/
|
|
243
|
+
function buildSendMessagePayload(params: {
|
|
244
|
+
sendID: string;
|
|
245
|
+
target: TeamilyMessageTarget;
|
|
246
|
+
content: Record<string, unknown>;
|
|
247
|
+
contentType: number;
|
|
248
|
+
replyToId?: string;
|
|
249
|
+
}): Record<string, unknown> {
|
|
250
|
+
const { sendID, target, content, contentType, replyToId } = params;
|
|
251
|
+
|
|
252
|
+
const payload: Record<string, unknown> = {
|
|
253
|
+
sendID,
|
|
254
|
+
recvID: target.type === "user" ? target.id : "",
|
|
255
|
+
groupID: target.type === "group" ? target.id : "",
|
|
256
|
+
content,
|
|
257
|
+
contentType,
|
|
258
|
+
sessionType: target.type === "group" ? SESSION_TYPES.GROUP : SESSION_TYPES.SINGLE,
|
|
259
|
+
isOnlineOnly: false,
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
if (replyToId) {
|
|
263
|
+
payload.quote = {
|
|
264
|
+
text: "",
|
|
265
|
+
content: {},
|
|
266
|
+
isReact: false,
|
|
267
|
+
userID: "",
|
|
268
|
+
msgID: replyToId,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return payload;
|
|
273
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Core configuration types for Teamily channel
|
|
2
|
+
|
|
3
|
+
export interface TeamilyServerConfig {
|
|
4
|
+
platformUrl: string;
|
|
5
|
+
apiURL: string;
|
|
6
|
+
wsURL: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TeamilyUserAccount {
|
|
10
|
+
userID: string;
|
|
11
|
+
token: string;
|
|
12
|
+
nickname?: string;
|
|
13
|
+
faceURL?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TeamilyConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
server: TeamilyServerConfig;
|
|
19
|
+
accounts: Record<string, TeamilyUserAccount>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResolvedTeamilyAccount {
|
|
23
|
+
accountId: string;
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
platformUrl: string;
|
|
26
|
+
apiURL: string;
|
|
27
|
+
wsURL: string;
|
|
28
|
+
userID: string;
|
|
29
|
+
token: string;
|
|
30
|
+
nickname?: string;
|
|
31
|
+
faceURL?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TeamilyMessageTarget {
|
|
35
|
+
type: "user" | "group";
|
|
36
|
+
id: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface TeamilyMessage {
|
|
40
|
+
serverMsgID: string;
|
|
41
|
+
sendID: string;
|
|
42
|
+
recvID: string;
|
|
43
|
+
content: {
|
|
44
|
+
text?: string;
|
|
45
|
+
picture?: TeamilyPictureContent;
|
|
46
|
+
video?: TeamilyVideoContent;
|
|
47
|
+
audio?: TeamilyAudioContent;
|
|
48
|
+
};
|
|
49
|
+
contentType: number;
|
|
50
|
+
sessionType: number;
|
|
51
|
+
sendTime: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface TeamilyPictureContent {
|
|
55
|
+
sourcePicture: {
|
|
56
|
+
uuid: string;
|
|
57
|
+
type: string;
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
url: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface TeamilyVideoContent {
|
|
65
|
+
videoPath: string;
|
|
66
|
+
videoUUID: string;
|
|
67
|
+
videoUrl: string;
|
|
68
|
+
videoType: string;
|
|
69
|
+
videoSize: number;
|
|
70
|
+
duration: number;
|
|
71
|
+
snapshotUUID: string;
|
|
72
|
+
snapshotUrl: string;
|
|
73
|
+
snapshotSize: number;
|
|
74
|
+
snapshotWidth: number;
|
|
75
|
+
snapshotHeight: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface TeamilyAudioContent {
|
|
79
|
+
uuid: string;
|
|
80
|
+
soundPath: string;
|
|
81
|
+
sourceUrl: string;
|
|
82
|
+
dataSize: number;
|
|
83
|
+
duration: number;
|
|
84
|
+
soundType: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type TeamilyProbeResult = {
|
|
88
|
+
connected: boolean;
|
|
89
|
+
error?: string;
|
|
90
|
+
userExists?: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Content type constants
|
|
94
|
+
export const CONTENT_TYPES = {
|
|
95
|
+
TEXT: 101,
|
|
96
|
+
PICTURE: 102,
|
|
97
|
+
VOICE: 103,
|
|
98
|
+
VIDEO: 104,
|
|
99
|
+
FILE: 105,
|
|
100
|
+
AT_TEXT: 106,
|
|
101
|
+
MERGER: 107,
|
|
102
|
+
CARD: 108,
|
|
103
|
+
LOCATION: 109,
|
|
104
|
+
CUSTOM: 110,
|
|
105
|
+
TYPING: 113,
|
|
106
|
+
QUOTE: 114,
|
|
107
|
+
FACE: 115,
|
|
108
|
+
ADVANCED_TEXT: 117,
|
|
109
|
+
} as const;
|
|
110
|
+
|
|
111
|
+
// Session type constants
|
|
112
|
+
export const SESSION_TYPES = {
|
|
113
|
+
SINGLE: 1,
|
|
114
|
+
GROUP: 3,
|
|
115
|
+
NOTIFICATION: 4,
|
|
116
|
+
} as const;
|