mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 13:17:08 +00:00
A bunch of new features for GitHub Repo
This commit is contained in:
parent
144b7b840f
commit
433c906c7b
@ -49,7 +49,7 @@ export interface AdminAccountData {
|
||||
}
|
||||
|
||||
export class AdminRoom extends EventEmitter {
|
||||
public static helpMessage: MatrixMessageContent;
|
||||
public static helpMessage: () => MatrixMessageContent;
|
||||
private widgetAccessToken = `abcdef`;
|
||||
static botCommands: BotCommands;
|
||||
|
||||
@ -149,7 +149,7 @@ export class AdminRoom extends EventEmitter {
|
||||
|
||||
@botCommand("help", "This help text")
|
||||
public async helpCommand() {
|
||||
return this.botIntent.sendEvent(this.roomId, AdminRoom.helpMessage);
|
||||
return this.botIntent.sendEvent(this.roomId, AdminRoom.helpMessage());
|
||||
}
|
||||
|
||||
@botCommand("github setpersonaltoken", "Set your personal access token for GitHub", ['accessToken'])
|
||||
|
@ -24,7 +24,7 @@ export type BotCommands = {[prefix: string]: {
|
||||
includeUserId: boolean,
|
||||
}};
|
||||
|
||||
export function compileBotCommands(prototype: Record<string, BotCommandFunction>): {helpMessage: MatrixMessageContent, botCommands: BotCommands} {
|
||||
export function compileBotCommands(prototype: Record<string, BotCommandFunction>): {helpMessage: (cmdPrefix?: string) => MatrixMessageContent, botCommands: BotCommands} {
|
||||
let content = "Commands:\n";
|
||||
const botCommands: BotCommands = {};
|
||||
Object.getOwnPropertyNames(prototype).forEach(propetyKey => {
|
||||
@ -32,7 +32,7 @@ export function compileBotCommands(prototype: Record<string, BotCommandFunction>
|
||||
if (b) {
|
||||
const requiredArgs = b.requiredArgs.join(" ");
|
||||
const optionalArgs = b.optionalArgs.map((arg: string) => `[${arg}]`).join(" ");
|
||||
content += ` - \`${b.prefix}\` ${requiredArgs} ${optionalArgs} - ${b.help}\n`;
|
||||
content += ` - \`££PREFIX££${b.prefix}\` ${requiredArgs} ${optionalArgs} - ${b.help}\n`;
|
||||
// We know that this is safe.
|
||||
botCommands[b.prefix as string] = {
|
||||
fn: prototype[propetyKey],
|
||||
@ -43,17 +43,23 @@ export function compileBotCommands(prototype: Record<string, BotCommandFunction>
|
||||
}
|
||||
});
|
||||
return {
|
||||
helpMessage: {
|
||||
helpMessage: (cmdPrefix?: string) => ({
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.render(content),
|
||||
formatted_body: md.render(content).replace(/££PREFIX££/g, cmdPrefix || ""),
|
||||
format: "org.matrix.custom.html"
|
||||
},
|
||||
}),
|
||||
botCommands,
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleCommand(userId: string, command: string, botCommands: BotCommands, obj: unknown): Promise<{error?: string, handled?: boolean}> {
|
||||
export async function handleCommand(userId: string, command: string, botCommands: BotCommands, obj: unknown, prefix?: string): Promise<{error?: string, handled?: boolean, humanError?: string}> {
|
||||
if (prefix) {
|
||||
if (!command.startsWith(prefix)) {
|
||||
return {handled: false};
|
||||
}
|
||||
command = command.substring(prefix.length);
|
||||
}
|
||||
const parts = stringArgv(command);
|
||||
for (let i = parts.length; i > 0; i--) {
|
||||
const prefix = parts.slice(0, i).join(" ").toLowerCase();
|
||||
@ -71,7 +77,7 @@ export async function handleCommand(userId: string, command: string, botCommands
|
||||
await botCommands[prefix].fn.apply(obj, args);
|
||||
return {handled: true};
|
||||
} catch (ex) {
|
||||
return {handled: true, error: ex.message};
|
||||
return {handled: true, error: ex.message, humanError: ex.humanError};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,11 +146,11 @@ export class GitHubIssueConnection implements IConnection {
|
||||
}
|
||||
|
||||
public get org() {
|
||||
return this.state.org;
|
||||
return this.state.org.toLowerCase();
|
||||
}
|
||||
|
||||
public get repo() {
|
||||
return this.state.repo;
|
||||
return this.state.repo.toLowerCase();
|
||||
}
|
||||
|
||||
public async onIssueCommentCreated(event: IssueCommentCreatedEvent) {
|
||||
@ -316,7 +316,7 @@ export class GitHubIssueConnection implements IConnection {
|
||||
}
|
||||
}
|
||||
|
||||
public onIssueStateChange() {
|
||||
public onIssueStateChange(data?: any) {
|
||||
return this.syncIssueState();
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,9 @@ import { FormatUtil } from "../FormatUtil";
|
||||
import axios from "axios";
|
||||
import { BotCommands, handleCommand, botCommand, compileBotCommands } from "../BotCommands";
|
||||
import { ReposGetResponseData } from "../Github/Types";
|
||||
import { IssuesOpenedEvent, IssuesEditedEvent } from "@octokit/webhooks-types";
|
||||
import { IssuesOpenedEvent, IssuesEditedEvent, PullRequestOpenedEvent, PullRequestClosedEvent, PullRequestReadyForReviewEvent, PullRequestReviewSubmittedEvent, ReleaseCreatedEvent } from "@octokit/webhooks-types";
|
||||
import emoji from "node-emoji";
|
||||
import { NotLoggedInError } from "../errors";
|
||||
const log = new LogWrapper("GitHubRepoConnection");
|
||||
const md = new markdown();
|
||||
|
||||
@ -28,6 +29,8 @@ interface IQueryRoomOpts {
|
||||
export interface GitHubRepoConnectionState {
|
||||
org: string;
|
||||
repo: string;
|
||||
ignoreHooks?: string[],
|
||||
commandPrefix?: string;
|
||||
}
|
||||
|
||||
const GITHUB_REACTION_CONTENT: {[emoji: string]: string} = {
|
||||
@ -140,7 +143,7 @@ export class GitHubRepoConnection implements IConnection {
|
||||
};
|
||||
}
|
||||
|
||||
static helpMessage: MatrixMessageContent;
|
||||
static helpMessage: (cmdPrefix: string) => MatrixMessageContent;
|
||||
static botCommands: BotCommands;
|
||||
|
||||
constructor(public readonly roomId: string,
|
||||
@ -158,20 +161,31 @@ export class GitHubRepoConnection implements IConnection {
|
||||
return this.state.repo.toLowerCase();
|
||||
}
|
||||
|
||||
private get commandPrefix() {
|
||||
return (this.state.commandPrefix || "gh") + " ";
|
||||
}
|
||||
|
||||
public isInterestedInStateEvent() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public async onMessageEvent(ev: MatrixEvent<MatrixMessageContent>) {
|
||||
const { error, handled } = await handleCommand(ev.sender, ev.content.body, GitHubRepoConnection.botCommands, this);
|
||||
const { error, handled, humanError } = await handleCommand(ev.sender, ev.content.body, GitHubRepoConnection.botCommands, this, this.commandPrefix);
|
||||
if (!handled) {
|
||||
// Not for us.
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
await this.as.botClient.sendEvent(this.roomId, "m.reaction", {
|
||||
"m.relates_to": {
|
||||
rel_type: "m.annotation",
|
||||
event_id: ev.event_id,
|
||||
key: "⛔",
|
||||
}
|
||||
});
|
||||
await this.as.botIntent.sendEvent(this.roomId,{
|
||||
msgtype: "m.notice",
|
||||
body: "Failed to handle command",
|
||||
body: humanError ? `Failed to handle command: ${humanError}` : "Failed to handle command",
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -184,12 +198,17 @@ export class GitHubRepoConnection implements IConnection {
|
||||
});
|
||||
}
|
||||
|
||||
@botCommand("gh create", "Create an issue for this repo", ["title"], ["description", "labels"], true)
|
||||
@botCommand("help", "This help text")
|
||||
public async helpCommand() {
|
||||
return this.as.botIntent.sendEvent(this.roomId, GitHubRepoConnection.helpMessage(this.commandPrefix));
|
||||
}
|
||||
|
||||
@botCommand("create", "Create an issue for this repo", ["title"], ["description", "labels"], true)
|
||||
// @ts-ignore
|
||||
private async onCreateIssue(userId: string, title: string, description?: string, labels?: string) {
|
||||
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||
if (!octokit) {
|
||||
return this.as.botIntent.sendText(this.roomId, "You must login to create an issue", "m.notice");
|
||||
throw new NotLoggedInError();
|
||||
}
|
||||
const labelsNames = labels?.split(",");
|
||||
const res = await octokit.issues.create({
|
||||
@ -209,7 +228,7 @@ export class GitHubRepoConnection implements IConnection {
|
||||
});
|
||||
}
|
||||
|
||||
@botCommand("gh assign", "Assign an issue to a user", ["number", "...users"], [], true)
|
||||
@botCommand("assign", "Assign an issue to a user", ["number", "...users"], [], true)
|
||||
// @ts-ignore
|
||||
private async onAssign(userId: string, number: string, ...users: string[]) {
|
||||
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||
@ -229,7 +248,7 @@ export class GitHubRepoConnection implements IConnection {
|
||||
});
|
||||
}
|
||||
|
||||
@botCommand("gh close", "Close an issue", ["number"], ["comment"], true)
|
||||
@botCommand("close", "Close an issue", ["number"], ["comment"], true)
|
||||
// @ts-ignore
|
||||
private async onClose(userId: string, number: string, comment?: string) {
|
||||
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||
@ -255,6 +274,9 @@ export class GitHubRepoConnection implements IConnection {
|
||||
}
|
||||
|
||||
public async onIssueCreated(event: IssuesOpenedEvent) {
|
||||
if (this.shouldSkipHook('issue.created', 'issue')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onIssueCreated ${this.roomId} ${this.org}/${this.repo} #${event.issue?.number}`);
|
||||
if (!event.issue) {
|
||||
throw Error('No issue content!');
|
||||
@ -262,20 +284,13 @@ export class GitHubRepoConnection implements IConnection {
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
const orgRepoName = event.issue.repository_url.substr("https://api.github.com/repos/".length);
|
||||
const orgRepoName = event.repository.full_name;
|
||||
|
||||
const content = emoji.emojify(`${event.issue.user?.login} created new issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${event.issue.title}"`);
|
||||
const labelsHtml = (event.issue.labels || []).map((label: {color?: string|null, name?: string, description?: string|null}|string) =>
|
||||
typeof(label) === "string" ?
|
||||
`<span>${label}</span>` :
|
||||
`<span title="${label.description}" data-mx-color="#CCCCCC" data-mx-bg-color="#${label.color}">${label.name}</span>`
|
||||
).join(" ") || "";
|
||||
const labels = (event.issue?.labels || []).map((label: {name?: string}|string) =>
|
||||
typeof(label) === "string" ? label : label.name
|
||||
).join(", ") || "";
|
||||
const { labelsHtml, labelsStr } = FormatUtil.formatLabels(event.issue.labels);
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content + (labels.length > 0 ? ` with labels ${labels}`: ""),
|
||||
body: content + (labelsStr.length > 0 ? ` with labels ${labelsStr}`: ""),
|
||||
formatted_body: md.renderInline(content) + (labelsHtml.length > 0 ? ` with labels ${labelsHtml}`: ""),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types.
|
||||
@ -284,6 +299,9 @@ export class GitHubRepoConnection implements IConnection {
|
||||
}
|
||||
|
||||
public async onIssueStateChange(event: IssuesEditedEvent) {
|
||||
if (this.shouldSkipHook('issue.changed', 'issue')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onIssueStateChange ${this.roomId} ${this.org}/${this.repo} #${event.issue?.number}`);
|
||||
if (!event.issue) {
|
||||
throw Error('No issue content!');
|
||||
@ -291,18 +309,158 @@ export class GitHubRepoConnection implements IConnection {
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
if (event.issue.state === "closed" && event.sender) {
|
||||
const orgRepoName = event.issue.repository_url.substr("https://api.github.com/repos/".length);
|
||||
const content = `**@${event.sender.login}** closed issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${emoji.emojify(event.issue.title)}"`;
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.issue as any),
|
||||
});
|
||||
const state = event.issue.state === 'open' ? 'reopened' : 'closed';
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const content = `**${event.sender.login}** ${state} issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${emoji.emojify(event.issue.title)}"`;
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.issue as any),
|
||||
});
|
||||
}
|
||||
|
||||
public async onIssueEdited(event: IssuesEditedEvent) {
|
||||
if (this.shouldSkipHook('issue.edited', 'issue')) {
|
||||
return;
|
||||
}
|
||||
if (!event.issue) {
|
||||
throw Error('No issue content!');
|
||||
}
|
||||
log.info(`onIssueEdited ${this.roomId} ${this.org}/${this.repo} #${event.issue.number}`);
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const content = `**${event.sender.login}** edited issue [${orgRepoName}#${event.issue.number}](${event.issue.html_url}): "${emoji.emojify(event.issue.title)}"`;
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.issue as any),
|
||||
});
|
||||
}
|
||||
|
||||
public async onPROpened(event: PullRequestOpenedEvent) {
|
||||
if (this.shouldSkipHook('pull_request.opened', 'pull_request')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onPROpened ${this.roomId} ${this.org}/${this.repo} #${event.pull_request.number}`);
|
||||
if (!event.pull_request) {
|
||||
throw Error('No pull_request content!');
|
||||
}
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const verb = event.pull_request.draft ? 'drafted' : 'opened';
|
||||
const content = emoji.emojify(`**${event.pull_request.user?.login}** ${verb} a new PR [${orgRepoName}#${event.pull_request.number}](${event.pull_request.html_url}): "${event.pull_request.title}"`);
|
||||
const { labelsHtml, labelsStr } = FormatUtil.formatLabels(event.pull_request.labels);
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content + (labelsStr.length > 0 ? ` with labels ${labelsStr}`: ""),
|
||||
formatted_body: md.renderInline(content) + (labelsHtml.length > 0 ? ` with labels ${labelsHtml}`: ""),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types.
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.pull_request as any),
|
||||
});
|
||||
}
|
||||
|
||||
public async onPRReadyForReview(event: PullRequestReadyForReviewEvent) {
|
||||
if (this.shouldSkipHook('pull_request.ready_for_review', 'pull_request')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onPRReadyForReview ${this.roomId} ${this.org}/${this.repo} #${event.pull_request.number}`);
|
||||
if (!event.pull_request) {
|
||||
throw Error('No pull_request content!');
|
||||
}
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const content = emoji.emojify(`**${event.pull_request.user?.login}** has marked [${orgRepoName}#${event.pull_request.number}](${event.pull_request.html_url}) as ready to review (${event.pull_request.title})`);
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types.
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.pull_request as any),
|
||||
});
|
||||
}
|
||||
|
||||
public async onPRReviewed(event: PullRequestReviewSubmittedEvent) {
|
||||
if (this.shouldSkipHook('pull_request.reviewed', 'pull_request')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onPRReadyForReview ${this.roomId} ${this.org}/${this.repo} #${event.pull_request.number}`);
|
||||
if (!event.pull_request) {
|
||||
throw Error('No pull_request content!');
|
||||
}
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const emojiForReview = {'approved': '✅', 'commented': '🗨️', 'changes_requested': '🔴'}[event.review.state.toLowerCase()];
|
||||
if (!emojiForReview) {
|
||||
// We don't recongnise this state, run away!
|
||||
return;
|
||||
}
|
||||
const content = emoji.emojify(`**${event.review.user?.login}** ${emojiForReview} ${event.review.state.toLowerCase()} [${orgRepoName}#${event.pull_request.number}](${event.pull_request.html_url}) (${event.pull_request.title})`);
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types.
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.pull_request as any),
|
||||
});
|
||||
}
|
||||
|
||||
public async onPRClosed(event: PullRequestClosedEvent) {
|
||||
if (this.shouldSkipHook('pull_request.closed', 'pull_request')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onPRClosed ${this.roomId} ${this.org}/${this.repo} #${event.pull_request.number}`);
|
||||
if (!event.pull_request) {
|
||||
throw Error('No pull_request content!');
|
||||
}
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const verb = event.pull_request.merged ? 'merged' : 'closed';
|
||||
const content = emoji.emojify(`**${event.pull_request.user?.login}** ${verb} PR [${orgRepoName}#${event.pull_request.number}](${event.pull_request.html_url}): "${event.pull_request.title}"`);
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
// TODO: Fix types.
|
||||
...FormatUtil.getPartialBodyForIssue(event.repository, event.pull_request as any),
|
||||
});
|
||||
}
|
||||
|
||||
public async onReleaseCreated(event: ReleaseCreatedEvent) {
|
||||
if (this.shouldSkipHook('release', 'release.created')) {
|
||||
return;
|
||||
}
|
||||
log.info(`onReleaseCreated ${this.roomId} ${this.org}/${this.repo} #${event.release.tag_name}`);
|
||||
if (!event.release) {
|
||||
throw Error('No release content!');
|
||||
}
|
||||
if (!event.repository) {
|
||||
throw Error('No repository content!');
|
||||
}
|
||||
const orgRepoName = event.repository.full_name;
|
||||
const content = `🪄 **${event.release.author?.login}** released [${event.release.name}]${event.release.html_url} for ${orgRepoName}`;
|
||||
await this.as.botIntent.sendEvent(this.roomId, {
|
||||
msgtype: "m.notice",
|
||||
body: content,
|
||||
formatted_body: md.renderInline(content),
|
||||
format: "org.matrix.custom.html",
|
||||
});
|
||||
}
|
||||
|
||||
public async onEvent(evt: MatrixEvent<unknown>) {
|
||||
@ -316,6 +474,7 @@ export class GitHubRepoConnection implements IConnection {
|
||||
const ev = await this.as.botClient.getEvent(this.roomId, event_id);
|
||||
const issueContent = ev.content["uk.half-shot.matrix-github.issue"];
|
||||
if (!issueContent) {
|
||||
log.debug('Reaction to event did not pertain to a issue');
|
||||
return; // Not our event.
|
||||
}
|
||||
|
||||
@ -335,27 +494,37 @@ export class GitHubRepoConnection implements IConnection {
|
||||
]
|
||||
}
|
||||
});
|
||||
} else if (action && action[1] === "close") {
|
||||
} else if (action && action === "close") {
|
||||
await octokit.issues.update({
|
||||
state: "closed",
|
||||
owner: this.org,
|
||||
repo: this.repo,
|
||||
issue_number: ev.number,
|
||||
issue_number: issueContent.number,
|
||||
});
|
||||
} else if (action && action[1] === "open") {
|
||||
} else if (action && action === "open") {
|
||||
await octokit.issues.update({
|
||||
state: "open",
|
||||
owner: this.org,
|
||||
repo: this.repo,
|
||||
issue_number: ev.number,
|
||||
issue_number: issueContent.number,
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `GitHubRepo`;
|
||||
return `GitHubRepo ${this.org}/${this.repo}`;
|
||||
}
|
||||
|
||||
private shouldSkipHook(...hookName: string[]) {
|
||||
if (this.state.ignoreHooks) {
|
||||
for (const name of hookName) {
|
||||
if (this.state.ignoreHooks?.includes(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,6 @@ export class GitLabRepoConnection implements IConnection {
|
||||
GitLabRepoConnection.CanonicalEventType, // Legacy event, with an awful name.
|
||||
];
|
||||
|
||||
static helpMessage: MatrixMessageContent;
|
||||
static botCommands: BotCommands;
|
||||
|
||||
constructor(public readonly roomId: string,
|
||||
@ -131,5 +130,4 @@ export class GitLabRepoConnection implements IConnection {
|
||||
// Typescript doesn't understand Prototypes very well yet.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const res = compileBotCommands(GitLabRepoConnection.prototype as any);
|
||||
GitLabRepoConnection.helpMessage = res.helpMessage;
|
||||
GitLabRepoConnection.botCommands = res.botCommands;
|
@ -79,4 +79,19 @@ export class FormatUtil {
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
public static formatLabels(labels: Array<{color?: string|null, name?: string, description?: string|null}|string> = []) {
|
||||
const labelsHtml = labels.map((label: {color?: string|null, name?: string, description?: string|null}|string) =>
|
||||
typeof(label) === "string" ?
|
||||
`<span>${label}</span>` :
|
||||
`<span title="${label.description}" data-mx-color="#CCCCCC" data-mx-bg-color="#${label.color}">${label.name}</span>`
|
||||
).join(" ") || "";
|
||||
const labelsStr = labels.map((label: {name?: string}|string) =>
|
||||
typeof(label) === "string" ? label : label.name
|
||||
).join(", ") || "";
|
||||
return {
|
||||
labelsStr,
|
||||
labelsHtml,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,6 +141,8 @@ export class GithubBridge {
|
||||
}
|
||||
|
||||
private getConnectionsForGithubIssue(org: string, repo: string, issueNumber: number): (GitHubIssueConnection|GitLabRepoConnection)[] {
|
||||
org = org.toLowerCase();
|
||||
repo = repo.toLowerCase();
|
||||
return this.connections.filter((c) => (c instanceof GitHubIssueConnection && c.org === org && c.repo === repo && c.issueNumber === issueNumber) ||
|
||||
(c instanceof GitHubRepoConnection && c.org === org && c.repo === repo)) as (GitHubIssueConnection|GitLabRepoConnection)[];
|
||||
}
|
||||
@ -313,7 +315,7 @@ export class GithubBridge {
|
||||
if (c instanceof GitHubIssueConnection)
|
||||
await c.onIssueCommentCreated(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.issue_comment.created:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -325,7 +327,7 @@ export class GithubBridge {
|
||||
try {
|
||||
await c.onIssueCreated(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.opened:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -339,7 +341,7 @@ export class GithubBridge {
|
||||
if (c instanceof GitHubIssueConnection /* || c instanceof GitHubRepoConnection*/)
|
||||
await c.onIssueEdited(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.edited:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -350,9 +352,9 @@ export class GithubBridge {
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
if (c instanceof GitHubIssueConnection || c instanceof GitHubRepoConnection)
|
||||
await c.onIssueStateChange();
|
||||
await c.onIssueStateChange(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.closed:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -363,9 +365,76 @@ export class GithubBridge {
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
if (c instanceof GitHubIssueConnection || c instanceof GitHubRepoConnection)
|
||||
await c.onIssueStateChange();
|
||||
await c.onIssueStateChange(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.reopened:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
||||
const { repository, issue, owner } = validateRepoIssue(data);
|
||||
const connections = this.getConnectionsForGithubRepo(owner, repository.name);
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
await c.onIssueEdited(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.edited:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.queue.on<GitHubWebhookTypes.PullRequestOpenedEvent>("github.pull_request.opened", async ({ data }) => {
|
||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
await c.onPROpened(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.pull_request.opened:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.queue.on<GitHubWebhookTypes.PullRequestClosedEvent>("github.pull_request.closed", async ({ data }) => {
|
||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
await c.onPRClosed(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.pull_request.closed:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.queue.on<GitHubWebhookTypes.PullRequestReadyForReviewEvent>("github.pull_request.ready_for_review", async ({ data }) => {
|
||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
await c.onPRReadyForReview(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.pull_request.closed:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.queue.on<GitHubWebhookTypes.PullRequestReviewSubmittedEvent>("github.pull_request_review.submitted", async ({ data }) => {
|
||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
await c.onPRReviewed(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.pull_request.closed:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.queue.on<GitHubWebhookTypes.ReleaseCreatedEvent>("github.release.created", async ({ data }) => {
|
||||
const connections = this.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
||||
connections.map(async (c) => {
|
||||
try {
|
||||
await c.onReleaseCreated(data);
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${c.toString()} failed to handle github.pull_request.closed:`, ex);
|
||||
}
|
||||
})
|
||||
});
|
||||
@ -614,6 +683,10 @@ export class GithubBridge {
|
||||
/* We ignore messages from our users */
|
||||
return;
|
||||
}
|
||||
if (Date.now() - event.origin_server_ts > 30000) {
|
||||
/* We ignore old messages too */
|
||||
return;
|
||||
}
|
||||
log.info(`Got message roomId=${roomId} type=${event.type} from=${event.sender}`);
|
||||
console.log(event);
|
||||
log.debug("Content:", JSON.stringify(event));
|
||||
@ -699,15 +772,22 @@ export class GithubBridge {
|
||||
this.connections.push(connection);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
if (event.sender === this.as.botUserId) {
|
||||
// It's us
|
||||
return;
|
||||
}
|
||||
|
||||
// Alas, it's just an event.
|
||||
return this.connections.filter((c) => c.roomId === roomId).map((c) => c.onEvent ? c.onEvent(event) : undefined);
|
||||
for (const connection of this.connections.filter((c) => c.roomId === roomId)) {
|
||||
try {
|
||||
if (connection.onEvent) {
|
||||
await connection.onEvent(event);
|
||||
}
|
||||
} catch (ex) {
|
||||
log.warn(`Connection ${connection.toString()} failed to handle event:`, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async onQueryRoom(roomAlias: string) {
|
||||
|
@ -96,6 +96,7 @@ export class Webhooks extends EventEmitter {
|
||||
private async onGitHubPayload({id, name, payload}: EmitterWebhookEvent) {
|
||||
log.info(`Got GitHub webhook event ${id} ${name}`);
|
||||
console.log(payload);
|
||||
log.debug("Payload:", payload);
|
||||
const action = (payload as unknown as {action: string|undefined}).action;
|
||||
try {
|
||||
await this.queue.push({
|
||||
|
Loading…
x
Reference in New Issue
Block a user