const { AutojoinRoomsMixin, MatrixClient, RustSdkCryptoStorageProvider, SimpleFsStorageProvider, } = require('matrix-bot-sdk'); const fastq = require('fastq'); const { appState, proposalEventIds } = require('./db'); const { MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, BOT_STORAGE_PATH, BOT_CRYPTO_STORAGE_PATH, BOT_INSTANCE_ID, ETH_NETWORK, } = process.env; const storageProvider = new SimpleFsStorageProvider(BOT_STORAGE_PATH); const cryptoProvider = new RustSdkCryptoStorageProvider(BOT_CRYPTO_STORAGE_PATH); let client; let joinedRooms; let targetRoomId; const processOutboundQueue = async ({ type, ...args }) => { if (!targetRoomId) return; switch (type) { case 'NewProposal': { const { proposalIndex, text } = args; try { await proposalEventIds.get(Number(proposalIndex)); } catch (e) { if (e.status === 404) { console.log('sending to room', targetRoomId, { text }); const eventId = await client.sendText(targetRoomId, text); await proposalEventIds.put(Number(proposalIndex), eventId); } } break; } default: } }; const outboundQueue = fastq(processOutboundQueue, 1); outboundQueue.pause(); const start = async () => { console.log('MATRIX_HOMESERVER_URL:', MATRIX_HOMESERVER_URL); client = new MatrixClient( MATRIX_HOMESERVER_URL, MATRIX_ACCESS_TOKEN, storageProvider, cryptoProvider, ); // Automatically join a room to which we are invited AutojoinRoomsMixin.setupOnClient(client); joinedRooms = await client.getJoinedRooms(); console.log('joined rooms:', joinedRooms); try { targetRoomId = await appState.get('targetRoomId'); } catch (e) { // Leave targetRoomId uninitialized for now } async function handleCommand(roomId, event) { // Don't handle unhelpful events (ones that aren't text messages, are redacted, or sent by us) if (event.content?.msgtype !== 'm.text') return; if (event.sender === await client.getUserId()) return; const helloRegex = /^!hello\b/i; const targetRegex = /^!target (.*)\b/i; const proposalRegex = /\bprop(|osal) ([0-9]+)\b/i; const { body } = event.content; if (helloRegex.test(body)) { console.log(`!hello roomId ${roomId}`); await client.replyNotice(roomId, event, 'Hello world!'); } else if (targetRegex.test(body)) { const [, instanceId] = targetRegex.exec(body); console.log(`!target roomId ${roomId} instanceId ${instanceId}`); if (instanceId === BOT_INSTANCE_ID) { targetRoomId = roomId; await appState.put('targetRoomId', targetRoomId); await client.replyNotice(roomId, event, `Proposal events will be sent to this room for network ${ETH_NETWORK}`); } } else if (proposalRegex.test(body)) { const [, , proposalIndexStr] = proposalRegex.exec(body); const proposalIndex = parseInt(proposalIndexStr, 10); console.log(`mention of proposal ${proposalIndex} in roomId ${roomId}`); try { const proposalEventId = await proposalEventIds.get(proposalIndex); const proposalEventUri = `https://matrix.to/#/${roomId}/${proposalEventId}`; await client.sendText(roomId, `Proposal ${proposalIndex}: ${proposalEventUri}`); } catch (e) { // Not found } } } // Before we start the bot, register our command handler client.on('room.message', handleCommand); client.start().then(() => { console.log('Bot started!'); outboundQueue.resume(); }); }; const sendNewProposalEvent = (proposalIndex, text) => { outboundQueue.push({ type: 'NewProposal', proposalIndex, text }); }; module.exports = { start, sendNewProposalEvent, };