Minor grammar corrections in code and documentation (#267)

* setupWidget -> setUpWidget

* setup -> (to) set up; add full stops

* setupAdminRoom -> setUpAdminRoom

* it's -> its

* Create 267.feature

* log in to; Currently, ; full stops

* Improve copy of error messages

* Update 267.feature

* Rename 267.feature to 267.misc

Co-authored-by: Will Hunt <will@half-shot.uk>
This commit is contained in:
Christian Paul 2022-04-07 17:14:35 +02:00 committed by GitHub
parent 14abb011b6
commit 4060ded7f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 125 additions and 124 deletions

View File

@ -65,7 +65,7 @@ Bugfixes
- Fix a bug which caused GitHub "ready for review" events to be unhandled. ([\#149](https://github.com/matrix-org/matrix-hookshot/issues/149)) - Fix a bug which caused GitHub "ready for review" events to be unhandled. ([\#149](https://github.com/matrix-org/matrix-hookshot/issues/149))
- Fix a bug preventing `!hookshot jira project` from working ([\#166](https://github.com/matrix-org/matrix-hookshot/issues/166)) - Fix a bug preventing `!hookshot jira project` from working ([\#166](https://github.com/matrix-org/matrix-hookshot/issues/166))
- Fix a few issues preventing GitHub notifications from working ([\#173](https://github.com/matrix-org/matrix-hookshot/issues/173)) - Fix a few issues preventing GitHub notifications from working ([\#173](https://github.com/matrix-org/matrix-hookshot/issues/173))
- Fixed an issue where the bridge bot would change it's displayname if a webhook event is handled while `generic.userIdPrefix` is not set in the config. ([\#215](https://github.com/matrix-org/matrix-hookshot/issues/215)) - Fixed an issue where the bridge bot would change its displayname if a webhook event is handled while `generic.userIdPrefix` is not set in the config. ([\#215](https://github.com/matrix-org/matrix-hookshot/issues/215))
- Remove nonfunctional `gitlab notifications toggle` command. ([\#226](https://github.com/matrix-org/matrix-hookshot/issues/226)) - Remove nonfunctional `gitlab notifications toggle` command. ([\#226](https://github.com/matrix-org/matrix-hookshot/issues/226))

1
changelog.d/267.misc Normal file
View File

@ -0,0 +1 @@
Make some grammar corrections in code, chat notices and documentation.

View File

@ -112,7 +112,7 @@ The `level` can be:
- `login` All the above, and can also log in to the bridge. - `login` All the above, and can also log in to the bridge.
- `notifications` All the above, and can also bridge their notifications. - `notifications` All the above, and can also bridge their notifications.
- `manageConnections` All the above, and can create and delete connections (either via the provisioner, setup commands, or state events). - `manageConnections` All the above, and can create and delete connections (either via the provisioner, setup commands, or state events).
- `admin` All permissions. Currently there are no admin features so this exists as a placeholder. - `admin` All permissions. Currently, there are no admin features so this exists as a placeholder.
When permissions are checked, if a user matches any of the permission set and one When permissions are checked, if a user matches any of the permission set and one
of those grants the right level for a service, they are allowed access. If none of the of those grants the right level for a service, they are allowed access. If none of the

View File

@ -160,7 +160,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
private async setGitHubNotificationsStateParticipating() { private async setGitHubNotificationsStateParticipating() {
const newData = await this.saveAccountData((data) => { const newData = await this.saveAccountData((data) => {
if (!data.github?.notifications?.enabled) { if (!data.github?.notifications?.enabled) {
throw Error('Notifications are not enabled') throw Error('Notifications are not enabled.')
} }
const oldState = data.github?.notifications?.participating ?? false; const oldState = data.github?.notifications?.participating ?? false;
return { return {
@ -174,17 +174,17 @@ export class AdminRoom extends AdminRoomCommandHandler {
}; };
}); });
if (newData.github?.notifications?.participating) { if (newData.github?.notifications?.participating) {
return this.sendNotice(`Filtering for events you are participating in`); return this.sendNotice(`Filtering for events you are participating in.`);
} }
return this.sendNotice(`Showing all events`); return this.sendNotice(`Showing all events.`);
} }
@botCommand("github notifications", {help: "Show the current notification settings", category: "github"}) @botCommand("github notifications", {help: "Show the current notification settings", category: "github"})
public async getGitHubNotificationsState() { public async getGitHubNotificationsState() {
if (!this.notificationsEnabled("github")) { if (!this.notificationsEnabled("github")) {
return this.sendNotice(`Notifications are disabled`); return this.sendNotice(`Notifications are disabled.`);
} }
return this.sendNotice(`Notifications are enabled, ${this.notificationsParticipating("github") ? "Showing only events you are particiapting in" : "Showing all events"}`); return this.sendNotice(`Notifications are enabled, ${this.notificationsParticipating("github") ? "Showing only events you are particiapting in." : "Showing all events."}`);
} }
@ -192,7 +192,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
// @ts-ignore - property is used // @ts-ignore - property is used
private async listProjects(username?: string, repo?: string) { private async listProjects(username?: string, repo?: string) {
if (!this.config.github) { if (!this.config.github) {
return this.sendNotice("The bridge is not configured with GitHub support"); return this.sendNotice("The bridge is not configured with GitHub support.");
} }
const octokit = await this.tokenStore.getOctokitForUser(this.userId); const octokit = await this.tokenStore.getOctokitForUser(this.userId);
if (!octokit) { if (!octokit) {
@ -218,7 +218,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
})).data; })).data;
} catch (ex) { } catch (ex) {
log.warn(`Failed to fetch projects:`, ex); log.warn(`Failed to fetch projects:`, ex);
return this.sendNotice(`Failed to fetch projects due to an error. See logs for details`); return this.sendNotice(`Failed to fetch projects due to an error. See logs for details.`);
} }
const content = `Projects for ${username}:\n${FormatUtil.projectListing(res)}\n`; const content = `Projects for ${username}:\n${FormatUtil.projectListing(res)}\n`;
@ -234,7 +234,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
// @ts-ignore - property is used // @ts-ignore - property is used
private async listProjects(org: string, repo?: string) { private async listProjects(org: string, repo?: string) {
if (!this.config.github) { if (!this.config.github) {
return this.sendNotice("The bridge is not configured with GitHub support"); return this.sendNotice("The bridge is not configured with GitHub support.");
} }
const octokit = await this.tokenStore.getOctokitForUser(this.userId); const octokit = await this.tokenStore.getOctokitForUser(this.userId);
if (!octokit) { if (!octokit) {
@ -254,10 +254,10 @@ export class AdminRoom extends AdminRoomCommandHandler {
})); }));
} catch (ex) { } catch (ex) {
if (ex.status === 404) { if (ex.status === 404) {
return this.sendNotice('Not found'); return this.sendNotice(`${repo ? "Repository" : "Org"} does not exist.`);
} }
log.warn(`Failed to fetch projects:`, ex); log.warn(`Failed to fetch projects:`, ex);
return this.sendNotice(`Failed to fetch projects due to an error. See logs for details`); return this.sendNotice(`Failed to fetch projects due to an error. See logs for details.`);
} }
const content = `Projects for ${org}:\n` + res.data.map(r => ` - ${FormatUtil.projectListing([r])}\n`).join("\n"); const content = `Projects for ${org}:\n` + res.data.map(r => ` - ${FormatUtil.projectListing([r])}\n`).join("\n");
@ -273,7 +273,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
// @ts-ignore - property is used // @ts-ignore - property is used
private async openProject(projectId: string) { private async openProject(projectId: string) {
if (!this.config.github) { if (!this.config.github) {
return this.sendNotice("The bridge is not configured with GitHub support"); return this.sendNotice("The bridge is not configured with GitHub support.");
} }
const octokit = await this.tokenStore.getOctokitForUser(this.userId); const octokit = await this.tokenStore.getOctokitForUser(this.userId);
if (!octokit) { if (!octokit) {
@ -287,10 +287,10 @@ export class AdminRoom extends AdminRoomCommandHandler {
this.emit('open.project', project.data); this.emit('open.project', project.data);
} catch (ex) { } catch (ex) {
if (ex.status === 404) { if (ex.status === 404) {
return this.sendNotice('Not found'); return this.sendNotice('Project does not exist.');
} }
log.warn(`Failed to fetch project:`, ex); log.warn(`Failed to fetch project:`, ex);
return this.sendNotice(`Failed to fetch project due to an error. See logs for details`); return this.sendNotice(`Failed to fetch project due to an error. See logs for details.`);
} }
} }
@ -299,7 +299,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
private async listDiscussions(owner: string, repo: string, numberStr: string) { private async listDiscussions(owner: string, repo: string, numberStr: string) {
const number = parseInt(numberStr); const number = parseInt(numberStr);
if (!this.config.github) { if (!this.config.github) {
return this.sendNotice("The bridge is not configured with GitHub support"); return this.sendNotice("The bridge is not configured with GitHub support.");
} }
const octokit = await this.tokenStore.getOctokitForUser(this.userId); const octokit = await this.tokenStore.getOctokitForUser(this.userId);
if (!octokit) { if (!octokit) {
@ -311,10 +311,10 @@ export class AdminRoom extends AdminRoomCommandHandler {
this.emit('open.discussion', owner, repo, discussions); this.emit('open.discussion', owner, repo, discussions);
} catch (ex) { } catch (ex) {
if (ex.status === 404) { if (ex.status === 404) {
return this.sendNotice('Not found'); return this.sendNotice('Discussion does not exist.');
} }
log.warn(`Failed to fetch discussions:`, ex); log.warn(`Failed to fetch discussions:`, ex);
return this.sendNotice(`Failed to fetch discussions due to an error. See logs for details`); return this.sendNotice(`Failed to fetch discussions due to an error. See logs for details.`);
} }
} }
@ -325,7 +325,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
// @ts-ignore - property is used // @ts-ignore - property is used
private async gitLabOpenIssue(url: string) { private async gitLabOpenIssue(url: string) {
if (!this.config.gitlab) { if (!this.config.gitlab) {
return this.sendNotice("The bridge is not configured with GitLab support"); return this.sendNotice("The bridge is not configured with GitLab support.");
} }
const urlResult = GitLabClient.splitUrlIntoParts(this.config.gitlab.instances, url); const urlResult = GitLabClient.splitUrlIntoParts(this.config.gitlab.instances, url);
@ -336,7 +336,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
const instance = this.config.gitlab.instances[instanceName]; const instance = this.config.gitlab.instances[instanceName];
const client = await this.tokenStore.getGitLabForUser(this.userId, instance.url); const client = await this.tokenStore.getGitLabForUser(this.userId, instance.url);
if (!client) { if (!client) {
return this.sendNotice("You have not added a personal access token for GitLab"); return this.sendNotice("You have not added a personal access token for GitLab.");
} }
const getIssueOpts = { const getIssueOpts = {
issue: parseInt(parts[parts.length-1]), issue: parseInt(parts[parts.length-1]),
@ -351,11 +351,11 @@ export class AdminRoom extends AdminRoomCommandHandler {
public async setGitLabPersonalAccessToken(instanceName: string, accessToken: string) { public async setGitLabPersonalAccessToken(instanceName: string, accessToken: string) {
let me: GetUserResponse; let me: GetUserResponse;
if (!this.config.gitlab) { if (!this.config.gitlab) {
return this.sendNotice("The bridge is not configured with GitLab support"); return this.sendNotice("The bridge is not configured with GitLab support.");
} }
const instance = this.config.gitlab.instances[instanceName]; const instance = this.config.gitlab.instances[instanceName];
if (!instance) { if (!instance) {
return this.sendNotice("The bridge is not configured for this GitLab instance"); return this.sendNotice("The bridge is not configured for this GitLab instance.");
} }
try { try {
const client = new GitLabClient(instance.url, accessToken); const client = new GitLabClient(instance.url, accessToken);
@ -365,22 +365,22 @@ export class AdminRoom extends AdminRoomCommandHandler {
log.error("Gitlab auth error:", ex); log.error("Gitlab auth error:", ex);
return this.sendNotice("Could not authenticate with GitLab. Is your token correct?"); return this.sendNotice("Could not authenticate with GitLab. Is your token correct?");
} }
await this.sendNotice(`Connected as ${me.username}. Token stored`); await this.sendNotice(`Connected as ${me.username}. Token stored.`);
return this.tokenStore.storeUserToken("gitlab", this.userId, accessToken, instance.url); return this.tokenStore.storeUserToken("gitlab", this.userId, accessToken, instance.url);
} }
@botCommand("gitlab hastoken", {help: "Check if you have a token stored for GitLab", requiredArgs: ["instanceName"], category: "gitlab"}) @botCommand("gitlab hastoken", {help: "Check if you have a token stored for GitLab", requiredArgs: ["instanceName"], category: "gitlab"})
public async gitlabHasPersonalToken(instanceName: string) { public async gitlabHasPersonalToken(instanceName: string) {
if (!this.config.gitlab) { if (!this.config.gitlab) {
return this.sendNotice("The bridge is not configured with GitLab support"); return this.sendNotice("The bridge is not configured with GitLab support.");
} }
const instance = this.config.gitlab.instances[instanceName]; const instance = this.config.gitlab.instances[instanceName];
if (!instance) { if (!instance) {
return this.sendNotice("The bridge is not configured for this GitLab instance"); return this.sendNotice("The bridge is not configured for this GitLab instance.");
} }
const result = await this.tokenStore.getUserToken("gitlab", this.userId, instance.url); const result = await this.tokenStore.getUserToken("gitlab", this.userId, instance.url);
if (result === null) { if (result === null) {
return this.sendNotice("You do not currently have a token stored"); return this.sendNotice("You do not currently have a token stored.");
} }
return this.sendNotice("A token is stored for your GitLab account."); return this.sendNotice("A token is stored for your GitLab account.");
} }
@ -388,7 +388,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
@botCommand("filters list", "List your saved filters") @botCommand("filters list", "List your saved filters")
public async getFilters() { public async getFilters() {
if (this.notifFilter.empty) { if (this.notifFilter.empty) {
return this.sendNotice("You do not currently have any filters"); return this.sendNotice("You do not currently have any filters.");
} }
const filterText = Object.entries(this.notifFilter.filters).map(([name, value]) => { const filterText = Object.entries(this.notifFilter.filters).map(([name, value]) => {
const userText = value.users.length ? `users: ${value.users.join("|")}` : ''; const userText = value.users.length ? `users: ${value.users.join("|")}` : '';
@ -407,7 +407,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
const users = parameters.filter(param => param.toLowerCase().startsWith("users:")).map(param => param.toLowerCase().substring("users:".length).split(",")).flat(); const users = parameters.filter(param => param.toLowerCase().startsWith("users:")).map(param => param.toLowerCase().substring("users:".length).split(",")).flat();
const repos = parameters.filter(param => param.toLowerCase().startsWith("repos:")).map(param => param.toLowerCase().substring("repos:".length).split(",")).flat(); const repos = parameters.filter(param => param.toLowerCase().startsWith("repos:")).map(param => param.toLowerCase().substring("repos:".length).split(",")).flat();
if (orgs.length + users.length + repos.length === 0) { if (orgs.length + users.length + repos.length === 0) {
return this.sendNotice("You must specify some filter options like 'orgs:matrix-org,half-shot', 'users:Half-Shot' or 'repos:matrix-hookshot'"); return this.sendNotice("You must specify some filter options like 'orgs:matrix-org,half-shot', 'users:Half-Shot' or 'repos:matrix-hookshot'.");
} }
this.notifFilter.setFilter(name, { this.notifFilter.setFilter(name, {
orgs, orgs,
@ -415,20 +415,20 @@ export class AdminRoom extends AdminRoomCommandHandler {
repos, repos,
}); });
await this.botIntent.underlyingClient.sendStateEvent(this.roomId, NotifFilter.StateType, "", this.notifFilter.getStateContent()); await this.botIntent.underlyingClient.sendStateEvent(this.roomId, NotifFilter.StateType, "", this.notifFilter.getStateContent());
return this.sendNotice(`Stored new filter "${name}". You can now apply the filter by saying 'filters notifications toggle $name'`); return this.sendNotice(`Stored new filter "${name}". You can now apply the filter by saying 'filters notifications toggle $name'.`);
} }
@botCommand("filters notifications toggle", "Apply a filter as a whitelist to your notifications", ["name"]) @botCommand("filters notifications toggle", "Apply a filter as a whitelist to your notifications", ["name"])
public async setFiltersNotificationsToggle(name: string) { public async setFiltersNotificationsToggle(name: string) {
if (!this.notifFilter.filters[name]) { if (!this.notifFilter.filters[name]) {
return this.sendNotice(`Filter "${name}" doesn't exist'`); return this.sendNotice(`Filter "${name}" doesn't exist.`);
} }
if (this.notifFilter.forNotifications.has(name)) { if (this.notifFilter.forNotifications.has(name)) {
this.notifFilter.forNotifications.delete(name); this.notifFilter.forNotifications.delete(name);
await this.sendNotice(`Filter "${name}" disabled for notifications`); await this.sendNotice(`Filter "${name}" disabled for notifications.`);
} else { } else {
this.notifFilter.forNotifications.add(name); this.notifFilter.forNotifications.add(name);
await this.sendNotice(`Filter "${name}" enabled for notifications`); await this.sendNotice(`Filter "${name}" enabled for notifications.`);
} }
return this.botIntent.underlyingClient.sendStateEvent(this.roomId, NotifFilter.StateType, "", this.notifFilter.getStateContent()); return this.botIntent.underlyingClient.sendStateEvent(this.roomId, NotifFilter.StateType, "", this.notifFilter.getStateContent());
} }
@ -449,7 +449,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
const checkPermission = (service: string, level: BridgePermissionLevel) => this.config.checkPermission(this.userId, service, level); const checkPermission = (service: string, level: BridgePermissionLevel) => this.config.checkPermission(this.userId, service, level);
const result = await handleCommand(this.userId, command, AdminRoom.botCommands, this, checkPermission); const result = await handleCommand(this.userId, command, AdminRoom.botCommands, this, checkPermission);
if (!result.handled) { if (!result.handled) {
return this.sendNotice("Command not understood"); return this.sendNotice("Command not understood.");
} }
if ("error" in result) { if ("error" in result) {
@ -488,7 +488,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
} }
} }
public async setupWidget() { public async setUpWidget() {
try { try {
const res = await this.botIntent.underlyingClient.getRoomStateEvent(this.roomId, "im.vector.modular.widgets", "bridge_control"); const res = await this.botIntent.underlyingClient.getRoomStateEvent(this.roomId, "im.vector.modular.widgets", "bridge_control");
if (res) { if (res) {

View File

@ -106,10 +106,10 @@ export async function handleCommand(
if (command) { if (command) {
const permissionService = command.permissionService || defaultPermissionService; const permissionService = command.permissionService || defaultPermissionService;
if (permissionService && !permissionCheckFn(permissionService, command.permissionLevel || BridgePermissionLevel.commands)) { if (permissionService && !permissionCheckFn(permissionService, command.permissionLevel || BridgePermissionLevel.commands)) {
return {handled: true, error: "You do not have permission to use this command"}; return {handled: true, error: "You do not have permission to use this command."};
} }
if (command.requiredArgs && command.requiredArgs.length > parts.length - i) { if (command.requiredArgs && command.requiredArgs.length > parts.length - i) {
return {handled: true, error: "Missing args"}; return {handled: true, error: "Missing at least one required parameter."};
} }
const args = parts.slice(i); const args = parts.slice(i);
if (command.includeUserId) { if (command.includeUserId) {

View File

@ -621,7 +621,7 @@ export class Bridge {
// No state yet // No state yet
} }
} }
const adminRoom = await this.setupAdminRoom(roomId, accountData, notifContent || NotifFilter.getDefaultContent()); const adminRoom = await this.setUpAdminRoom(roomId, accountData, notifContent || NotifFilter.getDefaultContent());
// Call this on startup to set the state // Call this on startup to set the state
await this.onAdminRoomSettingsChanged(adminRoom, accountData, { admin_user: accountData.admin_user }); await this.onAdminRoomSettingsChanged(adminRoom, accountData, { admin_user: accountData.admin_user });
log.debug(`Room ${roomId} is connected to: ${adminRoom.toString()}`); log.debug(`Room ${roomId} is connected to: ${adminRoom.toString()}`);
@ -678,7 +678,7 @@ export class Bridge {
} }
await retry(() => this.as.botIntent.joinRoom(roomId), 5); await retry(() => this.as.botIntent.joinRoom(roomId), 5);
if (event.content.is_direct) { if (event.content.is_direct) {
const room = await this.setupAdminRoom(roomId, {admin_user: event.sender}, NotifFilter.getDefaultContent()); const room = await this.setUpAdminRoom(roomId, {admin_user: event.sender}, NotifFilter.getDefaultContent());
await this.as.botIntent.underlyingClient.setRoomAccountData( await this.as.botIntent.underlyingClient.setRoomAccountData(
BRIDGE_ROOM_TYPE, roomId, room.accountData, BRIDGE_ROOM_TYPE, roomId, room.accountData,
); );
@ -1016,10 +1016,10 @@ export class Bridge {
is_direct: true, is_direct: true,
preset: "trusted_private_chat", preset: "trusted_private_chat",
}); });
return this.setupAdminRoom(roomId, {admin_user: userId}, NotifFilter.getDefaultContent()); return this.setUpAdminRoom(roomId, {admin_user: userId}, NotifFilter.getDefaultContent());
} }
private async setupAdminRoom(roomId: string, accountData: AdminAccountData, notifContent: NotificationFilterStateContent) { private async setUpAdminRoom(roomId: string, accountData: AdminAccountData, notifContent: NotificationFilterStateContent) {
const adminRoom = new AdminRoom( const adminRoom = new AdminRoom(
roomId, accountData, notifContent, this.as.botIntent, this.tokenStore, this.config, roomId, accountData, notifContent, this.as.botIntent, this.tokenStore, this.config,
); );
@ -1053,7 +1053,7 @@ export class Bridge {
}); });
this.adminRooms.set(roomId, adminRoom); this.adminRooms.set(roomId, adminRoom);
if (this.config.widgets?.addToAdminRooms && this.config.widgets.publicUrl) { if (this.config.widgets?.addToAdminRooms && this.config.widgets.publicUrl) {
await adminRoom.setupWidget(); await adminRoom.setUpWidget();
} }
log.debug(`Set up ${roomId} as an admin room for ${adminRoom.userId}`); log.debug(`Set up ${roomId} as an admin room for ${adminRoom.userId}`);
return adminRoom; return adminRoom;

View File

@ -48,7 +48,7 @@ export abstract class CommandConnection extends BaseConnection {
}); });
await this.botClient.sendEvent(this.roomId, 'm.room.message', { await this.botClient.sendEvent(this.roomId, 'm.room.message', {
msgtype: "m.notice", msgtype: "m.notice",
body: humanError ? `Failed to handle command: ${humanError}` : "Failed to handle command", body: humanError ? `Failed to handle command: ${humanError}` : "Failed to handle command.",
}); });
log.warn(`Failed to handle command:`, error); log.warn(`Failed to handle command:`, error);
return true; return true;

View File

@ -514,7 +514,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
const workflow = workflows.data.workflows.find(w => w.name.toLowerCase().trim() === name.toLowerCase().trim()); const workflow = workflows.data.workflows.find(w => w.name.toLowerCase().trim() === name.toLowerCase().trim());
if (!workflow) { if (!workflow) {
const workflowNames = workflows.data.workflows.map(w => w.name).join(', '); const workflowNames = workflows.data.workflows.map(w => w.name).join(', ');
await this.as.botIntent.sendText(this.roomId, `Could not find a workflow by the name of "${name}". The workflows on this repository are ${workflowNames}`, "m.notice"); await this.as.botIntent.sendText(this.roomId, `Could not find a workflow by the name of "${name}". The workflows on this repository are ${workflowNames}.`, "m.notice");
return; return;
} }
try { try {
@ -544,7 +544,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
throw ex; throw ex;
} }
await this.as.botIntent.sendText(this.roomId, `Workflow started`, "m.notice"); await this.as.botIntent.sendText(this.roomId, `Workflow started.`, "m.notice");
} }
public async onIssueCreated(event: IssuesOpenedEvent) { public async onIssueCreated(event: IssuesOpenedEvent) {

View File

@ -76,7 +76,7 @@ export class GitLabRepoConnection extends CommandConnection {
public async onCreateIssue(userId: string, title: string, description?: string, labels?: string) { public async onCreateIssue(userId: string, title: string, description?: string, labels?: string) {
const client = await this.tokenStore.getGitLabForUser(userId, this.instance.url); const client = await this.tokenStore.getGitLabForUser(userId, this.instance.url);
if (!client) { if (!client) {
await this.as.botIntent.sendText(this.roomId, "You must login to create an issue", "m.notice"); await this.as.botIntent.sendText(this.roomId, "You must be logged in to create an issue.", "m.notice");
throw Error('Not logged in'); throw Error('Not logged in');
} }
const res = await client.issues.create({ const res = await client.issues.create({
@ -99,7 +99,7 @@ export class GitLabRepoConnection extends CommandConnection {
public async onClose(userId: string, number: string) { public async onClose(userId: string, number: string) {
const client = await this.tokenStore.getGitLabForUser(userId, this.instance.url); const client = await this.tokenStore.getGitLabForUser(userId, this.instance.url);
if (!client) { if (!client) {
await this.as.botIntent.sendText(this.roomId, "You must login to create an issue", "m.notice"); await this.as.botIntent.sendText(this.roomId, "You must be logged in to create an issue.", "m.notice");
throw Error('Not logged in'); throw Error('Not logged in');
} }

View File

@ -16,7 +16,7 @@ export interface IConnection {
*/ */
get connectionId(): string; get connectionId(): string;
/** /**
* When a room gets an update to it's state. * When a room gets an update to its state.
*/ */
onStateUpdate?: (ev: MatrixEvent<unknown>) => Promise<void>; onStateUpdate?: (ev: MatrixEvent<unknown>) => Promise<void>;
/** /**

View File

@ -41,19 +41,19 @@ export class SetupConnection extends CommandConnection {
) )
} }
@botCommand("github repo", "Create a connection for a GitHub repository. (You must be logged in with GitHub to do this)", ["url"], [], true) @botCommand("github repo", "Create a connection for a GitHub repository. (You must be logged in with GitHub to do this.)", ["url"], [], true)
public async onGitHubRepo(userId: string, url: string) { public async onGitHubRepo(userId: string, url: string) {
if (!this.githubInstance || !this.config.github) { if (!this.githubInstance || !this.config.github) {
throw new CommandError("not-configured", "The bridge is not configured to support GitHub"); throw new CommandError("not-configured", "The bridge is not configured to support GitHub.");
} }
if (!this.config.checkPermission(userId, "github", BridgePermissionLevel.manageConnections)) { if (!this.config.checkPermission(userId, "github", BridgePermissionLevel.manageConnections)) {
throw new CommandError('You are not permitted to provision connections for GitHub'); throw new CommandError('You are not permitted to provision connections for GitHub.');
} }
if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) { if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) {
throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations."); throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations.");
} }
if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) { if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) {
throw new CommandError("Bot lacks power level to set room state", "I do not have permission to setup a bridge in this room. Please promote me to an Admin/Moderator"); throw new CommandError("Bot lacks power level to set room state", "I do not have permission to set up a bridge in this room. Please promote me to an Admin/Moderator.");
} }
const octokit = await this.tokenStore.getOctokitForUser(userId); const octokit = await this.tokenStore.getOctokitForUser(userId);
if (!octokit) { if (!octokit) {
@ -61,7 +61,7 @@ export class SetupConnection extends CommandConnection {
} }
const urlParts = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(url.trim().toLowerCase()); const urlParts = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(url.trim().toLowerCase());
if (!urlParts) { if (!urlParts) {
throw new CommandError("Invalid GitHub url", "The GitHub url you entered was not valid"); throw new CommandError("Invalid GitHub url", "The GitHub url you entered was not valid.");
} }
const [, org, repo] = urlParts; const [, org, repo] = urlParts;
const res = await GitHubRepoConnection.provisionConnection(this.roomId, userId, {org, repo}, this.as, this.tokenStore, this.githubInstance, this.config.github); const res = await GitHubRepoConnection.provisionConnection(this.roomId, userId, {org, repo}, this.as, this.tokenStore, this.githubInstance, this.config.github);
@ -69,20 +69,20 @@ export class SetupConnection extends CommandConnection {
await this.as.botClient.sendNotice(this.roomId, `Room configured to bridge ${org}/${repo}`); await this.as.botClient.sendNotice(this.roomId, `Room configured to bridge ${org}/${repo}`);
} }
@botCommand("jira project", "Create a connection for a JIRA project. (You must be logged in with JIRA to do this)", ["url"], [], true) @botCommand("jira project", "Create a connection for a JIRA project. (You must be logged in with JIRA to do this.)", ["url"], [], true)
public async onJiraProject(userId: string, urlStr: string) { public async onJiraProject(userId: string, urlStr: string) {
const url = new URL(urlStr); const url = new URL(urlStr);
if (!this.config.jira) { if (!this.config.jira) {
throw new CommandError("not-configured", "The bridge is not configured to support Jira"); throw new CommandError("not-configured", "The bridge is not configured to support Jira.");
} }
if (!this.config.checkPermission(userId, "jira", BridgePermissionLevel.manageConnections)) { if (!this.config.checkPermission(userId, "jira", BridgePermissionLevel.manageConnections)) {
throw new CommandError('You are not permitted to provision connections for Jira'); throw new CommandError('You are not permitted to provision connections for Jira.');
} }
if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) { if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) {
throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations."); throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations.");
} }
if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) { if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) {
throw new CommandError("Bot lacks power level to set room state", "I do not have permission to setup a bridge in this room. Please promote me to an Admin/Moderator"); throw new CommandError("Bot lacks power level to set room state", "I do not have permission to set up a bridge in this room. Please promote me to an Admin/Moderator.");
} }
const jiraClient = await this.tokenStore.getJiraForUser(userId, urlStr); const jiraClient = await this.tokenStore.getJiraForUser(userId, urlStr);
if (!jiraClient) { if (!jiraClient) {
@ -91,57 +91,57 @@ export class SetupConnection extends CommandConnection {
const urlParts = /.+\/projects\/(\w+)\/?(\w+\/?)*$/.exec(url.pathname.toLowerCase()); const urlParts = /.+\/projects\/(\w+)\/?(\w+\/?)*$/.exec(url.pathname.toLowerCase());
const projectKey = urlParts?.[1] || url.searchParams.get('projectKey'); const projectKey = urlParts?.[1] || url.searchParams.get('projectKey');
if (!projectKey) { if (!projectKey) {
throw new CommandError("Invalid Jira url", "The JIRA project url you entered was not valid. It should be in the format of `https://jira-instance/.../projects/PROJECTKEY/...` or `.../RapidBoard.jspa?projectKey=TEST`"); throw new CommandError("Invalid Jira url", "The JIRA project url you entered was not valid. It should be in the format of `https://jira-instance/.../projects/PROJECTKEY/...` or `.../RapidBoard.jspa?projectKey=TEST`.");
} }
const safeUrl = `https://${url.host}/projects/${projectKey}`; const safeUrl = `https://${url.host}/projects/${projectKey}`;
const res = await JiraProjectConnection.provisionConnection(this.roomId, userId, { url: safeUrl }, this.as, this.tokenStore); const res = await JiraProjectConnection.provisionConnection(this.roomId, userId, { url: safeUrl }, this.as, this.tokenStore);
await this.as.botClient.sendStateEvent(this.roomId, JiraProjectConnection.CanonicalEventType, safeUrl, res.stateEventContent); await this.as.botClient.sendStateEvent(this.roomId, JiraProjectConnection.CanonicalEventType, safeUrl, res.stateEventContent);
await this.as.botClient.sendNotice(this.roomId, `Room configured to bridge Jira project ${res.connection.projectKey}`); await this.as.botClient.sendNotice(this.roomId, `Room configured to bridge Jira project ${res.connection.projectKey}.`);
} }
@botCommand("webhook", "Create an inbound webhook", ["name"], [], true) @botCommand("webhook", "Create an inbound webhook.", ["name"], [], true)
public async onWebhook(userId: string, name: string) { public async onWebhook(userId: string, name: string) {
if (!this.config.generic?.enabled) { if (!this.config.generic?.enabled) {
throw new CommandError("not-configured", "The bridge is not configured to support webhooks"); throw new CommandError("not-configured", "The bridge is not configured to support webhooks.");
} }
if (!this.config.checkPermission(userId, "webhooks", BridgePermissionLevel.manageConnections)) { if (!this.config.checkPermission(userId, "webhooks", BridgePermissionLevel.manageConnections)) {
throw new CommandError('You are not permitted to provision connections for generic webhooks'); throw new CommandError('You are not permitted to provision connections for generic webhooks.');
} }
if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) { if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) {
throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations."); throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations.");
} }
if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) { if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) {
throw new CommandError("Bot lacks power level to set room state", "I do not have permission to setup a bridge in this room. Please promote me to an Admin/Moderator"); throw new CommandError("Bot lacks power level to set room state", "I do not have permission to set up a bridge in this room. Please promote me to an Admin/Moderator.");
} }
if (!name || name.length < 3 || name.length > 64) { if (!name || name.length < 3 || name.length > 64) {
throw new CommandError("Bad webhook name", "A webhook name must be between 3-64 characters"); throw new CommandError("Bad webhook name", "A webhook name must be between 3-64 characters.");
} }
const hookId = uuid(); const hookId = uuid();
const url = `${this.config.generic.urlPrefix}${this.config.generic.urlPrefix.endsWith('/') ? '' : '/'}${hookId}`; const url = `${this.config.generic.urlPrefix}${this.config.generic.urlPrefix.endsWith('/') ? '' : '/'}${hookId}`;
await GenericHookConnection.ensureRoomAccountData(this.roomId, this.as, hookId, name); await GenericHookConnection.ensureRoomAccountData(this.roomId, this.as, hookId, name);
await this.as.botClient.sendStateEvent(this.roomId, GenericHookConnection.CanonicalEventType, name, {hookId, name}); await this.as.botClient.sendStateEvent(this.roomId, GenericHookConnection.CanonicalEventType, name, {hookId, name});
const adminRoom = await this.getOrCreateAdminRoom(userId); const adminRoom = await this.getOrCreateAdminRoom(userId);
await adminRoom.sendNotice(`You have bridged a webhook. Please configure your webhook source to use \`${url}\``); await adminRoom.sendNotice(`You have bridged a webhook. Please configure your webhook source to use \`${url}\`.`);
return this.as.botClient.sendHtmlNotice(this.roomId, md.renderInline(`Room configured to bridge webhooks. See admin room for secret url.`)); return this.as.botClient.sendHtmlNotice(this.roomId, md.renderInline(`Room configured to bridge webhooks. See admin room for secret url.`));
} }
@botCommand("figma file", "Bridge a Figma file to the room", ["url"], [], true) @botCommand("figma file", "Bridge a Figma file to the room.", ["url"], [], true)
public async onFigma(userId: string, url: string) { public async onFigma(userId: string, url: string) {
if (!this.config.figma) { if (!this.config.figma) {
throw new CommandError("not-configured", "The bridge is not configured to support Figma"); throw new CommandError("not-configured", "The bridge is not configured to support Figma.");
} }
if (!this.config.checkPermission(userId, "figma", BridgePermissionLevel.manageConnections)) { if (!this.config.checkPermission(userId, "figma", BridgePermissionLevel.manageConnections)) {
throw new CommandError('You are not permitted to provision connections for Figma'); throw new CommandError('You are not permitted to provision connections for Figma.');
} }
if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) { if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) {
throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations."); throw new CommandError("not-configured", "You must be able to set state in a room ('Change settings') in order to set up new integrations.");
} }
if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) { if (!await this.as.botClient.userHasPowerLevelFor(this.as.botUserId, this.roomId, GitHubRepoConnection.CanonicalEventType, true)) {
throw new CommandError("Bot lacks power level to set room state", "I do not have permission to setup a bridge in this room. Please promote me to an Admin/Moderator"); throw new CommandError("Bot lacks power level to set room state", "I do not have permission to set up a bridge in this room. Please promote me to an Admin/Moderator.");
} }
const res = /https:\/\/www\.figma\.com\/file\/(\w+).+/.exec(url); const res = /https:\/\/www\.figma\.com\/file\/(\w+).+/.exec(url);
if (!res) { if (!res) {
throw new CommandError("Invalid Figma url", "The Figma file url you entered was not valid. It should be in the format of `https://figma.com/file/FILEID/...`"); throw new CommandError("Invalid Figma url", "The Figma file url you entered was not valid. It should be in the format of `https://figma.com/file/FILEID/...`.");
} }
const [, fileId] = res; const [, fileId] = res;
await this.as.botClient.sendStateEvent(this.roomId, FigmaFileConnection.CanonicalEventType, fileId, {fileId}); await this.as.botClient.sendStateEvent(this.roomId, FigmaFileConnection.CanonicalEventType, fileId, {fileId});

View File

@ -23,19 +23,19 @@ export class GitHubBotCommands extends AdminRoomCommandHandler {
@botCommand("github login", {help: "Log in to GitHub", category: "github"}) @botCommand("github login", {help: "Log in to GitHub", category: "github"})
public async loginCommand() { public async loginCommand() {
if (!this.config.github) { if (!this.config.github) {
throw new CommandError("no-github-support", "The bridge is not configured with GitHub support"); throw new CommandError("no-github-support", "The bridge is not configured with GitHub support.");
} }
if (!this.config.github.oauth) { if (!this.config.github.oauth) {
throw new CommandError("no-github-support", "The bridge is not configured with GitHub OAuth support"); throw new CommandError("no-github-support", "The bridge is not configured with GitHub OAuth support.");
} }
const state = this.tokenStore.createStateForOAuth(this.userId); const state = this.tokenStore.createStateForOAuth(this.userId);
return this.sendNotice(`To login, open ${generateGitHubOAuthUrl(this.config.github.oauth.client_id, this.config.github.oauth.redirect_uri, state)} to link your account to the bridge`); return this.sendNotice(`Open ${generateGitHubOAuthUrl(this.config.github.oauth.client_id, this.config.github.oauth.redirect_uri, state)} to link your account to the bridge.`);
} }
@botCommand("github setpersonaltoken", {help: "Set your personal access token for GitHub", requiredArgs: ['accessToken'], category: "github"}) @botCommand("github setpersonaltoken", {help: "Set your personal access token for GitHub", requiredArgs: ['accessToken'], category: "github"})
public async setGHPersonalAccessToken(accessToken: string) { public async setGHPersonalAccessToken(accessToken: string) {
if (!this.config.github) { if (!this.config.github) {
throw new CommandError("no-github-support", "The bridge is not configured with GitHub support"); throw new CommandError("no-github-support", "The bridge is not configured with GitHub support.");
} }
let me; let me;
try { try {
@ -46,18 +46,18 @@ export class GitHubBotCommands extends AdminRoomCommandHandler {
await this.sendNotice("Could not authenticate with GitHub. Is your token correct?"); await this.sendNotice("Could not authenticate with GitHub. Is your token correct?");
return; return;
} }
await this.sendNotice(`Connected as ${me.data.login}. Token stored`); await this.sendNotice(`Connected as ${me.data.login}. Token stored.`);
await this.tokenStore.storeUserToken("github", this.userId, JSON.stringify({access_token: accessToken, token_type: 'pat'} as GitHubOAuthToken)); await this.tokenStore.storeUserToken("github", this.userId, JSON.stringify({access_token: accessToken, token_type: 'pat'} as GitHubOAuthToken));
} }
@botCommand("github hastoken", {help: "Check if you have a token stored for GitHub", category: "github"}) @botCommand("github hastoken", {help: "Check if you have a token stored for GitHub", category: "github"})
public async hasPersonalToken() { public async hasPersonalToken() {
if (!this.config.github) { if (!this.config.github) {
throw new CommandError("no-github-support", "The bridge is not configured with GitHub support"); throw new CommandError("no-github-support", "The bridge is not configured with GitHub support.");
} }
const result = await this.tokenStore.getUserToken("github", this.userId); const result = await this.tokenStore.getUserToken("github", this.userId);
if (result === null) { if (result === null) {
await this.sendNotice("You do not currently have a token stored"); await this.sendNotice("You do not currently have a token stored.");
return; return;
} }
await this.sendNotice("A token is stored for your GitHub account."); await this.sendNotice("A token is stored for your GitHub account.");

View File

@ -10,19 +10,19 @@ export class JiraBotCommands extends AdminRoomCommandHandler {
@botCommand("jira login", {help: "Log in to JIRA", category: "jira"}) @botCommand("jira login", {help: "Log in to JIRA", category: "jira"})
public async loginCommand() { public async loginCommand() {
if (!this.config.jira?.oauth || !this.tokenStore.jiraOAuth) { if (!this.config.jira?.oauth || !this.tokenStore.jiraOAuth) {
this.sendNotice(`Bot is not configured with JIRA OAuth support`); this.sendNotice(`Bot is not configured with JIRA OAuth support.`);
return; return;
} }
const state = this.tokenStore.createStateForOAuth(this.userId); const state = this.tokenStore.createStateForOAuth(this.userId);
const url = await this.tokenStore.jiraOAuth?.getAuthUrl(state); const url = await this.tokenStore.jiraOAuth?.getAuthUrl(state);
await this.sendNotice(`To login, open ${url} to link your account to the bridge`); await this.sendNotice(`Open ${url} to link your account to the bridge.`);
} }
@botCommand("jira logout", {help: "Clear any login information", category: "jira"}) @botCommand("jira logout", {help: "Clear any login information", category: "jira"})
public async logout() { public async logout() {
if (!this.config.jira?.oauth || !this.tokenStore.jiraOAuth) { if (!this.config.jira?.oauth || !this.tokenStore.jiraOAuth) {
this.sendNotice(`Bot is not configured with JIRA OAuth support`); this.sendNotice(`Bot is not configured with JIRA OAuth support.`);
return; return;
} }
if (await this.tokenStore.clearUserToken("jira", this.userId, this.config.jira.url || CLOUD_INSTANCE)) { if (await this.tokenStore.clearUserToken("jira", this.userId, this.config.jira.url || CLOUD_INSTANCE)) {
@ -34,13 +34,13 @@ export class JiraBotCommands extends AdminRoomCommandHandler {
@botCommand("jira whoami", {help: "Determine JIRA identity", category: "jira"}) @botCommand("jira whoami", {help: "Determine JIRA identity", category: "jira"})
public async whoami() { public async whoami() {
if (!this.config.jira) { if (!this.config.jira) {
await this.sendNotice(`Bot is not configured with JIRA OAuth support`); await this.sendNotice(`Bot is not configured with JIRA OAuth support.`);
return; return;
} }
const client = await this.tokenStore.getJiraForUser(this.userId, this.config.jira.url); const client = await this.tokenStore.getJiraForUser(this.userId, this.config.jira.url);
if (!client) { if (!client) {
await this.sendNotice(`You are not logged into JIRA`); await this.sendNotice(`You are not logged into JIRA.`);
return; return;
} }
// Get all resources for user // Get all resources for user
@ -49,10 +49,10 @@ export class JiraBotCommands extends AdminRoomCommandHandler {
resources = await client.getAccessibleResources(); resources = await client.getAccessibleResources();
} catch (ex) { } catch (ex) {
log.warn(`Could not request resources from JIRA API: `, ex); log.warn(`Could not request resources from JIRA API: `, ex);
await this.sendNotice(`Could not request JIRA resources due to an error`); await this.sendNotice(`Could not request JIRA resources due to an error.`);
return; return;
} }
let response = resources.length === 0 ? `You do not have any instances authorised with this bot` : 'You have access to the following instances:'; let response = resources.length === 0 ? `You do not have any instances authorised with this bot.` : 'You have access to the following instances:';
for (const resource of resources) { for (const resource of resources) {
const clientForResource = await client.getClientForResource(resource); const clientForResource = await client.getClientForResource(resource);
if (!clientForResource) { if (!clientForResource) {

View File

@ -134,7 +134,7 @@ export class JiraProvisionerRouter {
private async onOAuth(req: Request<undefined, undefined, undefined, {userId: string}>, res: Response<{url: string}>) { private async onOAuth(req: Request<undefined, undefined, undefined, {userId: string}>, res: Response<{url: string}>) {
if (!this.tokenStore.jiraOAuth) { if (!this.tokenStore.jiraOAuth) {
throw new ApiError('JIRA OAuth is disabled', ErrCode.DisabledFeature); throw new ApiError('JIRA OAuth is disabled.', ErrCode.DisabledFeature);
} }
const url = await this.tokenStore.jiraOAuth.getAuthUrl(this.tokenStore.createStateForOAuth(req.query.userId)); const url = await this.tokenStore.jiraOAuth.getAuthUrl(this.tokenStore.createStateForOAuth(req.query.userId));
res.send({ url }); res.send({ url });
@ -157,7 +157,7 @@ export class JiraProvisionerRouter {
} }
} catch (ex) { } catch (ex) {
log.warn(`Failed to fetch accessible resources for ${req.query.userId}`, ex); log.warn(`Failed to fetch accessible resources for ${req.query.userId}`, ex);
return next( new ApiError("Could not fetch accessible resources for JIRA user", ErrCode.Unknown)); return next( new ApiError("Could not fetch accessible resources for JIRA user.", ErrCode.Unknown));
} }
return res.send({ return res.send({
loggedIn: true, loggedIn: true,
@ -177,10 +177,10 @@ export class JiraProvisionerRouter {
resClient = await jiraUser.getClientForName(req.params.instanceName); resClient = await jiraUser.getClientForName(req.params.instanceName);
} catch (ex) { } catch (ex) {
log.warn(`Failed to fetch client for ${req.params.instanceName} for ${req.query.userId}`, ex); log.warn(`Failed to fetch client for ${req.params.instanceName} for ${req.query.userId}`, ex);
return next( new ApiError("Could not fetch accessible resources for JIRA user", ErrCode.Unknown)); return next( new ApiError("Could not fetch accessible resources for JIRA user.", ErrCode.Unknown));
} }
if (!resClient) { if (!resClient) {
return next( new ApiError("Instance not known or not accessible to this user", ErrCode.ForbiddenUser)); return next( new ApiError("Instance not known or not accessible to this user.", ErrCode.ForbiddenUser));
} }
const projects = []; const projects = [];
@ -195,7 +195,7 @@ export class JiraProvisionerRouter {
} }
} catch (ex) { } catch (ex) {
log.warn(`Failed to fetch accessible projects for ${req.params.instanceName} / ${req.query.userId}`, ex); log.warn(`Failed to fetch accessible projects for ${req.params.instanceName} / ${req.query.userId}`, ex);
return next( new ApiError("Could not fetch accessible projects for JIRA user", ErrCode.Unknown)); return next( new ApiError("Could not fetch accessible projects for JIRA user.", ErrCode.Unknown));
} }
return res.send(projects); return res.send(projects);

View File

@ -45,7 +45,7 @@ Request the connection types enabled for this bridge.
"type": "JiraProject", // The name of the connection "type": "JiraProject", // The name of the connection
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type for the connection "eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type for the connection
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty "service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future. "botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
} }
} }
``` ```
@ -62,7 +62,7 @@ Request the connections for a given room. The `{roomId}` parameter is the target
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection "eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable. "id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty "service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future. "botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": { "config": {
// ... connection specific details, can be configured. // ... connection specific details, can be configured.
} }
@ -82,7 +82,7 @@ Request details of a single connection. The `{roomId}` parameter is the target M
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection "eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable. "id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty "service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future. "botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": { "config": {
// ... connection specific details, can be configured. // ... connection specific details, can be configured.
} }
@ -110,7 +110,7 @@ The body of the request is the configuration for the connection, which will be t
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection "eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable. "id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty "service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future. "botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": { "config": {
// ... connection specific details, can be configured. // ... connection specific details, can be configured.
} }
@ -138,7 +138,7 @@ The body of the request is the configuration for the connection, which will be t
"eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection "eventType": "uk.half-shot.matrix-hookshot.jira.project", // Corresponds to the state type in the connection
"id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable. "id": "opaque-unique-id", // An opaque ID used to refer to this connection. Should **NOT** be assumed to be stable.
"service": "jira", // or github, webhook. A human-readable service name to make things look pretty "service": "jira", // or github, webhook. A human-readable service name to make things look pretty
"botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently this is the sender_localpart, but may change in the future. "botUserId": "@hookshot:yourdomain.com", // The bot mxid for the service. Currently, this is the sender_localpart, but may change in the future.
"config": { "config": {
// ... connection specific details, can be configured. // ... connection specific details, can be configured.
} }

View File

@ -185,10 +185,10 @@ export class Provisioner {
private getConnection(req: Request<{roomId: string, connectionId: string}>, res: Response<GetConnectionsResponseItem>) { private getConnection(req: Request<{roomId: string, connectionId: string}>, res: Response<GetConnectionsResponseItem>) {
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId); const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
if (!connection) { if (!connection) {
throw new ApiError("Connection does not exist", ErrCode.NotFound); throw new ApiError("Connection does not exist.", ErrCode.NotFound);
} }
if (!connection.getProvisionerDetails) { if (!connection.getProvisionerDetails) {
throw new ApiError("Connection type does not support updates", ErrCode.UnsupportedOperation); throw new ApiError("Connection type does not support updates.", ErrCode.UnsupportedOperation);
} }
return res.send(connection.getProvisionerDetails()); return res.send(connection.getProvisionerDetails());
} }
@ -197,15 +197,15 @@ export class Provisioner {
// Need to figure out which connections are available // Need to figure out which connections are available
try { try {
if (!req.body || typeof req.body !== "object") { if (!req.body || typeof req.body !== "object") {
throw new ApiError("A JSON body must be provided", ErrCode.BadValue); throw new ApiError("A JSON body must be provided.", ErrCode.BadValue);
} }
const connection = await this.connMan.provisionConnection(req.params.roomId, req.query.userId, req.params.type, req.body); const connection = await this.connMan.provisionConnection(req.params.roomId, req.query.userId, req.params.type, req.body);
if (!connection.getProvisionerDetails) { if (!connection.getProvisionerDetails) {
throw new Error('Connection supported provisioning but not getProvisionerDetails'); throw new Error('Connection supported provisioning but not getProvisionerDetails.');
} }
res.send(connection.getProvisionerDetails()); res.send(connection.getProvisionerDetails());
} catch (ex) { } catch (ex) {
log.warn(`Failed to create connection for ${req.params.roomId}`, ex); log.warn(`Failed to create connection for ${req.params.roomId}.`, ex);
return next(ex); return next(ex);
} }
} }
@ -214,10 +214,10 @@ export class Provisioner {
try { try {
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId); const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
if (!connection) { if (!connection) {
return next(new ApiError("Connection does not exist", ErrCode.NotFound)); return next(new ApiError("Connection does not exist.", ErrCode.NotFound));
} }
if (!connection.provisionerUpdateConfig || !connection.getProvisionerDetails) { if (!connection.provisionerUpdateConfig || !connection.getProvisionerDetails) {
return next(new ApiError("Connection type does not support updates", ErrCode.UnsupportedOperation)); return next(new ApiError("Connection type does not support updates.", ErrCode.UnsupportedOperation));
} }
await connection.provisionerUpdateConfig(req.query.userId, req.body); await connection.provisionerUpdateConfig(req.query.userId, req.body);
res.send(connection.getProvisionerDetails()); res.send(connection.getProvisionerDetails());
@ -230,10 +230,10 @@ export class Provisioner {
try { try {
const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId); const connection = this.connMan.getConnectionById(req.params.roomId, req.params.connectionId);
if (!connection) { if (!connection) {
return next(new ApiError("Connection does not exist", ErrCode.NotFound)); return next(new ApiError("Connection does not exist.", ErrCode.NotFound));
} }
if (!connection.onRemove) { if (!connection.onRemove) {
return next(new ApiError("Connection does not support removal", ErrCode.UnsupportedOperation)); return next(new ApiError("Connection does not support removal.", ErrCode.UnsupportedOperation));
} }
await this.connMan.purgeConnection(req.params.roomId, req.params.connectionId); await this.connMan.purgeConnection(req.params.roomId, req.params.connectionId);
res.send({ok: true}); res.send({ok: true});