@yungho/noclaw-channel 0.1.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/README.md +109 -0
- package/bin/cli.js +81 -0
- package/dist/channels/TelegramChannel.d.ts +59 -0
- package/dist/channels/TelegramChannel.d.ts.map +1 -0
- package/dist/channels/TelegramChannel.js +223 -0
- package/dist/channels/TelegramChannel.js.map +1 -0
- package/dist/channels/WechatChannel.d.ts +65 -0
- package/dist/channels/WechatChannel.d.ts.map +1 -0
- package/dist/channels/WechatChannel.js +288 -0
- package/dist/channels/WechatChannel.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +50 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +180 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/server/ChannelServer.d.ts +46 -0
- package/dist/server/ChannelServer.d.ts.map +1 -0
- package/dist/server/ChannelServer.js +126 -0
- package/dist/server/ChannelServer.js.map +1 -0
- package/dist/server/WebSocketServer.d.ts +54 -0
- package/dist/server/WebSocketServer.d.ts.map +1 -0
- package/dist/server/WebSocketServer.js +234 -0
- package/dist/server/WebSocketServer.js.map +1 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/Logger.d.ts +17 -0
- package/dist/utils/Logger.d.ts.map +1 -0
- package/dist/utils/Logger.js +39 -0
- package/dist/utils/Logger.js.map +1 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @yungho/noclaw-channel
|
|
2
|
+
|
|
3
|
+
NoClaw Channel Server — WebSocket IPC bridge that connects external chat platforms (WeChat, Telegram) to [NoClaw](https://github.com/noclaw/noclaw) for Obsidian.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Phone (WeChat/Telegram)
|
|
9
|
+
↕ HTTP Long Polling
|
|
10
|
+
@noclaw/channel (this package)
|
|
11
|
+
↕ WebSocket (ws://127.0.0.1:8123)
|
|
12
|
+
NoClaw Plugin (Obsidian)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The Channel Server runs as a standalone Node.js process. It handles communication with external chat platforms and bridges messages to the NoClaw Obsidian plugin via WebSocket.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
- **Node.js** >= 18
|
|
20
|
+
- **NoClaw** plugin installed in Obsidian
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Option A — npx (no install required)
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @yungho/noclaw-channel start
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Option B — global install (recommended for daily use)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm i -g @yungho/noclaw-channel
|
|
34
|
+
noclaw-channel start
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Start the server
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Default (ws://127.0.0.1:8123)
|
|
43
|
+
noclaw-channel start
|
|
44
|
+
|
|
45
|
+
# Custom port
|
|
46
|
+
noclaw-channel start --port=9000
|
|
47
|
+
|
|
48
|
+
# Custom host
|
|
49
|
+
noclaw-channel start --host=0.0.0.0
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Other commands
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
noclaw-channel status # Check if server is running
|
|
56
|
+
noclaw-channel stop # Stop the server
|
|
57
|
+
noclaw-channel setup # Interactive configuration wizard
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Connecting to NoClaw
|
|
61
|
+
|
|
62
|
+
1. Start the Channel Server in your terminal
|
|
63
|
+
2. Open Obsidian → Settings → NoClaw → Chat Channels
|
|
64
|
+
3. Enable "Chat Channels"
|
|
65
|
+
4. Click "Connect"
|
|
66
|
+
5. For WeChat: scan the QR code shown in the terminal
|
|
67
|
+
|
|
68
|
+
## Supported Platforms
|
|
69
|
+
|
|
70
|
+
| Platform | Status | Auth Method |
|
|
71
|
+
|----------|--------|-------------|
|
|
72
|
+
| WeChat | ✅ Working | QR Code (ClawBot ilink API) |
|
|
73
|
+
| Telegram | 🔜 Planned | Bot Token |
|
|
74
|
+
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
The server stores its config at `~/.noclaw/channel-config.json`:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"port": 8123,
|
|
82
|
+
"host": "127.0.0.1",
|
|
83
|
+
"logLevel": "info",
|
|
84
|
+
"wechat": {
|
|
85
|
+
"enabled": true,
|
|
86
|
+
"permissions": {
|
|
87
|
+
"chat": "all",
|
|
88
|
+
"executeTools": true,
|
|
89
|
+
"permissionRelay": true,
|
|
90
|
+
"adminUsers": []
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"telegram": {
|
|
94
|
+
"enabled": false,
|
|
95
|
+
"botToken": "",
|
|
96
|
+
"allowedUsers": [],
|
|
97
|
+
"permissions": {
|
|
98
|
+
"chat": "whitelist",
|
|
99
|
+
"executeTools": true,
|
|
100
|
+
"permissionRelay": true,
|
|
101
|
+
"adminUsers": []
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
Apache-2.0
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import { ChannelServer } from '../dist/server/ChannelServer.js';
|
|
4
|
+
import { ConfigManager } from '../dist/config/ConfigManager.js';
|
|
5
|
+
import { Logger } from '../dist/utils/Logger.js';
|
|
6
|
+
|
|
7
|
+
const loggerWrapper = new Logger();
|
|
8
|
+
const logger = loggerWrapper.getPinoLogger();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name('noclaw-channel')
|
|
12
|
+
.description('NoClaw Channel Server - WebSocket IPC bridge for external chat platforms')
|
|
13
|
+
.version('0.1.0');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('start')
|
|
17
|
+
.description('Start the channel server')
|
|
18
|
+
.option('--port <port>', 'WebSocket port (default: 8123)', '8123')
|
|
19
|
+
.option('--host <host>', 'WebSocket host (default: 127.0.0.1)', '127.0.0.1')
|
|
20
|
+
.option('--background', 'Run as background process', false)
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
try {
|
|
23
|
+
const config = new ConfigManager();
|
|
24
|
+
const server = new ChannelServer(config, logger);
|
|
25
|
+
|
|
26
|
+
await server.start({
|
|
27
|
+
port: parseInt(options.port),
|
|
28
|
+
host: options.host,
|
|
29
|
+
background: options.background
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
logger.info('Channel server started successfully');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.error('Failed to start server:', error);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('stop')
|
|
41
|
+
.description('Stop the channel server')
|
|
42
|
+
.action(async () => {
|
|
43
|
+
try {
|
|
44
|
+
const config = new ConfigManager();
|
|
45
|
+
await config.stopChannel();
|
|
46
|
+
logger.info('Channel server stopped');
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.error('Failed to stop server:', error);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command('status')
|
|
55
|
+
.description('Check the status of the channel server')
|
|
56
|
+
.action(async () => {
|
|
57
|
+
try {
|
|
58
|
+
const config = new ConfigManager();
|
|
59
|
+
const status = await config.getStatus();
|
|
60
|
+
logger.info('Channel server status:', status);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
logger.error('Failed to get status:', error);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
program
|
|
68
|
+
.command('setup')
|
|
69
|
+
.description('Setup the channel server configuration')
|
|
70
|
+
.action(async () => {
|
|
71
|
+
try {
|
|
72
|
+
const config = new ConfigManager();
|
|
73
|
+
await config.interactiveSetup();
|
|
74
|
+
logger.info('Setup completed successfully');
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error('Setup failed:', error);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
program.parse();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Channel Implementation
|
|
3
|
+
*
|
|
4
|
+
* Uses Telegram Bot API for message communication.
|
|
5
|
+
* This is a placeholder implementation for future use.
|
|
6
|
+
*/
|
|
7
|
+
import { IChannel, IpcMessage, TelegramConfig, ChannelStatus, ChannelCapabilities, MediaObject } from '../types.js';
|
|
8
|
+
import pino from 'pino';
|
|
9
|
+
export declare class TelegramChannel implements IChannel {
|
|
10
|
+
private readonly baseUrl;
|
|
11
|
+
private config;
|
|
12
|
+
private logger;
|
|
13
|
+
private status;
|
|
14
|
+
private pollingInterval?;
|
|
15
|
+
private messageCallback?;
|
|
16
|
+
private statusCallbacks;
|
|
17
|
+
private qrCodeCallbacks;
|
|
18
|
+
private lastUpdateId;
|
|
19
|
+
private readonly POLLING_INTERVAL;
|
|
20
|
+
constructor(config: TelegramConfig, logger: pino.Logger);
|
|
21
|
+
connect(): Promise<void>;
|
|
22
|
+
disconnect(): Promise<void>;
|
|
23
|
+
send(text: string, contextToken: string, media?: MediaObject[]): Promise<void>;
|
|
24
|
+
onMessage(callback: (msg: IpcMessage) => void): void;
|
|
25
|
+
getStatus(): ChannelStatus;
|
|
26
|
+
getCapabilities(): ChannelCapabilities;
|
|
27
|
+
onStatusChange(callback: (status: ChannelStatus) => void): void;
|
|
28
|
+
requestQrCode(): void;
|
|
29
|
+
onQrCode(callback: (qrCodeUrl: string) => void): void;
|
|
30
|
+
/**
|
|
31
|
+
* Start polling for incoming messages
|
|
32
|
+
*/
|
|
33
|
+
private startMessagePolling;
|
|
34
|
+
/**
|
|
35
|
+
* Stop all polling
|
|
36
|
+
*/
|
|
37
|
+
private stopPolling;
|
|
38
|
+
/**
|
|
39
|
+
* Send text message
|
|
40
|
+
*/
|
|
41
|
+
private sendMessage;
|
|
42
|
+
/**
|
|
43
|
+
* Send photo
|
|
44
|
+
*/
|
|
45
|
+
private sendPhoto;
|
|
46
|
+
/**
|
|
47
|
+
* Make HTTP request to Telegram API
|
|
48
|
+
*/
|
|
49
|
+
private makeRequest;
|
|
50
|
+
/**
|
|
51
|
+
* Update connection status
|
|
52
|
+
*/
|
|
53
|
+
private setStatus;
|
|
54
|
+
/**
|
|
55
|
+
* Notify listeners about QR code
|
|
56
|
+
*/
|
|
57
|
+
private notifyQrCode;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=TelegramChannel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TelegramChannel.d.ts","sourceRoot":"","sources":["../../src/channels/TelegramChannel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpH,OAAO,IAAI,MAAM,MAAM,CAAC;AAqCxB,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,eAAe,CAAC,CAA4B;IACpD,OAAO,CAAC,eAAe,CAAmD;IAC1E,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;gBAE7B,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;IAMjD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA4BxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBpF,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIpD,SAAS,IAAI,aAAa;IAI1B,eAAe,IAAI,mBAAmB;IAUtC,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAI/D,aAAa,IAAI,IAAI;IAKrB,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIrD;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA+C3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAOnB;;OAEG;YACW,WAAW;IAyBzB;;OAEG;YACW,SAAS;IA2BvB;;OAEG;YACW,WAAW;IAYzB;;OAEG;IACH,OAAO,CAAC,SAAS;IAKjB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telegram Channel Implementation
|
|
3
|
+
*
|
|
4
|
+
* Uses Telegram Bot API for message communication.
|
|
5
|
+
* This is a placeholder implementation for future use.
|
|
6
|
+
*/
|
|
7
|
+
export class TelegramChannel {
|
|
8
|
+
baseUrl;
|
|
9
|
+
config;
|
|
10
|
+
logger;
|
|
11
|
+
status = 'disconnected';
|
|
12
|
+
pollingInterval;
|
|
13
|
+
messageCallback;
|
|
14
|
+
statusCallbacks = new Set();
|
|
15
|
+
qrCodeCallbacks = new Set();
|
|
16
|
+
lastUpdateId = 0;
|
|
17
|
+
POLLING_INTERVAL = 2000; // 2 seconds
|
|
18
|
+
constructor(config, logger) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.baseUrl = `https://api.telegram.org/bot${config.botToken}`;
|
|
21
|
+
this.logger = logger.child({ channel: 'telegram' });
|
|
22
|
+
}
|
|
23
|
+
async connect() {
|
|
24
|
+
if (!this.config.enabled) {
|
|
25
|
+
this.logger.warn('Telegram channel is disabled in config');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
this.setStatus('connecting');
|
|
29
|
+
try {
|
|
30
|
+
// Test token by making a request
|
|
31
|
+
const response = await this.makeRequest('/getUpdates', {
|
|
32
|
+
method: 'GET',
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error('Invalid bot token or API error');
|
|
36
|
+
}
|
|
37
|
+
this.startMessagePolling();
|
|
38
|
+
this.setStatus('connected');
|
|
39
|
+
this.logger.info('Connected to Telegram Bot API');
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.logger.error({ error }, 'Failed to connect to Telegram');
|
|
43
|
+
this.setStatus('error');
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async disconnect() {
|
|
48
|
+
this.stopPolling();
|
|
49
|
+
this.setStatus('disconnected');
|
|
50
|
+
this.logger.info('Disconnected from Telegram');
|
|
51
|
+
}
|
|
52
|
+
async send(text, contextToken, media) {
|
|
53
|
+
try {
|
|
54
|
+
const chatId = contextToken.replace('tg_', '');
|
|
55
|
+
if (media && media.length > 0) {
|
|
56
|
+
// Send media first
|
|
57
|
+
for (const item of media) {
|
|
58
|
+
if (item.type === 'image') {
|
|
59
|
+
await this.sendPhoto(chatId, item.data, text);
|
|
60
|
+
break; // Only send first image for now
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Send text message
|
|
66
|
+
await this.sendMessage(chatId, text);
|
|
67
|
+
}
|
|
68
|
+
this.logger.debug({ chatId, textLength: text.length }, 'Message sent to Telegram');
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
this.logger.error({ error, contextToken }, 'Failed to send message to Telegram');
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
onMessage(callback) {
|
|
76
|
+
this.messageCallback = callback;
|
|
77
|
+
}
|
|
78
|
+
getStatus() {
|
|
79
|
+
return this.status;
|
|
80
|
+
}
|
|
81
|
+
getCapabilities() {
|
|
82
|
+
return {
|
|
83
|
+
textMessaging: true,
|
|
84
|
+
mediaSharing: true,
|
|
85
|
+
permissionRelay: true,
|
|
86
|
+
richFormatting: true,
|
|
87
|
+
webhooks: false,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
onStatusChange(callback) {
|
|
91
|
+
this.statusCallbacks.add(callback);
|
|
92
|
+
}
|
|
93
|
+
requestQrCode() {
|
|
94
|
+
// Telegram uses bot token, not QR code
|
|
95
|
+
this.logger.info('Telegram uses bot token authentication, no QR code');
|
|
96
|
+
}
|
|
97
|
+
onQrCode(callback) {
|
|
98
|
+
this.qrCodeCallbacks.add(callback);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Start polling for incoming messages
|
|
102
|
+
*/
|
|
103
|
+
startMessagePolling() {
|
|
104
|
+
this.stopPolling(); // Clear any existing polling
|
|
105
|
+
this.pollingInterval = setInterval(async () => {
|
|
106
|
+
try {
|
|
107
|
+
const response = await this.makeRequest(`/getUpdates?offset=${this.lastUpdateId}`, {
|
|
108
|
+
method: 'GET',
|
|
109
|
+
});
|
|
110
|
+
if (response.result && response.result.length > 0) {
|
|
111
|
+
this.logger.debug({ messageCount: response.result.length }, 'Received messages from Telegram');
|
|
112
|
+
for (const update of response.result) {
|
|
113
|
+
// Update last update ID
|
|
114
|
+
this.lastUpdateId = update.update_id;
|
|
115
|
+
// Emit message to callback
|
|
116
|
+
if (update.message?.text) {
|
|
117
|
+
this.messageCallback?.({
|
|
118
|
+
type: 'MSG_IN',
|
|
119
|
+
platform: 'telegram',
|
|
120
|
+
content: update.message.text,
|
|
121
|
+
contextToken: `tg_${update.message.chat.id}`,
|
|
122
|
+
userId: String(update.message.from.id),
|
|
123
|
+
metadata: {
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
platform: 'telegram',
|
|
126
|
+
userId: String(update.message.from.id),
|
|
127
|
+
messageId: String(update.message.message_id),
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
this.logger.error({ error }, 'Error polling for messages');
|
|
136
|
+
// Don't stop polling on error, just log it
|
|
137
|
+
}
|
|
138
|
+
}, this.POLLING_INTERVAL);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Stop all polling
|
|
142
|
+
*/
|
|
143
|
+
stopPolling() {
|
|
144
|
+
if (this.pollingInterval) {
|
|
145
|
+
clearInterval(this.pollingInterval);
|
|
146
|
+
this.pollingInterval = undefined;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Send text message
|
|
151
|
+
*/
|
|
152
|
+
async sendMessage(chatId, text) {
|
|
153
|
+
const url = `${this.baseUrl}/sendMessage`;
|
|
154
|
+
const body = JSON.stringify({
|
|
155
|
+
chat_id: parseInt(chatId),
|
|
156
|
+
text,
|
|
157
|
+
});
|
|
158
|
+
const response = await fetch(url, {
|
|
159
|
+
method: 'POST',
|
|
160
|
+
headers: {
|
|
161
|
+
'Content-Type': 'application/json',
|
|
162
|
+
},
|
|
163
|
+
body,
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new Error(`Failed to send message: ${response.statusText}`);
|
|
167
|
+
}
|
|
168
|
+
const data = await response.json();
|
|
169
|
+
if (!data.ok) {
|
|
170
|
+
throw new Error('Failed to send message');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Send photo
|
|
175
|
+
*/
|
|
176
|
+
async sendPhoto(chatId, photoData, caption) {
|
|
177
|
+
const url = `${this.baseUrl}/sendPhoto`;
|
|
178
|
+
const formData = new FormData();
|
|
179
|
+
formData.append('chat_id', chatId);
|
|
180
|
+
const buffer = Buffer.from(photoData, 'base64');
|
|
181
|
+
const blob = new Blob([buffer], { type: 'image/jpeg' });
|
|
182
|
+
formData.append('photo', blob, 'photo.jpg');
|
|
183
|
+
if (caption) {
|
|
184
|
+
formData.append('caption', caption);
|
|
185
|
+
}
|
|
186
|
+
const response = await fetch(url, {
|
|
187
|
+
method: 'POST',
|
|
188
|
+
body: formData,
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
throw new Error(`Failed to send photo: ${response.statusText}`);
|
|
192
|
+
}
|
|
193
|
+
const data = await response.json();
|
|
194
|
+
if (!data.ok) {
|
|
195
|
+
throw new Error('Failed to send photo');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Make HTTP request to Telegram API
|
|
200
|
+
*/
|
|
201
|
+
async makeRequest(endpoint, options) {
|
|
202
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
203
|
+
const response = await fetch(url, options);
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
206
|
+
}
|
|
207
|
+
return await response.json();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Update connection status
|
|
211
|
+
*/
|
|
212
|
+
setStatus(status) {
|
|
213
|
+
this.status = status;
|
|
214
|
+
this.statusCallbacks.forEach((callback) => callback(status));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Notify listeners about QR code
|
|
218
|
+
*/
|
|
219
|
+
notifyQrCode(qrCodeUrl) {
|
|
220
|
+
this.qrCodeCallbacks.forEach((callback) => callback(qrCodeUrl));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=TelegramChannel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TelegramChannel.js","sourceRoot":"","sources":["../../src/channels/TelegramChannel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwCH,MAAM,OAAO,eAAe;IACT,OAAO,CAAS;IACzB,MAAM,CAAiB;IACvB,MAAM,CAAc;IACpB,MAAM,GAAkB,cAAc,CAAC;IACvC,eAAe,CAAkB;IACjC,eAAe,CAA6B;IAC5C,eAAe,GAAyC,IAAI,GAAG,EAAE,CAAC;IAClE,eAAe,GAAqC,IAAI,GAAG,EAAE,CAAC;IAC9D,YAAY,GAAG,CAAC,CAAC;IACR,gBAAgB,GAAG,IAAI,CAAC,CAAC,YAAY;IAEtD,YAAY,MAAsB,EAAE,MAAmB;QACrD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,+BAA+B,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACzB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;YAC3D,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QAE7B,IAAI,CAAC;YACH,iCAAiC;YACjC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CAAqB,aAAa,EAAE;gBACzE,MAAM,EAAE,KAAK;aACd,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;YACpD,CAAC;YAED,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC3B,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;YAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QACpD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,+BAA+B,CAAC,CAAC;YAC9D,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YACxB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,CAAC,WAAW,EAAE,CAAC;QACnB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAC/B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,YAAoB,EAAE,KAAqB;QAClE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAE/C,IAAI,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,mBAAmB;gBACnB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;oBACzB,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;wBAC1B,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,IAAc,EAAE,IAAI,CAAC,CAAC;wBACxD,MAAM,CAAC,gCAAgC;oBACzC,CAAC;gBACH,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,oBAAoB;gBACpB,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACvC,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,0BAA0B,CAAC,CAAC;QACrF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,EAAE,oCAAoC,CAAC,CAAC;YACjF,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,SAAS,CAAC,QAAmC;QAC3C,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC;IAClC,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,eAAe;QACb,OAAO;YACL,aAAa,EAAE,IAAI;YACnB,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,IAAI;YACpB,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAED,cAAc,CAAC,QAAyC;QACtD,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,aAAa;QACX,uCAAuC;QACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACzE,CAAC;IAED,QAAQ,CAAC,QAAqC;QAC5C,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED;;OAEG;IACK,mBAAmB;QACzB,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,6BAA6B;QAEjD,IAAI,CAAC,eAAe,GAAG,WAAW,CAAC,KAAK,IAAI,EAAE;YAC5C,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,WAAW,CACrC,sBAAsB,IAAI,CAAC,YAAY,EAAE,EACzC;oBACE,MAAM,EAAE,KAAK;iBACd,CACF,CAAC;gBAEF,IAAI,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAClD,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,EACxC,iCAAiC,CAClC,CAAC;oBAEF,KAAK,MAAM,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;wBACrC,wBAAwB;wBACxB,IAAI,CAAC,YAAY,GAAG,MAAM,CAAC,SAAS,CAAC;wBAErC,2BAA2B;wBAC3B,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;4BACzB,IAAI,CAAC,eAAe,EAAE,CAAC;gCACrB,IAAI,EAAE,QAAQ;gCACd,QAAQ,EAAE,UAAU;gCACpB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI;gCAC5B,YAAY,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE;gCAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;gCACtC,QAAQ,EAAE;oCACR,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;oCACrB,QAAQ,EAAE,UAAU;oCACpB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oCACtC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC;iCAC7C;6BACF,CAAC,CAAC;wBACL,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,EAAE,4BAA4B,CAAC,CAAC;gBAC3D,2CAA2C;YAC7C,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACK,WAAW;QACjB,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YACzB,aAAa,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACpC,IAAI,CAAC,eAAe,GAAG,SAAS,CAAC;QACnC,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,WAAW,CAAC,MAAc,EAAE,IAAY;QACpD,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,cAAc,CAAC;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;YAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC;YACzB,IAAI;SACL,CAAC,CAAC;QAEH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI;SACL,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,2BAA2B,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAyB,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,SAAS,CAAC,MAAc,EAAE,SAAiB,EAAE,OAAgB;QACzE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,YAAY,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAChD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC;QACxD,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAE5C,IAAI,OAAO,EAAE,CAAC;YACZ,QAAQ,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACtC,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,QAAe;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,yBAAyB,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAyB,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IAED;;OAEG;IACK,KAAK,CAAC,WAAW,CAAI,QAAgB,EAAE,OAAoB;QACjE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,GAAG,QAAQ,EAAE,CAAC;QAEzC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAE3C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;QACrE,CAAC;QAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAO,CAAC;IACpC,CAAC;IAED;;OAEG;IACK,SAAS,CAAC,MAAqB;QACrC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED;;OAEG;IACK,YAAY,CAAC,SAAiB;QACpC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,QAA+B,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACzF,CAAC;CACF"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat Channel Implementation
|
|
3
|
+
*
|
|
4
|
+
* Uses WeChat's official ClawBot ilink API for safe, official integration
|
|
5
|
+
* without risk of account suspension.
|
|
6
|
+
*/
|
|
7
|
+
import { IChannel, IpcMessage, WechatConfig, ChannelStatus, ChannelCapabilities, MediaObject } from '../types.js';
|
|
8
|
+
import pino from 'pino';
|
|
9
|
+
export declare class WechatChannel implements IChannel {
|
|
10
|
+
private readonly baseUrl;
|
|
11
|
+
private readonly defaultBaseUrl;
|
|
12
|
+
private botToken?;
|
|
13
|
+
private config;
|
|
14
|
+
private logger;
|
|
15
|
+
private status;
|
|
16
|
+
private pollingInterval?;
|
|
17
|
+
private qrCodePollingInterval?;
|
|
18
|
+
private messageCallback?;
|
|
19
|
+
private statusCallbacks;
|
|
20
|
+
private qrCodeCallbacks;
|
|
21
|
+
private lastQrCodeUrl?;
|
|
22
|
+
private lastUpdateId;
|
|
23
|
+
private readonly POLLING_INTERVAL;
|
|
24
|
+
private readonly QR_CODE_POLLING_INTERVAL;
|
|
25
|
+
private readonly LONG_POLLING_TIMEOUT;
|
|
26
|
+
constructor(config: WechatConfig, logger: pino.Logger);
|
|
27
|
+
connect(): Promise<void>;
|
|
28
|
+
disconnect(): Promise<void>;
|
|
29
|
+
send(text: string, contextToken: string, media?: MediaObject[]): Promise<void>;
|
|
30
|
+
onMessage(callback: (msg: IpcMessage) => void): void;
|
|
31
|
+
getStatus(): ChannelStatus;
|
|
32
|
+
getCapabilities(): ChannelCapabilities;
|
|
33
|
+
onStatusChange(callback: (status: ChannelStatus) => void): void;
|
|
34
|
+
requestQrCode(): void;
|
|
35
|
+
onQrCode(callback: (qrCodeUrl: string) => void): void;
|
|
36
|
+
/**
|
|
37
|
+
* Authenticate with WeChat using QR code
|
|
38
|
+
*/
|
|
39
|
+
private authenticateWithQrCode;
|
|
40
|
+
/**
|
|
41
|
+
* Poll for QR code scan status
|
|
42
|
+
*/
|
|
43
|
+
private pollQrCodeStatus;
|
|
44
|
+
/**
|
|
45
|
+
* Start polling for incoming messages
|
|
46
|
+
*/
|
|
47
|
+
private startMessagePolling;
|
|
48
|
+
/**
|
|
49
|
+
* Stop all polling
|
|
50
|
+
*/
|
|
51
|
+
private stopPolling;
|
|
52
|
+
/**
|
|
53
|
+
* Make HTTP request to WeChat API
|
|
54
|
+
*/
|
|
55
|
+
private makeRequest;
|
|
56
|
+
/**
|
|
57
|
+
* Update connection status
|
|
58
|
+
*/
|
|
59
|
+
private setStatus;
|
|
60
|
+
/**
|
|
61
|
+
* Notify listeners about QR code
|
|
62
|
+
*/
|
|
63
|
+
private notifyQrCode;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=WechatChannel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WechatChannel.d.ts","sourceRoot":"","sources":["../../src/channels/WechatChannel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClH,OAAO,IAAI,MAAM,MAAM,CAAC;AA8BxB,qBAAa,aAAc,YAAW,QAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6C;IAC5E,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,eAAe,CAAC,CAAiB;IACzC,OAAO,CAAC,qBAAqB,CAAC,CAAiB;IAC/C,OAAO,CAAC,eAAe,CAAC,CAA4B;IACpD,OAAO,CAAC,eAAe,CAAmD;IAC1E,OAAO,CAAC,eAAe,CAA+C;IACtE,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAQ;IACzC,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAQ;IACjD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAS;gBAElC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM;IAS/C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BpF,SAAS,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;IAIpD,SAAS,IAAI,aAAa;IAI1B,eAAe,IAAI,mBAAmB;IAUtC,cAAc,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GAAG,IAAI;IAI/D,aAAa,IAAI,IAAI;IAQrB,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAIrD;;OAEG;YACW,sBAAsB;IAqBpC;;OAEG;YACW,gBAAgB;IA+D9B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoD3B;;OAEG;IACH,OAAO,CAAC,WAAW;IAYnB;;OAEG;YACW,WAAW;IAgCzB;;OAEG;IACH,OAAO,CAAC,SAAS;IAKjB;;OAEG;IACH,OAAO,CAAC,YAAY;CAGrB"}
|