diff --git a/.gitignore b/.gitignore index 872efe5..384492b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /config /config.ts /docker-compose.yml +/db diff --git a/packages/bolt-matrix/deps.ts b/packages/bolt-matrix/deps.ts index dc688cb..cccf237 100644 --- a/packages/bolt-matrix/deps.ts +++ b/packages/bolt-matrix/deps.ts @@ -3,13 +3,15 @@ export { AppServiceRegistration, Bridge, Intent, + MatrixUser, Request, type ClientEncryptionSession, type WeakEvent } from 'npm:matrix-appservice-bridge@10.1.0'; export { Bolt, - BoltPlugin, - type BoltBridgeMessageArgs, - type BoltMessage + bolt_plugin, + type bridge_platform, + type message } from '../bolt/mod.ts'; +export { Buffer } from 'node:buffer'; diff --git a/packages/bolt-matrix/events.ts b/packages/bolt-matrix/events.ts index 94251e7..6bdd545 100644 --- a/packages/bolt-matrix/events.ts +++ b/packages/bolt-matrix/events.ts @@ -1,7 +1,10 @@ -import { BoltMessage, Intent, Request, WeakEvent } from './deps.ts'; -import MatrixPlugin from './mod.ts'; +import { message, Intent, Request, WeakEvent } from './deps.ts'; +import { matrix_plugin } from './mod.ts'; -export async function onEvent(this: MatrixPlugin, request: Request) { +export async function onEvent( + this: matrix_plugin, + request: Request +) { const event = request.getData(); const bot = this.bot.getBot(); const intent = this.bot.getIntent(); @@ -16,52 +19,64 @@ export async function onEvent(this: MatrixPlugin, request: Request) { this.emit('debug', `Failed to join room ${event.room_id}: ${e}`); } } - if (event.type === 'm.room.message' && !event['m.new_content']) { - this.emit('messageCreate', await messageToCore(event, intent)); + if (event.type === 'm.room.message' && !event.content['m.new_content']) { + this.emit( + 'create_message', + await messageToCore(event, intent, this.config.homeserverUrl) + ); } - if (event.type === 'm.room.message' && event['m.new_content']) { - this.emit('messageUpdate', await messageToCore(event, intent)); + if (event.type === 'm.room.message' && event.content['m.new_content']) { + this.emit( + 'edit_message', + await messageToCore(event, intent, this.config.homeserverUrl) + ); } if (event.type === 'm.room.redaction') { - this.emit('messageDelete', { + this.emit('delete_message', { id: event.redacts as string, platform: { name: 'bolt-matrix', message: event }, channel: event.room_id, - timestamp: event.origin_server_ts + timestamp: Temporal.Instant.fromEpochMilliseconds(event.origin_server_ts) }); } } export async function messageToCore( event: WeakEvent, - intent: Intent -): Promise> { + intent: Intent, + homeserverUrl: string +): Promise> { const sender = await intent.getProfileInfo(event.sender); return { author: { username: sender.displayname || event.sender, rawname: event.sender, id: event.sender, - profile: sender.avatar_url + profile: `${sender.avatar_url?.replace( + 'mxc://', + `${homeserverUrl}/_matrix/media/v3/thumbnail/` + )}?width=96&height=96&method=scale` }, channel: event.room_id, - id: event.event_id, - timestamp: event.origin_server_ts, - content: event.content.body as string, - reply: async (msg: BoltMessage) => { + id: + event.content['m.relates_to']?.rel_type == 'm.replace' + ? event.content['m.relates_to'].event_id + : event.event_id, + timestamp: Temporal.Instant.fromEpochMilliseconds(event.origin_server_ts), + content: (event.content['m.new_content']?.body || + event.content.body) as string, + reply: async (msg: message) => { await intent.sendMessage(event.room_id, coreToMessage(msg)); }, platform: { name: 'bolt-matrix', message: event } }; } -export function coreToMessage(msg: BoltMessage) { +export function coreToMessage(msg: message) { return { - content: { - body: msg.content - ? msg.content - : "*this bridge doesn't support anything except text at the moment*", - msgtype: 'm.text' - } + body: msg.content + ? msg.content + : "*this bridge doesn't support anything except text at the moment*", + msgtype: 'm.text' }; } diff --git a/packages/bolt-matrix/mod.ts b/packages/bolt-matrix/mod.ts index 7b3a13d..1158dc0 100644 --- a/packages/bolt-matrix/mod.ts +++ b/packages/bolt-matrix/mod.ts @@ -1,53 +1,36 @@ import { AppServiceRegistration, Bolt, - BoltBridgeMessageArgs, - BoltMessage, - BoltPlugin, Bridge, - ClientEncryptionSession, - existsSync + Buffer, + MatrixUser, + existsSync, + bolt_plugin, + message, + bridge_platform } from './deps.ts'; import { coreToMessage, onEvent } from './events.ts'; type MatrixConfig = { - accessToken: string; + appserviceUrl: string; homeserverUrl: string; domain: string; port?: number; reg_path: string; }; -export default class MatrixPlugin extends BoltPlugin { +export class matrix_plugin extends bolt_plugin { bot: Bridge; - config: MatrixConfig; - name = 'bolt-revolt'; - version = '0.5.4'; - bolt?: Bolt; - constructor(config: MatrixConfig) { - super(); - this.config = config; + name = 'bolt-matrix'; + version = '0.5.6'; + support = ['0.5.5']; + + constructor(bolt: Bolt, config: MatrixConfig) { + super(bolt, config); this.bot = new Bridge({ homeserverUrl: this.config.homeserverUrl, domain: this.config.domain, registration: this.config.reg_path, - bridgeEncryption: { - homeserverUrl: config.homeserverUrl, - store: { - getStoredSession: async (userId: string) => { - return JSON.parse( - (await this.bolt?.redis?.get(`mtx-session-${userId}`)) || 'null' - ); - }, - setStoredSession: async (session: ClientEncryptionSession) => { - await this.bolt?.redis?.set( - `mtx-session-${session.userId}`, - JSON.stringify(session) - ); - }, - async updateSyncToken() {} - } - }, controller: { onEvent: onEvent.bind(this) }, @@ -55,77 +38,102 @@ export default class MatrixPlugin extends BoltPlugin { userStore: './db/userStore.db', userActivityStore: './db/userActivityStore.db' }); - } - async start(bolt: Bolt) { - this.bolt = bolt; if (!existsSync(this.config.reg_path)) { - const reg = new AppServiceRegistration(this.config.homeserverUrl); + const reg = new AppServiceRegistration(this.config.appserviceUrl); reg.setAppServiceToken(AppServiceRegistration.generateToken()); reg.setHomeserverToken(AppServiceRegistration.generateToken()); - reg.setId( - 'b4d15f02f7e406db25563c1a74ac78863dc4fbcc5595db8d835f6ee6ffef1448' - ); + reg.setId(AppServiceRegistration.generateToken()); reg.setProtocols(['bolt']); reg.setRateLimited(false); - reg.setSenderLocalpart('boltbot'); - reg.addRegexPattern('users', '@bolt_*', true); + reg.setSenderLocalpart('bot.bolt'); + reg.addRegexPattern('users', `@bolt-.+_.+:${this.config.domain}`, true); reg.outputAsYaml(this.config.reg_path); } - await this.bot.run(this.config.port || 8081); + this.bot.run(this.config.port || 8081); } - bridgeSupport = { text: true }; + // deno-lint-ignore require-await - async createSenddata(channelId: string) { + async create_bridge(channelId: string) { return channelId; } - async bridgeMessage(data: BoltBridgeMessageArgs) { - const intent = this.bot.getIntent( - `${data.data.platform.name}_${ - 'author' in data.data ? data.data.author.id : 'deletion' - }` - ); - const room = data.data.bridgePlatform.senddata as string; - switch (data.type) { - case 'create': - case 'update': { - const message = coreToMessage( - data.data as unknown as BoltMessage - ); - let editinfo = {}; - if (data.type === 'update') { - editinfo = { - 'm.new_content': message, - 'm.relates_to': { - rel_type: 'm.replace', - event_id: data.data.id - } - }; + + is_bridged(_msg: message) { + // TODO: implement this + return true; + } + + async create_message( + msg: message, + platform: bridge_platform, + edit = false + ) { + const room = platform.senddata as string; + const name = `@${platform.plugin}_${msg.author.id}:${this.config.domain}`; + const intent = this.bot.getIntent(name); + // check for profile + await intent.ensureProfile(msg.author.username); + const store = this.bot.getUserStore(); + let storeUser = await store?.getMatrixUser(name); + if (!storeUser) { + storeUser = new MatrixUser(name); + } + if (storeUser?.get('avatar') != msg.author.profile) { + storeUser?.set('avatar', msg.author.profile); + const b = await (await fetch(msg.author.profile || '')).blob(); + const newMxc = await intent.uploadContent( + Buffer.from(await b.arrayBuffer()), + { type: b.type } + ); + await intent.ensureProfile(msg.author.username, newMxc); + await store?.setMatrixUser(storeUser); + } + // now to our message + const message = coreToMessage(msg); + let editinfo = {}; + if (edit) { + editinfo = { + 'm.new_content': message, + 'm.relates_to': { + rel_type: 'm.replace', + event_id: msg.id } - const result = await intent.sendMessage(room, { - ...message, - ...editinfo - }); - return { - channel: room, - id: result.event_id, - plugin: 'bolt-matrix', - senddata: room - }; - } - case 'delete': { - await intent.sendEvent(room, 'm.room.redaction', { - content: { - reason: 'bridge message deletion' - }, - redacts: data.data.id - }); - return { - channel: room, - id: data.data.id, - plugin: 'bolt-matrix', - senddata: room - }; - } + }; } + const result = await intent.sendMessage(room, { + ...message, + ...editinfo + }); + return { + channel: room, + id: result.event_id, + plugin: 'bolt-matrix', + senddata: room + }; + } + + async edit_message( + msg: message, + platform: bridge_platform & { id: string } + ) { + return await this.create_message(msg, platform, true); + } + + async delete_message( + _msg: message, + platform: bridge_platform & { id: string } + ) { + const room = platform.senddata as string; + const intent = this.bot.getIntent(); + await intent.botSdkIntent.underlyingClient.redactEvent( + room, + platform.id, + 'bridge message deletion' + ); + return { + channel: room, + id: platform.id, + plugin: 'bolt-matrix', + senddata: room + }; } } diff --git a/packages/bolt/utils/plugins.ts b/packages/bolt/utils/plugins.ts index bfee511..68831d9 100644 --- a/packages/bolt/utils/plugins.ts +++ b/packages/bolt/utils/plugins.ts @@ -54,6 +54,7 @@ export type plugin_events = { create_message: [message]; create_command: [command_arguments]; create_nonbridged_message: [message]; + debug: [unknown]; edit_message: [message]; delete_message: [deleted_message]; ready: [];