refactor matrix.js into separate files
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 38s Details

This commit is contained in:
Ladd Hoffman 2024-04-27 13:37:16 -05:00
parent 17e8a559cf
commit f7bd1fc67b
5 changed files with 168 additions and 121 deletions

View File

@ -0,0 +1,62 @@
const { setTargetRoomId } = require('./outbound-queue');
const {
appState,
proposalEventIds,
} = require('../util/db');
const {
BOT_INSTANCE_ID,
ETH_NETWORK,
} = process.env;
const handleCommand = async (client, 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) {
setTargetRoomId(roomId);
await appState.put('targetRoomId', roomId);
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}`;
const content = {
body: `Proposal ${proposalIndex}: ${proposalEventUri}`,
msgtype: 'm.text',
};
if (event.content['m.relates_to']?.rel_type === 'm.thread') {
content['m.relates_to'] = event.content['m.relates_to'];
}
await client.sendEvent(roomId, 'm.room.message', content);
} catch (e) {
// Not found
}
}
};
const registerCommands = (client) => {
client.on('room.message', (roomId, event) => handleCommand(client, roomId, event));
};
module.exports = {
registerCommands,
};

View File

@ -4,53 +4,23 @@ const {
RustSdkCryptoStorageProvider,
SimpleFsStorageProvider,
} = require('matrix-bot-sdk');
const fastq = require('fastq');
const { recoverPersonalSignature } = require('@metamask/eth-sig-util');
const {
appState,
proposalEventIds,
matrixUserToAuthorAddress,
authorAddressToMatrixUser,
} = require('../util/db');
const { registerCommands } = require('./commands');
const { registerRoomEventHandler } = require('./room-events');
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 { outboundQueue, startOutboundQueue } = require('./outbound-queue');
const start = async () => {
console.log('MATRIX_HOMESERVER_URL:', MATRIX_HOMESERVER_URL);
@ -67,100 +37,16 @@ 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
}
const handleCommand = async (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}`;
const content = {
body: `Proposal ${proposalIndex}: ${proposalEventUri}`,
msgtype: 'm.text',
};
if (event.content['m.relates_to']?.rel_type === 'm.thread') {
content['m.relates_to'] = event.content['m.relates_to'];
}
await client.sendEvent(roomId, 'm.room.message', content);
} catch (e) {
// Not found
}
}
};
const handleRegisterIdentity = async (roomId, event) => {
const { message, signature } = event.content;
console.log('Received request to register identity');
let account;
try {
account = recoverPersonalSignature({ data: message, signature });
} catch (e) {
console.log('error: failed to recover signature:', e.message);
}
if (account) {
try {
const authorAddress = await matrixUserToAuthorAddress.get(event.sender);
if (account === authorAddress) {
await client.sendNotice(roomId, `Matrix user ${event.sender} already linked to author address ${account}`);
} else {
await client.sendNotice(roomId, `Matrix user ${event.sender} was linked to author address ${authorAddress}, now linked to ${account}`);
}
} catch (e) {
// Not found
await client.sendNotice(roomId, `Registered matrix user ${event.sender} to author address ${account}`);
}
await matrixUserToAuthorAddress.put(event.sender, account);
await authorAddressToMatrixUser.put(account, event.sender);
}
};
// Before we start the bot, register our command handler
client.on('room.message', handleCommand);
registerCommands(client);
// Handler for custom events
client.on('room.event', (roomId, event) => {
// Note that state events can also be sent down this listener too
if (event.state_key !== undefined) return; // state event
switch (event.type) {
case 'io.dgov.identity.register':
handleRegisterIdentity(roomId, event);
break;
default:
}
});
registerRoomEventHandler(client);
client.start().then(() => {
console.log('Bot started!');
outboundQueue.resume();
// Start the outbound queue
startOutboundQueue(client);
});
};

View File

@ -0,0 +1,49 @@
const fastq = require('fastq');
const {
proposalEventIds,
} = require('../util/db');
let client;
let targetRoomId;
const setTargetRoomId = (roomId) => {
targetRoomId = roomId;
};
const processOutboundQueue = async ({ type, ...args }) => {
if (!targetRoomId) return;
switch (type) {
case 'NewProposal': {
const { proposalIndex, text } = args;
try {
await proposalEventIds.get(Number(proposalIndex));
// If this doesn't throw, it means we already sent a message for this proposal
} 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);
// Pause until client is set
outboundQueue.pause();
const startOutboundQueue = (c) => {
client = c;
// Resume now that client is set
outboundQueue.resume();
};
module.exports = {
setTargetRoomId,
outboundQueue,
startOutboundQueue,
};

View File

@ -0,0 +1,50 @@
const { recoverPersonalSignature } = require('@metamask/eth-sig-util');
const {
matrixUserToAuthorAddress,
authorAddressToMatrixUser,
} = require('../util/db');
const handleRegisterIdentity = async (client, roomId, event) => {
const { message, signature } = event.content;
console.log('Received request to register identity');
let account;
try {
account = recoverPersonalSignature({ data: message, signature });
} catch (e) {
console.log('error: failed to recover signature:', e.message);
}
if (account) {
try {
const authorAddress = await matrixUserToAuthorAddress.get(event.sender);
if (account === authorAddress) {
await client.sendNotice(roomId, `Matrix user ${event.sender} author address ${account} already registered`);
} else {
await client.sendNotice(roomId, `Matrix user ${event.sender} updated author address from ${authorAddress} to ${account}`);
}
} catch (e) {
// Not found
await client.sendNotice(roomId, `Matrix user ${event.sender} registered author address ${account}`);
}
await matrixUserToAuthorAddress.put(event.sender, account);
await authorAddressToMatrixUser.put(account, event.sender);
}
};
const registerRoomEventHandler = (client) => {
client.on('room.event', (roomId, event) => {
// Note that state events can also be sent down this listener too
if (event.state_key !== undefined) return; // state event
switch (event.type) {
case 'io.dgov.identity.register':
handleRegisterIdentity(client, roomId, event);
break;
default:
}
});
};
module.exports = {
registerRoomEventHandler,
};