@towns-protocol/bot 0.0.413 → 0.0.414

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/dist/bot.test.js CHANGED
@@ -13,6 +13,7 @@ import { Hono } from 'hono';
13
13
  import { randomUUID } from 'crypto';
14
14
  import { getBalance, readContract, waitForTransactionReceipt } from 'viem/actions';
15
15
  import townsAppAbi from '@towns-protocol/generated/dev/abis/ITownsApp.abi';
16
+ import channelsFacetAbi from '@towns-protocol/generated/dev/abis/Channels.abi';
16
17
  import { parseEther } from 'viem';
17
18
  import { execute } from 'viem/experimental/erc7821';
18
19
  const log = dlog('test:bot');
@@ -22,6 +23,7 @@ const SLASH_COMMANDS = [
22
23
  { name: 'status', description: 'Check bot status' },
23
24
  ];
24
25
  describe('Bot', { sequential: true }, () => {
26
+ const subscriptions = [];
25
27
  const townsConfig = townsEnv().makeTownsConfig();
26
28
  const bob = new SyncAgentTest(undefined, townsConfig);
27
29
  const appRegistryDapp = new AppRegistryDapp(townsConfig.base.chainConfig, makeBaseProvider(townsConfig));
@@ -59,6 +61,10 @@ describe('Bot', { sequential: true }, () => {
59
61
  client.debugForceMakeMiniblock(channelId, { forceSnapshot: true }),
60
62
  ]));
61
63
  });
64
+ afterEach(() => {
65
+ subscriptions.forEach((unsub) => unsub());
66
+ subscriptions.splice(0, subscriptions.length);
67
+ });
62
68
  const setForwardSetting = async (forwardSetting) => {
63
69
  await appRegistryRpcClient.setAppSettings({
64
70
  appId: bin_fromHexString(botClientAddress),
@@ -117,6 +123,7 @@ describe('Bot', { sequential: true }, () => {
117
123
  const cryptoStore = RiverDbManager.getCryptoDb(appAddress);
118
124
  const botClient = new Client(signerContext, rpcClient, cryptoStore, new MockEntitlementsDelegate());
119
125
  await expect(botClient.initializeUser({ appAddress, skipSync: true })).resolves.toBeDefined();
126
+ await botClient.uploadDeviceKeys();
120
127
  await bobClient.riverConnection.call((client) => client.joinUser(spaceId, botClient.userId));
121
128
  await bobClient.riverConnection.call((client) => client.joinUser(channelId, botClient.userId));
122
129
  const addResult = await botClient.uploadDeviceKeys();
@@ -188,13 +195,34 @@ describe('Bot', { sequential: true }, () => {
188
195
  expect(joined.has(botClientAddress)).toBe(true);
189
196
  expect(joined.get(botClientAddress)?.appAddress).toBe(appAddress);
190
197
  });
198
+ it('should be entitled', async () => {
199
+ const spaceDapp = bobClient.riverConnection.spaceDapp;
200
+ const isInstalled = await spaceDapp.isAppInstalled(spaceId, bot.appAddress);
201
+ expect(isInstalled).toBe(true);
202
+ const isEntitledRead = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.Read);
203
+ const isEntitledWrite = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.Write);
204
+ const isEntitledReact = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.React);
205
+ const isEntitledModifyBanning = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.ModifyBanning);
206
+ const isEntitledModifySpaceSettings = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.ModifySpaceSettings);
207
+ const isEntitledRedact = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.Redact);
208
+ const isEntitledPinMessage = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.PinMessage);
209
+ const isEntitledAddRemove = await spaceDapp.isAppEntitled(spaceId, bot.botId, bot.appAddress, Permission.AddRemoveChannels);
210
+ expect(isEntitledRead).toBe(true);
211
+ expect(isEntitledWrite).toBe(true);
212
+ expect(isEntitledReact).toBe(true);
213
+ expect(isEntitledModifyBanning).toBe(true);
214
+ expect(isEntitledModifySpaceSettings).toBe(true);
215
+ expect(isEntitledRedact).toBe(true);
216
+ expect(isEntitledPinMessage).toBe(true);
217
+ expect(isEntitledAddRemove).toBe(true);
218
+ });
191
219
  it('should receive a message forwarded', async () => {
192
220
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
193
221
  const timeBeforeSendMessage = Date.now();
194
222
  let receivedMessages = [];
195
- bot.onMessage((_h, e) => {
223
+ subscriptions.push(bot.onMessage((_h, e) => {
196
224
  receivedMessages.push(e);
197
- });
225
+ }));
198
226
  const TEST_MESSAGE = 'Hello bot!';
199
227
  const { eventId } = await bobDefaultChannel.sendMessage(TEST_MESSAGE);
200
228
  await waitFor(() => receivedMessages.length > 0, { timeoutMS: 15_000 });
@@ -222,9 +250,9 @@ describe('Bot', { sequential: true }, () => {
222
250
  it('should not receive messages when forwarding is set to no messages', async () => {
223
251
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_NO_MESSAGES);
224
252
  const receivedMessages = [];
225
- bot.onMessage((_h, e) => {
253
+ subscriptions.push(bot.onMessage((_h, e) => {
226
254
  receivedMessages.push(e);
227
- });
255
+ }));
228
256
  const TEST_MESSAGE = 'This message should not be forwarded';
229
257
  await bobDefaultChannel.sendMessage(TEST_MESSAGE);
230
258
  await new Promise((resolve) => setTimeout(resolve, 2500));
@@ -233,9 +261,9 @@ describe('Bot', { sequential: true }, () => {
233
261
  it('should receive channel join event when alice joins the channel if bot is listening to channel join events', async () => {
234
262
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
235
263
  const receivedChannelJoinEvents = [];
236
- bot.onChannelJoin((_h, e) => {
264
+ subscriptions.push(bot.onChannelJoin((_h, e) => {
237
265
  receivedChannelJoinEvents.push(e);
238
- });
266
+ }));
239
267
  await aliceClient.spaces.joinSpace(spaceId, alice.signer);
240
268
  await waitFor(() => receivedChannelJoinEvents.length > 0);
241
269
  expect(receivedChannelJoinEvents.find((x) => x.userId === alice.userId)).toBeDefined();
@@ -249,9 +277,9 @@ describe('Bot', { sequential: true }, () => {
249
277
  it('should receive slash command messages', async () => {
250
278
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
251
279
  const receivedMessages = [];
252
- bot.onSlashCommand('help', (_h, e) => {
280
+ subscriptions.push(bot.onSlashCommand('help', (_h, e) => {
253
281
  receivedMessages.push(e);
254
- });
282
+ }));
255
283
  const { eventId } = await bobDefaultChannel.sendMessage('/help', {
256
284
  appClientAddress: bot.botId,
257
285
  });
@@ -263,9 +291,9 @@ describe('Bot', { sequential: true }, () => {
263
291
  it('should receive slash command in a thread', async () => {
264
292
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
265
293
  const receivedMessages = [];
266
- bot.onSlashCommand('help', (_h, e) => {
294
+ subscriptions.push(bot.onSlashCommand('help', (_h, e) => {
267
295
  receivedMessages.push(e);
268
- });
296
+ }));
269
297
  const { eventId: threadId } = await bobDefaultChannel.sendMessage('starting a thread');
270
298
  const { eventId } = await bobDefaultChannel.sendMessage('/help', {
271
299
  appClientAddress: bot.botId,
@@ -280,9 +308,9 @@ describe('Bot', { sequential: true }, () => {
280
308
  it('should receive slash command as a reply', async () => {
281
309
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
282
310
  const receivedMessages = [];
283
- bot.onSlashCommand('help', (_h, e) => {
311
+ subscriptions.push(bot.onSlashCommand('help', (_h, e) => {
284
312
  receivedMessages.push(e);
285
- });
313
+ }));
286
314
  const { eventId: replyId } = await bobDefaultChannel.sendMessage('yo');
287
315
  const { eventId } = await bobDefaultChannel.sendMessage('/help', {
288
316
  appClientAddress: bot.botId,
@@ -297,9 +325,9 @@ describe('Bot', { sequential: true }, () => {
297
325
  it('should receive slash command with arguments', async () => {
298
326
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
299
327
  const receivedMessages = [];
300
- bot.onSlashCommand('status', (_h, e) => {
328
+ subscriptions.push(bot.onSlashCommand('status', (_h, e) => {
301
329
  receivedMessages.push(e);
302
- });
330
+ }));
303
331
  const { eventId } = await bobDefaultChannel.sendMessage('/status detailed info', {
304
332
  appClientAddress: bot.botId,
305
333
  });
@@ -311,9 +339,9 @@ describe('Bot', { sequential: true }, () => {
311
339
  it('onMessageEdit should be triggered when a message is edited', async () => {
312
340
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
313
341
  const receivedEditEvents = [];
314
- bot.onMessageEdit((_h, e) => {
342
+ subscriptions.push(bot.onMessageEdit((_h, e) => {
315
343
  receivedEditEvents.push(e);
316
- });
344
+ }));
317
345
  const originalMessage = 'Original message to delete';
318
346
  const editedMessage = 'Edited message content';
319
347
  const { eventId: originalMessageId } = await bobDefaultChannel.sendMessage(originalMessage);
@@ -326,11 +354,11 @@ describe('Bot', { sequential: true }, () => {
326
354
  it('onMessage should be triggered with threadId when a message is sent in a thread', async () => {
327
355
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
328
356
  const receivedThreadMessages = [];
329
- bot.onMessage((_h, e) => {
357
+ subscriptions.push(bot.onMessage((_h, e) => {
330
358
  if (e.threadId) {
331
359
  receivedThreadMessages.push(e);
332
360
  }
333
- });
361
+ }));
334
362
  const initialMessage = 'Starting a thread';
335
363
  const threadReply = 'Replying in thread';
336
364
  const { eventId: initialMessageId } = await bobDefaultChannel.sendMessage(initialMessage);
@@ -347,11 +375,11 @@ describe('Bot', { sequential: true }, () => {
347
375
  it('onMessage should be triggered with isMentioned when a bot is mentioned', async () => {
348
376
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
349
377
  const receivedMentionedEvents = [];
350
- bot.onMessage((_h, e) => {
378
+ subscriptions.push(bot.onMessage((_h, e) => {
351
379
  if (e.isMentioned) {
352
380
  receivedMentionedEvents.push(e);
353
381
  }
354
- });
382
+ }));
355
383
  const TEST_MESSAGE = 'Hello @bot';
356
384
  const { eventId } = await bobDefaultChannel.sendMessage(TEST_MESSAGE, {
357
385
  mentions: [
@@ -372,9 +400,9 @@ describe('Bot', { sequential: true }, () => {
372
400
  it('isMentioned should be false when someone else is mentioned', async () => {
373
401
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
374
402
  const receivedMessages = [];
375
- bot.onMessage((_h, e) => {
403
+ subscriptions.push(bot.onMessage((_h, e) => {
376
404
  receivedMessages.push(e);
377
- });
405
+ }));
378
406
  const TEST_MESSAGE = 'Hello @alice';
379
407
  const { eventId } = await bobDefaultChannel.sendMessage(TEST_MESSAGE, {
380
408
  mentions: [
@@ -393,9 +421,9 @@ describe('Bot', { sequential: true }, () => {
393
421
  it('onMessage should be triggered with both threadId and isMentioned when bot is mentioned in a thread', async () => {
394
422
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
395
423
  const receivedMentionedInThreadEvents = [];
396
- bot.onMessage((_h, e) => {
424
+ subscriptions.push(bot.onMessage((_h, e) => {
397
425
  receivedMentionedInThreadEvents.push(e);
398
- });
426
+ }));
399
427
  const { eventId: initialMessageId } = await bobDefaultChannel.sendMessage('starting a thread');
400
428
  const { eventId: threadMentionEventId } = await bobDefaultChannel.sendMessage('yo @bot check this thread', {
401
429
  threadId: initialMessageId,
@@ -417,9 +445,9 @@ describe('Bot', { sequential: true }, () => {
417
445
  it('thread message without bot mention should have isMentioned false', async () => {
418
446
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
419
447
  const receivedMessages = [];
420
- bot.onMessage((_h, e) => {
448
+ subscriptions.push(bot.onMessage((_h, e) => {
421
449
  receivedMessages.push(e);
422
- });
450
+ }));
423
451
  const initialMessage = 'Starting another thread';
424
452
  const threadMessageWithoutMention = 'Thread message without mention';
425
453
  const { eventId: initialMessageId } = await bobDefaultChannel.sendMessage(initialMessage);
@@ -435,9 +463,9 @@ describe('Bot', { sequential: true }, () => {
435
463
  it('onReaction should be triggered when a reaction is added', async () => {
436
464
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
437
465
  const receivedReactionEvents = [];
438
- bot.onReaction((_h, e) => {
466
+ subscriptions.push(bot.onReaction((_h, e) => {
439
467
  receivedReactionEvents.push(e);
440
- });
468
+ }));
441
469
  const { eventId: messageId } = await bobClient.spaces
442
470
  .getSpace(spaceId)
443
471
  .getChannel(channelId)
@@ -452,9 +480,9 @@ describe('Bot', { sequential: true }, () => {
452
480
  it('onRedaction should be triggered when a message is redacted', async () => {
453
481
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
454
482
  const receivedRedactionEvents = [];
455
- bot.onRedaction((_h, e) => {
483
+ subscriptions.push(bot.onRedaction((_h, e) => {
456
484
  receivedRedactionEvents.push(e);
457
- });
485
+ }));
458
486
  const { eventId: messageId } = await bobDefaultChannel.sendMessage('Hello');
459
487
  const { eventId: redactionId } = await bobDefaultChannel.redact(messageId);
460
488
  await waitFor(() => receivedRedactionEvents.length > 0);
@@ -528,9 +556,9 @@ describe('Bot', { sequential: true }, () => {
528
556
  it('bot can redact other people messages', async () => {
529
557
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
530
558
  const messages = [];
531
- bot.onMessage((_h, e) => {
559
+ subscriptions.push(bot.onMessage((_h, e) => {
532
560
  messages.push(e);
533
- });
561
+ }));
534
562
  const { eventId: bobMessageId } = await bobDefaultChannel.sendMessage('Hello');
535
563
  await waitFor(() => expect(bobDefaultChannel.timeline.events.value.find((x) => x.eventId === bobMessageId)
536
564
  ?.content?.kind).toBe(RiverTimelineEvent.ChannelMessage));
@@ -544,9 +572,9 @@ describe('Bot', { sequential: true }, () => {
544
572
  it.skip('onMessage should be triggered with replyId when a message is replied to', async () => {
545
573
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_MENTIONS_REPLIES_REACTIONS);
546
574
  const receivedReplyEvents = [];
547
- bot.onMessage((_h, e) => {
575
+ subscriptions.push(bot.onMessage((_h, e) => {
548
576
  receivedReplyEvents.push(e);
549
- });
577
+ }));
550
578
  const { eventId: messageId } = await bot.sendMessage(channelId, 'hii');
551
579
  await waitFor(() => expect(bobDefaultChannel.timeline.events.value.find((x) => x.eventId === messageId)).toBeDefined());
552
580
  const { eventId: replyEventId } = await bobDefaultChannel.sendMessage('hi back', {
@@ -560,9 +588,9 @@ describe('Bot', { sequential: true }, () => {
560
588
  it('onTip should be triggered when a tip is received', async () => {
561
589
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
562
590
  const receivedTipEvents = [];
563
- bot.onTip((_h, e) => {
591
+ subscriptions.push(bot.onTip((_h, e) => {
564
592
  receivedTipEvents.push(e);
565
- });
593
+ }));
566
594
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
567
595
  const { eventId: messageId } = await bot.sendMessage(channelId, 'hii');
568
596
  const balanceBefore = (await ethersProvider.getBalance(appAddress)).toBigInt();
@@ -602,7 +630,7 @@ describe('Bot', { sequential: true }, () => {
602
630
  it('bot can use sendTip() to send tips using app balance', async () => {
603
631
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
604
632
  const receivedMessages = [];
605
- bot.onMessage(async (handler, event) => {
633
+ subscriptions.push(bot.onMessage(async (handler, event) => {
606
634
  const result = await handler.sendTip({
607
635
  userId: bob.userId,
608
636
  amount: ethers.utils.parseUnits('0.005').toBigInt(),
@@ -612,7 +640,7 @@ describe('Bot', { sequential: true }, () => {
612
640
  expect(result.txHash).toBeDefined();
613
641
  expect(result.eventId).toBeDefined();
614
642
  receivedMessages.push(event);
615
- });
643
+ }));
616
644
  const bobBalanceBefore = (await ethersProvider.getBalance(bob.userId)).toBigInt();
617
645
  // Bob sends a message asking for a tip
618
646
  const { eventId: bobMessageId } = await bobDefaultChannel.sendMessage('Tip me please!');
@@ -624,9 +652,9 @@ describe('Bot', { sequential: true }, () => {
624
652
  it('onEventRevoke (FORWARD_SETTING_ALL_MESSAGES) should be triggered when a message is revoked', async () => {
625
653
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
626
654
  const receivedEventRevokeEvents = [];
627
- bot.onEventRevoke((_h, e) => {
655
+ subscriptions.push(bot.onEventRevoke((_h, e) => {
628
656
  receivedEventRevokeEvents.push(e);
629
- });
657
+ }));
630
658
  const { eventId: messageId } = await bot.sendMessage(channelId, 'hii');
631
659
  await bobDefaultChannel.adminRedact(messageId);
632
660
  await waitFor(() => receivedEventRevokeEvents.length > 0);
@@ -635,9 +663,9 @@ describe('Bot', { sequential: true }, () => {
635
663
  it.fails('onEventRevoke (FORWARD_SETTING_MENTIONS_REPLIES_REACTIONS) should be triggered when a message that mentions the bot is revoked', async () => {
636
664
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_MENTIONS_REPLIES_REACTIONS);
637
665
  const receivedEventRevokeEvents = [];
638
- bot.onEventRevoke((_h, e) => {
666
+ subscriptions.push(bot.onEventRevoke((_h, e) => {
639
667
  receivedEventRevokeEvents.push(e);
640
- });
668
+ }));
641
669
  const { eventId: messageId } = await bobDefaultChannel.sendMessage('hii @bot', {
642
670
  mentions: [
643
671
  {
@@ -656,9 +684,9 @@ describe('Bot', { sequential: true }, () => {
656
684
  await appRegistryDapp.uninstallApp(bob.signer, appAddress, SpaceAddressFromSpaceId(spaceId));
657
685
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
658
686
  const receivedMentionedEvents = [];
659
- bot.onMessage((_h, e) => {
687
+ subscriptions.push(bot.onMessage((_h, e) => {
660
688
  receivedMentionedEvents.push(e);
661
- });
689
+ }));
662
690
  const TEST_MESSAGE = 'wont be received';
663
691
  const { eventId } = await bobDefaultChannel.sendMessage(TEST_MESSAGE);
664
692
  await expect(waitFor(() => receivedMentionedEvents.length > 0)).rejects.toThrow();
@@ -865,9 +893,9 @@ describe('Bot', { sequential: true }, () => {
865
893
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
866
894
  const messageSchema = z.object({ text: z.string(), count: z.number() });
867
895
  const receivedGmEvents = [];
868
- bot.onGmMessage('test.typed.v1', messageSchema, (_h, e) => {
896
+ subscriptions.push(bot.onGmMessage('test.typed.v1', messageSchema, (_h, e) => {
869
897
  receivedGmEvents.push({ typeUrl: e.typeUrl, data: e.data });
870
- });
898
+ }));
871
899
  const testData = { text: 'Hello', count: 42 };
872
900
  // Bob sends the message so bot receives it (bot filters its own messages)
873
901
  const jsonString = superjsonStringify(testData);
@@ -910,12 +938,12 @@ describe('Bot', { sequential: true }, () => {
910
938
  const schema2 = z.object({ type: z.literal('type2'), text: z.string() });
911
939
  const receivedType1 = [];
912
940
  const receivedType2 = [];
913
- bot.onGmMessage('test.multi.type1', schema1, (_h, e) => {
941
+ subscriptions.push(bot.onGmMessage('test.multi.type1', schema1, (_h, e) => {
914
942
  receivedType1.push(e.data);
915
- });
916
- bot.onGmMessage('test.multi.type2', schema2, (_h, e) => {
943
+ }));
944
+ subscriptions.push(bot.onGmMessage('test.multi.type2', schema2, (_h, e) => {
917
945
  receivedType2.push(e.data);
918
- });
946
+ }));
919
947
  const data1 = { type: 'type1', value: 123 };
920
948
  const data2 = { type: 'type2', text: 'hello' };
921
949
  await bobClient.riverConnection.call((client) => client.sendChannelMessage_GM(channelId, {
@@ -937,9 +965,9 @@ describe('Bot', { sequential: true }, () => {
937
965
  it('should handle raw GM messages', async () => {
938
966
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
939
967
  const receivedMessages = [];
940
- bot.onRawGmMessage((_h, e) => {
968
+ subscriptions.push(bot.onRawGmMessage((_h, e) => {
941
969
  receivedMessages.push({ typeUrl: e.typeUrl, message: e.message });
942
- });
970
+ }));
943
971
  const message = new TextEncoder().encode('Hello, world!');
944
972
  await bobClient.riverConnection.call((client) => client.sendChannelMessage_GM(channelId, {
945
973
  content: {
@@ -984,9 +1012,9 @@ describe('Bot', { sequential: true }, () => {
984
1012
  it('should log error and continue processing if throws an error when handling an event', async () => {
985
1013
  const consoleErrorSpy = vi.spyOn(console, 'error');
986
1014
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
987
- expect(() => bot.onMessage(() => {
1015
+ subscriptions.push(bot.onMessage(() => {
988
1016
  throw new Error('test error');
989
- })).not.toThrow();
1017
+ }));
990
1018
  await bobDefaultChannel.sendMessage('lol');
991
1019
  await waitFor(() => consoleErrorSpy.mock.calls.length > 0);
992
1020
  expect(consoleErrorSpy.mock.calls[0][0]).toContain('[@towns-protocol/bot] Error while handling event');
@@ -1024,6 +1052,73 @@ describe('Bot', { sequential: true }, () => {
1024
1052
  ?.kind).toBe(RiverTimelineEvent.ChannelMessage);
1025
1053
  }, { timeoutMS: 20000 });
1026
1054
  });
1055
+ it('bot can create channel, channel has the role, bob joins and sends message, bot receives message', async () => {
1056
+ await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_ALL_MESSAGES);
1057
+ const streamEvents = [];
1058
+ const receivedMessages = [];
1059
+ subscriptions.push(bot.onStreamEvent((_h, e) => {
1060
+ log('stream event', e);
1061
+ streamEvents.push(e);
1062
+ }));
1063
+ subscriptions.push(bot.onMessage((_h, e) => {
1064
+ receivedMessages.push(e);
1065
+ }));
1066
+ const spaceDapp = bobClient.riverConnection.spaceDapp;
1067
+ const testNft1Address = await TestERC721.getContractAddress('TestNFT1');
1068
+ const ruleData = getNftRuleData(testNft1Address);
1069
+ const permissions = [Permission.Read, Permission.Write];
1070
+ const roleName = `TestRole for bot channel bot wallet`;
1071
+ // Bob creates a role with Read permission
1072
+ const txn = await spaceDapp.createRole(spaceId, roleName, permissions, [], ruleData, bob.signer);
1073
+ const { roleId, error: roleError } = await waitForRoleCreated(spaceDapp, spaceId, txn);
1074
+ expect(roleError).toBeUndefined();
1075
+ check(isDefined(roleId), 'roleId is defined');
1076
+ log('bob created role', roleId);
1077
+ // Bot creates a new channel
1078
+ const newChannelId = await bot.createChannel(spaceId, {
1079
+ name: `test-channel-with-role-bot`,
1080
+ description: `Channel with role created by bot with bot wallet`,
1081
+ });
1082
+ log(`bot created channel`, newChannelId);
1083
+ // Query the roles assigned to the channel
1084
+ const channelRoles = await readContract(bot.viem, {
1085
+ address: SpaceAddressFromSpaceId(spaceId),
1086
+ abi: channelsFacetAbi,
1087
+ functionName: 'getRolesByChannel',
1088
+ args: [
1089
+ newChannelId.startsWith('0x')
1090
+ ? newChannelId
1091
+ : `0x${newChannelId}`,
1092
+ ],
1093
+ });
1094
+ log('channel roles', channelRoles);
1095
+ // Verify the created role is included in the channel's roles
1096
+ expect(channelRoles).toContain(BigInt(roleId));
1097
+ // Bob joins the new channel
1098
+ await bobClient.riverConnection.call((client) => client.joinStream(newChannelId));
1099
+ // Bob gets the channel
1100
+ const bobNewChannel = bobClient.spaces.getSpace(spaceId).getChannel(newChannelId);
1101
+ await waitFor(() => bobNewChannel.value.status !== 'loading', { timeoutMS: 10000 });
1102
+ // Bob sends a message in the new channel
1103
+ const testMessage = `Hello bot in new channel`;
1104
+ log('bob sending message in new channel', {
1105
+ testMessage,
1106
+ botId: bot.botId,
1107
+ appAddress: bot.appAddress,
1108
+ bobUserId: bob.userId,
1109
+ });
1110
+ const { eventId } = await bobNewChannel.sendMessage(testMessage);
1111
+ log('bob sent message in new channel', eventId);
1112
+ await waitFor(() => streamEvents.length > 0, { timeoutMS: 15000 });
1113
+ await waitFor(() => expect(streamEvents.find((x) => x.eventId === eventId)).toBeDefined());
1114
+ // Wait for bot to receive the message
1115
+ await waitFor(() => receivedMessages.length > 0, { timeoutMS: 15000 });
1116
+ const receivedEvent = receivedMessages.find((x) => x.eventId === eventId);
1117
+ expect(receivedEvent).toBeDefined();
1118
+ expect(receivedEvent?.message).toBe(testMessage);
1119
+ expect(receivedEvent?.channelId).toBe(newChannelId);
1120
+ expect(receivedEvent?.userId).toBe(bobClient.userId);
1121
+ });
1027
1122
  it('bot should be able to send encrypted interaction request and user should send encrypted response', async () => {
1028
1123
  await setForwardSetting(ForwardSettingValue.FORWARD_SETTING_MENTIONS_REPLIES_REACTIONS);
1029
1124
  const requestId = randomUUID();
@@ -1074,9 +1169,9 @@ describe('Bot', { sequential: true }, () => {
1074
1169
  },
1075
1170
  };
1076
1171
  const receivedInteractionResponses = [];
1077
- bot.onInteractionResponse((_h, e) => {
1172
+ subscriptions.push(bot.onInteractionResponse((_h, e) => {
1078
1173
  receivedInteractionResponses.push(e.response);
1079
- });
1174
+ }));
1080
1175
  await bobClient.riverConnection.call(async (client) => {
1081
1176
  // from the client, to the channel, encrypted so that only the bot can read it
1082
1177
  return await client.sendInteractionResponse(channelId, recipient, interactionResponsePayload, encryptionDevice);
@@ -1143,9 +1238,9 @@ describe('Bot', { sequential: true }, () => {
1143
1238
  },
1144
1239
  };
1145
1240
  const receivedInteractionResponses = [];
1146
- bot.onInteractionResponse((_h, e) => {
1241
+ subscriptions.push(bot.onInteractionResponse((_h, e) => {
1147
1242
  receivedInteractionResponses.push(e.response);
1148
- });
1243
+ }));
1149
1244
  await bobClient.riverConnection.call(async (client) => {
1150
1245
  return await client.sendInteractionResponse(channelId, recipient, interactionResponsePayload, bot.getUserDevice());
1151
1246
  });