From 32806f4470ebb50c3965fb92ca102f8d8a5a37e7 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 7 Jan 2020 11:25:56 +0000 Subject: [PATCH 1/9] Initial work on MSC2346 --- src/bridge/BridgeStateSyncer.ts | 40 +++++++++++++++++++++++++++++++++ src/bridge/IrcBridge.ts | 17 +++++++++++++- src/config/BridgeConfig.ts | 4 ++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/bridge/BridgeStateSyncer.ts diff --git a/src/bridge/BridgeStateSyncer.ts b/src/bridge/BridgeStateSyncer.ts new file mode 100644 index 000000000..7b66fc31e --- /dev/null +++ b/src/bridge/BridgeStateSyncer.ts @@ -0,0 +1,40 @@ +import { DataStore } from "../datastore/DataStore"; +import { QueuePool } from "../util/QueuePool"; +import { Bridge } from "matrix-appservice-bridge"; + +const SYNC_INTERVAL = 1500; +const SYNC_CONCURRENCY = 3; +const TYPE = "uk.half-shot.bridge"; + +interface QueueItem { + roomId: string; + mappings: Array<{networkId: string; channel: string}> +} + +/** + * This class will set bridge room state according to [MSC2346](https://github.com/matrix-org/matrix-doc/pull/2346) + */ +export class BridgeStateSyncer { + private syncQueue: QueuePool; + constructor(private datastore: DataStore, private bridge: Bridge) { + this.syncQueue = new QueuePool(SYNC_CONCURRENCY, this.syncRoom.bind(this)); + } + + public async beginSync() { + const mappings = this.datastore.getAllChannelMappings(); + } + + public async syncRoom(item: QueueItem) { + const intent = this.bridge.getIntent(); + for (const mapping of item.mappings) { + const key = BridgeStateSyncer.createStateKey(mapping.networkId, mapping.channel); + const eventData = await intent.getStateEvent(item.roomId, TYPE, key); + } + } + + public static createStateKey(networkId: string, channel: string) { + networkId = networkId.replace(/\//g, "%2F"); + channel = channel.replace(/\//g, "%2F"); + return `org.matrix.appservice-irc://irc/${networkId}/${channel}` + } +} diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index f29b6b56c..8fe31d402 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -36,7 +36,7 @@ import { DataStore } from "../datastore/DataStore"; import { MatrixAction } from "../models/MatrixAction"; import { BridgeConfig } from "../config/BridgeConfig"; import { MembershipQueue } from "../util/MembershipQueue"; - +import { BridgeStateSyncer } from "./BridgeStateSyncer"; const log = getLogger("IrcBridge"); const DEFAULT_PORT = 8090; @@ -67,7 +67,12 @@ export class IrcBridge { remote_request_seconds: Histogram; }|null = null; private membershipCache: MembershipCache; +<<<<<<< HEAD private readonly membershipQueue: MembershipQueue; +======= + private bridgeStateSyncer!: BridgeStateSyncer; + +>>>>>>> Initial work on MSC2346 constructor(public readonly config: BridgeConfig, private registration: AppServiceRegistration) { // TODO: Don't log this to stdout Logging.configure({console: config.ircService.logging.level}); @@ -414,6 +419,16 @@ export class IrcBridge { for (const roomId of this.joinedRoomList) { this.membershipCache.setMemberEntry(roomId, this.appServiceUserId, "join"); } + if (this.config.ircService.bridgeInfoState?.enabled) { + this.bridgeStateSyncer = new BridgeStateSyncer(this.dataStore, this.bridge); + if (this.config.ircService.bridgeInfoState.syncExisting) { + this.bridgeStateSyncer.beginSync().then(() => { + log.info("Bridge state syncing completed"); + }).catch((err) => { + log.error("Bridge state syncing resulted in an error:", err); + }); + } + } log.info("Joining mapped Matrix rooms..."); await this.joinMappedMatrixRooms(); diff --git a/src/config/BridgeConfig.ts b/src/config/BridgeConfig.ts index 0a6d78475..7db4f5c1d 100644 --- a/src/config/BridgeConfig.ts +++ b/src/config/BridgeConfig.ts @@ -49,6 +49,10 @@ export interface BridgeConfig { hostname: string; port: number; }; + bridgeInfoState?: { + enabled: boolean; + syncExisting: boolean; + }; }; sentry?: { enabled: boolean; From 904bc257bceef2887d54010cf7d626d045bfdf26 Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 7 Jan 2020 13:24:57 +0000 Subject: [PATCH 2/9] Add bridge state feature --- src/bridge/BridgeStateSyncer.ts | 100 ++++++++++++++++++++++++++++++-- src/bridge/IrcBridge.ts | 4 +- src/config/BridgeConfig.ts | 2 +- 3 files changed, 98 insertions(+), 8 deletions(-) diff --git a/src/bridge/BridgeStateSyncer.ts b/src/bridge/BridgeStateSyncer.ts index 7b66fc31e..65740e575 100644 --- a/src/bridge/BridgeStateSyncer.ts +++ b/src/bridge/BridgeStateSyncer.ts @@ -1,6 +1,10 @@ import { DataStore } from "../datastore/DataStore"; import { QueuePool } from "../util/QueuePool"; import { Bridge } from "matrix-appservice-bridge"; +import logging from "../logging"; +import { IrcBridge } from "./IrcBridge"; + +const log = logging("BridgeStateSyncer"); const SYNC_INTERVAL = 1500; const SYNC_CONCURRENCY = 3; @@ -16,25 +20,111 @@ interface QueueItem { */ export class BridgeStateSyncer { private syncQueue: QueuePool; - constructor(private datastore: DataStore, private bridge: Bridge) { + constructor(private datastore: DataStore, private bridge: Bridge, private ircBridge: IrcBridge) { this.syncQueue = new QueuePool(SYNC_CONCURRENCY, this.syncRoom.bind(this)); } public async beginSync() { - const mappings = this.datastore.getAllChannelMappings(); + log.info("Beginning sync of bridge state events"); + const allMappings = await this.datastore.getAllChannelMappings(); + Object.entries(allMappings).forEach(([roomId, mappings]) => { + this.syncQueue.enqueue(roomId, {roomId, mappings}); + }); } - public async syncRoom(item: QueueItem) { + private async syncRoom(item: QueueItem) { + log.info(`Syncing ${item.roomId}`); const intent = this.bridge.getIntent(); for (const mapping of item.mappings) { const key = BridgeStateSyncer.createStateKey(mapping.networkId, mapping.channel); - const eventData = await intent.getStateEvent(item.roomId, TYPE, key); + try { + const eventData = await this.getStateEvent(item.roomId, TYPE, key); + if (eventData !== null) { + const expectedContent = this.createBridgeInfoContent(item.roomId, mapping.networkId, mapping.channel); + // Validate + const isValid = expectedContent.channel.id === eventData.channel.id && + expectedContent.network.id === eventData.network.id && + expectedContent.network.displayname === eventData.network.displayname && + expectedContent.protocol.id === eventData.protocol.id && + expectedContent.protocol.displayname === eventData.protocol.displayname; + if (isValid) { + log.debug(`${key} is valid`); + continue; + } + log.info(`${key} is invalid`); + } + } + catch (ex) { + log.warn(`Encountered error when trying to sync ${item.roomId}`); + break; // To be on the safe side, do not retry this room. + } + + // Event wasn't found or was invalid, let's try setting one. + const eventContent = this.createBridgeInfoContent(item.roomId, mapping.networkId, mapping.channel); + const owner = await this.determineProvisionedOwner(item.roomId, mapping.networkId, mapping.channel); + eventContent.creator = owner || intent.client.credentials.userId; + try { + await intent.sendStateEvent(item.roomId, TYPE, key, eventContent); + } catch (ex) { + log.error(`Failed to update room with new state content: ${ex.message}`); + } } } - public static createStateKey(networkId: string, channel: string) { + private async determineProvisionedOwner(roomId: string, networkId: string, channel: string): Promise { + const room = await this.datastore.getRoom(roomId, networkId, channel); + if (!room || room.data.origin !== "provision") { + return null; + } + // Find out who dun it + try { + const ev = await this.getStateEvent(roomId, "m.room.bridging", `irc://${networkId}/${channel}`); + if (ev?.status === "success") { + return ev.user_id; + } + // Event not found or invalid, leave blank. + } catch (ex) { + log.warn(`Failed to get m.room.bridging information for room: ${ex.message}`); + } + return null; + } + + private static createStateKey(networkId: string, channel: string) { networkId = networkId.replace(/\//g, "%2F"); channel = channel.replace(/\//g, "%2F"); return `org.matrix.appservice-irc://irc/${networkId}/${channel}` } + + private createBridgeInfoContent(roomId: string, networkId: string, channel: string) { + const server = this.ircBridge.getServer(networkId); + const serverName = server?.getReadableName() || undefined; + return { + creator: "", // Is this known? + protocol: { + id: "irc", + displayname: "IRC", + }, + network: { + id: networkId, + displayname: serverName, + }, + channel: { + id: channel, + external_url: `irc://${networkId}/${channel}` + } + } + } + + private async getStateEvent(roomId: string, eventType: string, key: string) { + const intent = this.bridge.getIntent(); + try { + return await intent.getStateEvent(roomId, eventType, key); + } + catch (ex) { + if (ex.errcode !== "M_NOT_FOUND") { + throw ex; + } + } + return null; + } } diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index 8fe31d402..188516d40 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -420,8 +420,8 @@ export class IrcBridge { this.membershipCache.setMemberEntry(roomId, this.appServiceUserId, "join"); } if (this.config.ircService.bridgeInfoState?.enabled) { - this.bridgeStateSyncer = new BridgeStateSyncer(this.dataStore, this.bridge); - if (this.config.ircService.bridgeInfoState.syncExisting) { + this.bridgeStateSyncer = new BridgeStateSyncer(this.dataStore, this.bridge, this); + if (this.config.ircService.bridgeInfoState.initial) { this.bridgeStateSyncer.beginSync().then(() => { log.info("Bridge state syncing completed"); }).catch((err) => { diff --git a/src/config/BridgeConfig.ts b/src/config/BridgeConfig.ts index 7db4f5c1d..a95cab4d6 100644 --- a/src/config/BridgeConfig.ts +++ b/src/config/BridgeConfig.ts @@ -51,7 +51,7 @@ export interface BridgeConfig { }; bridgeInfoState?: { enabled: boolean; - syncExisting: boolean; + initial: boolean; }; }; sentry?: { From af6da213a324c879fdfb0b82adbe63b2534dd33a Mon Sep 17 00:00:00 2001 From: Half-Shot Date: Tue, 7 Jan 2020 16:27:31 +0000 Subject: [PATCH 3/9] Fix lint --- src/bridge/BridgeStateSyncer.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/bridge/BridgeStateSyncer.ts b/src/bridge/BridgeStateSyncer.ts index 65740e575..e30dcd008 100644 --- a/src/bridge/BridgeStateSyncer.ts +++ b/src/bridge/BridgeStateSyncer.ts @@ -6,13 +6,12 @@ import { IrcBridge } from "./IrcBridge"; const log = logging("BridgeStateSyncer"); -const SYNC_INTERVAL = 1500; const SYNC_CONCURRENCY = 3; const TYPE = "uk.half-shot.bridge"; interface QueueItem { roomId: string; - mappings: Array<{networkId: string; channel: string}> + mappings: Array<{networkId: string; channel: string}>; } /** @@ -39,14 +38,17 @@ export class BridgeStateSyncer { const key = BridgeStateSyncer.createStateKey(mapping.networkId, mapping.channel); try { const eventData = await this.getStateEvent(item.roomId, TYPE, key); - if (eventData !== null) { - const expectedContent = this.createBridgeInfoContent(item.roomId, mapping.networkId, mapping.channel); - // Validate + if (eventData !== null) { // If found, validate. + const expectedContent = this.createBridgeInfoContent( + item.roomId, mapping.networkId, mapping.channel + ); + const isValid = expectedContent.channel.id === eventData.channel.id && expectedContent.network.id === eventData.network.id && expectedContent.network.displayname === eventData.network.displayname && expectedContent.protocol.id === eventData.protocol.id && expectedContent.protocol.displayname === eventData.protocol.displayname; + if (isValid) { log.debug(`${key} is valid`); continue; @@ -65,7 +67,8 @@ export class BridgeStateSyncer { eventContent.creator = owner || intent.client.credentials.userId; try { await intent.sendStateEvent(item.roomId, TYPE, key, eventContent); - } catch (ex) { + } + catch (ex) { log.error(`Failed to update room with new state content: ${ex.message}`); } } @@ -83,7 +86,8 @@ export class BridgeStateSyncer { return ev.user_id; } // Event not found or invalid, leave blank. - } catch (ex) { + } + catch (ex) { log.warn(`Failed to get m.room.bridging information for room: ${ex.message}`); } return null; From af04a1ab2cf91f8566d2ff9aeec4191e8bd6a018 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Jan 2020 13:52:31 +0000 Subject: [PATCH 4/9] Tidy up BridgeStateSyncer --- src/bridge/BridgeStateSyncer.ts | 67 ++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/bridge/BridgeStateSyncer.ts b/src/bridge/BridgeStateSyncer.ts index e30dcd008..f3deb6d06 100644 --- a/src/bridge/BridgeStateSyncer.ts +++ b/src/bridge/BridgeStateSyncer.ts @@ -3,11 +3,11 @@ import { QueuePool } from "../util/QueuePool"; import { Bridge } from "matrix-appservice-bridge"; import logging from "../logging"; import { IrcBridge } from "./IrcBridge"; +import { IrcServer } from "../irc/IrcServer"; const log = logging("BridgeStateSyncer"); const SYNC_CONCURRENCY = 3; -const TYPE = "uk.half-shot.bridge"; interface QueueItem { roomId: string; @@ -18,6 +18,7 @@ interface QueueItem { * This class will set bridge room state according to [MSC2346](https://github.com/matrix-org/matrix-doc/pull/2346) */ export class BridgeStateSyncer { + public static readonly EventType = "uk.half-shot.bridge"; private syncQueue: QueuePool; constructor(private datastore: DataStore, private bridge: Bridge, private ircBridge: IrcBridge) { this.syncQueue = new QueuePool(SYNC_CONCURRENCY, this.syncRoom.bind(this)); @@ -40,7 +41,7 @@ export class BridgeStateSyncer { const eventData = await this.getStateEvent(item.roomId, TYPE, key); if (eventData !== null) { // If found, validate. const expectedContent = this.createBridgeInfoContent( - item.roomId, mapping.networkId, mapping.channel + mapping.networkId, mapping.channel ); const isValid = expectedContent.channel.id === eventData.channel.id && @@ -62,7 +63,7 @@ export class BridgeStateSyncer { } // Event wasn't found or was invalid, let's try setting one. - const eventContent = this.createBridgeInfoContent(item.roomId, mapping.networkId, mapping.channel); + const eventContent = this.createBridgeInfoContent(mapping.networkId, mapping.channel); const owner = await this.determineProvisionedOwner(item.roomId, mapping.networkId, mapping.channel); eventContent.creator = owner || intent.client.credentials.userId; try { @@ -74,51 +75,63 @@ export class BridgeStateSyncer { } } - private async determineProvisionedOwner(roomId: string, networkId: string, channel: string): Promise { - const room = await this.datastore.getRoom(roomId, networkId, channel); - if (!room || room.data.origin !== "provision") { - return null; - } - // Find out who dun it - try { - const ev = await this.getStateEvent(roomId, "m.room.bridging", `irc://${networkId}/${channel}`); - if (ev?.status === "success") { - return ev.user_id; - } - // Event not found or invalid, leave blank. - } - catch (ex) { - log.warn(`Failed to get m.room.bridging information for room: ${ex.message}`); - } - return null; + public createInitialState(server: IrcServer, channel: string, owner?: string) { + return { + type: TYPE, + content: this.createBridgeInfoContent(server, channel, owner), + state_key: BridgeStateSyncer.createStateKey(server.domain, channel) + }; } - private static createStateKey(networkId: string, channel: string) { + public static createStateKey(networkId: string, channel: string) { networkId = networkId.replace(/\//g, "%2F"); channel = channel.replace(/\//g, "%2F"); return `org.matrix.appservice-irc://irc/${networkId}/${channel}` } - private createBridgeInfoContent(roomId: string, networkId: string, channel: string) { - const server = this.ircBridge.getServer(networkId); - const serverName = server?.getReadableName() || undefined; + public createBridgeInfoContent(networkIdOrServer: string|IrcServer, channel: string, creator?: string) { + const server = typeof(networkIdOrServer) === "string" ? + this.ircBridge.getServer(networkIdOrServer) : networkIdOrServer; + if (!server) { + throw Error("Server not known"); + } + const serverName = server.getReadableName(); return { - creator: "", // Is this known? + creator: creator || "", // Is this known? protocol: { id: "irc", displayname: "IRC", }, network: { - id: networkId, + id: server.domain, displayname: serverName, }, channel: { id: channel, - external_url: `irc://${networkId}/${channel}` + external_url: `irc://${server.domain}/${channel}` } } } + private async determineProvisionedOwner(roomId: string, networkId: string, channel: string): Promise { + const room = await this.datastore.getRoom(roomId, networkId, channel); + if (!room || room.data.origin !== "provision") { + return null; + } + // Find out who dun it + try { + const ev = await this.getStateEvent(roomId, "m.room.bridging", `irc://${networkId}/${channel}`); + if (ev?.status === "success") { + return ev.user_id; + } + // Event not found or invalid, leave blank. + } + catch (ex) { + log.warn(`Failed to get m.room.bridging information for room: ${ex.message}`); + } + return null; + } + private async getStateEvent(roomId: string, eventType: string, key: string) { const intent = this.bridge.getIntent(); try { From f8a11ec59173ae28c21027e274bd2eec25401544 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Jan 2020 13:53:05 +0000 Subject: [PATCH 5/9] Support adding bridge state to provisioned / joined rooms --- src/bridge/AdminRoomHandler.ts | 8 ++++++++ src/bridge/IrcBridge.ts | 27 ++++++++++++++++++++++----- src/bridge/IrcHandler.ts | 9 +++++++++ src/bridge/MatrixHandler.ts | 9 +++++++++ 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/bridge/AdminRoomHandler.ts b/src/bridge/AdminRoomHandler.ts index e4a5e0f7d..a26c09bc5 100644 --- a/src/bridge/AdminRoomHandler.ts +++ b/src/bridge/AdminRoomHandler.ts @@ -227,6 +227,14 @@ export class AdminRoomHandler { } }); } + if (this.ircBridge.stateSyncer) { + initialState.push( + this.ircBridge.stateSyncer.createInitialState( + server, + ircChannel, + ) + ) + } const ircRoom = await this.ircBridge.trackChannel(server, ircChannel, key); const response = await this.ircBridge.getAppServiceBridge().getIntent( sender, diff --git a/src/bridge/IrcBridge.ts b/src/bridge/IrcBridge.ts index 188516d40..7086610c0 100644 --- a/src/bridge/IrcBridge.ts +++ b/src/bridge/IrcBridge.ts @@ -67,12 +67,9 @@ export class IrcBridge { remote_request_seconds: Histogram; }|null = null; private membershipCache: MembershipCache; -<<<<<<< HEAD private readonly membershipQueue: MembershipQueue; -======= private bridgeStateSyncer!: BridgeStateSyncer; ->>>>>>> Initial work on MSC2346 constructor(public readonly config: BridgeConfig, private registration: AppServiceRegistration) { // TODO: Don't log this to stdout Logging.configure({console: config.ircService.logging.level}); @@ -319,6 +316,10 @@ export class IrcBridge { return this.config.homeserver.domain; } + public get stateSyncer() { + return this.bridgeStateSyncer; + } + public async run(port: number|null) { const dbConfig = this.config.database; // cli port, then config port, then default port @@ -419,6 +420,7 @@ export class IrcBridge { for (const roomId of this.joinedRoomList) { this.membershipCache.setMemberEntry(roomId, this.appServiceUserId, "join"); } + if (this.config.ircService.bridgeInfoState?.enabled) { this.bridgeStateSyncer = new BridgeStateSyncer(this.dataStore, this.bridge, this); if (this.config.ircService.bridgeInfoState.initial) { @@ -1034,12 +1036,12 @@ export class IrcBridge { } }); const bridgingEvent = stateEvents.find((ev: {type: string}) => ev.type === "m.room.bridging"); + const bridgeInfoEvent = stateEvents.find((ev: {type: string}) => ev.type === BridgeStateSyncer.EventType); if (bridgingEvent) { - // The room had a bridge state event, so try to stick it in the new one. try { await this.bridge.getIntent().sendStateEvent( newRoomId, - "m.room.bridging", + bridgingEvent.type, bridgingEvent.state_key, bridgingEvent.content ); @@ -1050,6 +1052,21 @@ export class IrcBridge { log.warn("Could not send m.room.bridging event to new room:", ex); } } + if (bridgeInfoEvent) { + try { + await this.bridge.getIntent().sendStateEvent( + newRoomId, + bridgeInfoEvent.type, + bridgingEvent.state_key, + bridgingEvent.content + ); + log.info("Bridge info event copied to new room"); + } + catch (ex) { + // We may not have permissions to do so, which means we are basically stuffed. + log.warn("Could not send bridge info event to new room:", ex); + } + } await Bluebird.all(rooms.map((room) => { return this.getBotClient(room.getServer()).then((bot) => { // This will invoke NAMES and make members join the new room, diff --git a/src/bridge/IrcHandler.ts b/src/bridge/IrcHandler.ts index 0c457d0d7..9553dde16 100644 --- a/src/bridge/IrcHandler.ts +++ b/src/bridge/IrcHandler.ts @@ -351,6 +351,15 @@ export class IrcHandler { } }); } + + if (this.ircBridge.stateSyncer) { + initialState.push( + this.ircBridge.stateSyncer.createInitialState( + server, + channel, + ) + ) + } const ircRoom = await this.ircBridge.trackChannel(server, channel); const response = await this.ircBridge.getAppServiceBridge().getIntent( virtualMatrixUser.getId() diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index 62d6272fd..d2663b1d9 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -13,6 +13,7 @@ import { IrcAction } from "../models/IrcAction"; import { toIrcLowerCase } from "../irc/formatting"; import { AdminRoomHandler } from "./AdminRoomHandler"; import { MembershipQueue } from "../util/MembershipQueue"; +import { BridgeStateSyncer } from "./BridgeStateSyncer"; function reqHandler(req: BridgeRequest, promise: PromiseLike) { return promise.then(function(res) { @@ -1032,6 +1033,14 @@ export class MatrixHandler { } }); } + if (this.ircBridge.stateSyncer) { + options.initial_state.push( + this.ircBridge.stateSyncer.createInitialState( + channelInfo.server, + channelInfo.channel, + ) + ) + } if (channelInfo.server.forceRoomVersion()) { options.room_version = channelInfo.server.forceRoomVersion(); } From 54243107578abdd9f1f4f4d4727bb84ebf8cbad2 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Jan 2020 13:53:24 +0000 Subject: [PATCH 6/9] Add example config --- config.sample.yaml | 6 +++++- config.schema.yml | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/config.sample.yaml b/config.sample.yaml index b78ef2511..73d3bace4 100644 --- a/config.sample.yaml +++ b/config.sample.yaml @@ -354,7 +354,11 @@ ircService: # through the bridge e.g. caller ID as there is no way to /ACCEPT. # Default: "" (no user modes) # userModes: "R" - + # Set information about the bridged channel in the room state, so that client's may + # present relevant UI to the user. MSC2346 + bridgeInfoState: + enabled: false + initial: false # Configuration for an ident server. If you are running a public bridge it is # advised you setup an ident server so IRC mods can ban specific matrix users # rather than the application service itself. diff --git a/config.schema.yml b/config.schema.yml index a41208258..76c4c37ec 100644 --- a/config.schema.yml +++ b/config.schema.yml @@ -130,6 +130,13 @@ properties: mapIrcMentionsToMatrix: type: "string" enum: ["on", "off", "force-off"] + bridgeInfoState: + type: "object" + properties: + enabled: + type: "boolean" + initial: + type: "boolean" servers: type: "object" # all properties must follow the following From 671832dd806dcc2b2461186026616c5196123160 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Jan 2020 13:53:40 +0000 Subject: [PATCH 7/9] Add bridge info to provisioned rooms --- src/provisioning/Provisioner.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/provisioning/Provisioner.ts b/src/provisioning/Provisioner.ts index d468e90a3..5ced8724e 100644 --- a/src/provisioning/Provisioner.ts +++ b/src/provisioning/Provisioner.ts @@ -1,7 +1,7 @@ import { Request } from "express"; import { IrcBridge } from "../bridge/IrcBridge"; import { Defer } from "../promiseutil"; -import { ConfigValidator, MatrixRoom, MatrixUser } from "matrix-appservice-bridge"; +import { ConfigValidator, MatrixRoom, MatrixUser, Intent } from "matrix-appservice-bridge"; import Bluebird from "bluebird"; import { IrcRoom } from "../models/IrcRoom"; import { IrcAction } from "../models/IrcAction"; @@ -13,6 +13,7 @@ import * as express from "express"; import { IrcServer } from "../irc/IrcServer"; import { IrcUser } from "../models/IrcUser"; import { BridgedClient, GetNicksResponseOperators } from "../irc/BridgedClient"; +import { BridgeStateSyncer } from "../bridge/BridgeStateSyncer"; const log = logging("Provisioner"); @@ -566,6 +567,16 @@ export class Provisioner { return; } await this.updateBridgingState(roomId, userId, 'success', skey); + // Send bridge info state event + if (this.ircBridge.stateSyncer) { + const intent = this.ircBridge.getAppServiceBridge().getIntent(); + await intent.sendStateEvent( + roomId, + BridgeStateSyncer.EventType, + BridgeStateSyncer.createStateKey(server.domain, ircChannel), + this.ircBridge.stateSyncer.createBridgeInfoContent(server, ircChannel, userId) + ); + } } private removeRequest (server: IrcServer, opNick: string) { From 0afb88d2f8f07334d48811acd912e24fa74edb99 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Jan 2020 14:02:23 +0000 Subject: [PATCH 8/9] lint --- src/bridge/BridgeStateSyncer.ts | 6 +++--- src/bridge/MatrixHandler.ts | 1 - src/provisioning/Provisioner.ts | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/bridge/BridgeStateSyncer.ts b/src/bridge/BridgeStateSyncer.ts index f3deb6d06..dc8a03a8e 100644 --- a/src/bridge/BridgeStateSyncer.ts +++ b/src/bridge/BridgeStateSyncer.ts @@ -38,7 +38,7 @@ export class BridgeStateSyncer { for (const mapping of item.mappings) { const key = BridgeStateSyncer.createStateKey(mapping.networkId, mapping.channel); try { - const eventData = await this.getStateEvent(item.roomId, TYPE, key); + const eventData = await this.getStateEvent(item.roomId, BridgeStateSyncer.EventType, key); if (eventData !== null) { // If found, validate. const expectedContent = this.createBridgeInfoContent( mapping.networkId, mapping.channel @@ -67,7 +67,7 @@ export class BridgeStateSyncer { const owner = await this.determineProvisionedOwner(item.roomId, mapping.networkId, mapping.channel); eventContent.creator = owner || intent.client.credentials.userId; try { - await intent.sendStateEvent(item.roomId, TYPE, key, eventContent); + await intent.sendStateEvent(item.roomId, BridgeStateSyncer.EventType, key, eventContent); } catch (ex) { log.error(`Failed to update room with new state content: ${ex.message}`); @@ -77,7 +77,7 @@ export class BridgeStateSyncer { public createInitialState(server: IrcServer, channel: string, owner?: string) { return { - type: TYPE, + type: BridgeStateSyncer.EventType, content: this.createBridgeInfoContent(server, channel, owner), state_key: BridgeStateSyncer.createStateKey(server.domain, channel) }; diff --git a/src/bridge/MatrixHandler.ts b/src/bridge/MatrixHandler.ts index d2663b1d9..9fc67c005 100644 --- a/src/bridge/MatrixHandler.ts +++ b/src/bridge/MatrixHandler.ts @@ -13,7 +13,6 @@ import { IrcAction } from "../models/IrcAction"; import { toIrcLowerCase } from "../irc/formatting"; import { AdminRoomHandler } from "./AdminRoomHandler"; import { MembershipQueue } from "../util/MembershipQueue"; -import { BridgeStateSyncer } from "./BridgeStateSyncer"; function reqHandler(req: BridgeRequest, promise: PromiseLike) { return promise.then(function(res) { diff --git a/src/provisioning/Provisioner.ts b/src/provisioning/Provisioner.ts index 5ced8724e..a6bdb75de 100644 --- a/src/provisioning/Provisioner.ts +++ b/src/provisioning/Provisioner.ts @@ -1,7 +1,7 @@ import { Request } from "express"; import { IrcBridge } from "../bridge/IrcBridge"; import { Defer } from "../promiseutil"; -import { ConfigValidator, MatrixRoom, MatrixUser, Intent } from "matrix-appservice-bridge"; +import { ConfigValidator, MatrixRoom, MatrixUser } from "matrix-appservice-bridge"; import Bluebird from "bluebird"; import { IrcRoom } from "../models/IrcRoom"; import { IrcAction } from "../models/IrcAction"; From 6bc9a7b4b1331fbab434d6eace7c0fd53cbb92a7 Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Mon, 20 Jan 2020 14:04:20 +0000 Subject: [PATCH 9/9] Changelog file --- changelog.d/941.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/941.feature diff --git a/changelog.d/941.feature b/changelog.d/941.feature new file mode 100644 index 000000000..956a620ce --- /dev/null +++ b/changelog.d/941.feature @@ -0,0 +1 @@ +Add support for MSC2346; adding information about the bridged channel into room state. \ No newline at end of file