@alemonjs/bubble 2.1.0-alpha.1

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.
@@ -0,0 +1,127 @@
1
+ export declare const AvailableIntentsEventsEnum: readonly ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE", "MESSAGE_UNPIN", "DM_MESSAGE_CREATE", "DM_MESSAGE_UPDATE", "DM_MESSAGE_DELETE", "DM_MESSAGE_UNPIN", "GUILD_MEMBER_ADD", "GUILD_MEMBER_UPDATE", "GUILD_MEMBER_REMOVE", "EVENTS_SUBSCRIBED", "EVENTS_UNSUBSCRIBED", "SUBSCRIBE_DENIED", "BOT_READY"];
2
+ export type IntentsEnum = (typeof AvailableIntentsEventsEnum)[number];
3
+ export declare const AIntentsEventsEnum: IntentsEnum[];
4
+ export interface User {
5
+ id: string | number;
6
+ username: string;
7
+ avatar?: string | null;
8
+ bot?: boolean;
9
+ nickname?: string;
10
+ }
11
+ export interface Channel {
12
+ id: string | number;
13
+ name: string;
14
+ type?: number;
15
+ guild_id?: string | number;
16
+ }
17
+ export interface Guild {
18
+ id: string | number;
19
+ name: string;
20
+ }
21
+ export interface Attachment {
22
+ path: string;
23
+ url: string;
24
+ contentType: string;
25
+ size: number;
26
+ filename: string;
27
+ category?: string;
28
+ }
29
+ export interface BaseMessage {
30
+ id: string | number;
31
+ content: string;
32
+ type?: string | number;
33
+ channel_id?: string | number;
34
+ guild_id?: string | number;
35
+ author_id?: string | number;
36
+ author?: User;
37
+ mentions?: User[];
38
+ attachments?: Attachment[];
39
+ created_at?: string;
40
+ timestamp?: string;
41
+ }
42
+ export interface MessageCreateEvent extends BaseMessage {
43
+ member?: {
44
+ nickname?: string;
45
+ roles?: number[];
46
+ };
47
+ }
48
+ export interface DmMessageCreateEvent extends BaseMessage {
49
+ thread_id?: string | number;
50
+ }
51
+ export type MessageUpdateEvent = BaseMessage;
52
+ export interface MessageDeleteEvent {
53
+ id: string | number;
54
+ channel_id?: string | number;
55
+ guild_id?: string | number;
56
+ }
57
+ export interface GuildMemberEvent {
58
+ user?: User;
59
+ user_id?: string | number;
60
+ guild_id?: string | number;
61
+ nickname?: string;
62
+ roles?: number[];
63
+ }
64
+ export interface BotReadyEvent {
65
+ session_id?: string;
66
+ resume_gateway_url?: string;
67
+ guilds?: Guild[];
68
+ dm_threads?: Array<{
69
+ id: string | number;
70
+ user_id?: string | number;
71
+ }>;
72
+ available_events?: string[];
73
+ }
74
+ export interface EventsSubscribedEvent {
75
+ subscribedEvents: string[];
76
+ }
77
+ export interface EventsUnsubscribedEvent {
78
+ unsubscribedEvents: string[];
79
+ }
80
+ export interface SubscribeDeniedEvent {
81
+ reason?: string;
82
+ events?: string[];
83
+ }
84
+ export interface BotInfo {
85
+ id: number;
86
+ name: string;
87
+ botUser?: {
88
+ id: number;
89
+ name: string;
90
+ };
91
+ }
92
+ export interface SendMessagePayload {
93
+ content?: string;
94
+ type?: string;
95
+ attachments?: Attachment[];
96
+ components?: any[];
97
+ }
98
+ export interface FileUploadResponse {
99
+ file: Attachment;
100
+ }
101
+ export interface FileQuota {
102
+ usage: {
103
+ dailyUsedBytes: number;
104
+ monthlyUsedBytes: number;
105
+ };
106
+ limits: {
107
+ withMessage: Record<string, any>;
108
+ noMessage: Record<string, any>;
109
+ };
110
+ }
111
+ export interface BubbleEventMap {
112
+ MESSAGE_CREATE: MessageCreateEvent;
113
+ MESSAGE_UPDATE: MessageUpdateEvent;
114
+ MESSAGE_DELETE: MessageDeleteEvent;
115
+ MESSAGE_UNPIN: MessageDeleteEvent;
116
+ DM_MESSAGE_CREATE: DmMessageCreateEvent;
117
+ DM_MESSAGE_UPDATE: DmMessageCreateEvent;
118
+ DM_MESSAGE_DELETE: MessageDeleteEvent;
119
+ DM_MESSAGE_UNPIN: MessageDeleteEvent;
120
+ GUILD_MEMBER_ADD: GuildMemberEvent;
121
+ GUILD_MEMBER_UPDATE: GuildMemberEvent;
122
+ GUILD_MEMBER_REMOVE: GuildMemberEvent;
123
+ BOT_READY: BotReadyEvent;
124
+ EVENTS_SUBSCRIBED: EventsSubscribedEvent;
125
+ EVENTS_UNSUBSCRIBED: EventsUnsubscribedEvent;
126
+ SUBSCRIBE_DENIED: SubscribeDeniedEvent;
127
+ }
@@ -0,0 +1,20 @@
1
+ const AvailableIntentsEventsEnum = [
2
+ 'MESSAGE_CREATE',
3
+ 'MESSAGE_UPDATE',
4
+ 'MESSAGE_DELETE',
5
+ 'MESSAGE_UNPIN',
6
+ 'DM_MESSAGE_CREATE',
7
+ 'DM_MESSAGE_UPDATE',
8
+ 'DM_MESSAGE_DELETE',
9
+ 'DM_MESSAGE_UNPIN',
10
+ 'GUILD_MEMBER_ADD',
11
+ 'GUILD_MEMBER_UPDATE',
12
+ 'GUILD_MEMBER_REMOVE',
13
+ 'EVENTS_SUBSCRIBED',
14
+ 'EVENTS_UNSUBSCRIBED',
15
+ 'SUBSCRIBE_DENIED',
16
+ 'BOT_READY'
17
+ ];
18
+ const AIntentsEventsEnum = Array.from(AvailableIntentsEventsEnum);
19
+
20
+ export { AIntentsEventsEnum, AvailableIntentsEventsEnum };
@@ -0,0 +1,11 @@
1
+ import { BubbleAPI } from './api.js';
2
+ import { type IntentsEnum, type BubbleEventMap } from './types.js';
3
+ export declare class BubbleClient extends BubbleAPI {
4
+ #private;
5
+ constructor();
6
+ on<K extends keyof BubbleEventMap>(event: K, handler: (payload: BubbleEventMap[K]) => void | Promise<void>): this;
7
+ on(event: string, handler: (payload: any) => void | Promise<void>): this;
8
+ sendSubscribe(events: IntentsEnum[]): void;
9
+ sendUnsubscribe(events: IntentsEnum[]): void;
10
+ connect(): void;
11
+ }
package/lib/sdk/wss.js ADDED
@@ -0,0 +1,149 @@
1
+ import WebSocket from 'ws';
2
+ import { BubbleAPI } from './api.js';
3
+ import { getBubbleConfig } from '../config.js';
4
+ import { HttpsProxyAgent } from 'https-proxy-agent';
5
+ import { AIntentsEventsEnum } from './types.js';
6
+ import dayjs from 'dayjs';
7
+ import { OpCode } from './wss.types.js';
8
+
9
+ class BubbleClient extends BubbleAPI {
10
+ #heartbeat_interval = 0;
11
+ #session_id = '';
12
+ #gateway_url = '';
13
+ #timeout_id = null;
14
+ #seq = null;
15
+ #ws = null;
16
+ #count = 0;
17
+ #events = {};
18
+ constructor() {
19
+ super();
20
+ return this;
21
+ }
22
+ #getReConnectTime() {
23
+ const time = this.#count > 3 ? 1000 * 6 : 1000 * 1;
24
+ const curTime = this.#count > 6 ? 1000 * this.#count * 2 : time;
25
+ logger?.info?.(`[ws-bubble] 等待 ${dayjs(curTime).format('mm:ss')} 后重新连接`);
26
+ return curTime;
27
+ }
28
+ on(event, handler) {
29
+ this.#events[event] = handler;
30
+ return this;
31
+ }
32
+ sendSubscribe(events) {
33
+ if (!this.#ws) {
34
+ return;
35
+ }
36
+ const payload = { events };
37
+ this.#ws.send(JSON.stringify({ op: OpCode.Subscribe, d: payload }));
38
+ }
39
+ sendUnsubscribe(events) {
40
+ if (!this.#ws) {
41
+ return;
42
+ }
43
+ const payload = { events };
44
+ this.#ws.send(JSON.stringify({ op: OpCode.Unsubscribe, d: payload }));
45
+ }
46
+ connect() {
47
+ this.#count++;
48
+ const value = getBubbleConfig();
49
+ const url = value.URL;
50
+ this.#seq = null;
51
+ clearTimeout(this.#timeout_id);
52
+ try {
53
+ if (!url) {
54
+ logger?.error?.('[ws-bubble] 未在配置中找到 WebSocket `URL`,请在配置中设置 `URL` 字段以连接网关');
55
+ const curTime = this.#getReConnectTime();
56
+ setTimeout(() => {
57
+ void this.connect();
58
+ }, curTime);
59
+ return;
60
+ }
61
+ const callHeartbeat = () => {
62
+ if (!this.#ws) {
63
+ return;
64
+ }
65
+ this.#ws.send(JSON.stringify({ op: OpCode.Heartbeat, d: this.#seq }));
66
+ clearTimeout(this.#timeout_id);
67
+ this.#timeout_id = setTimeout(callHeartbeat, this.#heartbeat_interval);
68
+ };
69
+ const handlers = {
70
+ [OpCode.Dispatch]: ({ d, t, s }) => {
71
+ if (s !== null) {
72
+ this.#seq = s;
73
+ }
74
+ if (t === 'BOT_READY') {
75
+ if (d?.resume_gateway_url) {
76
+ this.#gateway_url = d.resume_gateway_url;
77
+ logger?.info?.('[ws-bubble] gateway_url', this.#gateway_url);
78
+ }
79
+ if (d?.session_id) {
80
+ this.#session_id = d.session_id;
81
+ logger?.info?.('[ws-bubble] session_id', this.#session_id);
82
+ }
83
+ const events = value.intent || AIntentsEventsEnum;
84
+ this.sendSubscribe(events);
85
+ }
86
+ if (t && this.#events[t]) {
87
+ try {
88
+ this.#events[t](d);
89
+ }
90
+ catch (err) {
91
+ logger?.error?.('[ws-bubble] 事件处理错误', err);
92
+ }
93
+ }
94
+ },
95
+ [OpCode.Hello]: ({ d }) => {
96
+ this.#heartbeat_interval = d.heartbeat_interval || 30000;
97
+ callHeartbeat();
98
+ },
99
+ [OpCode.HeartbeatAck]: () => {
100
+ logger?.debug?.('[ws-bubble] 心跳确认');
101
+ }
102
+ };
103
+ const ClientOptions = value.websocket_options || {};
104
+ const token = value.token;
105
+ const clientName = value.clientName || 'alemonjs-bot';
106
+ ClientOptions.headers = Object.assign({}, ClientOptions.headers || {}, {
107
+ Authorization: `Bearer ${token}`,
108
+ 'X-Client': clientName
109
+ });
110
+ if (value.websocket_proxy) {
111
+ ClientOptions.agent = new HttpsProxyAgent(value.websocket_proxy);
112
+ }
113
+ this.#ws = new WebSocket(url, ClientOptions);
114
+ this.#ws.on('open', () => {
115
+ logger?.info?.('[ws-bubble] 打开连接');
116
+ this.#count = 0;
117
+ });
118
+ this.#ws.on('message', data => {
119
+ let message;
120
+ try {
121
+ message = JSON.parse(data.toString());
122
+ }
123
+ catch (err) {
124
+ logger?.error?.('[ws-bubble] 解析消息错误', err);
125
+ return;
126
+ }
127
+ const op = message.op;
128
+ if (handlers[op]) {
129
+ handlers[op](message);
130
+ }
131
+ });
132
+ this.#ws.on('close', err => {
133
+ logger?.info?.('[ws-bubble] 连接关闭', err);
134
+ const curTime = this.#getReConnectTime();
135
+ setTimeout(() => {
136
+ void this.connect();
137
+ }, curTime);
138
+ });
139
+ this.#ws.on('error', err => {
140
+ logger?.error?.('[ws-bubble] 出错', err?.message || err);
141
+ });
142
+ }
143
+ catch (err) {
144
+ logger?.error?.('[ws-bubble] 内部错误', err);
145
+ }
146
+ }
147
+ }
148
+
149
+ export { BubbleClient };
@@ -0,0 +1,24 @@
1
+ import { IntentsEnum } from './types.js';
2
+ export declare enum OpCode {
3
+ Dispatch = 0,
4
+ Heartbeat = 1,
5
+ Hello = 10,
6
+ HeartbeatAck = 11,
7
+ Subscribe = 30,
8
+ Unsubscribe = 31
9
+ }
10
+ export interface BUBBLEOptions {
11
+ API_URL: string;
12
+ CDN_URL: string;
13
+ GATEWAY_URL: string;
14
+ token: string;
15
+ intent?: IntentsEnum[];
16
+ clientName?: string;
17
+ }
18
+ export type BubbleEventMap = Record<string, any>;
19
+ export interface HelloPayload {
20
+ heartbeat_interval: number;
21
+ }
22
+ export interface SubscribePayload {
23
+ events: string[];
24
+ }
@@ -0,0 +1,11 @@
1
+ var OpCode;
2
+ (function (OpCode) {
3
+ OpCode[OpCode["Dispatch"] = 0] = "Dispatch";
4
+ OpCode[OpCode["Heartbeat"] = 1] = "Heartbeat";
5
+ OpCode[OpCode["Hello"] = 10] = "Hello";
6
+ OpCode[OpCode["HeartbeatAck"] = 11] = "HeartbeatAck";
7
+ OpCode[OpCode["Subscribe"] = 30] = "Subscribe";
8
+ OpCode[OpCode["Unsubscribe"] = 31] = "Unsubscribe";
9
+ })(OpCode || (OpCode = {}));
10
+
11
+ export { OpCode };
package/lib/send.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { DataEnums } from 'alemonjs';
2
+ import { BubbleClient } from './sdk/wss';
3
+ type Client = typeof BubbleClient.prototype;
4
+ export declare const sendchannel: (client: Client, param: {
5
+ channel_id: string | number;
6
+ }, val: DataEnums[]) => Promise<import("alemonjs").Result<any>[]>;
7
+ export declare const senduser: (client: Client, param: {
8
+ author_id?: string | number;
9
+ channel_id?: string | number;
10
+ }, val: DataEnums[]) => Promise<import("alemonjs").Result<any>[]>;
11
+ declare const _default: {};
12
+ export default _default;
package/lib/send.js ADDED
@@ -0,0 +1,201 @@
1
+ import { createResult, ResultCode } from 'alemonjs';
2
+ import { readFileSync } from 'fs';
3
+
4
+ const ImageURLToBuffer = async (url) => {
5
+ const arrayBuffer = await fetch(url).then(res => res.arrayBuffer());
6
+ return Buffer.from(arrayBuffer);
7
+ };
8
+ const createButtonsData = (rows) => {
9
+ return rows.map(row => {
10
+ const val = row.value;
11
+ return {
12
+ type: 1,
13
+ components: val.map(button => {
14
+ const value = button.value;
15
+ let text = '';
16
+ if (typeof button.options?.data === 'object') {
17
+ text = button.options?.data.click;
18
+ }
19
+ else {
20
+ text = button.options.data;
21
+ }
22
+ return {
23
+ type: 2,
24
+ custom_id: text,
25
+ style: 1,
26
+ label: typeof value === 'object' ? value.title : value
27
+ };
28
+ })
29
+ };
30
+ });
31
+ };
32
+ const sendchannel = async (client, param, val) => {
33
+ try {
34
+ if (!val || val.length <= 0) {
35
+ return [];
36
+ }
37
+ const channelId = String(param?.channel_id ?? '');
38
+ const images = val.filter(item => item.type === 'Image' || item.type === 'ImageURL' || item.type === 'ImageFile');
39
+ const buttons = val.filter(item => item.type === 'BT.group');
40
+ const mds = val.filter(item => item.type === 'Markdown');
41
+ const content = val
42
+ .filter(item => item.type === 'Mention' || item.type === 'Text' || item.type === 'Link')
43
+ .map(item => {
44
+ if (item.type === 'Link') {
45
+ return `[${item.value}](${item?.options?.link ?? item.value})`;
46
+ }
47
+ else if (item.type === 'Mention') {
48
+ if (item.value === 'everyone' || item.value === 'all' || item.value === '' || typeof item.value !== 'string') {
49
+ return '<@everyone>';
50
+ }
51
+ if (item.options?.belong === 'user') {
52
+ return `<@${item.value}>`;
53
+ }
54
+ else if (item.options?.belong === 'channel') {
55
+ return `<#${item.value}>`;
56
+ }
57
+ return '';
58
+ }
59
+ else if (item.type === 'Text') {
60
+ if (item.options?.style === 'block') {
61
+ return `\`${item.value}\``;
62
+ }
63
+ else if (item.options?.style === 'italic') {
64
+ return `*${item.value}*`;
65
+ }
66
+ else if (item.options?.style === 'bold') {
67
+ return `**${item.value}**`;
68
+ }
69
+ else if (item.options?.style === 'strikethrough') {
70
+ return `~~${item.value}~~`;
71
+ }
72
+ return item.value ?? '';
73
+ }
74
+ return '';
75
+ })
76
+ .join('');
77
+ if (images.length > 0) {
78
+ let bufferData = null;
79
+ for (let i = 0; i < images.length; i++) {
80
+ if (bufferData) {
81
+ break;
82
+ }
83
+ const item = images[i];
84
+ if (item.type === 'Image') {
85
+ bufferData = Buffer.from(item.value, 'base64');
86
+ }
87
+ else if (item.type === 'ImageURL') {
88
+ const res = await ImageURLToBuffer(item.value);
89
+ bufferData = res;
90
+ }
91
+ else if (item.type === 'ImageFile') {
92
+ bufferData = readFileSync(item.value);
93
+ }
94
+ }
95
+ const uploadRes = await client.uploadFile(bufferData, undefined, { channelId });
96
+ const fileAttachment = uploadRes?.file;
97
+ if (!fileAttachment) {
98
+ throw new Error('文件上传失败:未返回文件信息');
99
+ }
100
+ const attachments = [fileAttachment];
101
+ const payload = {
102
+ content: content,
103
+ type: 'text',
104
+ attachments
105
+ };
106
+ const res = await client.sendMessage(channelId, payload);
107
+ return [createResult(ResultCode.Ok, '完成', res)];
108
+ }
109
+ let contentMd = '';
110
+ if (mds && mds.length > 0) {
111
+ mds.forEach(item => {
112
+ if (item.type === 'Markdown') {
113
+ const md = item.value;
114
+ md.forEach(line => {
115
+ if (line.type === 'MD.text') {
116
+ contentMd += line.value;
117
+ }
118
+ else if (line.type === 'MD.blockquote') {
119
+ contentMd += `> ${line.value}\n`;
120
+ }
121
+ else if (line.type === 'MD.bold') {
122
+ contentMd += `**${line.value}**`;
123
+ }
124
+ else if (line.type === 'MD.italic') {
125
+ contentMd += `*${line.value}*`;
126
+ }
127
+ else if (line.type === 'MD.divider') {
128
+ contentMd += '---\n';
129
+ }
130
+ else if (line.type === 'MD.image') {
131
+ contentMd += `![${line.value}](${line.value})`;
132
+ }
133
+ else if (line.type === 'MD.link') {
134
+ contentMd += `[${line.value}](${line.value})`;
135
+ }
136
+ else if (line.type === 'MD.list') {
137
+ const listStr = line.value.map(listItem => {
138
+ if (typeof listItem.value === 'object') {
139
+ return `\n${listItem.value.index}. ${listItem.value.text}`;
140
+ }
141
+ return `\n- ${listItem.value}`;
142
+ });
143
+ contentMd += `${listStr}\n`;
144
+ }
145
+ else if (line.type === 'MD.newline') {
146
+ contentMd += '\n';
147
+ }
148
+ else if (line.type === 'MD.code') {
149
+ const language = line?.options?.language || '';
150
+ contentMd += `\`\`\`${language}\n${line.value}\n\`\`\`\n`;
151
+ }
152
+ else {
153
+ const value = line['value'] || '';
154
+ contentMd += String(value);
155
+ }
156
+ });
157
+ }
158
+ });
159
+ }
160
+ if (buttons && buttons.length > 0) {
161
+ let components = null;
162
+ buttons.forEach(item => {
163
+ if (components) {
164
+ return;
165
+ }
166
+ const rows = item.value;
167
+ components = createButtonsData(rows);
168
+ });
169
+ const res = await client.sendMessage(channelId, {
170
+ content: contentMd || content,
171
+ components
172
+ });
173
+ return [createResult(ResultCode.Ok, '完成', res)];
174
+ }
175
+ if (content && content.length > 0) {
176
+ const res = await client.sendMessage(channelId, { content: contentMd || content });
177
+ return [createResult(ResultCode.Ok, '完成', res)];
178
+ }
179
+ return [];
180
+ }
181
+ catch (err) {
182
+ return [createResult(ResultCode.Fail, err?.response?.data ?? err?.message ?? err, null)];
183
+ }
184
+ };
185
+ const senduser = async (client, param, val) => {
186
+ if (!val || val.length <= 0) {
187
+ return [];
188
+ }
189
+ let channelId = param?.channel_id;
190
+ if (!channelId && param.author_id) {
191
+ const dm = await client.getOrCreateDm(param.author_id);
192
+ channelId = dm?.id;
193
+ }
194
+ if (!channelId) {
195
+ return [];
196
+ }
197
+ return sendchannel(client, { channel_id: channelId }, val);
198
+ };
199
+ var send = {};
200
+
201
+ export { send as default, sendchannel, senduser };
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@alemonjs/bubble",
3
+ "version": "2.1.0-alpha.1",
4
+ "description": "bubble platform",
5
+ "author": "lemonade",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "lib/index.js",
9
+ "types": "lib",
10
+ "scripts": {
11
+ "build": "lvy build"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "import": "./lib/index.js",
16
+ "types": "./lib/index.d.ts",
17
+ "require": "./lib/index.js"
18
+ },
19
+ "./package": "./package.json",
20
+ "./desktop": "./lib/desktop.js"
21
+ },
22
+ "dependencies": {
23
+ "http-proxy-agent": "^7.0.2",
24
+ "https-proxy-agent": "^7"
25
+ },
26
+ "devDependencies": {
27
+ "@types/qrcode": "^1.5.5"
28
+ },
29
+ "peerDependencies": {
30
+ "alemonjs": "^2.1.0-alpha.15"
31
+ },
32
+ "alemonjs": {
33
+ "desktop": {
34
+ "platform": [
35
+ {
36
+ "name": "bubble"
37
+ }
38
+ ],
39
+ "logo": "antd.bubbleOutlined",
40
+ "command": [
41
+ {
42
+ "name": "打开bubble",
43
+ "icon": "antd.bubbleOutlined",
44
+ "command": "open.bubble"
45
+ }
46
+ ],
47
+ "sidebars": [
48
+ {
49
+ "name": "dc",
50
+ "icon": "antd.bubbleOutlined",
51
+ "command": "open.bubble"
52
+ }
53
+ ]
54
+ }
55
+ },
56
+ "keywords": [
57
+ "alemonjs"
58
+ ],
59
+ "publishConfig": {
60
+ "registry": "https://registry.npmjs.org",
61
+ "access": "public"
62
+ },
63
+ "bugs": "https://github.com/lemonade-lab/alemonjs/issues",
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "https://github.com/lemonade-lab/alemonjs.git"
67
+ }
68
+ }