improvements to proposal event forwarding to matrix, and other bot features
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 33s Details

This commit is contained in:
Ladd Hoffman 2024-04-24 13:55:10 -05:00
parent d8df672a80
commit d3bc08dd24
4 changed files with 69 additions and 18 deletions

View File

@ -8,4 +8,5 @@ MATRIX_HOMESERVER_URL="https://matrix.dgov.io"
MATRIX_USER="forum-api"
MATRIX_PASSWORD=
BOT_STORAGE_PATH="./data/bot-storage.json"
BOT_CRYPTO_STORAGE_PATH="./data/bot-crypto"
BOT_CRYPTO_STORAGE_PATH="./data/bot-crypto"
BOT_INSTANCE_ID=

View File

@ -6,4 +6,7 @@ module.exports = {
forum: new Level(`${dataDir}/forum`, { valueEncoding: 'json' }),
authorAddresses: new Level(`${dataDir}/authorAddresses`, { valueEncoding: 'utf8' }),
authorPrivKeys: new Level(`${dataDir}/authorPrivKeys`, { valueEncoding: 'utf8' }),
appState: new Level(`${dataDir}/appState`, { valueEncoding: 'json' }),
proposalEventIds: new Level(`${dataDir}/proposalEventIds`, { keyEncoding: 'json', valueEncoding: 'utf8' }),
referendumEventIds: new Level(`${dataDir}/referendumEventIds`, { keyEncoding: 'json', valueEncoding: 'utf8' }),
};

View File

@ -6,7 +6,8 @@ const {
SimpleFsStorageProvider,
} = require('matrix-bot-sdk');
const fastq = require('fastq');
const Promise = require('bluebird');
const { appState, proposalEventIds } = require('./db');
const {
MATRIX_HOMESERVER_URL,
@ -14,18 +15,36 @@ const {
MATRIX_PASSWORD,
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;
const processOutboundQueue = async ({ text }) => {
console.log('broadcasting', { text });
await Promise.each(joinedRooms, async (roomId) => {
await client.sendText(roomId, text);
});
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();
@ -46,18 +65,46 @@ const start = async () => {
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;
// Check to ensure that the `!hello` command is being run
const { body } = event.content;
if (!body?.startsWith('!hello')) return;
const helloRegex = /^!hello\b/i;
const targetRegex = /^!target (.*)\b/i;
const proposalRegex = /\bprop(|osal) ([0-9]+)\b/i;
// Now that we've passed all the checks, we can actually act upon the command
console.log(`!hello roomId ${roomId}`);
await client.replyNotice(roomId, event, 'Hello world!');
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
@ -69,11 +116,11 @@ const start = async () => {
});
};
const broadcastMessage = (text) => {
outboundQueue.push({ text });
const sendNewProposalEvent = (proposalIndex, text) => {
outboundQueue.push({ type: 'NewProposal', proposalIndex, text });
};
module.exports = {
start,
broadcastMessage,
sendNewProposalEvent,
};

View File

@ -1,6 +1,6 @@
const { proposals } = require('./contracts');
const read = require('./read');
const { broadcastMessage } = require('./matrix');
const { sendNewProposalEvent } = require('./matrix');
// Subscribe to proposal events
const start = () => {
@ -20,7 +20,7 @@ const start = () => {
if (post.embeddedData && Object.entries(post.embeddedData).length) {
message += `\n\n${JSON.stringify(post.embeddedData, null, 2)}`;
}
broadcastMessage(message);
sendNewProposalEvent(proposalIndex, message);
});
};