mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
Refactor setup commands (#141)
* Ensure setup connection uses provisionConnection function * Drop unused parameters from JiraProjectConnection * Fix typo * Convert ApiErrors into CommandError when possible * Fix a stray bug * changelog
This commit is contained in:
parent
05852d9de8
commit
632791be63
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -147,7 +147,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "matrix-hookshot"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"contrast",
|
||||
"hex",
|
||||
|
1
changelog.d/141.misc
Normal file
1
changelog.d/141.misc
Normal file
@ -0,0 +1 @@
|
||||
Refactor setup commands code to use the same checks as the provisioning code.
|
@ -4,18 +4,17 @@ import { AdminAccountData, AdminRoomCommandHandler } from "./AdminRoomCommandHan
|
||||
import { botCommand, compileBotCommands, handleCommand, BotCommands } from "./BotCommands";
|
||||
import { BridgeConfig } from "./Config/Config";
|
||||
import { BridgeRoomState, BridgeRoomStateGitHub } from "./Widgets/BridgeWidgetInterface";
|
||||
import { CommandError } from "./errors";
|
||||
import { Endpoints } from "@octokit/types";
|
||||
import { FormatUtil } from "./FormatUtil";
|
||||
import { GetUserResponse } from "./Gitlab/Types";
|
||||
import { GitHubBotCommands } from "./Github/AdminCommands";
|
||||
import { GithubGraphQLClient, GithubInstance } from "./Github/GithubInstance";
|
||||
import { GithubGraphQLClient } from "./Github/GithubInstance";
|
||||
import { GitLabClient } from "./Gitlab/Client";
|
||||
import { Intent } from "matrix-bot-sdk";
|
||||
import { JiraBotCommands } from "./Jira/AdminCommands";
|
||||
import { MatrixMessageContent } from "./MatrixEvent";
|
||||
import { NotifFilter, NotificationFilterStateContent } from "./NotificationFilters";
|
||||
import { GitHubOAuthToken, ProjectsListResponseData } from "./Github/Types";
|
||||
import { ProjectsListResponseData } from "./Github/Types";
|
||||
import { UserTokenStore } from "./UserTokenStore";
|
||||
import {v4 as uuid} from "uuid";
|
||||
import LogWrapper from "./LogWrapper";
|
||||
@ -200,7 +199,7 @@ export class AdminRoom extends AdminRoomCommandHandler {
|
||||
if (!username) {
|
||||
const me = await octokit.users.getAuthenticated();
|
||||
// TODO: Fix
|
||||
username = me.data.name!;
|
||||
username = me.data.login;
|
||||
}
|
||||
|
||||
let res: ProjectsListResponseData;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import markdown from "markdown-it";
|
||||
import stringArgv from "string-argv";
|
||||
import { CommandError } from "./errors";
|
||||
import { ApiError } from "./provisioning/api";
|
||||
import { MatrixMessageContent } from "./MatrixEvent";
|
||||
|
||||
const md = new markdown();
|
||||
@ -15,7 +16,7 @@ export function botCommand(prefix: string, help: string, requiredArgs: string[]
|
||||
includeUserId,
|
||||
});
|
||||
}
|
||||
type BotCommandResult = {status: boolean, reaction?: string};
|
||||
type BotCommandResult = {status?: boolean, reaction?: string}|undefined;
|
||||
type BotCommandFunction = (...args: string[]) => Promise<BotCommandResult>;
|
||||
|
||||
export type BotCommands = {[prefix: string]: {
|
||||
@ -82,6 +83,9 @@ export async function handleCommand(userId: string, command: string, botCommands
|
||||
return {handled: true, result};
|
||||
} catch (ex) {
|
||||
const commandError = ex as CommandError;
|
||||
if (ex instanceof ApiError) {
|
||||
return {handled: true, error: ex.error, humanError: ex.error};
|
||||
}
|
||||
return {handled: true, error: commandError.message, humanError: commandError.humanError};
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ export class ConnectionManager {
|
||||
if (!this.config.jira) {
|
||||
throw Error('JIRA is not configured');
|
||||
}
|
||||
const res = await JiraProjectConnection.provisionConnection(roomId, userId, data, this.as, this.commentProcessor, this.messageClient, this.tokenStore);
|
||||
const res = await JiraProjectConnection.provisionConnection(roomId, userId, data, this.as, this.tokenStore);
|
||||
await this.as.botIntent.underlyingClient.sendStateEvent(roomId, JiraProjectConnection.CanonicalEventType, res.connection.stateKey, res.stateEventContent);
|
||||
this.push(res.connection);
|
||||
return res.connection;
|
||||
@ -191,7 +191,7 @@ export class ConnectionManager {
|
||||
if (!this.config.jira) {
|
||||
throw Error('JIRA is not configured');
|
||||
}
|
||||
return new JiraProjectConnection(roomId, this.as, state.content, state.stateKey, this.commentProcessor, this.messageClient, this.tokenStore);
|
||||
return new JiraProjectConnection(roomId, this.as, state.content, state.stateKey, this.tokenStore);
|
||||
}
|
||||
|
||||
if (FigmaFileConnection.EventTypes.includes(state.type)) {
|
||||
|
@ -48,7 +48,7 @@ export abstract class CommandConnection extends BaseConnection {
|
||||
log.warn(`Failed to handle command:`, error);
|
||||
return true;
|
||||
} else {
|
||||
const reaction = commandResult.result.reaction || '✅';
|
||||
const reaction = commandResult.result?.reaction || '✅';
|
||||
await this.botClient.sendEvent(this.roomId, "m.reaction", {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.annotation",
|
||||
|
@ -141,7 +141,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
||||
const validData = validateState(data);
|
||||
const octokit = await tokenStore.getOctokitForUser(userId);
|
||||
if (!octokit) {
|
||||
throw new ApiError("User is not authenticated with JIRA", ErrCode.ForbiddenUser);
|
||||
throw new ApiError("User is not authenticated with GitHub", ErrCode.ForbiddenUser);
|
||||
}
|
||||
const me = await octokit.users.getAuthenticated();
|
||||
let permissionLevel;
|
||||
@ -479,7 +479,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
||||
}
|
||||
const orgRepoName = event.repository.full_name;
|
||||
|
||||
let message = `${event.issue.user?.login} created new issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${event.issue.title}"`;
|
||||
let message = `**${event.issue.user.login}** created new issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${event.issue.title}"`;
|
||||
message += (event.issue.assignee ? ` assigned to ${event.issue.assignee.login}` : '');
|
||||
if (this.showIssueRoomLink) {
|
||||
const appInstance = await this.githubInstance.getSafeOctokitForRepo(this.org, this.repo);
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { IConnection } from "./IConnection";
|
||||
import { Appservice } from "matrix-bot-sdk";
|
||||
import LogWrapper from "../LogWrapper";
|
||||
import { CommentProcessor } from "../CommentProcessor";
|
||||
import { MessageSenderClient } from "../MatrixSender"
|
||||
import { JiraIssueEvent, JiraIssueUpdatedEvent } from "../Jira/WebhookTypes";
|
||||
import { FormatUtil } from "../FormatUtil";
|
||||
import markdownit from "markdown-it";
|
||||
@ -64,8 +62,7 @@ export class JiraProjectConnection extends CommandConnection implements IConnect
|
||||
static botCommands: BotCommands;
|
||||
static helpMessage: (cmdPrefix?: string) => MatrixMessageContent;
|
||||
|
||||
static async provisionConnection(roomId: string, userId: string, data: Record<string, unknown>, as: Appservice,
|
||||
commentProcessor: CommentProcessor, messageClient: MessageSenderClient, tokenStore: UserTokenStore) {
|
||||
static async provisionConnection(roomId: string, userId: string, data: Record<string, unknown>, as: Appservice, tokenStore: UserTokenStore) {
|
||||
const validData = validateJiraConnectionState(data);
|
||||
const jiraClient = await tokenStore.getJiraForUser(userId);
|
||||
if (!jiraClient) {
|
||||
@ -75,7 +72,7 @@ export class JiraProjectConnection extends CommandConnection implements IConnect
|
||||
if (!jiraResourceClient) {
|
||||
throw new ApiError("User is not authenticated with this JIRA instance", ErrCode.ForbiddenUser);
|
||||
}
|
||||
const connection = new JiraProjectConnection(roomId, as, data, validData.url, commentProcessor, messageClient, tokenStore);
|
||||
const connection = new JiraProjectConnection(roomId, as, data, validData.url, tokenStore);
|
||||
if (!connection.projectKey) {
|
||||
throw Error('Expected projectKey to be defined');
|
||||
}
|
||||
@ -83,7 +80,7 @@ export class JiraProjectConnection extends CommandConnection implements IConnect
|
||||
// Just need to check that the user can access this.
|
||||
await jiraResourceClient.getProject(connection.projectKey);
|
||||
} catch (ex) {
|
||||
throw new ApiError("User cannot open this project", ErrCode.ForbiddenUser);
|
||||
throw new ApiError("Requested project was not found", ErrCode.ForbiddenUser);
|
||||
}
|
||||
log.info(`Created connection via provisionConnection ${connection.toString()}`);
|
||||
return {stateEventContent: validData, connection};
|
||||
@ -131,8 +128,6 @@ export class JiraProjectConnection extends CommandConnection implements IConnect
|
||||
private readonly as: Appservice,
|
||||
private state: JiraProjectConnectionState,
|
||||
stateKey: string,
|
||||
private readonly commentProcessor: CommentProcessor,
|
||||
private readonly messageClient: MessageSenderClient,
|
||||
private readonly tokenStore: UserTokenStore,) {
|
||||
super(
|
||||
roomId,
|
||||
|
@ -2,21 +2,17 @@
|
||||
import { Appservice } from "matrix-bot-sdk";
|
||||
import { BotCommands, botCommand, compileBotCommands } from "../BotCommands";
|
||||
import { MatrixMessageContent } from "../MatrixEvent";
|
||||
import LogWrapper from "../LogWrapper";
|
||||
import { CommandConnection } from "./CommandConnection";
|
||||
import { GenericHookConnection, GitHubRepoConnection, GitHubRepoConnectionState, JiraProjectConnection, JiraProjectConnectionState } from ".";
|
||||
import { GenericHookConnection, GitHubRepoConnection, JiraProjectConnection } from ".";
|
||||
import { CommandError } from "../errors";
|
||||
import { UserTokenStore } from "../UserTokenStore";
|
||||
import { GithubInstance } from "../Github/GithubInstance";
|
||||
import { JiraProject } from "../Jira/Types";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { BridgeConfig } from "../Config/Config";
|
||||
import markdown from "markdown-it";
|
||||
import { FigmaFileConnection } from "./FigmaFileConnection";
|
||||
const md = new markdown();
|
||||
|
||||
const log = new LogWrapper("SetupConnection");
|
||||
|
||||
/**
|
||||
* Handles setting up a room with connections. This connection is "virtual" in that it has
|
||||
* no state, and is only invoked when messages from other clients fall through.
|
||||
@ -44,7 +40,7 @@ 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)
|
||||
public async onGitHubRepo(userId: string, url: string) {
|
||||
if (!this.githubInstance) {
|
||||
if (!this.githubInstance || !this.config.github) {
|
||||
throw new CommandError("not-configured", "The bridge is not configured to support GitHub");
|
||||
}
|
||||
if (!await this.as.botClient.userHasPowerLevelFor(userId, this.roomId, "", true)) {
|
||||
@ -57,30 +53,14 @@ export class SetupConnection extends CommandConnection {
|
||||
if (!octokit) {
|
||||
throw new CommandError("User not logged in", "You are not logged into GitHub. Start a DM with this bot and use the command `github login`.");
|
||||
}
|
||||
const res = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(url.trim().toLowerCase());
|
||||
if (!res) {
|
||||
const urlParts = /^https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/.exec(url.trim().toLowerCase());
|
||||
if (!urlParts) {
|
||||
throw new CommandError("Invalid GitHub url", "The GitHub url you entered was not valid");
|
||||
}
|
||||
const [, org, repo] = res;
|
||||
let resultRepo
|
||||
try {
|
||||
resultRepo = await octokit.repos.get({owner: org, repo});
|
||||
} catch (ex) {
|
||||
throw new CommandError("Invalid GitHub repo", "Could not find the requested GitHub repo. Do you have permission to view it?");
|
||||
}
|
||||
// Check if we have a webhook for this repo
|
||||
try {
|
||||
await this.githubInstance.getOctokitForRepo(org, repo);
|
||||
} catch (ex) {
|
||||
log.warn(`No app instance for new git connection:`, ex);
|
||||
// We might be able to do it via a personal access token
|
||||
await this.as.botClient.sendNotice(this.roomId, `Note: There doesn't appear to be a GitHub App install that covers this repository so webhooks won't work.`)
|
||||
}
|
||||
await this.as.botClient.sendStateEvent(this.roomId, GitHubRepoConnection.CanonicalEventType, url, {
|
||||
org,
|
||||
repo,
|
||||
} as GitHubRepoConnectionState);
|
||||
await this.as.botClient.sendNotice(this.roomId, `Room configured to bridge ${resultRepo.data.full_name}`);
|
||||
const [, org, repo] = urlParts;
|
||||
const res = await GitHubRepoConnection.provisionConnection(this.roomId, userId, {org, repo}, this.as, this.tokenStore, this.githubInstance, this.config.github);
|
||||
await this.as.botClient.sendStateEvent(this.roomId, GitHubRepoConnection.CanonicalEventType, url, res.stateEventContent);
|
||||
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)
|
||||
@ -98,27 +78,15 @@ export class SetupConnection extends CommandConnection {
|
||||
if (!jiraClient) {
|
||||
throw new CommandError("User not logged in", "You are not logged into Jira. Start a DM with this bot and use the command `jira login`.");
|
||||
}
|
||||
const res = /^https:\/\/([A-z.\-_]+)\/.+\/projects\/(\w+)\/?(\w+\/?)*$/.exec(url.trim().toLowerCase());
|
||||
if (!res) {
|
||||
const urlParts = /^https:\/\/([A-z.\-_]+)\/.+\/projects\/(\w+)\/?(\w+\/?)*$/.exec(url.trim().toLowerCase());
|
||||
if (!urlParts) {
|
||||
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/...`");
|
||||
}
|
||||
const [, origin, projectKey] = res;
|
||||
const [, origin, projectKey] = urlParts;
|
||||
const safeUrl = `https://${origin}/projects/${projectKey}`;
|
||||
const jiraOriginClient = await jiraClient.getClientForUrl(new URL(safeUrl));
|
||||
if (!jiraOriginClient) {
|
||||
throw new CommandError("User does not have permission to access this JIRA instance", "You do not have access to this JIRA instance. You may need to log into Jira again to provide access");
|
||||
}
|
||||
let jiraProject: JiraProject;
|
||||
try {
|
||||
jiraProject = await jiraOriginClient.getProject(projectKey.toUpperCase());
|
||||
} catch (ex) {
|
||||
log.warn(`Failed to get jira project:`, ex);
|
||||
throw new CommandError("Missing or invalid JIRA project", "Could not find the requested JIRA project. Do you have permission to view it?");
|
||||
}
|
||||
await this.as.botClient.sendStateEvent(this.roomId, JiraProjectConnection.CanonicalEventType, safeUrl, {
|
||||
url: safeUrl,
|
||||
} as JiraProjectConnectionState);
|
||||
await this.as.botClient.sendNotice(this.roomId, `Room configured to bridge Jira project '${jiraProject.name}' (${jiraProject.key})`);
|
||||
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.sendNotice(this.roomId, `Room configured to bridge Jira project ${projectKey}`);
|
||||
}
|
||||
|
||||
@botCommand("webhook", "Create a inbound webhook", ["name"], [], true)
|
||||
|
Loading…
x
Reference in New Issue
Block a user