Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Matrix bridge #46

Merged
merged 19 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/config
/config.ts
/docker-compose.yml
db
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
db
/db

2 changes: 2 additions & 0 deletions packages/bolt-matrix/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export {
AppServiceRegistration,
Bridge,
Intent,
MatrixUser,
Request,
type ClientEncryptionSession,
type WeakEvent
Expand All @@ -13,3 +14,4 @@ export {
type BoltBridgeMessageArgs,
type BoltMessage
} from '../bolt/mod.ts';
export { Buffer } from "https://deno.land/[email protected]/node/buffer.ts";
austinhuang0131 marked this conversation as resolved.
Show resolved Hide resolved
29 changes: 15 additions & 14 deletions packages/bolt-matrix/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ export async function onEvent(this: MatrixPlugin, request: Request<WeakEvent>) {
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('messageCreate', await messageToCore(event, intent, this.config.homeserverUrl));
austinhuang0131 marked this conversation as resolved.
Show resolved Hide resolved
}
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('messageUpdate', await messageToCore(event, intent, this.config.homeserverUrl));
austinhuang0131 marked this conversation as resolved.
Show resolved Hide resolved
}
if (event.type === 'm.room.redaction') {
this.emit('messageDelete', {
Expand All @@ -34,20 +34,23 @@ export async function onEvent(this: MatrixPlugin, request: Request<WeakEvent>) {

export async function messageToCore(
event: WeakEvent,
intent: Intent
intent: Intent,
homeserverUrl: String
austinhuang0131 marked this conversation as resolved.
Show resolved Hide resolved
): Promise<BoltMessage<WeakEvent>> {
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`
austinhuang0131 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
profile: `${sender.avatar_url.replace("mxc://", `${homeserverUrl}/_matrix/media/v3/thumbnail/`)}?width=96&height=96&method=scale`
profile: `${sender.avatar_url.replace("mxc://", `${homeserverUrl}/_matrix/media/v3/thumbnail/`)}?width=96&height=96&method=scale`,
color: '#0DBD8B'

},
channel: event.room_id,
id: event.event_id,
id: event.content['m.relates_to']?.rel_type == "m.replace"
? event.content['m.relates_to'].event_id
: event.event_id,
timestamp: event.origin_server_ts,
content: event.content.body as string,
content: (event.content['m.new_content']?.body || event.content.body) as string,
reply: async (msg: BoltMessage<unknown>) => {
await intent.sendMessage(event.room_id, coreToMessage(msg));
},
Expand All @@ -57,11 +60,9 @@ export async function messageToCore(

export function coreToMessage(msg: BoltMessage<unknown>) {
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'
};
}
70 changes: 33 additions & 37 deletions packages/bolt-matrix/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import {
BoltMessage,
BoltPlugin,
Bridge,
Buffer,
ClientEncryptionSession,
MatrixUser,
existsSync
} from './deps.ts';
import { coreToMessage, onEvent } from './events.ts';

type MatrixConfig = {
accessToken: string;
appserviceUrl: string;
williamhorning marked this conversation as resolved.
Show resolved Hide resolved
homeserverUrl: string;
domain: string;
port?: number;
Expand All @@ -21,7 +23,7 @@ type MatrixConfig = {
export default class MatrixPlugin extends BoltPlugin {
bot: Bridge;
config: MatrixConfig;
name = 'bolt-revolt';
name = 'bolt-matrix';
version = '0.5.4';
bolt?: Bolt;
constructor(config: MatrixConfig) {
Expand All @@ -31,23 +33,6 @@ export default class MatrixPlugin extends BoltPlugin {
homeserverUrl: this.config.homeserverUrl,
domain: this.config.domain,
registration: this.config.reg_path,
bridgeEncryption: {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to remove all the encryption code? I'd prefer it if bolt-matrix supported encrypted rooms but if there's a reason I guess that's fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because it doesn't launch on my end with it, i might revisit it in the future, but almost all matrix-appservice-${platform} don't have encryption

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah okay, i guess we could go back to that in the future then

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)
},
Expand All @@ -59,16 +44,14 @@ export default class MatrixPlugin extends BoltPlugin {
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());
austinhuang0131 marked this conversation as resolved.
Show resolved Hide resolved
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);
Expand All @@ -79,15 +62,30 @@ export default class MatrixPlugin extends BoltPlugin {
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 name = `@${data.data.platform.name}_${data.data.author.id}:${this.config.domain}`;
const intent = this.bot.getIntent(name);
// check for profile
await intent.ensureProfile(data.data.author.username);
const store = this.bot.getUserStore();
let storeUser = await store?.getMatrixUser(name);
if (!storeUser) {
storeUser = new MatrixUser(name);
}
if (storeUser?.get("avatar") != data.data.author.profile) {
storeUser?.set("avatar", data.data.author.profile);
let b = await (await fetch(data.data.author.profile)).blob();
const newMxc = await intent.uploadContent(
Buffer.from(await b.arrayBuffer()),
{ type: b.type }
);
await intent.ensureProfile(data.data.author.username, newMxc);
await store?.setMatrixUser(storeUser);
}
// now to our message
const message = coreToMessage(
data.data as unknown as BoltMessage<unknown>
);
Expand All @@ -113,15 +111,13 @@ export default class MatrixPlugin extends BoltPlugin {
};
}
case 'delete': {
await intent.sendEvent(room, 'm.room.redaction', {
content: {
reason: 'bridge message deletion'
},
redacts: data.data.id
});
const intent = this.bot.getIntent();
await intent.botSdkIntent.underlyingClient.redactEvent(
room, data.data.id, 'bridge message deletion'
);
return {
channel: room,
id: data.data.id,
id: data.data.id,
plugin: 'bolt-matrix',
senddata: room
};
Expand Down
2 changes: 1 addition & 1 deletion packages/bolt-revolt/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export async function messageToCore(
return {
author: {
username:
message.member?.nickname ||
message.member?.displayName ||
message.author?.username ||
`${message.authorId || 'unknown user'} on revolt`,
rawname:
Expand Down
7 changes: 4 additions & 3 deletions packages/bolt-revolt/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export default class RevoltPlugin extends BoltPlugin {
try {
const msg = await coreToMessage({ ...dat, replyto });
const result = data.type === 'update'
? (await channel.fetchMessage(dat.id)).edit(msg) // TODO
: channel.sendMessage(msg);
? await (await channel.fetchMessage(dat.id)).edit(msg) // TODO
: await channel.sendMessage(msg);
return {
channel: dat.channel,
id: 'id' in result ? result.id : result._id,
Expand All @@ -76,7 +76,8 @@ export default class RevoltPlugin extends BoltPlugin {
}
case 'delete': {
const channel = await this.bot.channels.fetch(data.data.channel);
await channel.deleteMessages([data.data.id]);
const msg = await channel.fetchMessage(data.data.id);
await msg.delete();
return { ...data.data.bridgePlatform, id: data.data.id };
}
}
Expand Down
5 changes: 2 additions & 3 deletions packages/bolt/lib/bolt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,14 @@ export class Bolt extends EventEmitter<BoltPluginEvents> {
private registerPluginEvents() {
// TODO: move all code below to bridge folder
this.on('messageCreate', async msg => {
if (await getBoltBridgedMessage(this, msg.id)) return;
if (await getBoltBridgedMessage(this, true, msg.id)) return;
bridgeBoltMessage(this, 'create', msg);
});
this.on('messageUpdate', async msg => {
if (await getBoltBridgedMessage(this, msg.id)) return;
if (await getBoltBridgedMessage(this, true, msg.id)) return;
bridgeBoltMessage(this, 'update', msg);
});
this.on('messageDelete', async msg => {
if (await getBoltBridgedMessage(this, msg.id)) return;
bridgeBoltMessage(this, 'delete', msg);
});
this.on('threadCreate', thread => bridgeBoltThread(this, 'create', thread));
Expand Down
6 changes: 3 additions & 3 deletions packages/bolt/lib/bridge/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export async function bridgeBoltMessage(
const platforms: (BoltBridgePlatform | BoltBridgeSentPlatform)[] | false =
type === 'create'
? bridge.platforms.filter(i => i.channel !== message.channel)
: await getBoltBridgedMessage(bolt, message.id);
: await getBoltBridgedMessage(bolt, false, message.id);

if (!platforms || platforms.length < 1) return;

Expand All @@ -46,7 +46,7 @@ export async function bridgeBoltMessage(
: (platform as BoltBridgeSentPlatform).thread?.id
: undefined;

const replyto = await getBoltBridgedMessage(bolt, message.replyto?.id);
const replyto = await getBoltBridgedMessage(bolt, false, message.replyto?.id);

if (bridge.settings?.realnames && 'author' in message) {
message.author.username = message.author.rawname;
Expand Down Expand Up @@ -110,7 +110,7 @@ export async function bridgeBoltMessage(
if (type !== 'delete') {
for (const i of data) {
// since this key is used to prevent echo, 15 sec expiry should be enough
await bolt.redis?.set(`message-${i.id}`, JSON.stringify(data), {
await bolt.redis?.set(`message-temp-${i.id}`, JSON.stringify(data), {
ex: 15
});
}
Expand Down
4 changes: 2 additions & 2 deletions packages/bolt/lib/bridge/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Bolt } from '../bolt.ts';
import { BoltBridgeDocument, BoltBridgeSentPlatform } from './types.ts';

export async function getBoltBridgedMessage(bolt: Bolt, id?: string) {
return JSON.parse((await bolt.redis?.get(`message-${id}`)) || 'false') as
export async function getBoltBridgedMessage(bolt: Bolt, bCheck: Boolean, id?: string) {
return JSON.parse((await bolt.redis?.get(`message${bCheck ? "-temp" : ""}-${id}`)) || 'false') as
| BoltBridgeSentPlatform[]
| false;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/bolt/lib/commands/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class BoltCommands {
this.bolt = bolt;
this.registerCommands(...defaultcommands);
bolt.on('messageCreate', async msg => {
if (await getBoltBridgedMessage(bolt, msg.id)) return;
if (await getBoltBridgedMessage(bolt, true, msg.id)) return;
if (msg.content?.startsWith('!bolt')) {
let [_, name, ...arg] = msg.content.split(' ');
this.runCommand({
Expand Down