signal-sdk 0.1.0 → 0.1.2
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 +26 -17
- package/dist/MultiAccountManager.d.ts +149 -0
- package/dist/MultiAccountManager.js +320 -0
- package/dist/SignalBot.d.ts +1 -0
- package/dist/SignalBot.js +20 -2
- package/dist/SignalCli.d.ts +264 -15
- package/dist/SignalCli.js +652 -25
- package/dist/__tests__/MultiAccountManager.test.d.ts +4 -0
- package/dist/__tests__/MultiAccountManager.test.js +209 -0
- package/dist/__tests__/SignalBot.additional.test.js +31 -11
- package/dist/__tests__/SignalBot.test.js +5 -0
- package/dist/__tests__/SignalCli.advanced.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.advanced.test.js +295 -0
- package/dist/__tests__/SignalCli.e2e.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.e2e.test.js +240 -0
- package/dist/__tests__/SignalCli.integration.test.js +9 -2
- package/dist/__tests__/SignalCli.methods.test.js +171 -1
- package/dist/__tests__/SignalCli.parsing.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.parsing.test.js +258 -0
- package/dist/__tests__/SignalCli.test.js +50 -13
- package/dist/config.d.ts +16 -1
- package/dist/config.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/interfaces.d.ts +73 -9
- package/dist/retry.js +25 -8
- package/package.json +1 -1
- package/scripts/install.js +1 -1
package/README.md
CHANGED
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
|
|
15
15
|
[](https://badge.fury.io/js/signal-sdk)
|
|
16
16
|
[](https://opensource.org/licenses/MIT)
|
|
17
|
-
[](https://github.com/AsamK/signal-cli)
|
|
18
18
|
[](https://www.typescriptlang.org/)
|
|
19
19
|
[](https://nodejs.org/)
|
|
20
|
-
[](./src/__tests__)
|
|
20
|
+
[](./src/__tests__)
|
|
22
21
|
[](https://liberapay.com/devbyben/donate)
|
|
23
22
|
|
|
24
23
|
</div>
|
|
@@ -48,6 +47,14 @@
|
|
|
48
47
|
|
|
49
48
|
### Advanced Features
|
|
50
49
|
|
|
50
|
+
- **Multi-Account Support** - Manage multiple Signal accounts simultaneously with `MultiAccountManager`
|
|
51
|
+
- **Advanced Messaging** - Text styles (bold, italic), mentions, quotes, URL previews, message editing
|
|
52
|
+
- **Enhanced Receive** - Modern `receive()` method with options (timeout, ignore filters)
|
|
53
|
+
- **Username Management** - Set and delete Signal usernames
|
|
54
|
+
- **Identity Verification** - Get and verify safety numbers, manage untrusted identities
|
|
55
|
+
- **Advanced Groups** - Send invite links, manage banned members, reset group links
|
|
56
|
+
- **Enhanced Parsing** - Extract givenName, familyName, mobileCoinAddress from profiles and contacts
|
|
57
|
+
- **Daemon Modes** - Support for Unix socket, TCP, and HTTP daemon connections
|
|
51
58
|
- **File Attachments** - Send and receive files, images, and media
|
|
52
59
|
- **Group Operations** - Create and manage groups with detailed information
|
|
53
60
|
- **Contact Management** - Manage contacts with export/import capabilities
|
|
@@ -55,11 +62,11 @@
|
|
|
55
62
|
- **Typing Indicators** - Send and receive typing notifications
|
|
56
63
|
- **Read Receipts** - Track message delivery and read status
|
|
57
64
|
- **Profile Management** - Update profile information and avatars
|
|
58
|
-
- **Payment Notifications** - Send payment notifications
|
|
65
|
+
- **Payment Notifications** - Send MobileCoin payment notifications with receipts
|
|
59
66
|
- **Sticker Packs** - Upload and manage custom sticker packs
|
|
60
67
|
- **User Status** - Verify Signal registration status
|
|
61
68
|
- **Rate Limiting** - Handle and recover from rate limits
|
|
62
|
-
- **Phone Number Changes** - Change registered phone numbers
|
|
69
|
+
- **Phone Number Changes** - Change registered phone numbers with verification
|
|
63
70
|
- **Progress Tracking** - Monitor upload progress
|
|
64
71
|
- **Polls** - Create, vote, and terminate polls
|
|
65
72
|
- **Attachment Retrieval** - Retrieve attachments, avatars, and stickers
|
|
@@ -357,7 +364,7 @@ await bot.start();
|
|
|
357
364
|
- **TypeScript**: 5.8+ with strict mode
|
|
358
365
|
- **Test Coverage**: 225 passing tests across 9 suites
|
|
359
366
|
- **Code Coverage**: 57.52% overall, critical modules at 96-100%
|
|
360
|
-
- **signal-cli**: Compatible with v0.13.
|
|
367
|
+
- **signal-cli**: Compatible with v0.13.23
|
|
361
368
|
|
|
362
369
|
## Testing
|
|
363
370
|
|
|
@@ -371,14 +378,14 @@ The SDK has comprehensive test coverage to ensure reliability and quality.
|
|
|
371
378
|
|
|
372
379
|
### Coverage by Module
|
|
373
380
|
|
|
374
|
-
| Module
|
|
375
|
-
|
|
376
|
-
| **validators.ts** | 100%
|
|
377
|
-
| **config.ts**
|
|
378
|
-
| **errors.ts**
|
|
379
|
-
| **retry.ts**
|
|
380
|
-
| **SignalCli.ts**
|
|
381
|
-
| **SignalBot.ts**
|
|
381
|
+
| Module | Statements | Branches | Functions | Lines | Status |
|
|
382
|
+
| ----------------- | ---------- | -------- | --------- | ------ | -------------- |
|
|
383
|
+
| **validators.ts** | 100% | 100% | 100% | 100% | ✅ Perfect |
|
|
384
|
+
| **config.ts** | 100% | 97.22% | 100% | 100% | ✅ Excellent |
|
|
385
|
+
| **errors.ts** | 100% | 100% | 100% | 100% | ✅ Perfect |
|
|
386
|
+
| **retry.ts** | 96.15% | 85.71% | 100% | 97.95% | ✅ Excellent |
|
|
387
|
+
| **SignalCli.ts** | 68.68% | 55.46% | 65.9% | 72.7% | ✅ Good |
|
|
388
|
+
| **SignalBot.ts** | 24.33% | 16.94% | 29.68% | 24.59% | ⚠️ In Progress |
|
|
382
389
|
|
|
383
390
|
### Running Tests
|
|
384
391
|
|
|
@@ -408,7 +415,7 @@ npm test -- --watch
|
|
|
408
415
|
8. **SignalBot.test.ts** - Bot framework
|
|
409
416
|
9. **SignalBot.additional.test.ts** - Extended bot features
|
|
410
417
|
|
|
411
|
-
|
|
418
|
+
````
|
|
412
419
|
|
|
413
420
|
## Development
|
|
414
421
|
|
|
@@ -428,7 +435,7 @@ npm run build && node examples/sdk/01-basic-usage.js
|
|
|
428
435
|
|
|
429
436
|
# Run tests
|
|
430
437
|
npm test
|
|
431
|
-
|
|
438
|
+
````
|
|
432
439
|
|
|
433
440
|
## Configuration
|
|
434
441
|
|
|
@@ -563,7 +570,7 @@ This project is licensed under the MIT License - see the [LICENSE](./LICENSE) fi
|
|
|
563
570
|
|
|
564
571
|
## API Methods
|
|
565
572
|
|
|
566
|
-
Compatible with signal-cli v0.13.
|
|
573
|
+
Compatible with signal-cli v0.13.23 - **100% Feature Coverage**
|
|
567
574
|
|
|
568
575
|
| Category | Method | Description | Status |
|
|
569
576
|
| --------------- | -------------------------- | ---------------------------------- | ------ |
|
|
@@ -588,6 +595,8 @@ Compatible with signal-cli v0.13.22 - **100% Feature Coverage**
|
|
|
588
595
|
| | `getUserStatus` | Check registration status | ✅ |
|
|
589
596
|
| **Account** | `updateAccount` | Update account settings | ✅ |
|
|
590
597
|
| | `listAccountsDetailed` | List accounts with detailed info | ✅ |
|
|
598
|
+
| **Devices** | `listDevices` | List linked devices | ✅ |
|
|
599
|
+
| | `updateDevice` | Update device name (v0.13.23+) | ✅ |
|
|
591
600
|
| **Attachments** | `getAttachment` | Retrieve attachment by ID | ✅ |
|
|
592
601
|
| | `getAvatar` | Retrieve avatar by ID | ✅ |
|
|
593
602
|
| | `getSticker` | Retrieve sticker by ID | ✅ |
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Account Manager for Signal SDK
|
|
3
|
+
*
|
|
4
|
+
* Manages multiple Signal accounts simultaneously with event routing
|
|
5
|
+
* and isolated process management.
|
|
6
|
+
*/
|
|
7
|
+
import { SignalCli } from './SignalCli';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
import { SignalCliConfig } from './config';
|
|
10
|
+
/**
|
|
11
|
+
* Configuration for a managed account
|
|
12
|
+
*/
|
|
13
|
+
export interface ManagedAccount {
|
|
14
|
+
/** Account phone number */
|
|
15
|
+
account: string;
|
|
16
|
+
/** SignalCli instance */
|
|
17
|
+
instance: SignalCli;
|
|
18
|
+
/** Whether the account is connected */
|
|
19
|
+
connected: boolean;
|
|
20
|
+
/** Last activity timestamp */
|
|
21
|
+
lastActivity: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Options for MultiAccountManager
|
|
25
|
+
*/
|
|
26
|
+
export interface MultiAccountOptions {
|
|
27
|
+
/** Path to signal-cli executable */
|
|
28
|
+
signalCliPath?: string;
|
|
29
|
+
/** Data directory for all accounts */
|
|
30
|
+
dataPath?: string;
|
|
31
|
+
/** Enable verbose logging */
|
|
32
|
+
verbose?: boolean;
|
|
33
|
+
/** Auto-reconnect on failure */
|
|
34
|
+
autoReconnect?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Multi-Account Manager
|
|
38
|
+
*
|
|
39
|
+
* Manages multiple Signal accounts with event routing and lifecycle management.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* const manager = new MultiAccountManager({
|
|
44
|
+
* dataPath: '/path/to/data',
|
|
45
|
+
* autoReconnect: true
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* // Add accounts
|
|
49
|
+
* await manager.addAccount('+33123456789');
|
|
50
|
+
* await manager.addAccount('+33987654321');
|
|
51
|
+
*
|
|
52
|
+
* // Listen to events from all accounts
|
|
53
|
+
* manager.on('message', (account, message) => {
|
|
54
|
+
* console.log(`Message from ${account}: ${message.text}`);
|
|
55
|
+
* });
|
|
56
|
+
*
|
|
57
|
+
* // Connect all accounts
|
|
58
|
+
* await manager.connectAll();
|
|
59
|
+
*
|
|
60
|
+
* // Send from specific account
|
|
61
|
+
* await manager.sendMessage('+33123456789', '+33111111111', 'Hello!');
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare class MultiAccountManager extends EventEmitter {
|
|
65
|
+
private accounts;
|
|
66
|
+
private options;
|
|
67
|
+
private logger;
|
|
68
|
+
constructor(options?: MultiAccountOptions);
|
|
69
|
+
/**
|
|
70
|
+
* Add an account to the manager
|
|
71
|
+
*
|
|
72
|
+
* @param account - Phone number of the account
|
|
73
|
+
* @param config - Optional SignalCli configuration
|
|
74
|
+
* @returns The SignalCli instance
|
|
75
|
+
*/
|
|
76
|
+
addAccount(account: string, config?: Partial<SignalCliConfig>): Promise<SignalCli>;
|
|
77
|
+
/**
|
|
78
|
+
* Remove an account from the manager
|
|
79
|
+
*
|
|
80
|
+
* @param account - Phone number of the account
|
|
81
|
+
*/
|
|
82
|
+
removeAccount(account: string): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* Get a specific account instance
|
|
85
|
+
*
|
|
86
|
+
* @param account - Phone number of the account
|
|
87
|
+
* @returns The SignalCli instance
|
|
88
|
+
*/
|
|
89
|
+
getAccount(account: string): SignalCli | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Get all managed accounts
|
|
92
|
+
*
|
|
93
|
+
* @returns Array of account phone numbers
|
|
94
|
+
*/
|
|
95
|
+
getAccounts(): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Check if an account exists
|
|
98
|
+
*
|
|
99
|
+
* @param account - Phone number of the account
|
|
100
|
+
* @returns True if the account exists
|
|
101
|
+
*/
|
|
102
|
+
hasAccount(account: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Connect a specific account
|
|
105
|
+
*
|
|
106
|
+
* @param account - Phone number of the account
|
|
107
|
+
*/
|
|
108
|
+
connect(account: string): Promise<void>;
|
|
109
|
+
/**
|
|
110
|
+
* Disconnect a specific account
|
|
111
|
+
*
|
|
112
|
+
* @param account - Phone number of the account
|
|
113
|
+
*/
|
|
114
|
+
disconnect(account: string): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Connect all accounts
|
|
117
|
+
*/
|
|
118
|
+
connectAll(): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Disconnect all accounts
|
|
121
|
+
*/
|
|
122
|
+
disconnectAll(): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Send a message from a specific account
|
|
125
|
+
*
|
|
126
|
+
* @param fromAccount - Account to send from
|
|
127
|
+
* @param recipient - Recipient phone number or group ID
|
|
128
|
+
* @param message - Message text
|
|
129
|
+
* @param options - Send options
|
|
130
|
+
*/
|
|
131
|
+
sendMessage(fromAccount: string, recipient: string, message: string, options?: any): Promise<any>;
|
|
132
|
+
/**
|
|
133
|
+
* Get account status information
|
|
134
|
+
*
|
|
135
|
+
* @param account - Phone number of the account (optional)
|
|
136
|
+
* @returns Status information for all or specific account
|
|
137
|
+
*/
|
|
138
|
+
getStatus(account?: string): any;
|
|
139
|
+
/**
|
|
140
|
+
* Setup event forwarding from an account instance
|
|
141
|
+
*
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
private setupEventForwarding;
|
|
145
|
+
/**
|
|
146
|
+
* Shutdown the manager and cleanup all accounts
|
|
147
|
+
*/
|
|
148
|
+
shutdown(): Promise<void>;
|
|
149
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-Account Manager for Signal SDK
|
|
4
|
+
*
|
|
5
|
+
* Manages multiple Signal accounts simultaneously with event routing
|
|
6
|
+
* and isolated process management.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.MultiAccountManager = void 0;
|
|
10
|
+
const SignalCli_1 = require("./SignalCli");
|
|
11
|
+
const events_1 = require("events");
|
|
12
|
+
const config_1 = require("./config");
|
|
13
|
+
/**
|
|
14
|
+
* Multi-Account Manager
|
|
15
|
+
*
|
|
16
|
+
* Manages multiple Signal accounts with event routing and lifecycle management.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const manager = new MultiAccountManager({
|
|
21
|
+
* dataPath: '/path/to/data',
|
|
22
|
+
* autoReconnect: true
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Add accounts
|
|
26
|
+
* await manager.addAccount('+33123456789');
|
|
27
|
+
* await manager.addAccount('+33987654321');
|
|
28
|
+
*
|
|
29
|
+
* // Listen to events from all accounts
|
|
30
|
+
* manager.on('message', (account, message) => {
|
|
31
|
+
* console.log(`Message from ${account}: ${message.text}`);
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // Connect all accounts
|
|
35
|
+
* await manager.connectAll();
|
|
36
|
+
*
|
|
37
|
+
* // Send from specific account
|
|
38
|
+
* await manager.sendMessage('+33123456789', '+33111111111', 'Hello!');
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
class MultiAccountManager extends events_1.EventEmitter {
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
super();
|
|
44
|
+
this.accounts = new Map();
|
|
45
|
+
this.options = options;
|
|
46
|
+
this.logger = new config_1.Logger({
|
|
47
|
+
level: options.verbose ? 'debug' : 'info',
|
|
48
|
+
enableFile: false
|
|
49
|
+
});
|
|
50
|
+
this.logger.info('MultiAccountManager initialized');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Add an account to the manager
|
|
54
|
+
*
|
|
55
|
+
* @param account - Phone number of the account
|
|
56
|
+
* @param config - Optional SignalCli configuration
|
|
57
|
+
* @returns The SignalCli instance
|
|
58
|
+
*/
|
|
59
|
+
async addAccount(account, config = {}) {
|
|
60
|
+
if (this.accounts.has(account)) {
|
|
61
|
+
throw new Error(`Account ${account} already exists`);
|
|
62
|
+
}
|
|
63
|
+
this.logger.info(`Adding account: ${account}`);
|
|
64
|
+
// Create SignalCli instance with merged config
|
|
65
|
+
const signalConfig = {
|
|
66
|
+
signalCliPath: this.options.signalCliPath,
|
|
67
|
+
verbose: this.options.verbose,
|
|
68
|
+
...config
|
|
69
|
+
};
|
|
70
|
+
const instance = new SignalCli_1.SignalCli(account, undefined, signalConfig);
|
|
71
|
+
// Forward events from this instance
|
|
72
|
+
this.setupEventForwarding(account, instance);
|
|
73
|
+
// Store the managed account
|
|
74
|
+
const managedAccount = {
|
|
75
|
+
account,
|
|
76
|
+
instance,
|
|
77
|
+
connected: false,
|
|
78
|
+
lastActivity: Date.now()
|
|
79
|
+
};
|
|
80
|
+
this.accounts.set(account, managedAccount);
|
|
81
|
+
this.emit('accountAdded', account);
|
|
82
|
+
this.logger.info(`Account ${account} added successfully`);
|
|
83
|
+
return instance;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Remove an account from the manager
|
|
87
|
+
*
|
|
88
|
+
* @param account - Phone number of the account
|
|
89
|
+
*/
|
|
90
|
+
async removeAccount(account) {
|
|
91
|
+
const managedAccount = this.accounts.get(account);
|
|
92
|
+
if (!managedAccount) {
|
|
93
|
+
throw new Error(`Account ${account} not found`);
|
|
94
|
+
}
|
|
95
|
+
this.logger.info(`Removing account: ${account}`);
|
|
96
|
+
// Disconnect if connected
|
|
97
|
+
if (managedAccount.connected) {
|
|
98
|
+
await managedAccount.instance.disconnect();
|
|
99
|
+
}
|
|
100
|
+
// Remove all listeners
|
|
101
|
+
managedAccount.instance.removeAllListeners();
|
|
102
|
+
// Remove from map
|
|
103
|
+
this.accounts.delete(account);
|
|
104
|
+
this.emit('accountRemoved', account);
|
|
105
|
+
this.logger.info(`Account ${account} removed successfully`);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get a specific account instance
|
|
109
|
+
*
|
|
110
|
+
* @param account - Phone number of the account
|
|
111
|
+
* @returns The SignalCli instance
|
|
112
|
+
*/
|
|
113
|
+
getAccount(account) {
|
|
114
|
+
return this.accounts.get(account)?.instance;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all managed accounts
|
|
118
|
+
*
|
|
119
|
+
* @returns Array of account phone numbers
|
|
120
|
+
*/
|
|
121
|
+
getAccounts() {
|
|
122
|
+
return Array.from(this.accounts.keys());
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if an account exists
|
|
126
|
+
*
|
|
127
|
+
* @param account - Phone number of the account
|
|
128
|
+
* @returns True if the account exists
|
|
129
|
+
*/
|
|
130
|
+
hasAccount(account) {
|
|
131
|
+
return this.accounts.has(account);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Connect a specific account
|
|
135
|
+
*
|
|
136
|
+
* @param account - Phone number of the account
|
|
137
|
+
*/
|
|
138
|
+
async connect(account) {
|
|
139
|
+
const managedAccount = this.accounts.get(account);
|
|
140
|
+
if (!managedAccount) {
|
|
141
|
+
throw new Error(`Account ${account} not found`);
|
|
142
|
+
}
|
|
143
|
+
if (managedAccount.connected) {
|
|
144
|
+
this.logger.warn(`Account ${account} already connected`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
this.logger.info(`Connecting account: ${account}`);
|
|
148
|
+
try {
|
|
149
|
+
await managedAccount.instance.connect();
|
|
150
|
+
managedAccount.connected = true;
|
|
151
|
+
managedAccount.lastActivity = Date.now();
|
|
152
|
+
this.emit('accountConnected', account);
|
|
153
|
+
this.logger.info(`Account ${account} connected successfully`);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
this.logger.error(`Failed to connect account ${account}:`, error);
|
|
157
|
+
if (this.options.autoReconnect) {
|
|
158
|
+
this.logger.info(`Will retry connection for ${account}`);
|
|
159
|
+
setTimeout(() => this.connect(account), 5000);
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Disconnect a specific account
|
|
166
|
+
*
|
|
167
|
+
* @param account - Phone number of the account
|
|
168
|
+
*/
|
|
169
|
+
async disconnect(account) {
|
|
170
|
+
const managedAccount = this.accounts.get(account);
|
|
171
|
+
if (!managedAccount) {
|
|
172
|
+
throw new Error(`Account ${account} not found`);
|
|
173
|
+
}
|
|
174
|
+
if (!managedAccount.connected) {
|
|
175
|
+
this.logger.warn(`Account ${account} not connected`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
this.logger.info(`Disconnecting account: ${account}`);
|
|
179
|
+
await managedAccount.instance.disconnect();
|
|
180
|
+
managedAccount.connected = false;
|
|
181
|
+
this.emit('accountDisconnected', account);
|
|
182
|
+
this.logger.info(`Account ${account} disconnected`);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Connect all accounts
|
|
186
|
+
*/
|
|
187
|
+
async connectAll() {
|
|
188
|
+
this.logger.info('Connecting all accounts');
|
|
189
|
+
const promises = Array.from(this.accounts.keys()).map(account => this.connect(account).catch(error => {
|
|
190
|
+
this.logger.error(`Failed to connect ${account}:`, error);
|
|
191
|
+
}));
|
|
192
|
+
await Promise.all(promises);
|
|
193
|
+
this.logger.info('All accounts connected');
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Disconnect all accounts
|
|
197
|
+
*/
|
|
198
|
+
async disconnectAll() {
|
|
199
|
+
this.logger.info('Disconnecting all accounts');
|
|
200
|
+
const promises = Array.from(this.accounts.keys()).map(account => this.disconnect(account).catch(error => {
|
|
201
|
+
this.logger.error(`Failed to disconnect ${account}:`, error);
|
|
202
|
+
}));
|
|
203
|
+
await Promise.all(promises);
|
|
204
|
+
this.logger.info('All accounts disconnected');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Send a message from a specific account
|
|
208
|
+
*
|
|
209
|
+
* @param fromAccount - Account to send from
|
|
210
|
+
* @param recipient - Recipient phone number or group ID
|
|
211
|
+
* @param message - Message text
|
|
212
|
+
* @param options - Send options
|
|
213
|
+
*/
|
|
214
|
+
async sendMessage(fromAccount, recipient, message, options = {}) {
|
|
215
|
+
const managedAccount = this.accounts.get(fromAccount);
|
|
216
|
+
if (!managedAccount) {
|
|
217
|
+
throw new Error(`Account ${fromAccount} not found`);
|
|
218
|
+
}
|
|
219
|
+
managedAccount.lastActivity = Date.now();
|
|
220
|
+
return managedAccount.instance.sendMessage(recipient, message, options);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get account status information
|
|
224
|
+
*
|
|
225
|
+
* @param account - Phone number of the account (optional)
|
|
226
|
+
* @returns Status information for all or specific account
|
|
227
|
+
*/
|
|
228
|
+
getStatus(account) {
|
|
229
|
+
if (account) {
|
|
230
|
+
const managedAccount = this.accounts.get(account);
|
|
231
|
+
if (!managedAccount) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
account: managedAccount.account,
|
|
236
|
+
connected: managedAccount.connected,
|
|
237
|
+
lastActivity: managedAccount.lastActivity,
|
|
238
|
+
uptime: Date.now() - managedAccount.lastActivity
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// Return status for all accounts
|
|
242
|
+
const status = {
|
|
243
|
+
totalAccounts: this.accounts.size,
|
|
244
|
+
connectedAccounts: 0,
|
|
245
|
+
accounts: []
|
|
246
|
+
};
|
|
247
|
+
for (const [account, managed] of this.accounts) {
|
|
248
|
+
if (managed.connected) {
|
|
249
|
+
status.connectedAccounts++;
|
|
250
|
+
}
|
|
251
|
+
status.accounts.push({
|
|
252
|
+
account,
|
|
253
|
+
connected: managed.connected,
|
|
254
|
+
lastActivity: managed.lastActivity,
|
|
255
|
+
uptime: Date.now() - managed.lastActivity
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return status;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Setup event forwarding from an account instance
|
|
262
|
+
*
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
setupEventForwarding(account, instance) {
|
|
266
|
+
// Forward all events with account prefix
|
|
267
|
+
const events = [
|
|
268
|
+
'message',
|
|
269
|
+
'receipt',
|
|
270
|
+
'typing',
|
|
271
|
+
'reaction',
|
|
272
|
+
'error',
|
|
273
|
+
'connected',
|
|
274
|
+
'disconnected'
|
|
275
|
+
];
|
|
276
|
+
events.forEach(event => {
|
|
277
|
+
instance.on(event, (...args) => {
|
|
278
|
+
// Emit with account information
|
|
279
|
+
this.emit(event, account, ...args);
|
|
280
|
+
// Also emit a generic event with account
|
|
281
|
+
this.emit('accountEvent', {
|
|
282
|
+
account,
|
|
283
|
+
event,
|
|
284
|
+
data: args
|
|
285
|
+
});
|
|
286
|
+
// Update last activity
|
|
287
|
+
const managedAccount = this.accounts.get(account);
|
|
288
|
+
if (managedAccount) {
|
|
289
|
+
managedAccount.lastActivity = Date.now();
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
// Handle disconnection
|
|
294
|
+
instance.on('disconnected', () => {
|
|
295
|
+
const managedAccount = this.accounts.get(account);
|
|
296
|
+
if (managedAccount) {
|
|
297
|
+
managedAccount.connected = false;
|
|
298
|
+
}
|
|
299
|
+
// Auto-reconnect if enabled
|
|
300
|
+
if (this.options.autoReconnect) {
|
|
301
|
+
this.logger.info(`Auto-reconnecting account ${account}`);
|
|
302
|
+
setTimeout(() => this.connect(account), 5000);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Shutdown the manager and cleanup all accounts
|
|
308
|
+
*/
|
|
309
|
+
async shutdown() {
|
|
310
|
+
this.logger.info('Shutting down MultiAccountManager');
|
|
311
|
+
await this.disconnectAll();
|
|
312
|
+
// Remove all accounts
|
|
313
|
+
for (const account of this.accounts.keys()) {
|
|
314
|
+
await this.removeAccount(account);
|
|
315
|
+
}
|
|
316
|
+
this.removeAllListeners();
|
|
317
|
+
this.logger.info('MultiAccountManager shutdown complete');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
exports.MultiAccountManager = MultiAccountManager;
|
package/dist/SignalBot.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export declare class SignalBot extends EventEmitter {
|
|
|
12
12
|
private actionQueue;
|
|
13
13
|
private isProcessingQueue;
|
|
14
14
|
private incomingMessageBuffer;
|
|
15
|
+
private activeTimers;
|
|
15
16
|
constructor(config: BotConfig, signalCliPath?: string);
|
|
16
17
|
/**
|
|
17
18
|
* Downloads an image from URL to a temporary file
|
package/dist/SignalBot.js
CHANGED
|
@@ -50,6 +50,7 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
50
50
|
this.actionQueue = [];
|
|
51
51
|
this.isProcessingQueue = false;
|
|
52
52
|
this.incomingMessageBuffer = [];
|
|
53
|
+
this.activeTimers = [];
|
|
53
54
|
this.config = {
|
|
54
55
|
phoneNumber: config.phoneNumber,
|
|
55
56
|
admins: config.admins || [],
|
|
@@ -317,6 +318,9 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
317
318
|
async stop() {
|
|
318
319
|
this.log('- Stopping Signal Bot...');
|
|
319
320
|
this.isRunning = false;
|
|
321
|
+
// Clear all active timers
|
|
322
|
+
this.activeTimers.forEach(timer => clearTimeout(timer));
|
|
323
|
+
this.activeTimers = [];
|
|
320
324
|
this.signalCli.disconnect();
|
|
321
325
|
this.emit('stopped');
|
|
322
326
|
this.log('- Bot stopped');
|
|
@@ -324,6 +328,9 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
324
328
|
async gracefulShutdown() {
|
|
325
329
|
this.log('- Gracefully shutting down Signal Bot...');
|
|
326
330
|
this.isRunning = false;
|
|
331
|
+
// Clear all active timers
|
|
332
|
+
this.activeTimers.forEach(timer => clearTimeout(timer));
|
|
333
|
+
this.activeTimers = [];
|
|
327
334
|
try {
|
|
328
335
|
await this.signalCli.gracefulShutdown();
|
|
329
336
|
this.log('- Signal Bot shutdown completed gracefully');
|
|
@@ -725,11 +732,18 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
725
732
|
// Wait a bit for signal-cli to finish processing the files before cleanup
|
|
726
733
|
// signal-cli responds immediately but continues processing files in background
|
|
727
734
|
if (action.cleanup && action.cleanup.length > 0) {
|
|
728
|
-
setTimeout(() => {
|
|
735
|
+
const cleanupTimer = setTimeout(() => {
|
|
729
736
|
action.cleanup.forEach(filePath => {
|
|
730
737
|
this.cleanupTempFile(filePath);
|
|
731
738
|
});
|
|
739
|
+
// Remove timer from active list
|
|
740
|
+
const index = this.activeTimers.indexOf(cleanupTimer);
|
|
741
|
+
if (index > -1)
|
|
742
|
+
this.activeTimers.splice(index, 1);
|
|
732
743
|
}, 2000); // Wait 2 seconds for signal-cli to upload files
|
|
744
|
+
if (cleanupTimer.unref)
|
|
745
|
+
cleanupTimer.unref();
|
|
746
|
+
this.activeTimers.push(cleanupTimer);
|
|
733
747
|
}
|
|
734
748
|
break;
|
|
735
749
|
case 'sendReaction':
|
|
@@ -738,7 +752,11 @@ class SignalBot extends events_1.EventEmitter {
|
|
|
738
752
|
break;
|
|
739
753
|
}
|
|
740
754
|
// Wait a bit between actions to be safe
|
|
741
|
-
await new Promise(resolve =>
|
|
755
|
+
await new Promise(resolve => {
|
|
756
|
+
const timer = setTimeout(resolve, 250);
|
|
757
|
+
if (timer.unref)
|
|
758
|
+
timer.unref();
|
|
759
|
+
});
|
|
742
760
|
}
|
|
743
761
|
catch (error) {
|
|
744
762
|
this.log(`ERROR: Failed to execute action ${action.type}: ${error?.message || error}`, 'ERROR');
|