Skip to content

Commit

Permalink
implement most of the appservice code from scratch
Browse files Browse the repository at this point in the history
  • Loading branch information
williamhorning committed Jun 24, 2024
1 parent 39b1861 commit 5d2fab4
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 148 deletions.
100 changes: 100 additions & 0 deletions packages/bolt-matrix/appservice_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import type { TimelineEvents } from './deps.ts';
import type { matrix_user, matrix_config } from './matrix_types.ts';

export class appservice {
cfg: matrix_config;
request_id = 0;
user_store: Map<string, matrix_user>;

constructor(cfg: matrix_config, store: Map<string, matrix_user>) {
this.cfg = cfg;
this.user_store = store;
}

async request(
path: string,
method: string,
body?: Record<string, unknown> | Blob,
) {
return await (await fetch(
`${this.cfg.homeserver_url}/_matrix/client/${path}`,
{
method,
body: body
? body instanceof Blob ? body : JSON.stringify(body)
: undefined,
headers: {
'Authorization': `Bearer ${this.cfg.appservice_token}`,
},
},
)).json();
}

async ensure_user(localpart: string) {
if (this.user_store.has(localpart)) return;

if (
!(await this.request(
`v3/register/available?username=${
encodeURIComponent(localpart)
}`,
'GET',
)).available
) return;

const register_req = await this.request(
`v3/register`,
'POST',
{ username: localpart, type: 'm.login.application_service' },
);

if (register_req.user_id) return;

throw new Error(
`recieved ${register_req.errcode} when registering for ${localpart}`,
{ cause: register_req },
);
}

async redact_event(room_id: string, id: string) {
await this.request(
`v3/rooms/${room_id}/redact/${id}/${this.request_id++}`,
'PUT',
{
reason: 'bridge message deletion',
},
);
}

async join_room(room_id: string) {
await this.request(`v3/join/${room_id}`, 'POST');
}

async upload_content(blob: Blob) {
return (await this.request('v3/upload', 'POST', blob))
.content_uri as string;
}

async send_message(
room_id: string,
msg: TimelineEvents["m.room.message"],
user_id?: string,
) {
return (await this.request(
`v3/rooms/${room_id}/send/m.room.message/${this
.request_id++}?user_id=${
user_id ||
`@${this.cfg.homeserver_localpart}:${this.cfg.homeserver_domain}`
}`,
'PUT',
msg as unknown as Record<string, unknown>,
)).event_id as string;
}

async get_profile_info(user_id: string) {
return await this.request(
`v3/profile/${user_id}`,
'GET',
) as matrix_user;
}
}
11 changes: 2 additions & 9 deletions packages/bolt-matrix/deps.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
export { render } from 'jsr:@deno/gfm@0.8.2';
export { existsSync } from 'jsr:@std/fs@0.229.3/exists';
export { Buffer } from 'node:buffer';
export {
AppServiceRegistration,
Bridge,
Intent,
Request,
type WeakEvent,
MatrixUser
} from 'npm:matrix-appservice-bridge@10.1.0';
export {
type bridge_channel,
type lightning,
type message,
plugin,
} from '../lightning/mod.ts';
export { Hono } from 'jsr:@hono/hono@4.5.0-rc.1';
export type { TimelineEvents } from 'npm:matrix-js-sdk@33.1.0';
19 changes: 8 additions & 11 deletions packages/bolt-matrix/events.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,37 @@
import type { Request, WeakEvent } from './deps.ts';
import type { matrix_plugin } from './mod.ts';
import { to_lightning } from './to_lightning.ts';
import type { matrix_client_event } from './matrix_types.ts';

export async function onEvent(
export async function on_event(
this: matrix_plugin,
request: Request<WeakEvent>,
event: matrix_client_event
) {
const event = request.getData();
const bot = this.bot.getBot();
const intent = this.bot.getIntent();
if (
event.type === 'm.room.member' &&
event.content.membership === 'invite' &&
event.state_key === bot.getUserId()
event.state_key === `@${this.config.homeserver_localpart}:${this.config.homeserver_domain}`
) {
try {
await intent.join(event.room_id);
await this.bot.join_room(event.room_id);
} catch (e) {
console.debug(`Failed to join room ${event.room_id}: ${e}`);
}
}
if (event.type === 'm.room.message' && !event.content['m.new_content']) {
this.emit(
'create_message',
await to_lightning(event, intent, this.config.homeserverUrl),
await to_lightning(event, this.bot, this.config.homeserver_url),
);
}
if (event.type === 'm.room.message' && event.content['m.new_content']) {
this.emit(
'edit_message',
await to_lightning(event, intent, this.config.homeserverUrl),
await to_lightning(event, this.bot, this.config.homeserver_url),
);
}
if (event.type === 'm.room.redaction') {
this.emit('delete_message', {
id: event.redacts as string,
id: event.content.redacts as string,
plugin: 'bolt-matrix',
channel: event.room_id,
timestamp: Temporal.Instant.fromEpochMilliseconds(
Expand Down
55 changes: 55 additions & 0 deletions packages/bolt-matrix/matrix_server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Hono } from './deps.ts';
import type {
matrix_client_event,
matrix_user,
matrix_config
} from './matrix_types.ts';

export function start_matrix_server(
cfg: matrix_config,
store: Map<string, matrix_user>,
on_event: (e: matrix_client_event) => Promise<void>,
) {
const app = new Hono();
const processed_events = new Set<string>();

app.use('/_matrix/*', async (c, next) => {
if (c.req.header()['Authorization'] !== cfg.homeservice_token) {
return c.json({
errcode: 'M_FORBIDDEN',
error: 'for this route, you need to authenticate',
}, 403);
} else {
await next();
}
});

app.put('/_matrix/app/v1/transactions/:txn_id', async (c) => {
if (processed_events.has(c.req.param('txn_id'))) {
return c.json({}, 200);
}

const body = await c.req.json();

await on_event(body);

return c.json({}, 200);
});

app.post('/_matrix/app/v1/ping', (c) => {
return c.json({}, 200);
});

app.get('/_matrix/app/v1/users/:user_id', (c) => {
if (store.has(c.req.param('user_id'))) return c.json({});

return c.json({
'errcode': 'dev.williamhorning.lightning.notfound',
'error': "that user doesn't exist",
}, 404);
});

Deno.serve({
port: cfg.plugin_port,
}, app.fetch);
}
47 changes: 47 additions & 0 deletions packages/bolt-matrix/matrix_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export interface matrix_client_event {
content: Record<string, unknown>;
event_id: string;
origin_server_ts: number;
room_id: string;
sender: string;
state_key?: string;
type: string;
unsigned?: matrix_unsigned_data;
}

export interface matrix_unsigned_data {
age?: number;
membership?: string;
prev_content?: Record<string, unknown>;
redacted_because?: matrix_client_event;
transaction_id?: string;
}

export interface matrix_user {
display_name?: string;
avatar_url?: string;
avatar_mxc?: string;
}

export interface matrix_config {
/** token used to authenticate to the homeserver */
appservice_token: string;
/** token used to authenticate to the plugin */
homeservice_token: string;
/** id used to identify the appservice on the homeserver */
appservice_id: string;
/** the username of the bot on the homeserver */
homeserver_localpart: string;
/** the url the plugin's server is accessable from */
plugin_url: string;
/** the port the plugin listens on */
plugin_port: number;
/** the prefix for bridged users (such as `lightning-`) */
homeserver_prefix: string;
/** the path to store the registration file */
registration_file: string;
/** the url where the homeserver is at */
homeserver_url: string;
/** the domain users on the homeservers are associated with */
homeserver_domain: string;
}
28 changes: 0 additions & 28 deletions packages/bolt-matrix/matrix_user.ts

This file was deleted.

Loading

0 comments on commit 5d2fab4

Please sign in to comment.