@theowlops/channelhub 1.0.0
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/LICENSE +21 -0
- package/README.md +179 -0
- package/bin/cli.ts +36 -0
- package/dist/bridges/mcp/index.d.ts +14 -0
- package/dist/bridges/mcp/index.js +96 -0
- package/dist/bridges/webhook/index.d.ts +32 -0
- package/dist/bridges/webhook/index.js +123 -0
- package/dist/channels/discord/adapter.d.ts +24 -0
- package/dist/channels/discord/index.d.ts +1 -0
- package/dist/channels/discord/index.js +130 -0
- package/dist/channels/slack/adapter.d.ts +20 -0
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.js +122 -0
- package/dist/channels/telegram/adapter.d.ts +21 -0
- package/dist/channels/telegram/index.d.ts +2 -0
- package/dist/channels/telegram/index.js +179 -0
- package/dist/channels/telegram/types.d.ts +6 -0
- package/dist/channels/zalo/adapter.d.ts +22 -0
- package/dist/channels/zalo/index.d.ts +5 -0
- package/dist/channels/zalo/index.js +38337 -0
- package/dist/commands/modules/general.d.ts +4 -0
- package/dist/commands/modules/group.d.ts +4 -0
- package/dist/commands/modules/reaction.d.ts +4 -0
- package/dist/commands/router.d.ts +7 -0
- package/dist/commands/types.d.ts +15 -0
- package/dist/config/env.d.ts +13 -0
- package/dist/core/adapter.d.ts +12 -0
- package/dist/core/bus.d.ts +12 -0
- package/dist/core/context.d.ts +9 -0
- package/dist/core/hub.d.ts +14 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +99 -0
- package/dist/core/types.d.ts +63 -0
- package/dist/index.cjs +39235 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +39191 -0
- package/dist/login_personal.js +60 -0
- package/dist/oa/client.d.ts +25 -0
- package/dist/oa/index.d.ts +3 -0
- package/dist/oa/index.js +556 -0
- package/dist/personal/client.d.ts +162 -0
- package/dist/personal/index.d.ts +5 -0
- package/dist/personal/index.js +37098 -0
- package/package.json +107 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// src/core/adapter.ts
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
|
|
4
|
+
class BaseChannel extends EventEmitter {
|
|
5
|
+
_connected = false;
|
|
6
|
+
isConnected() {
|
|
7
|
+
return this._connected;
|
|
8
|
+
}
|
|
9
|
+
setConnected(value) {
|
|
10
|
+
const changed = this._connected !== value;
|
|
11
|
+
this._connected = value;
|
|
12
|
+
if (changed) {
|
|
13
|
+
this.emit("status", value ? "connected" : "disconnected");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/channels/slack/adapter.ts
|
|
19
|
+
class SlackChannelAdapter extends BaseChannel {
|
|
20
|
+
name = "slack";
|
|
21
|
+
config;
|
|
22
|
+
apiBase = "https://slack.com/api";
|
|
23
|
+
constructor(config) {
|
|
24
|
+
super();
|
|
25
|
+
this.config = config;
|
|
26
|
+
}
|
|
27
|
+
async connect() {
|
|
28
|
+
if (!this.config.botToken)
|
|
29
|
+
throw new Error("Slack botToken is required.");
|
|
30
|
+
await this.callApi("auth.test", {});
|
|
31
|
+
this.setConnected(true);
|
|
32
|
+
}
|
|
33
|
+
async disconnect() {
|
|
34
|
+
this.setConnected(false);
|
|
35
|
+
}
|
|
36
|
+
normalizeEvent(event) {
|
|
37
|
+
const msg = event.event || event;
|
|
38
|
+
if (!msg || msg.type !== "message")
|
|
39
|
+
return null;
|
|
40
|
+
if (msg.subtype === "bot_message" || msg.bot_id)
|
|
41
|
+
return null;
|
|
42
|
+
const isDm = msg.channel_type === "im" || msg.channel && msg.channel.startsWith("D");
|
|
43
|
+
return {
|
|
44
|
+
id: String(msg.client_msg_id || msg.ts),
|
|
45
|
+
channel: "slack",
|
|
46
|
+
sender: {
|
|
47
|
+
id: String(msg.user || ""),
|
|
48
|
+
isBot: Boolean(msg.bot_id)
|
|
49
|
+
},
|
|
50
|
+
chat: {
|
|
51
|
+
id: String(msg.channel),
|
|
52
|
+
type: isDm ? "dm" : "channel"
|
|
53
|
+
},
|
|
54
|
+
content: {
|
|
55
|
+
text: msg.text || "",
|
|
56
|
+
replyToId: msg.thread_ts ? String(msg.thread_ts) : undefined
|
|
57
|
+
},
|
|
58
|
+
raw: event,
|
|
59
|
+
timestamp: msg.ts ? parseFloat(msg.ts) * 1000 : Date.now()
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async callApi(method, body) {
|
|
63
|
+
const res = await fetch(`${this.apiBase}/${method}`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${this.config.botToken}`,
|
|
67
|
+
"Content-Type": "application/json"
|
|
68
|
+
},
|
|
69
|
+
body: JSON.stringify(body)
|
|
70
|
+
});
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
const errText = await res.text();
|
|
73
|
+
throw new Error(`Slack API ${method} failed: ${res.status} ${errText}`);
|
|
74
|
+
}
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
if (!data.ok) {
|
|
77
|
+
throw new Error(`Slack API ${method} error: ${data.error}`);
|
|
78
|
+
}
|
|
79
|
+
return data;
|
|
80
|
+
}
|
|
81
|
+
async sendText(chatId, text, options) {
|
|
82
|
+
const payload = {
|
|
83
|
+
channel: chatId,
|
|
84
|
+
text
|
|
85
|
+
};
|
|
86
|
+
if (options?.replyToId) {
|
|
87
|
+
payload.thread_ts = options.replyToId;
|
|
88
|
+
}
|
|
89
|
+
const res = await this.callApi("chat.postMessage", payload);
|
|
90
|
+
return {
|
|
91
|
+
messageId: String(res.ts),
|
|
92
|
+
chatId,
|
|
93
|
+
timestamp: parseFloat(res.ts) * 1000
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
async sendMedia(chatId, media, options) {
|
|
97
|
+
const payload = {
|
|
98
|
+
channel: chatId,
|
|
99
|
+
text: media.caption || "Attachment"
|
|
100
|
+
};
|
|
101
|
+
if (options?.replyToId) {
|
|
102
|
+
payload.thread_ts = options.replyToId;
|
|
103
|
+
}
|
|
104
|
+
const res = await this.callApi("chat.postMessage", payload);
|
|
105
|
+
return {
|
|
106
|
+
messageId: String(res.ts),
|
|
107
|
+
chatId,
|
|
108
|
+
timestamp: parseFloat(res.ts) * 1000
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async addReaction(chatId, messageId, emoji) {
|
|
112
|
+
const cleanName = emoji.replace(/:/g, "");
|
|
113
|
+
await this.callApi("reactions.add", {
|
|
114
|
+
channel: chatId,
|
|
115
|
+
timestamp: messageId,
|
|
116
|
+
name: cleanName
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
SlackChannelAdapter
|
|
122
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BaseChannel } from "../../core/adapter";
|
|
2
|
+
import type { ChannelType, MediaPayload, SendOptions, SentMessageResult, UnifiedMessage } from "../../core/types";
|
|
3
|
+
import type { TelegramAdapterConfig } from "./types";
|
|
4
|
+
export declare class TelegramChannelAdapter extends BaseChannel {
|
|
5
|
+
readonly name: ChannelType;
|
|
6
|
+
private config;
|
|
7
|
+
private apiRoot;
|
|
8
|
+
private pollTimer;
|
|
9
|
+
private lastUpdateId;
|
|
10
|
+
private isPolling;
|
|
11
|
+
constructor(config: TelegramAdapterConfig);
|
|
12
|
+
connect(): Promise<void>;
|
|
13
|
+
disconnect(): Promise<void>;
|
|
14
|
+
normalizeUpdate(update: any): UnifiedMessage | null;
|
|
15
|
+
private callApi;
|
|
16
|
+
private startPolling;
|
|
17
|
+
private stopPolling;
|
|
18
|
+
sendText(chatId: string, text: string, options?: SendOptions): Promise<SentMessageResult>;
|
|
19
|
+
sendMedia(chatId: string, media: MediaPayload, options?: SendOptions): Promise<SentMessageResult>;
|
|
20
|
+
addReaction(chatId: string, messageId: string, emoji: string): Promise<void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// src/core/adapter.ts
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
|
|
4
|
+
class BaseChannel extends EventEmitter {
|
|
5
|
+
_connected = false;
|
|
6
|
+
isConnected() {
|
|
7
|
+
return this._connected;
|
|
8
|
+
}
|
|
9
|
+
setConnected(value) {
|
|
10
|
+
const changed = this._connected !== value;
|
|
11
|
+
this._connected = value;
|
|
12
|
+
if (changed) {
|
|
13
|
+
this.emit("status", value ? "connected" : "disconnected");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/channels/telegram/adapter.ts
|
|
19
|
+
class TelegramChannelAdapter extends BaseChannel {
|
|
20
|
+
name = "telegram";
|
|
21
|
+
config;
|
|
22
|
+
apiRoot;
|
|
23
|
+
pollTimer = null;
|
|
24
|
+
lastUpdateId = 0;
|
|
25
|
+
isPolling = false;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
super();
|
|
28
|
+
this.config = config;
|
|
29
|
+
this.apiRoot = config.apiRoot || "https://api.telegram.org";
|
|
30
|
+
}
|
|
31
|
+
async connect() {
|
|
32
|
+
if (!this.config.botToken) {
|
|
33
|
+
throw new Error("Telegram botToken is required.");
|
|
34
|
+
}
|
|
35
|
+
this.setConnected(true);
|
|
36
|
+
if (this.config.autoStart !== false) {
|
|
37
|
+
this.startPolling();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async disconnect() {
|
|
41
|
+
this.stopPolling();
|
|
42
|
+
this.setConnected(false);
|
|
43
|
+
}
|
|
44
|
+
normalizeUpdate(update) {
|
|
45
|
+
if (!update)
|
|
46
|
+
return null;
|
|
47
|
+
const msg = update.message || update.edited_message || update.channel_post;
|
|
48
|
+
if (!msg)
|
|
49
|
+
return null;
|
|
50
|
+
const chatType = msg.chat.type === "private" ? "dm" : msg.chat.type === "channel" ? "channel" : "group";
|
|
51
|
+
const senderName = [msg.from?.first_name, msg.from?.last_name].filter(Boolean).join(" ");
|
|
52
|
+
return {
|
|
53
|
+
id: String(msg.message_id),
|
|
54
|
+
channel: "telegram",
|
|
55
|
+
sender: {
|
|
56
|
+
id: String(msg.from?.id ?? ""),
|
|
57
|
+
name: senderName || undefined,
|
|
58
|
+
username: msg.from?.username,
|
|
59
|
+
isBot: Boolean(msg.from?.is_bot)
|
|
60
|
+
},
|
|
61
|
+
chat: {
|
|
62
|
+
id: String(msg.chat.id),
|
|
63
|
+
type: chatType,
|
|
64
|
+
title: msg.chat.title
|
|
65
|
+
},
|
|
66
|
+
content: {
|
|
67
|
+
text: msg.text || msg.caption || "",
|
|
68
|
+
replyToId: msg.reply_to_message?.message_id ? String(msg.reply_to_message.message_id) : undefined
|
|
69
|
+
},
|
|
70
|
+
raw: update,
|
|
71
|
+
timestamp: (msg.date || Math.floor(Date.now() / 1000)) * 1000
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async callApi(method, body) {
|
|
75
|
+
const url = `${this.apiRoot}/bot${this.config.botToken}/${method}`;
|
|
76
|
+
const res = await fetch(url, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/json" },
|
|
79
|
+
body: JSON.stringify(body)
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok) {
|
|
82
|
+
const errText = await res.text();
|
|
83
|
+
throw new Error(`Telegram API ${method} failed: ${res.status} ${errText}`);
|
|
84
|
+
}
|
|
85
|
+
const data = await res.json();
|
|
86
|
+
if (!data.ok) {
|
|
87
|
+
throw new Error(`Telegram API ${method} error: ${data.description}`);
|
|
88
|
+
}
|
|
89
|
+
return data.result;
|
|
90
|
+
}
|
|
91
|
+
startPolling() {
|
|
92
|
+
if (this.isPolling)
|
|
93
|
+
return;
|
|
94
|
+
this.isPolling = true;
|
|
95
|
+
const interval = this.config.pollIntervalMs || 1000;
|
|
96
|
+
const poll = async () => {
|
|
97
|
+
if (!this.isPolling)
|
|
98
|
+
return;
|
|
99
|
+
try {
|
|
100
|
+
const updates = await this.callApi("getUpdates", {
|
|
101
|
+
offset: this.lastUpdateId + 1,
|
|
102
|
+
timeout: 10
|
|
103
|
+
});
|
|
104
|
+
if (Array.isArray(updates)) {
|
|
105
|
+
for (const u of updates) {
|
|
106
|
+
this.lastUpdateId = Math.max(this.lastUpdateId, u.update_id);
|
|
107
|
+
const unified = this.normalizeUpdate(u);
|
|
108
|
+
if (unified) {
|
|
109
|
+
this.emit("message", unified);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
this.emit("error", err);
|
|
115
|
+
} finally {
|
|
116
|
+
if (this.isPolling) {
|
|
117
|
+
this.pollTimer = setTimeout(poll, interval);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
poll();
|
|
122
|
+
}
|
|
123
|
+
stopPolling() {
|
|
124
|
+
this.isPolling = false;
|
|
125
|
+
if (this.pollTimer) {
|
|
126
|
+
clearTimeout(this.pollTimer);
|
|
127
|
+
this.pollTimer = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async sendText(chatId, text, options) {
|
|
131
|
+
const payload = {
|
|
132
|
+
chat_id: chatId,
|
|
133
|
+
text
|
|
134
|
+
};
|
|
135
|
+
if (options?.replyToId) {
|
|
136
|
+
payload.reply_to_message_id = Number(options.replyToId);
|
|
137
|
+
}
|
|
138
|
+
const res = await this.callApi("sendMessage", payload);
|
|
139
|
+
return {
|
|
140
|
+
messageId: String(res.message_id),
|
|
141
|
+
chatId,
|
|
142
|
+
timestamp: res.date * 1000
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async sendMedia(chatId, media, options) {
|
|
146
|
+
const method = media.type === "image" ? "sendPhoto" : media.type === "video" ? "sendVideo" : "sendDocument";
|
|
147
|
+
const payload = {
|
|
148
|
+
chat_id: chatId,
|
|
149
|
+
caption: media.caption
|
|
150
|
+
};
|
|
151
|
+
if (typeof media.source === "string") {
|
|
152
|
+
if (media.type === "image")
|
|
153
|
+
payload.photo = media.source;
|
|
154
|
+
else if (media.type === "video")
|
|
155
|
+
payload.video = media.source;
|
|
156
|
+
else
|
|
157
|
+
payload.document = media.source;
|
|
158
|
+
}
|
|
159
|
+
if (options?.replyToId) {
|
|
160
|
+
payload.reply_to_message_id = Number(options.replyToId);
|
|
161
|
+
}
|
|
162
|
+
const res = await this.callApi(method, payload);
|
|
163
|
+
return {
|
|
164
|
+
messageId: String(res.message_id),
|
|
165
|
+
chatId,
|
|
166
|
+
timestamp: (res.date || Date.now()) * 1000
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
async addReaction(chatId, messageId, emoji) {
|
|
170
|
+
await this.callApi("setMessageReaction", {
|
|
171
|
+
chat_id: chatId,
|
|
172
|
+
message_id: Number(messageId),
|
|
173
|
+
reaction: [{ type: "emoji", emoji }]
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export {
|
|
178
|
+
TelegramChannelAdapter
|
|
179
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BaseChannel } from "../../core/adapter";
|
|
2
|
+
import type { ChannelType, MediaPayload, SendOptions, SentMessageResult } from "../../core/types";
|
|
3
|
+
export interface ZaloAdapterConfig {
|
|
4
|
+
api?: any;
|
|
5
|
+
credentialsPath?: string;
|
|
6
|
+
ownId?: string;
|
|
7
|
+
defaultIsGroup?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class ZaloChannelAdapter extends BaseChannel {
|
|
10
|
+
readonly name: ChannelType;
|
|
11
|
+
private api;
|
|
12
|
+
private ownId?;
|
|
13
|
+
private config;
|
|
14
|
+
constructor(config?: ZaloAdapterConfig);
|
|
15
|
+
connect(): Promise<void>;
|
|
16
|
+
disconnect(): Promise<void>;
|
|
17
|
+
private setupEventListener;
|
|
18
|
+
private normalizeMessage;
|
|
19
|
+
sendText(chatId: string, text: string, options?: SendOptions): Promise<SentMessageResult>;
|
|
20
|
+
sendMedia(chatId: string, media: MediaPayload, _options?: SendOptions): Promise<SentMessageResult>;
|
|
21
|
+
addReaction(chatId: string, messageId: string, emoji: string): Promise<void>;
|
|
22
|
+
}
|