mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
Properly handle multiple installations
This commit is contained in:
parent
3efd2b2bd3
commit
38cfdc5d8a
@ -19,6 +19,7 @@ import { ProjectsListResponseData } from "./Github/Types";
|
|||||||
import { NotifFilter, NotificationFilterStateContent } from "./NotificationFilters";
|
import { NotifFilter, NotificationFilterStateContent } from "./NotificationFilters";
|
||||||
import { JiraBotCommands } from "./Jira/AdminCommands";
|
import { JiraBotCommands } from "./Jira/AdminCommands";
|
||||||
import { AdminAccountData, AdminRoomCommandHandler } from "./AdminRoomCommandHandler";
|
import { AdminAccountData, AdminRoomCommandHandler } from "./AdminRoomCommandHandler";
|
||||||
|
import { CommandError } from "./errors";
|
||||||
|
|
||||||
type ProjectsListForRepoResponseData = Endpoints["GET /repos/{owner}/{repo}/projects"]["response"];
|
type ProjectsListForRepoResponseData = Endpoints["GET /repos/{owner}/{repo}/projects"]["response"];
|
||||||
type ProjectsListForUserResponseData = Endpoints["GET /users/{username}/projects"]["response"];
|
type ProjectsListForUserResponseData = Endpoints["GET /users/{username}/projects"]["response"];
|
||||||
@ -137,10 +138,9 @@ export class AdminRoom extends AdminRoomCommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("github setpersonaltoken", "Set your personal access token for GitHub", ['accessToken'])
|
@botCommand("github setpersonaltoken", "Set your personal access token for GitHub", ['accessToken'])
|
||||||
// @ts-ignore - property is used
|
public async setGHPersonalAccessToken(accessToken: string) {
|
||||||
private async setGHPersonalAccessToken(accessToken: string) {
|
|
||||||
if (!this.config.github) {
|
if (!this.config.github) {
|
||||||
return this.sendNotice("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 {
|
||||||
@ -156,10 +156,9 @@ export class AdminRoom extends AdminRoomCommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("github hastoken", "Check if you have a token stored for GitHub")
|
@botCommand("github hastoken", "Check if you have a token stored for GitHub")
|
||||||
// @ts-ignore - property is used
|
public async hasPersonalToken() {
|
||||||
private async hasPersonalToken() {
|
|
||||||
if (!this.config.github) {
|
if (!this.config.github) {
|
||||||
return this.sendNotice("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) {
|
||||||
@ -170,10 +169,9 @@ export class AdminRoom extends AdminRoomCommandHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("github startoauth", "Start the OAuth process with GitHub")
|
@botCommand("github startoauth", "Start the OAuth process with GitHub")
|
||||||
// @ts-ignore - property is used
|
public async beginOAuth() {
|
||||||
private async beginOAuth() {
|
|
||||||
if (!this.config.github) {
|
if (!this.config.github) {
|
||||||
return this.sendNotice("The bridge is not configured with GitHub support");
|
throw new CommandError("no-github-support", "The bridge is not configured with GitHub support");
|
||||||
}
|
}
|
||||||
// If this is already set, calling this command will invalidate the previous session.
|
// If this is already set, calling this command will invalidate the previous session.
|
||||||
this.pendingOAuthState = uuid();
|
this.pendingOAuthState = uuid();
|
||||||
|
@ -20,7 +20,7 @@ import { MessageQueue, createMessageQueue } from "./MessageQueue";
|
|||||||
import { MessageSenderClient } from "./MatrixSender";
|
import { MessageSenderClient } from "./MatrixSender";
|
||||||
import { NotifFilter, NotificationFilterStateContent } from "./NotificationFilters";
|
import { NotifFilter, NotificationFilterStateContent } from "./NotificationFilters";
|
||||||
import { NotificationProcessor } from "./NotificationsProcessor";
|
import { NotificationProcessor } from "./NotificationsProcessor";
|
||||||
import { GitHubOAuthTokens, NotificationsEnableEvent, NotificationsDisableEvent, GenericWebhookEvent,} from "./Webhooks";
|
import { GitHubOAuthTokens, NotificationsEnableEvent, NotificationsDisableEvent, GenericWebhookEvent } from "./Webhooks";
|
||||||
import { ProjectsGetResponseData } from "./Github/Types";
|
import { ProjectsGetResponseData } from "./Github/Types";
|
||||||
import { RedisStorageProvider } from "./Stores/RedisStorageProvider";
|
import { RedisStorageProvider } from "./Stores/RedisStorageProvider";
|
||||||
import { retry } from "./PromiseUtil";
|
import { retry } from "./PromiseUtil";
|
||||||
@ -29,6 +29,7 @@ import { UserTokenStore } from "./UserTokenStore";
|
|||||||
import * as GitHubWebhookTypes from "@octokit/webhooks-types";
|
import * as GitHubWebhookTypes from "@octokit/webhooks-types";
|
||||||
import LogWrapper from "./LogWrapper";
|
import LogWrapper from "./LogWrapper";
|
||||||
import { OAuthRequest } from "./WebhookTypes";
|
import { OAuthRequest } from "./WebhookTypes";
|
||||||
|
import { promises as fs } from "fs";
|
||||||
const log = new LogWrapper("Bridge");
|
const log = new LogWrapper("Bridge");
|
||||||
|
|
||||||
export class Bridge {
|
export class Bridge {
|
||||||
@ -84,7 +85,7 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.config.github) {
|
if (this.config.github) {
|
||||||
this.github = new GithubInstance(this.config.github);
|
this.github = new GithubInstance(this.config.github.auth.id, await fs.readFile(this.config.github.auth.privateKeyFile, 'utf-8'));
|
||||||
await this.github.start();
|
await this.github.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +160,20 @@ export class Bridge {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
this.queue.on<GitHubWebhookTypes.InstallationCreatedEvent>("github.installation.created", async (data) => {
|
||||||
|
this.github?.onInstallationCreated(data.data);
|
||||||
|
});
|
||||||
|
this.queue.on<GitHubWebhookTypes.InstallationUnsuspendEvent>("github.installation.unsuspend", async (data) => {
|
||||||
|
this.github?.onInstallationCreated(data.data);
|
||||||
|
});
|
||||||
|
this.queue.on<GitHubWebhookTypes.InstallationDeletedEvent>("github.installation.deleted", async (data) => {
|
||||||
|
this.github?.onInstallationRemoved(data.data);
|
||||||
|
});
|
||||||
|
this.queue.on<GitHubWebhookTypes.InstallationSuspendEvent>("github.installation.suspend", async (data) => {
|
||||||
|
this.github?.onInstallationRemoved(data.data);
|
||||||
|
});
|
||||||
|
|
||||||
this.bindHandlerToQueue<GitHubWebhookTypes.IssueCommentCreatedEvent, GitHubIssueConnection>(
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssueCommentCreatedEvent, GitHubIssueConnection>(
|
||||||
"github.issue_comment.created",
|
"github.issue_comment.created",
|
||||||
(data) => {
|
(data) => {
|
||||||
@ -401,6 +416,7 @@ export class Bridge {
|
|||||||
await this.tokenStore.storeUserToken("jira", adminRoom.userId, JSON.stringify(msg.data));
|
await this.tokenStore.storeUserToken("jira", adminRoom.userId, JSON.stringify(msg.data));
|
||||||
await adminRoom.sendNotice(`Logged into Jira`);
|
await adminRoom.sendNotice(`Logged into Jira`);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.bindHandlerToQueue<GenericWebhookEvent, GenericHookConnection>(
|
this.bindHandlerToQueue<GenericWebhookEvent, GenericHookConnection>(
|
||||||
"generic-webhook.event",
|
"generic-webhook.event",
|
||||||
(data) => connManager.getConnectionsForGenericWebhook(data.hookId),
|
(data) => connManager.getConnectionsForGenericWebhook(data.hookId),
|
||||||
@ -693,7 +709,7 @@ export class Bridge {
|
|||||||
tokenStore: this.tokenStore,
|
tokenStore: this.tokenStore,
|
||||||
messageClient: this.messageClient,
|
messageClient: this.messageClient,
|
||||||
commentProcessor: this.commentProcessor,
|
commentProcessor: this.commentProcessor,
|
||||||
octokit: this.github.octokit,
|
githubInstance: this.github,
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.error(`Could not handle alias with GitHubIssueConnection`, ex);
|
log.error(`Could not handle alias with GitHubIssueConnection`, ex);
|
||||||
@ -708,7 +724,7 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await GitHubDiscussionSpace.onQueryRoom(res, {
|
return await GitHubDiscussionSpace.onQueryRoom(res, {
|
||||||
octokit: this.github.octokit,
|
githubInstance: this.github,
|
||||||
as: this.as,
|
as: this.as,
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
@ -728,7 +744,7 @@ export class Bridge {
|
|||||||
tokenStore: this.tokenStore,
|
tokenStore: this.tokenStore,
|
||||||
messageClient: this.messageClient,
|
messageClient: this.messageClient,
|
||||||
commentProcessor: this.commentProcessor,
|
commentProcessor: this.commentProcessor,
|
||||||
octokit: this.github.octokit,
|
githubInstance: this.github,
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
log.error(`Could not handle alias with GitHubRepoConnection`, ex);
|
log.error(`Could not handle alias with GitHubRepoConnection`, ex);
|
||||||
@ -743,7 +759,7 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await GitHubUserSpace.onQueryRoom(res, {
|
return await GitHubUserSpace.onQueryRoom(res, {
|
||||||
octokit: this.github.octokit,
|
githubInstance: this.github,
|
||||||
as: this.as,
|
as: this.as,
|
||||||
});
|
});
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
|
@ -5,6 +5,7 @@ import { Octokit } from "@octokit/rest";
|
|||||||
import { ReposGetResponseData } from "../Github/Types";
|
import { ReposGetResponseData } from "../Github/Types";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { GitHubDiscussionConnection } from "./GithubDiscussion";
|
import { GitHubDiscussionConnection } from "./GithubDiscussion";
|
||||||
|
import { GithubInstance } from "../Github/GithubInstance";
|
||||||
|
|
||||||
const log = new LogWrapper("GitHubDiscussionSpace");
|
const log = new LogWrapper("GitHubDiscussionSpace");
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ export class GitHubDiscussionSpace implements IConnection {
|
|||||||
|
|
||||||
static readonly QueryRoomRegex = /#github_disc_(.+)_(.+):.*/;
|
static readonly QueryRoomRegex = /#github_disc_(.+)_(.+):.*/;
|
||||||
|
|
||||||
static async onQueryRoom(result: RegExpExecArray, opts: {octokit: Octokit, as: Appservice}): Promise<Record<string, unknown>> {
|
static async onQueryRoom(result: RegExpExecArray, opts: {githubInstance: GithubInstance, as: Appservice}): Promise<Record<string, unknown>> {
|
||||||
if (!result || result.length < 2) {
|
if (!result || result.length < 2) {
|
||||||
log.error(`Invalid alias pattern '${result}'`);
|
log.error(`Invalid alias pattern '${result}'`);
|
||||||
throw Error("Could not find issue");
|
throw Error("Could not find issue");
|
||||||
@ -37,9 +38,10 @@ export class GitHubDiscussionSpace implements IConnection {
|
|||||||
|
|
||||||
log.info(`Fetching ${owner}/${repo}`);
|
log.info(`Fetching ${owner}/${repo}`);
|
||||||
let repoRes: ReposGetResponseData;
|
let repoRes: ReposGetResponseData;
|
||||||
|
const octokit = opts.githubInstance.getOctokitForRepo(owner, repo);
|
||||||
try {
|
try {
|
||||||
// TODO: Determine if the repo has discussions?
|
// TODO: Determine if the repo has discussions?
|
||||||
repoRes = (await opts.octokit.repos.get({
|
repoRes = (await octokit.repos.get({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
})).data;
|
})).data;
|
||||||
@ -58,7 +60,7 @@ export class GitHubDiscussionSpace implements IConnection {
|
|||||||
// URL hack so we don't need to fetch the repo itself.
|
// URL hack so we don't need to fetch the repo itself.
|
||||||
let avatarUrl = undefined;
|
let avatarUrl = undefined;
|
||||||
try {
|
try {
|
||||||
const profile = await opts.octokit.users.getByUsername({
|
const profile = await octokit.users.getByUsername({
|
||||||
username: owner,
|
username: owner,
|
||||||
});
|
});
|
||||||
if (profile.data.avatar_url) {
|
if (profile.data.avatar_url) {
|
||||||
|
@ -5,7 +5,6 @@ import markdown from "markdown-it";
|
|||||||
import { UserTokenStore } from "../UserTokenStore";
|
import { UserTokenStore } from "../UserTokenStore";
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import { CommentProcessor } from "../CommentProcessor";
|
import { CommentProcessor } from "../CommentProcessor";
|
||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import { MessageSenderClient } from "../MatrixSender";
|
import { MessageSenderClient } from "../MatrixSender";
|
||||||
import { getIntentForUser } from "../IntentUtils";
|
import { getIntentForUser } from "../IntentUtils";
|
||||||
import { FormatUtil } from "../FormatUtil";
|
import { FormatUtil } from "../FormatUtil";
|
||||||
@ -31,7 +30,7 @@ interface IQueryRoomOpts {
|
|||||||
tokenStore: UserTokenStore;
|
tokenStore: UserTokenStore;
|
||||||
commentProcessor: CommentProcessor;
|
commentProcessor: CommentProcessor;
|
||||||
messageClient: MessageSenderClient;
|
messageClient: MessageSenderClient;
|
||||||
octokit: Octokit;
|
githubInstance: GithubInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,8 +60,9 @@ export class GitHubIssueConnection implements IConnection {
|
|||||||
|
|
||||||
log.info(`Fetching ${owner}/${repo}/${issueNumber}`);
|
log.info(`Fetching ${owner}/${repo}/${issueNumber}`);
|
||||||
let issue: IssuesGetResponseData;
|
let issue: IssuesGetResponseData;
|
||||||
|
const octokit = opts.githubInstance.getOctokitForRepo(owner, repo);
|
||||||
try {
|
try {
|
||||||
issue = (await opts.octokit.issues.get({
|
issue = (await octokit.issues.get({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
issue_number: issueNumber,
|
issue_number: issueNumber,
|
||||||
@ -78,7 +78,7 @@ export class GitHubIssueConnection implements IConnection {
|
|||||||
const orgRepoName = issue.repository_url.substr("https://api.github.com/repos/".length);
|
const orgRepoName = issue.repository_url.substr("https://api.github.com/repos/".length);
|
||||||
let avatarUrl = undefined;
|
let avatarUrl = undefined;
|
||||||
try {
|
try {
|
||||||
const profile = await opts.octokit.users.getByUsername({
|
const profile = await octokit.users.getByUsername({
|
||||||
username: owner,
|
username: owner,
|
||||||
});
|
});
|
||||||
if (profile.data.avatar_url) {
|
if (profile.data.avatar_url) {
|
||||||
@ -200,7 +200,7 @@ export class GitHubIssueConnection implements IConnection {
|
|||||||
|
|
||||||
public async syncIssueState() {
|
public async syncIssueState() {
|
||||||
log.debug("Syncing issue state for", this.roomId);
|
log.debug("Syncing issue state for", this.roomId);
|
||||||
const issue = await this.github.octokit.issues.get({
|
const issue = await this.github.getOctokitForRepo(this.org, this.repo).issues.get({
|
||||||
owner: this.state.org,
|
owner: this.state.org,
|
||||||
repo: this.state.repo,
|
repo: this.state.repo,
|
||||||
issue_number: this.issueNumber,
|
issue_number: this.issueNumber,
|
||||||
@ -231,7 +231,7 @@ export class GitHubIssueConnection implements IConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.comments_processed !== issue.data.comments) {
|
if (this.state.comments_processed !== issue.data.comments) {
|
||||||
const comments = (await this.github.octokit.issues.listComments({
|
const comments = (await this.github.getOctokitForRepo(this.org, this.repo).issues.listComments({
|
||||||
owner: this.state.org,
|
owner: this.state.org,
|
||||||
repo: this.state.repo,
|
repo: this.state.repo,
|
||||||
issue_number: this.issueNumber,
|
issue_number: this.issueNumber,
|
||||||
|
@ -8,15 +8,14 @@ import { IssuesOpenedEvent, IssuesReopenedEvent, IssuesEditedEvent, PullRequestO
|
|||||||
import { MatrixMessageContent, MatrixEvent, MatrixReactionContent } from "../MatrixEvent";
|
import { MatrixMessageContent, MatrixEvent, MatrixReactionContent } from "../MatrixEvent";
|
||||||
import { MessageSenderClient } from "../MatrixSender";
|
import { MessageSenderClient } from "../MatrixSender";
|
||||||
import { CommandError, NotLoggedInError } from "../errors";
|
import { CommandError, NotLoggedInError } from "../errors";
|
||||||
import { RequestError } from "@octokit/types";
|
|
||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import { ReposGetResponseData } from "../Github/Types";
|
import { ReposGetResponseData } from "../Github/Types";
|
||||||
import { UserTokenStore } from "../UserTokenStore";
|
import { UserTokenStore } from "../UserTokenStore";
|
||||||
import axios, { Axios, AxiosError } from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
import emoji from "node-emoji";
|
import emoji from "node-emoji";
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import markdown from "markdown-it";
|
import markdown from "markdown-it";
|
||||||
import { CommandConnection } from "./CommandConnection";
|
import { CommandConnection } from "./CommandConnection";
|
||||||
|
import { GithubInstance } from "../Github/GithubInstance";
|
||||||
const log = new LogWrapper("GitHubRepoConnection");
|
const log = new LogWrapper("GitHubRepoConnection");
|
||||||
const md = new markdown();
|
const md = new markdown();
|
||||||
|
|
||||||
@ -25,7 +24,7 @@ interface IQueryRoomOpts {
|
|||||||
tokenStore: UserTokenStore;
|
tokenStore: UserTokenStore;
|
||||||
commentProcessor: CommentProcessor;
|
commentProcessor: CommentProcessor;
|
||||||
messageClient: MessageSenderClient;
|
messageClient: MessageSenderClient;
|
||||||
octokit: Octokit;
|
githubInstance: GithubInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitHubRepoConnectionState {
|
export interface GitHubRepoConnectionState {
|
||||||
@ -83,8 +82,9 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
|
|
||||||
log.info(`Fetching ${owner}/${repo}/${issueNumber}`);
|
log.info(`Fetching ${owner}/${repo}/${issueNumber}`);
|
||||||
let repoRes: ReposGetResponseData;
|
let repoRes: ReposGetResponseData;
|
||||||
|
const octokit = opts.githubInstance.getOctokitForRepo(owner, repo);
|
||||||
try {
|
try {
|
||||||
repoRes = (await opts.octokit.repos.get({
|
repoRes = (await octokit.repos.get({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
})).data;
|
})).data;
|
||||||
@ -97,7 +97,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
const orgRepoName = repoRes.url.substr("https://api.github.com/repos/".length);
|
const orgRepoName = repoRes.url.substr("https://api.github.com/repos/".length);
|
||||||
let avatarUrl = undefined;
|
let avatarUrl = undefined;
|
||||||
try {
|
try {
|
||||||
const profile = await opts.octokit.users.getByUsername({
|
const profile = await octokit.users.getByUsername({
|
||||||
username: owner,
|
username: owner,
|
||||||
});
|
});
|
||||||
if (profile.data.avatar_url) {
|
if (profile.data.avatar_url) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { IConnection } from "./IConnection";
|
import { IConnection } from "./IConnection";
|
||||||
import { Appservice, Space } from "matrix-bot-sdk";
|
import { Appservice, Space } from "matrix-bot-sdk";
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import { Octokit } from "@octokit/rest";
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { GitHubDiscussionSpace } from ".";
|
import { GitHubDiscussionSpace } from ".";
|
||||||
|
import { GithubInstance } from "../Github/GithubInstance";
|
||||||
|
|
||||||
const log = new LogWrapper("GitHubOwnerSpace");
|
const log = new LogWrapper("GitHubOwnerSpace");
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export class GitHubUserSpace implements IConnection {
|
|||||||
|
|
||||||
static readonly QueryRoomRegex = /#github_(.+):.*/;
|
static readonly QueryRoomRegex = /#github_(.+):.*/;
|
||||||
|
|
||||||
static async onQueryRoom(result: RegExpExecArray, opts: {octokit: Octokit, as: Appservice}): Promise<Record<string, unknown>> {
|
static async onQueryRoom(result: RegExpExecArray, opts: {githubInstance: GithubInstance, as: Appservice}): Promise<Record<string, unknown>> {
|
||||||
if (!result || result.length < 1) {
|
if (!result || result.length < 1) {
|
||||||
log.error(`Invalid alias pattern '${result}'`);
|
log.error(`Invalid alias pattern '${result}'`);
|
||||||
throw Error("Could not find issue");
|
throw Error("Could not find issue");
|
||||||
@ -37,9 +37,10 @@ export class GitHubUserSpace implements IConnection {
|
|||||||
let state: GitHubUserSpaceConnectionState;
|
let state: GitHubUserSpaceConnectionState;
|
||||||
let avatarUrl: string|undefined;
|
let avatarUrl: string|undefined;
|
||||||
let name: string;
|
let name: string;
|
||||||
|
const octokit = opts.githubInstance.getOctokitForRepo(username);
|
||||||
try {
|
try {
|
||||||
// TODO: Determine if the repo has discussions?
|
// TODO: Determine if the repo has discussions?
|
||||||
const userRes = (await opts.octokit.users.getByUsername({
|
const userRes = (await octokit.users.getByUsername({
|
||||||
username,
|
username,
|
||||||
})).data;
|
})).data;
|
||||||
if (!userRes) {
|
if (!userRes) {
|
||||||
|
@ -1,22 +1,33 @@
|
|||||||
import { createAppAuth } from "@octokit/auth-app";
|
import { createAppAuth } from "@octokit/auth-app";
|
||||||
import { createTokenAuth } from "@octokit/auth-token";
|
import { createTokenAuth } from "@octokit/auth-token";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import { promises as fs } from "fs";
|
|
||||||
import { BridgeConfigGitHub } from "../Config/Config";
|
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import { DiscussionQLResponse, DiscussionQL } from "./Discussion";
|
import { DiscussionQLResponse, DiscussionQL } from "./Discussion";
|
||||||
|
import * as GitHubWebhookTypes from "@octokit/webhooks-types";
|
||||||
|
import { InstallationDataType } from "./Types";
|
||||||
|
import e from "express";
|
||||||
|
|
||||||
const log = new LogWrapper("GithubInstance");
|
const log = new LogWrapper("GithubInstance");
|
||||||
|
|
||||||
const USER_AGENT = "matrix-hookshot v0.0.1";
|
const USER_AGENT = "matrix-hookshot v0.0.1";
|
||||||
|
|
||||||
|
interface Installation {
|
||||||
|
account: {
|
||||||
|
login?: string;
|
||||||
|
} | null;
|
||||||
|
id: number;
|
||||||
|
repository_selection: "selected"|"all";
|
||||||
|
matchesRepository: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export class GithubInstance {
|
export class GithubInstance {
|
||||||
private internalOctokit!: Octokit;
|
private internalOctokit!: Octokit;
|
||||||
|
|
||||||
public get octokit() {
|
private readonly installationsCache = new Map<number, Installation>();
|
||||||
return this.internalOctokit;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor (private config: BridgeConfigGitHub) { }
|
constructor (private readonly appId: number|string, private readonly privateKey: string) {
|
||||||
|
this.appId = parseInt(appId as string, 10);
|
||||||
|
}
|
||||||
|
|
||||||
public static createUserOctokit(token: string) {
|
public static createUserOctokit(token: string) {
|
||||||
return new Octokit({
|
return new Octokit({
|
||||||
@ -28,11 +39,34 @@ export class GithubInstance {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getOctokitForRepo(orgName: string, repoName?: string) {
|
||||||
|
const targetName = (repoName ? `${orgName}/${repoName}` : orgName).toLowerCase();
|
||||||
|
for (const install of this.installationsCache.values()) {
|
||||||
|
if (install.matchesRepository.includes(targetName) || install.matchesRepository.includes(`${targetName.split('/')[0]}/*`)) {
|
||||||
|
return this.createOctokitForInstallation(install.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Refresh cache?
|
||||||
|
throw Error(`No installation found to handle ${targetName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private createOctokitForInstallation(installationId: number) {
|
||||||
|
return new Octokit({
|
||||||
|
authStrategy: createAppAuth,
|
||||||
|
auth: {
|
||||||
|
appId: this.appId,
|
||||||
|
privateKey: this.privateKey,
|
||||||
|
installationId,
|
||||||
|
},
|
||||||
|
userAgent: USER_AGENT,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async start() {
|
public async start() {
|
||||||
// TODO: Make this generic.
|
// TODO: Make this generic.
|
||||||
const auth = {
|
const auth = {
|
||||||
appId: parseInt(this.config.auth.id as string, 10),
|
appId: this.appId,
|
||||||
privateKey: await fs.readFile(this.config.auth.privateKeyFile, "utf-8"),
|
privateKey: this.privateKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.internalOctokit = new Octokit({
|
this.internalOctokit = new Octokit({
|
||||||
@ -41,26 +75,45 @@ export class GithubInstance {
|
|||||||
userAgent: USER_AGENT,
|
userAgent: USER_AGENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
let installPageSize = 100;
|
||||||
const installation = (await this.octokit.apps.listInstallations()).data[0];
|
let page = 1;
|
||||||
if (!installation) {
|
do {
|
||||||
throw Error("App has no installations, cannot continue. Please ensure you've installed the app somewhere (https://github.com/settings/installations)");
|
const installations = await this.internalOctokit.apps.listInstallations({ per_page: 100, page: page++ });
|
||||||
|
for (const install of installations.data) {
|
||||||
|
await this.addInstallation(install);
|
||||||
}
|
}
|
||||||
log.info(`Using installation ${installation.id} (${installation.app_slug})`)
|
installPageSize = installations.data.length;
|
||||||
this.internalOctokit = new Octokit({
|
} while(installPageSize === 100)
|
||||||
authStrategy: createAppAuth,
|
|
||||||
auth: {
|
log.info(`Found ${this.installationsCache.size} installations`);
|
||||||
...auth,
|
}
|
||||||
installationId: installation.id,
|
|
||||||
},
|
private async addInstallation(install: InstallationDataType, repos?: {full_name: string}[]) {
|
||||||
userAgent: USER_AGENT,
|
let matchesRepository: string[] = [];
|
||||||
});
|
if (install.repository_selection === "all") {
|
||||||
await this.octokit.rateLimit.get();
|
matchesRepository = [`${install.account?.login}/*`.toLowerCase()];
|
||||||
log.info("Auth check success");
|
} else if (repos) {
|
||||||
} catch (ex) {
|
matchesRepository = repos.map(r => r.full_name.toLowerCase());
|
||||||
log.warn("Auth check failed:", ex);
|
} else {
|
||||||
throw Error("Attempting to verify GitHub authentication configration failed");
|
const installOctokit = this.createOctokitForInstallation(install.id);
|
||||||
|
const repos = await installOctokit.apps.listReposAccessibleToInstallation({ per_page: 100 });
|
||||||
|
matchesRepository.push(...repos.data.repositories.map(r => r.full_name.toLowerCase()));
|
||||||
}
|
}
|
||||||
|
this.installationsCache.set(install.id, {
|
||||||
|
account: install.account,
|
||||||
|
id: install.id,
|
||||||
|
repository_selection: install.repository_selection,
|
||||||
|
matchesRepository,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInstallationCreated(data: GitHubWebhookTypes.InstallationCreatedEvent|GitHubWebhookTypes.InstallationUnsuspendEvent) {
|
||||||
|
this.addInstallation(data.installation as InstallationDataType, data.repositories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInstallationRemoved(data: GitHubWebhookTypes.InstallationDeletedEvent|GitHubWebhookTypes.InstallationSuspendEvent) {
|
||||||
|
this.installationsCache.delete(data.installation.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,9 @@ export type IssuesListAssigneesResponseData = Endpoints["GET /repos/{owner}/{rep
|
|||||||
export type PullsGetResponseData = Endpoints["GET /repos/{owner}/{repo}/pulls"]["response"]["data"];
|
export type PullsGetResponseData = Endpoints["GET /repos/{owner}/{repo}/pulls"]["response"]["data"];
|
||||||
export type PullGetResponseData = Endpoints["GET /repos/{owner}/{repo}/pulls/{pull_number}"]["response"]["data"];
|
export type PullGetResponseData = Endpoints["GET /repos/{owner}/{repo}/pulls/{pull_number}"]["response"]["data"];
|
||||||
export type DiscussionDataType = Endpoints["GET /repos/{owner}/{repo}/pulls/{pull_number}"]["response"]["data"];
|
export type DiscussionDataType = Endpoints["GET /repos/{owner}/{repo}/pulls/{pull_number}"]["response"]["data"];
|
||||||
|
export type InstallationDataType = Endpoints["GET /app/installations/{installation_id}"]["response"]["data"];
|
||||||
|
export type CreateInstallationAccessTokenDataType = Endpoints["POST /app/installations/{installation_id}/access_tokens"]["response"]["data"];
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
export interface GitHubUserNotification {
|
export interface GitHubUserNotification {
|
||||||
id: string;
|
id: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user