87 lines
3.2 KiB
TypeScript
87 lines
3.2 KiB
TypeScript
import { test } from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import { MatrixEvent, Room } from 'matrix-js-sdk';
|
||
|
|
import {
|
||
|
|
describeRoomBridge,
|
||
|
|
getBridgedUserNetwork,
|
||
|
|
getRoomBridges,
|
||
|
|
parseBridgeEvent,
|
||
|
|
} from './bridge';
|
||
|
|
|
||
|
|
const bridgeEvent = (type: string, stateKey: string, content: Record<string, unknown>) =>
|
||
|
|
new MatrixEvent({ type, state_key: stateKey, content, room_id: '!r:x', sender: '@discordbot:x' });
|
||
|
|
|
||
|
|
// Shape of what mautrix-discord 0.7.7 sends for a guild channel.
|
||
|
|
const discordContent = {
|
||
|
|
bridgebot: '@discordbot:matrix.lotusguild.org',
|
||
|
|
protocol: { id: 'discordgo', displayname: 'Discord' },
|
||
|
|
channel: {
|
||
|
|
id: '1565159372122882070',
|
||
|
|
displayname: '#general',
|
||
|
|
external_url: 'https://discord.com/channels/1/1565159372122882070',
|
||
|
|
},
|
||
|
|
network: { id: '1', displayname: 'Lotus Guild' },
|
||
|
|
};
|
||
|
|
|
||
|
|
const roomWithState = (events: MatrixEvent[]): Room =>
|
||
|
|
({
|
||
|
|
getLiveTimeline: () => ({
|
||
|
|
getState: () => ({
|
||
|
|
getStateEvents: (type: string) => events.filter((e) => e.getType() === type),
|
||
|
|
}),
|
||
|
|
}),
|
||
|
|
}) as unknown as Room;
|
||
|
|
|
||
|
|
test('Discord ghosts are recognised; the bridge bot and lookalikes are not', () => {
|
||
|
|
assert.equal(
|
||
|
|
getBridgedUserNetwork('@discord_123456789012345678:matrix.lotusguild.org'),
|
||
|
|
'discord',
|
||
|
|
);
|
||
|
|
assert.equal(getBridgedUserNetwork('@discordbot:matrix.lotusguild.org'), undefined);
|
||
|
|
assert.equal(getBridgedUserNetwork('@discord_fan:matrix.lotusguild.org'), undefined);
|
||
|
|
assert.equal(getBridgedUserNetwork('@discord-relay:matrix.lotusguild.org'), undefined);
|
||
|
|
assert.equal(getBridgedUserNetwork('@jared:matrix.lotusguild.org'), undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('parseBridgeEvent reads a mautrix-discord m.bridge event', () => {
|
||
|
|
assert.deepEqual(parseBridgeEvent(bridgeEvent('m.bridge', 'k', discordContent)), {
|
||
|
|
network: 'discord',
|
||
|
|
channelName: '#general',
|
||
|
|
networkName: 'Lotus Guild',
|
||
|
|
externalUrl: 'https://discord.com/channels/1/1565159372122882070',
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('parseBridgeEvent ignores unknown protocols and empty content', () => {
|
||
|
|
assert.equal(
|
||
|
|
parseBridgeEvent(bridgeEvent('m.bridge', 'k', { protocol: { id: 'telegram' } })),
|
||
|
|
undefined,
|
||
|
|
);
|
||
|
|
assert.equal(parseBridgeEvent(bridgeEvent('m.bridge', 'k', {})), undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('only http(s) channel links are kept', () => {
|
||
|
|
const content = {
|
||
|
|
...discordContent,
|
||
|
|
channel: { displayname: '#x', external_url: ['javascript', 'alert(1)'].join(':') },
|
||
|
|
};
|
||
|
|
assert.equal(parseBridgeEvent(bridgeEvent('m.bridge', 'k', content))?.externalUrl, undefined);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('getRoomBridges merges m.bridge with its unstable copy, one entry per channel', () => {
|
||
|
|
const room = roomWithState([
|
||
|
|
bridgeEvent('m.bridge', 'fi.mau.discord://discord/1/2', discordContent),
|
||
|
|
bridgeEvent('uk.half-shot.bridge', 'fi.mau.discord://discord/1/2', discordContent),
|
||
|
|
]);
|
||
|
|
assert.equal(getRoomBridges(room).length, 1);
|
||
|
|
assert.equal(getRoomBridges(roomWithState([])).length, 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('describeRoomBridge degrades gracefully without names', () => {
|
||
|
|
assert.equal(
|
||
|
|
describeRoomBridge({ network: 'discord', channelName: '#general', networkName: 'Lotus Guild' }),
|
||
|
|
'Bridged to #general in Lotus Guild on Discord',
|
||
|
|
);
|
||
|
|
assert.equal(describeRoomBridge({ network: 'discord' }), 'Bridged to Discord');
|
||
|
|
});
|