signal-sdk 0.0.9 → 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 +175 -59
- package/dist/SignalCli.d.ts +72 -2
- package/dist/SignalCli.js +257 -1
- package/dist/__tests__/SignalBot.additional.test.d.ts +5 -0
- package/dist/__tests__/SignalBot.additional.test.js +333 -0
- package/dist/__tests__/SignalCli.integration.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.integration.test.js +218 -0
- package/dist/__tests__/SignalCli.methods.test.d.ts +5 -0
- package/dist/__tests__/SignalCli.methods.test.js +470 -0
- package/dist/__tests__/SignalCli.test.js +244 -0
- package/dist/__tests__/config.test.d.ts +5 -0
- package/dist/__tests__/config.test.js +252 -0
- package/dist/__tests__/errors.test.d.ts +5 -0
- package/dist/__tests__/errors.test.js +276 -0
- package/dist/__tests__/retry.test.d.ts +4 -0
- package/dist/__tests__/retry.test.js +123 -0
- package/dist/__tests__/validators.test.d.ts +4 -0
- package/dist/__tests__/validators.test.js +147 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.js +111 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +75 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/interfaces.d.ts +136 -1
- package/dist/interfaces.js +1 -1
- package/dist/retry.d.ts +56 -0
- package/dist/retry.js +135 -0
- package/dist/validators.d.ts +59 -0
- package/dist/validators.js +170 -0
- package/package.json +1 -1
package/dist/SignalCli.js
CHANGED
|
@@ -39,11 +39,27 @@ const uuid_1 = require("uuid");
|
|
|
39
39
|
const qrcodeTerminal = __importStar(require("qrcode-terminal"));
|
|
40
40
|
const events_1 = require("events");
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const validators_1 = require("./validators");
|
|
43
|
+
const retry_1 = require("./retry");
|
|
44
|
+
const config_1 = require("./config");
|
|
45
|
+
const errors_1 = require("./errors");
|
|
42
46
|
class SignalCli extends events_1.EventEmitter {
|
|
43
|
-
constructor(accountOrPath, account) {
|
|
47
|
+
constructor(accountOrPath, account, config = {}) {
|
|
44
48
|
super();
|
|
45
49
|
this.cliProcess = null;
|
|
46
50
|
this.requestPromises = new Map();
|
|
51
|
+
this.reconnectAttempts = 0;
|
|
52
|
+
this.maxReconnectAttempts = 5;
|
|
53
|
+
// Validate and merge configuration
|
|
54
|
+
this.config = (0, config_1.validateConfig)(config);
|
|
55
|
+
// Initialize logger
|
|
56
|
+
this.logger = new config_1.Logger({
|
|
57
|
+
level: this.config.verbose ? 'debug' : 'info',
|
|
58
|
+
enableFile: !!this.config.logFile,
|
|
59
|
+
filePath: this.config.logFile
|
|
60
|
+
});
|
|
61
|
+
// Initialize rate limiter
|
|
62
|
+
this.rateLimiter = new retry_1.RateLimiter(this.config.maxConcurrentRequests, this.config.minRequestInterval);
|
|
47
63
|
let signalCliPath;
|
|
48
64
|
let phoneNumber;
|
|
49
65
|
// Smart parameter detection
|
|
@@ -707,5 +723,245 @@ class SignalCli extends events_1.EventEmitter {
|
|
|
707
723
|
}
|
|
708
724
|
return this.sendMessage(recipient, message, sendOptions);
|
|
709
725
|
}
|
|
726
|
+
// ========== NEW METHODS FOR 100% signal-cli COMPATIBILITY ==========
|
|
727
|
+
/**
|
|
728
|
+
* Send a poll create message to a recipient or group.
|
|
729
|
+
* @param options Poll creation options
|
|
730
|
+
* @returns Send response with timestamp
|
|
731
|
+
*/
|
|
732
|
+
async sendPollCreate(options) {
|
|
733
|
+
this.logger.debug('Sending poll create', options);
|
|
734
|
+
(0, validators_1.validateMessage)(options.question, 500);
|
|
735
|
+
if (!options.options || options.options.length < 2) {
|
|
736
|
+
throw new errors_1.MessageError('Poll must have at least 2 options');
|
|
737
|
+
}
|
|
738
|
+
if (options.options.length > 10) {
|
|
739
|
+
throw new errors_1.MessageError('Poll cannot have more than 10 options');
|
|
740
|
+
}
|
|
741
|
+
const params = {
|
|
742
|
+
question: options.question,
|
|
743
|
+
options: options.options,
|
|
744
|
+
account: this.account
|
|
745
|
+
};
|
|
746
|
+
if (options.multiSelect !== undefined) {
|
|
747
|
+
params.multiSelect = options.multiSelect;
|
|
748
|
+
}
|
|
749
|
+
if (options.groupId) {
|
|
750
|
+
(0, validators_1.validateGroupId)(options.groupId);
|
|
751
|
+
params.groupId = options.groupId;
|
|
752
|
+
}
|
|
753
|
+
else if (options.recipients) {
|
|
754
|
+
params.recipients = options.recipients.map(r => {
|
|
755
|
+
(0, validators_1.validateRecipient)(r);
|
|
756
|
+
return r;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
throw new errors_1.MessageError('Must specify either recipients or groupId');
|
|
761
|
+
}
|
|
762
|
+
return this.sendJsonRpcRequest('sendPollCreate', params);
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Send a poll vote message to vote on a poll.
|
|
766
|
+
* @param recipient Recipient or group ID
|
|
767
|
+
* @param options Poll vote options
|
|
768
|
+
* @returns Send response with timestamp
|
|
769
|
+
*/
|
|
770
|
+
async sendPollVote(recipient, options) {
|
|
771
|
+
this.logger.debug('Sending poll vote', { recipient, options });
|
|
772
|
+
(0, validators_1.validateRecipient)(options.pollAuthor);
|
|
773
|
+
(0, validators_1.validateTimestamp)(options.pollTimestamp);
|
|
774
|
+
if (!options.optionIndexes || options.optionIndexes.length === 0) {
|
|
775
|
+
throw new errors_1.MessageError('Must specify at least one option to vote for');
|
|
776
|
+
}
|
|
777
|
+
const params = {
|
|
778
|
+
pollAuthor: options.pollAuthor,
|
|
779
|
+
pollTimestamp: options.pollTimestamp,
|
|
780
|
+
options: options.optionIndexes,
|
|
781
|
+
account: this.account
|
|
782
|
+
};
|
|
783
|
+
if (options.voteCount !== undefined) {
|
|
784
|
+
params.voteCount = options.voteCount;
|
|
785
|
+
}
|
|
786
|
+
if (this.isGroupId(recipient)) {
|
|
787
|
+
(0, validators_1.validateGroupId)(recipient);
|
|
788
|
+
params.groupId = recipient;
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
(0, validators_1.validateRecipient)(recipient);
|
|
792
|
+
params.recipient = recipient;
|
|
793
|
+
}
|
|
794
|
+
return this.sendJsonRpcRequest('sendPollVote', params);
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Send a poll terminate message to close a poll.
|
|
798
|
+
* @param recipient Recipient or group ID
|
|
799
|
+
* @param options Poll terminate options
|
|
800
|
+
* @returns Send response with timestamp
|
|
801
|
+
*/
|
|
802
|
+
async sendPollTerminate(recipient, options) {
|
|
803
|
+
this.logger.debug('Sending poll terminate', { recipient, options });
|
|
804
|
+
(0, validators_1.validateTimestamp)(options.pollTimestamp);
|
|
805
|
+
const params = {
|
|
806
|
+
pollTimestamp: options.pollTimestamp,
|
|
807
|
+
account: this.account
|
|
808
|
+
};
|
|
809
|
+
if (this.isGroupId(recipient)) {
|
|
810
|
+
(0, validators_1.validateGroupId)(recipient);
|
|
811
|
+
params.groupId = recipient;
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
(0, validators_1.validateRecipient)(recipient);
|
|
815
|
+
params.recipient = recipient;
|
|
816
|
+
}
|
|
817
|
+
return this.sendJsonRpcRequest('sendPollTerminate', params);
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Update account information (device name, username, privacy settings).
|
|
821
|
+
* @param options Account update options
|
|
822
|
+
* @returns Account update result
|
|
823
|
+
*/
|
|
824
|
+
async updateAccount(options) {
|
|
825
|
+
this.logger.debug('Updating account', options);
|
|
826
|
+
const params = { account: this.account };
|
|
827
|
+
if (options.deviceName) {
|
|
828
|
+
params.deviceName = options.deviceName;
|
|
829
|
+
}
|
|
830
|
+
if (options.username) {
|
|
831
|
+
params.username = options.username;
|
|
832
|
+
}
|
|
833
|
+
if (options.deleteUsername) {
|
|
834
|
+
params.deleteUsername = true;
|
|
835
|
+
}
|
|
836
|
+
if (options.unrestrictedUnidentifiedSender !== undefined) {
|
|
837
|
+
params.unrestrictedUnidentifiedSender = options.unrestrictedUnidentifiedSender;
|
|
838
|
+
}
|
|
839
|
+
if (options.discoverableByNumber !== undefined) {
|
|
840
|
+
params.discoverableByNumber = options.discoverableByNumber;
|
|
841
|
+
}
|
|
842
|
+
if (options.numberSharing !== undefined) {
|
|
843
|
+
params.numberSharing = options.numberSharing;
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
const result = await this.sendJsonRpcRequest('updateAccount', params);
|
|
847
|
+
return {
|
|
848
|
+
success: true,
|
|
849
|
+
username: result.username,
|
|
850
|
+
usernameLink: result.usernameLink
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
catch (error) {
|
|
854
|
+
return {
|
|
855
|
+
success: false,
|
|
856
|
+
error: error instanceof Error ? error.message : 'Unknown error'
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Get raw attachment data as base64 string.
|
|
862
|
+
* @param options Attachment retrieval options
|
|
863
|
+
* @returns Base64 encoded attachment data
|
|
864
|
+
*/
|
|
865
|
+
async getAttachment(options) {
|
|
866
|
+
this.logger.debug('Getting attachment', options);
|
|
867
|
+
if (!options.id) {
|
|
868
|
+
throw new errors_1.MessageError('Attachment ID is required');
|
|
869
|
+
}
|
|
870
|
+
const params = {
|
|
871
|
+
id: options.id,
|
|
872
|
+
account: this.account
|
|
873
|
+
};
|
|
874
|
+
if (options.groupId) {
|
|
875
|
+
(0, validators_1.validateGroupId)(options.groupId);
|
|
876
|
+
params.groupId = options.groupId;
|
|
877
|
+
}
|
|
878
|
+
else if (options.recipient) {
|
|
879
|
+
(0, validators_1.validateRecipient)(options.recipient);
|
|
880
|
+
params.recipient = options.recipient;
|
|
881
|
+
}
|
|
882
|
+
const result = await this.sendJsonRpcRequest('getAttachment', params);
|
|
883
|
+
return result.data || result;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Get raw avatar data as base64 string.
|
|
887
|
+
* @param options Avatar retrieval options
|
|
888
|
+
* @returns Base64 encoded avatar data
|
|
889
|
+
*/
|
|
890
|
+
async getAvatar(options) {
|
|
891
|
+
this.logger.debug('Getting avatar', options);
|
|
892
|
+
const params = { account: this.account };
|
|
893
|
+
if (options.contact) {
|
|
894
|
+
(0, validators_1.validateRecipient)(options.contact);
|
|
895
|
+
params.contact = options.contact;
|
|
896
|
+
}
|
|
897
|
+
else if (options.profile) {
|
|
898
|
+
(0, validators_1.validateRecipient)(options.profile);
|
|
899
|
+
params.profile = options.profile;
|
|
900
|
+
}
|
|
901
|
+
else if (options.groupId) {
|
|
902
|
+
(0, validators_1.validateGroupId)(options.groupId);
|
|
903
|
+
params.groupId = options.groupId;
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
throw new errors_1.MessageError('Must specify contact, profile, or groupId');
|
|
907
|
+
}
|
|
908
|
+
const result = await this.sendJsonRpcRequest('getAvatar', params);
|
|
909
|
+
return result.data || result;
|
|
910
|
+
}
|
|
911
|
+
/**
|
|
912
|
+
* Get raw sticker data as base64 string.
|
|
913
|
+
* @param options Sticker retrieval options
|
|
914
|
+
* @returns Base64 encoded sticker data
|
|
915
|
+
*/
|
|
916
|
+
async getSticker(options) {
|
|
917
|
+
this.logger.debug('Getting sticker', options);
|
|
918
|
+
if (!options.packId || !options.stickerId) {
|
|
919
|
+
throw new errors_1.MessageError('Pack ID and sticker ID are required');
|
|
920
|
+
}
|
|
921
|
+
const params = {
|
|
922
|
+
packId: options.packId,
|
|
923
|
+
stickerId: options.stickerId,
|
|
924
|
+
account: this.account
|
|
925
|
+
};
|
|
926
|
+
const result = await this.sendJsonRpcRequest('getSticker', params);
|
|
927
|
+
return result.data || result;
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* Send contacts synchronization message to linked devices.
|
|
931
|
+
* @param options Contacts sync options
|
|
932
|
+
*/
|
|
933
|
+
async sendContacts(options = {}) {
|
|
934
|
+
this.logger.debug('Sending contacts sync');
|
|
935
|
+
const params = { account: this.account };
|
|
936
|
+
if (options.includeAllRecipients) {
|
|
937
|
+
params.allRecipients = true;
|
|
938
|
+
}
|
|
939
|
+
await this.sendJsonRpcRequest('sendContacts', params);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* List groups with optional filtering and details.
|
|
943
|
+
* @param options List groups options
|
|
944
|
+
* @returns Array of group information
|
|
945
|
+
*/
|
|
946
|
+
async listGroupsDetailed(options = {}) {
|
|
947
|
+
this.logger.debug('Listing groups with options', options);
|
|
948
|
+
const params = { account: this.account };
|
|
949
|
+
if (options.detailed) {
|
|
950
|
+
params.detailed = true;
|
|
951
|
+
}
|
|
952
|
+
if (options.groupIds && options.groupIds.length > 0) {
|
|
953
|
+
params.groupId = options.groupIds;
|
|
954
|
+
}
|
|
955
|
+
return this.sendJsonRpcRequest('listGroups', params);
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* List all local accounts.
|
|
959
|
+
* @returns Array of account phone numbers
|
|
960
|
+
*/
|
|
961
|
+
async listAccountsDetailed() {
|
|
962
|
+
this.logger.debug('Listing all accounts');
|
|
963
|
+
const result = await this.sendJsonRpcRequest('listAccounts');
|
|
964
|
+
return result.accounts || [];
|
|
965
|
+
}
|
|
710
966
|
}
|
|
711
967
|
exports.SignalCli = SignalCli;
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Additional tests for SignalBot
|
|
4
|
+
* Covers message handling, commands, and edge cases
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const SignalBot_1 = require("../SignalBot");
|
|
8
|
+
describe('SignalBot Additional Tests', () => {
|
|
9
|
+
let bot;
|
|
10
|
+
let mockConfig;
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockConfig = {
|
|
13
|
+
phoneNumber: '+1234567890',
|
|
14
|
+
admins: ['+0987654321'],
|
|
15
|
+
settings: {
|
|
16
|
+
commandPrefix: '/',
|
|
17
|
+
autoReact: true,
|
|
18
|
+
logMessages: true,
|
|
19
|
+
welcomeNewMembers: true,
|
|
20
|
+
cooldownSeconds: 2,
|
|
21
|
+
maxMessageLength: 1000
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
if (bot) {
|
|
27
|
+
await bot.stop();
|
|
28
|
+
// Wait for any pending async operations to complete
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
describe('Command Management', () => {
|
|
33
|
+
test('should add custom command and execute it', () => {
|
|
34
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
35
|
+
const customHandler = jest.fn().mockResolvedValue('Custom response');
|
|
36
|
+
bot.addCommand({
|
|
37
|
+
name: 'custom',
|
|
38
|
+
description: 'Custom command',
|
|
39
|
+
handler: customHandler
|
|
40
|
+
});
|
|
41
|
+
const commands = bot.getCommands();
|
|
42
|
+
expect(commands.find(c => c.name === 'custom')).toBeDefined();
|
|
43
|
+
});
|
|
44
|
+
test('should remove command successfully', () => {
|
|
45
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
46
|
+
bot.addCommand({
|
|
47
|
+
name: 'temp',
|
|
48
|
+
description: 'Temporary command',
|
|
49
|
+
handler: async () => 'temp'
|
|
50
|
+
});
|
|
51
|
+
expect(bot.removeCommand('temp')).toBe(true);
|
|
52
|
+
expect(bot.removeCommand('nonexistent')).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
test('should prevent duplicate commands', () => {
|
|
55
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
56
|
+
bot.addCommand({
|
|
57
|
+
name: 'test',
|
|
58
|
+
description: 'Test command',
|
|
59
|
+
handler: async () => 'test1'
|
|
60
|
+
});
|
|
61
|
+
bot.addCommand({
|
|
62
|
+
name: 'test',
|
|
63
|
+
description: 'Test command 2',
|
|
64
|
+
handler: async () => 'test2'
|
|
65
|
+
});
|
|
66
|
+
const commands = bot.getCommands();
|
|
67
|
+
const testCommands = commands.filter(c => c.name === 'test');
|
|
68
|
+
expect(testCommands.length).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('Admin Management', () => {
|
|
72
|
+
test('should identify admin correctly', () => {
|
|
73
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
74
|
+
expect(bot.isAdmin('+0987654321')).toBe(true);
|
|
75
|
+
expect(bot.isAdmin('+1111111111')).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
describe('Statistics', () => {
|
|
79
|
+
test('should track bot statistics', () => {
|
|
80
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
81
|
+
const stats = bot.getStats();
|
|
82
|
+
expect(stats).toHaveProperty('messagesReceived');
|
|
83
|
+
expect(stats).toHaveProperty('commandsExecuted');
|
|
84
|
+
expect(stats).toHaveProperty('startTime');
|
|
85
|
+
expect(stats).toHaveProperty('lastActivity');
|
|
86
|
+
expect(stats.messagesReceived).toBe(0);
|
|
87
|
+
expect(stats.commandsExecuted).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
describe('Group Management', () => {
|
|
91
|
+
test('should get bot group ID', () => {
|
|
92
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
93
|
+
const groupId = bot.getBotGroupId();
|
|
94
|
+
expect(groupId).toBeNull(); // Not set until bot starts
|
|
95
|
+
});
|
|
96
|
+
test('should create bot without group config', () => {
|
|
97
|
+
bot = new SignalBot_1.SignalBot({
|
|
98
|
+
phoneNumber: '+1234567890'
|
|
99
|
+
});
|
|
100
|
+
expect(bot).toBeDefined();
|
|
101
|
+
expect(bot.getBotGroupId()).toBeNull();
|
|
102
|
+
});
|
|
103
|
+
test('should create bot with group config', () => {
|
|
104
|
+
bot = new SignalBot_1.SignalBot({
|
|
105
|
+
phoneNumber: '+1234567890',
|
|
106
|
+
group: {
|
|
107
|
+
name: 'Test Group',
|
|
108
|
+
description: 'Test Description',
|
|
109
|
+
createIfNotExists: true,
|
|
110
|
+
initialMembers: ['+1111111111', '+2222222222']
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
expect(bot).toBeDefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('Message Sending', () => {
|
|
117
|
+
test('should queue message for sending', async () => {
|
|
118
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
119
|
+
const mockSignalCli = bot.getSignalCli();
|
|
120
|
+
const sendMessageSpy = jest.spyOn(mockSignalCli, 'sendMessage').mockResolvedValue({
|
|
121
|
+
results: [{ type: 'SUCCESS' }],
|
|
122
|
+
timestamp: Date.now()
|
|
123
|
+
});
|
|
124
|
+
await bot.sendMessage('+1111111111', 'Test message');
|
|
125
|
+
// Wait for queue to process
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
127
|
+
expect(sendMessageSpy).toHaveBeenCalledWith('+1111111111', 'Test message');
|
|
128
|
+
});
|
|
129
|
+
test('should queue reaction for sending', async () => {
|
|
130
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
131
|
+
const mockSignalCli = bot.getSignalCli();
|
|
132
|
+
const sendReactionSpy = jest.spyOn(mockSignalCli, 'sendReaction').mockResolvedValue({
|
|
133
|
+
results: [{ type: 'SUCCESS' }],
|
|
134
|
+
timestamp: Date.now()
|
|
135
|
+
});
|
|
136
|
+
await bot.sendReaction('+1111111111', '+2222222222', 123456, '👍');
|
|
137
|
+
// Wait for queue to process
|
|
138
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
139
|
+
expect(sendReactionSpy).toHaveBeenCalledWith('+1111111111', '+2222222222', 123456, '👍');
|
|
140
|
+
});
|
|
141
|
+
test('should handle message with attachments', async () => {
|
|
142
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
143
|
+
const mockSignalCli = bot.getSignalCli();
|
|
144
|
+
const sendMessageSpy = jest.spyOn(mockSignalCli, 'sendMessage').mockResolvedValue({
|
|
145
|
+
results: [{ type: 'SUCCESS' }],
|
|
146
|
+
timestamp: Date.now()
|
|
147
|
+
});
|
|
148
|
+
await bot.sendMessageWithAttachment('+1111111111', 'Message with files', ['file1.txt', 'file2.jpg']);
|
|
149
|
+
// Wait for queue to process
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 150));
|
|
151
|
+
expect(sendMessageSpy).toHaveBeenCalledWith('+1111111111', 'Message with files', {
|
|
152
|
+
attachments: ['file1.txt', 'file2.jpg']
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
describe('Configuration', () => {
|
|
157
|
+
test('should use default settings when not provided', () => {
|
|
158
|
+
bot = new SignalBot_1.SignalBot({
|
|
159
|
+
phoneNumber: '+1234567890'
|
|
160
|
+
});
|
|
161
|
+
const commands = bot.getCommands();
|
|
162
|
+
expect(commands.length).toBeGreaterThan(0); // Should have default commands
|
|
163
|
+
});
|
|
164
|
+
test('should use custom command prefix', () => {
|
|
165
|
+
bot = new SignalBot_1.SignalBot({
|
|
166
|
+
phoneNumber: '+1234567890',
|
|
167
|
+
settings: {
|
|
168
|
+
commandPrefix: '!'
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
expect(bot).toBeDefined();
|
|
172
|
+
});
|
|
173
|
+
test('should handle all settings', () => {
|
|
174
|
+
bot = new SignalBot_1.SignalBot({
|
|
175
|
+
phoneNumber: '+1234567890',
|
|
176
|
+
admins: ['+1111111111'],
|
|
177
|
+
settings: {
|
|
178
|
+
commandPrefix: '$',
|
|
179
|
+
autoReact: false,
|
|
180
|
+
logMessages: false,
|
|
181
|
+
welcomeNewMembers: false,
|
|
182
|
+
cooldownSeconds: 5,
|
|
183
|
+
maxMessageLength: 2000
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
expect(bot).toBeDefined();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('Signal CLI Access', () => {
|
|
190
|
+
test('should provide access to underlying SignalCli', () => {
|
|
191
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
192
|
+
const signalCli = bot.getSignalCli();
|
|
193
|
+
expect(signalCli).toBeDefined();
|
|
194
|
+
expect(signalCli.connect).toBeDefined();
|
|
195
|
+
expect(signalCli.disconnect).toBeDefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe('Bot State', () => {
|
|
199
|
+
test('should track running state', () => {
|
|
200
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
201
|
+
// Bot is not running initially
|
|
202
|
+
expect(bot.getStats().startTime).toBeDefined();
|
|
203
|
+
});
|
|
204
|
+
test('should handle stop when not running', async () => {
|
|
205
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
206
|
+
await expect(bot.stop()).resolves.toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
describe('Image Download Feature', () => {
|
|
210
|
+
test('should have downloadImageFromUrl method', () => {
|
|
211
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
212
|
+
expect(bot.downloadImageFromUrl).toBeDefined();
|
|
213
|
+
expect(typeof bot.downloadImageFromUrl).toBe('function');
|
|
214
|
+
});
|
|
215
|
+
test('should have sendMessageWithImage method', () => {
|
|
216
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
217
|
+
expect(bot.sendMessageWithImage).toBeDefined();
|
|
218
|
+
expect(typeof bot.sendMessageWithImage).toBe('function');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
describe('Event Emitters', () => {
|
|
222
|
+
test('should emit ready event structure', (done) => {
|
|
223
|
+
bot = new SignalBot_1.SignalBot({
|
|
224
|
+
phoneNumber: '+1234567890',
|
|
225
|
+
group: {
|
|
226
|
+
name: 'Test Group',
|
|
227
|
+
createIfNotExists: false
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
const mockSignalCli = bot.getSignalCli();
|
|
231
|
+
jest.spyOn(mockSignalCli, 'listDevices').mockResolvedValue([
|
|
232
|
+
{ id: 1, name: 'Test Device', created: Date.now(), lastSeen: Date.now() }
|
|
233
|
+
]);
|
|
234
|
+
jest.spyOn(mockSignalCli, 'connect').mockResolvedValue(undefined);
|
|
235
|
+
jest.spyOn(mockSignalCli, 'on').mockReturnValue(mockSignalCli);
|
|
236
|
+
bot.on('ready', () => {
|
|
237
|
+
done();
|
|
238
|
+
});
|
|
239
|
+
bot.start().catch(done);
|
|
240
|
+
});
|
|
241
|
+
test('should have stopped event', async () => {
|
|
242
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
243
|
+
const stoppedHandler = jest.fn();
|
|
244
|
+
bot.on('stopped', stoppedHandler);
|
|
245
|
+
await bot.stop();
|
|
246
|
+
expect(stoppedHandler).toHaveBeenCalled();
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
describe('Default Commands', () => {
|
|
250
|
+
test('should include help command', () => {
|
|
251
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
252
|
+
const commands = bot.getCommands();
|
|
253
|
+
const helpCmd = commands.find(c => c.name === 'help');
|
|
254
|
+
expect(helpCmd).toBeDefined();
|
|
255
|
+
expect(helpCmd?.description).toBeDefined();
|
|
256
|
+
});
|
|
257
|
+
test('should include stats command', () => {
|
|
258
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
259
|
+
const commands = bot.getCommands();
|
|
260
|
+
const statsCmd = commands.find(c => c.name === 'stats');
|
|
261
|
+
expect(statsCmd).toBeDefined();
|
|
262
|
+
expect(statsCmd?.description).toBeDefined();
|
|
263
|
+
});
|
|
264
|
+
test('should include ping command', () => {
|
|
265
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
266
|
+
const commands = bot.getCommands();
|
|
267
|
+
const pingCmd = commands.find(c => c.name === 'ping');
|
|
268
|
+
expect(pingCmd).toBeDefined();
|
|
269
|
+
expect(pingCmd?.description).toBeDefined();
|
|
270
|
+
});
|
|
271
|
+
test('should include info command', () => {
|
|
272
|
+
bot = new SignalBot_1.SignalBot(mockConfig);
|
|
273
|
+
const commands = bot.getCommands();
|
|
274
|
+
const infoCmd = commands.find(c => c.name === 'info');
|
|
275
|
+
expect(infoCmd).toBeDefined();
|
|
276
|
+
expect(infoCmd?.description).toBeDefined();
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
describe('Group Configuration with Avatar', () => {
|
|
280
|
+
test('should handle group config with avatar URL', () => {
|
|
281
|
+
bot = new SignalBot_1.SignalBot({
|
|
282
|
+
phoneNumber: '+1234567890',
|
|
283
|
+
group: {
|
|
284
|
+
name: 'Test Group',
|
|
285
|
+
description: 'Test Description',
|
|
286
|
+
createIfNotExists: true,
|
|
287
|
+
initialMembers: ['+1111111111'],
|
|
288
|
+
avatar: 'https://example.com/avatar.jpg'
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
expect(bot).toBeDefined();
|
|
292
|
+
});
|
|
293
|
+
test('should handle group config with local avatar path', () => {
|
|
294
|
+
bot = new SignalBot_1.SignalBot({
|
|
295
|
+
phoneNumber: '+1234567890',
|
|
296
|
+
group: {
|
|
297
|
+
name: 'Test Group',
|
|
298
|
+
description: 'Test Description',
|
|
299
|
+
createIfNotExists: true,
|
|
300
|
+
initialMembers: ['+1111111111'],
|
|
301
|
+
avatar: '/path/to/local/avatar.jpg'
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
expect(bot).toBeDefined();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
describe('Edge Cases', () => {
|
|
308
|
+
test('should handle empty admins list', () => {
|
|
309
|
+
bot = new SignalBot_1.SignalBot({
|
|
310
|
+
phoneNumber: '+1234567890',
|
|
311
|
+
admins: []
|
|
312
|
+
});
|
|
313
|
+
expect(bot.isAdmin('+1234567890')).toBe(false);
|
|
314
|
+
});
|
|
315
|
+
test('should handle undefined settings', () => {
|
|
316
|
+
bot = new SignalBot_1.SignalBot({
|
|
317
|
+
phoneNumber: '+1234567890',
|
|
318
|
+
settings: undefined
|
|
319
|
+
});
|
|
320
|
+
expect(bot).toBeDefined();
|
|
321
|
+
});
|
|
322
|
+
test('should handle partial settings', () => {
|
|
323
|
+
bot = new SignalBot_1.SignalBot({
|
|
324
|
+
phoneNumber: '+1234567890',
|
|
325
|
+
settings: {
|
|
326
|
+
commandPrefix: '!'
|
|
327
|
+
// Other settings will use defaults
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
expect(bot).toBeDefined();
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
});
|