Skip to content

Commit

Permalink
Implement separate bot users per service (#573)
Browse files Browse the repository at this point in the history
* Add service bots config

* Add joined rooms manager and keep track of joined rooms

* Add bot users manager and ensure registration and profiles

* Improve joined rooms manager and set up already joined rooms

* Handle invites with service bots

* Handle messages with service bots

* Use service bots for connections

* Use service bots in widget and provisioning APIs

* Use service bots in setup connections

* Use service bots for feed connections

* Handle admin rooms for service bots

* Fix confused event type and service type in provisioning and widget APIs

* Fix generic webhooks service name

* Fix enabled services config

* Handle power level change

* Create widgets with service scope

* Use service bots for gitlab repo connections

* Use service bots for gitlab issue connections

* Use service bots for generic webhook connections

* Use service bots for figma file connections

* Use service bots when verifying state events

* Use service bots for github repo connections

* Use service bots for github discussion connections

* Use service bots for github discussion space connections

* Use service bots for github project connections

* Use service bots for github issue connections

* Use service bots for github user space connections

* Use service bots for jira connections

* Make sure ghost users are invited for gitlab issue comments

* Configure one service per service bot

* Add changelog

* Update tests

* Fix up following rebase

* Fix comment

* Use getter for enabled services

* Ensure homeserver can be reached before registering bots

* Add intent getter on bot user

* Update config comment

* Merge joined rooms manager with bot users manager

* Remove unused localpart from bot user class

* Refactor to pass in bot users manager

* Improve priority sort function

Co-authored-by: Christian Paul <[email protected]>

* Fix priority sort

Higher priority should come first

* Add debug log when invites are rejected

* Use different state key for scoped setup widgets

* Use different subtitles to differentiate service bots setup widgets

* Refactor bot user setup into bot users manager

* Refactor to reduce duplication in widget API

* Consistent room ID and intent args order

* Add docs and update changelog

* Add overrideUserId deprecation warning

* Add service bots link

Co-authored-by: Christian Paul <[email protected]>
Co-authored-by: Will Hunt <[email protected]>
  • Loading branch information
3 people authored Jan 13, 2023
1 parent 46467ac commit 9a7839c
Show file tree
Hide file tree
Showing 34 changed files with 1,133 additions and 565 deletions.
1 change: 1 addition & 0 deletions changelog.d/573.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for additional bot users called "service bots" which handle a particular connection type, so that different services can be used through different bot users.
10 changes: 9 additions & 1 deletion config.sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,16 @@ passFile:
bot:
# (Optional) Define profile information for the bot user
#
displayname: GitHub Bot
displayname: Hookshot Bot
avatar: mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d
serviceBots:
# (Optional) Define additional bot users for specific services
#
- localpart: feeds
displayname: Feeds
avatar: mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d
prefix: "!feeds"
service: feeds
metrics:
# (Optional) Prometheus metrics support
#
Expand Down
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@
- [Workers](./advanced/workers.md)
- [🔒 Encryption](./advanced/encryption.md)
- [🪀 Widgets](./advanced/widgets.md)
- [Service Bots](./advanced/service_bots.md)
28 changes: 28 additions & 0 deletions docs/advanced/service_bots.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Service Bots

Hookshot supports additional bot users called "service bots" which handle a particular connection type
(in addition to the default bot user which can handle any connection type).
These bots can coexist in a room, each handling a different service.

## Configuration

Service bots can be given a different localpart, display name, avatar, and command prefix.
They will only handle connections for the specified service, which can be one of:
* `feeds` - [Feeds](../setup/feeds.md)
* `figma` - [Figma](../setup/figma.md)
* `generic` - [Webhooks](../setup/webhooks.md)
* `github` - [GitHub](../setup/github.md)
* `gitlab` - [GitLab](../setup/gitlab.md)
* `jira` - [Jira](../setup/jira.md)

For example with this configuration:
```yaml
serviceBots:
- localpart: feeds
displayname: Feeds
avatar: mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d
prefix: "!feeds"
service: feeds
```
There will be a bot user `@feeds:example.com` which responds to commands prefixed with `!feeds`, and only handles feeds connections.
5 changes: 4 additions & 1 deletion src/App/BridgeApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ListenerService } from "../ListenerService";
import { Logger } from "matrix-appservice-bridge";
import { LogService } from "matrix-bot-sdk";
import { getAppservice } from "../appservice";
import BotUsersManager from "../Managers/BotUsersManager";

Logger.configure({console: "info"});
const log = new Logger("App");
Expand Down Expand Up @@ -35,7 +36,9 @@ async function start() {
userNotificationWatcher.start();
}

const bridgeApp = new Bridge(config, listener, appservice, storage);
const botUsersManager = new BotUsersManager(config, appservice);

const bridgeApp = new Bridge(config, listener, appservice, storage, botUsersManager);

process.once("SIGTERM", () => {
log.error("Got SIGTERM");
Expand Down
358 changes: 219 additions & 139 deletions src/Bridge.ts

Large diffs are not rendered by default.

57 changes: 48 additions & 9 deletions src/Config/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class BridgeConfigGitHub {

@configKey("Prefix used when creating ghost users for GitHub accounts.", true)
readonly userIdPrefix: string;

@configKey("URL for enterprise deployments. Does not include /api/v3", true)
private enterpriseUrl?: string;

Expand Down Expand Up @@ -129,12 +129,12 @@ export interface BridgeConfigJiraYAML {
}
export class BridgeConfigJira implements BridgeConfigJiraYAML {
static CLOUD_INSTANCE_NAME = "api.atlassian.com";

@configKey("Webhook settings for JIRA")
readonly webhook: {
secret: string;
};

// To hide the undefined for now
@hideKey()
@configKey("URL for the instance if using on prem. Ignore if targetting cloud (atlassian.net)", true)
Expand Down Expand Up @@ -411,6 +411,14 @@ interface BridgeConfigEncryption {
storagePath: string;
}

export interface BridgeConfigServiceBot {
localpart: string;
displayname?: string;
avatar?: string;
prefix: string;
service: string;
}

export interface BridgeConfigProvisioning {
bindAddress?: string;
port?: number;
Expand All @@ -425,6 +433,7 @@ export interface BridgeConfigMetrics {

export interface BridgeConfigRoot {
bot?: BridgeConfigBot;
serviceBots?: BridgeConfigServiceBot[];
bridge: BridgeConfigBridge;
experimentalEncryption?: BridgeConfigEncryption;
figma?: BridgeConfigFigma;
Expand Down Expand Up @@ -478,6 +487,8 @@ export class BridgeConfig {
public readonly feeds?: BridgeConfigFeeds;
@configKey("Define profile information for the bot user", true)
public readonly bot?: BridgeConfigBot;
@configKey("Define additional bot users for specific services", true)
public readonly serviceBots?: BridgeConfigServiceBot[];
@configKey("EXPERIMENTAL support for complimentary widgets", true)
public readonly widgets?: BridgeWidgetConfig;
@configKey("Provisioning API for integration managers", true)
Expand Down Expand Up @@ -513,6 +524,7 @@ export class BridgeConfig {
this.provisioning = configData.provisioning;
this.passFile = configData.passFile;
this.bot = configData.bot;
this.serviceBots = configData.serviceBots;
this.metrics = configData.metrics;
this.queue = configData.queue || {
monolithic: true,
Expand All @@ -525,7 +537,7 @@ export class BridgeConfig {
}

this.widgets = configData.widgets && new BridgeWidgetConfig(configData.widgets);

// To allow DEBUG as well as debug
this.logging.level = this.logging.level.toLowerCase() as "debug"|"info"|"warn"|"error"|"trace";
if (!ValidLogLevelStrings.includes(this.logging.level)) {
Expand All @@ -547,7 +559,7 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
}];
this.bridgePermissions = new BridgePermissions(this.permissions);

if (!configData.permissions) {
if (!configData.permissions) {
log.warn(`You have not configured any permissions for the bridge, which by default means all users on ${this.bridge.domain} have admin levels of control. Please adjust your config.`);
}

Expand All @@ -574,7 +586,7 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
});
log.warn("The `webhook` configuration still specifies a port/bindAddress. This should be moved to the `listeners` config.");
}

if (configData.widgets?.port) {
this.listeners.push({
resources: ['widgets'],
Expand All @@ -590,7 +602,7 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
})
log.warn("The `provisioning` configuration still specifies a port/bindAddress. This should be moved to the `listeners` config.");
}

if (this.metrics?.port) {
this.listeners.push({
resources: ['metrics'],
Expand All @@ -599,7 +611,7 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
})
log.warn("The `metrics` configuration still specifies a port/bindAddress. This should be moved to the `listeners` config.");
}

if (configData.widgets?.port) {
this.listeners.push({
resources: ['widgets'],
Expand Down Expand Up @@ -628,6 +640,10 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
if (this.encryption && !this.queue.port) {
throw new ConfigError("queue.port", "You must enable redis support for encryption to work.");
}

if (this.figma?.overrideUserId) {
log.warn("The `figma.overrideUserId` config value is deprecated. A service bot should be configured instead.");
}
}

public async prefillMembershipCache(client: MatrixClient) {
Expand All @@ -637,7 +653,7 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
const membership = await client.getJoinedRoomMembers(await client.resolveRoom(roomEntry));
membership.forEach(userId => this.bridgePermissions.addMemberToCache(roomEntry, userId));
log.debug(`Found ${membership.length} users for ${roomEntry}`);
}
}
}

public addMemberToCache(roomId: string, userId: string) {
Expand All @@ -656,6 +672,29 @@ For more details, see https://github.com/matrix-org/matrix-hookshot/issues/594.
return this.bridgePermissions.checkAction(mxid, service, BridgePermissionLevel[permission]);
}

public get enabledServices(): string[] {
const services = [];
if (this.feeds && this.feeds.enabled) {
services.push("feeds");
}
if (this.figma) {
services.push("figma");
}
if (this.generic && this.generic.enabled) {
services.push("generic");
}
if (this.github) {
services.push("github");
}
if (this.gitlab) {
services.push("gitlab");
}
if (this.jira) {
services.push("jira");
}
return services;
}

public getPublicConfigForService(serviceName: string): Record<string, unknown> {
let config: undefined|Record<string, unknown>;
switch (serviceName) {
Expand Down
13 changes: 11 additions & 2 deletions src/Config/Defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const DefaultConfig = new BridgeConfig({
url: "http://localhost:8008",
mediaUrl: "http://example.com",
port: 9993,
bindAddress: "127.0.0.1",
bindAddress: "127.0.0.1",
},
queue: {
monolithic: true,
Expand Down Expand Up @@ -44,9 +44,18 @@ export const DefaultConfig = new BridgeConfig({
},
},
bot: {
displayname: "GitHub Bot",
displayname: "Hookshot Bot",
avatar: "mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d"
},
serviceBots: [
{
localpart: "feeds",
displayname: "Feeds",
avatar: "mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d",
prefix: "!feeds",
service: "feeds",
},
],
github: {
auth: {
id: 123,
Expand Down
Loading

0 comments on commit 9a7839c

Please sign in to comment.