mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
Merge pull request #85 from Half-Shot/hs/gitlab-fixes
GitLab/GitHub improvements
This commit is contained in:
commit
60bfd41cdf
@ -11,7 +11,6 @@ bridge:
|
|||||||
github:
|
github:
|
||||||
# (Optional) Configure this to enable GitHub support
|
# (Optional) Configure this to enable GitHub support
|
||||||
#
|
#
|
||||||
installationId: 6854059
|
|
||||||
auth:
|
auth:
|
||||||
id: 123
|
id: 123
|
||||||
privateKeyFile: github-key.pem
|
privateKeyFile: github-key.pem
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"main": "lib/app.js",
|
"main": "lib/app.js",
|
||||||
"repository": "https://github.com/Half-Shot/matrix-hookshot",
|
"repository": "https://github.com/Half-Shot/matrix-hookshot",
|
||||||
"author": "Half-Shot",
|
"author": "Half-Shot",
|
||||||
"license": "Apache2",
|
"license": "Apache-2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"napi": {
|
"napi": {
|
||||||
"name": "matrix-hookshot-rs"
|
"name": "matrix-hookshot-rs"
|
||||||
|
@ -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();
|
||||||
|
417
src/Bridge.ts
417
src/Bridge.ts
@ -1,26 +1,26 @@
|
|||||||
|
import { AdminAccountData } from "./AdminRoomCommandHandler";
|
||||||
import { AdminRoom, BRIDGE_ROOM_TYPE, LEGACY_BRIDGE_ROOM_TYPE } from "./AdminRoom";
|
import { AdminRoom, BRIDGE_ROOM_TYPE, LEGACY_BRIDGE_ROOM_TYPE } from "./AdminRoom";
|
||||||
import { Appservice, IAppserviceRegistration, RichRepliesPreprocessor, IRichReplyMetadata, StateEvent, PantalaimonClient, MatrixClient } from "matrix-bot-sdk";
|
import { Appservice, IAppserviceRegistration, RichRepliesPreprocessor, IRichReplyMetadata, StateEvent, PantalaimonClient, MatrixClient } from "matrix-bot-sdk";
|
||||||
import { BridgeConfig, GitLabInstance } from "./Config/Config";
|
import { BridgeConfig, GitLabInstance } from "./Config/Config";
|
||||||
import { BridgeWidgetApi } from "./Widgets/BridgeWidgetApi";
|
import { BridgeWidgetApi } from "./Widgets/BridgeWidgetApi";
|
||||||
import { CommentProcessor } from "./CommentProcessor";
|
import { CommentProcessor } from "./CommentProcessor";
|
||||||
import { ConnectionManager } from "./ConnectionManager";
|
import { ConnectionManager } from "./ConnectionManager";
|
||||||
|
import { GenericHookConnection } from "./Connections";
|
||||||
import { GetIssueResponse, GetIssueOpts } from "./Gitlab/Types"
|
import { GetIssueResponse, GetIssueOpts } from "./Gitlab/Types"
|
||||||
import { GithubInstance } from "./Github/GithubInstance";
|
import { GithubInstance } from "./Github/GithubInstance";
|
||||||
import { GitHubIssueConnection } from "./Connections/GithubIssue";
|
|
||||||
import { GitHubProjectConnection } from "./Connections/GithubProject";
|
|
||||||
import { GitHubRepoConnection } from "./Connections/GithubRepo";
|
|
||||||
import { GitLabIssueConnection } from "./Connections/GitlabIssue";
|
|
||||||
import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
import { IBridgeStorageProvider } from "./Stores/StorageProvider";
|
||||||
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace } from "./Connections";
|
import { IConnection, GitHubDiscussionSpace, GitHubDiscussionConnection, GitHubUserSpace, JiraProjectConnection, GitLabRepoConnection,
|
||||||
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent } from "./Gitlab/WebhookTypes";
|
GitHubIssueConnection, GitHubProjectConnection, GitHubRepoConnection, GitLabIssueConnection } from "./Connections";
|
||||||
|
import { IGitLabWebhookIssueStateEvent, IGitLabWebhookMREvent, IGitLabWebhookNoteEvent, IGitLabWebhookTagPushEvent } from "./Gitlab/WebhookTypes";
|
||||||
import { JiraIssueEvent, JiraIssueUpdatedEvent } from "./Jira/WebhookTypes";
|
import { JiraIssueEvent, JiraIssueUpdatedEvent } from "./Jira/WebhookTypes";
|
||||||
|
import { JiraOAuthResult } from "./Jira/Types";
|
||||||
import { MatrixEvent, MatrixMemberContent, MatrixMessageContent } from "./MatrixEvent";
|
import { MatrixEvent, MatrixMemberContent, MatrixMessageContent } from "./MatrixEvent";
|
||||||
import { MemoryStorageProvider } from "./Stores/MemoryStorageProvider";
|
import { MemoryStorageProvider } from "./Stores/MemoryStorageProvider";
|
||||||
import { MessageQueue, createMessageQueue } from "./MessageQueue/MessageQueue";
|
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 { OAuthRequest, 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";
|
||||||
@ -28,8 +28,8 @@ import { UserNotificationsEvent } from "./Notifications/UserNotificationWatcher"
|
|||||||
import { UserTokenStore } from "./UserTokenStore";
|
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 { JiraOAuthResult } from "./Jira/Types";
|
import { OAuthRequest } from "./WebhookTypes";
|
||||||
import { AdminAccountData } from "./AdminRoomCommandHandler";
|
import { promises as fs } from "fs";
|
||||||
const log = new LogWrapper("Bridge");
|
const log = new LogWrapper("Bridge");
|
||||||
|
|
||||||
export class Bridge {
|
export class Bridge {
|
||||||
@ -85,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,160 +160,136 @@ export class Bridge {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssueCommentCreatedEvent>("github.issue_comment.created", async ({ data }) => {
|
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
this.queue.on<GitHubWebhookTypes.InstallationCreatedEvent>("github.installation.created", async (data) => {
|
||||||
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
this.github?.onInstallationCreated(data.data);
|
||||||
connections.map(async (c) => {
|
});
|
||||||
try {
|
this.queue.on<GitHubWebhookTypes.InstallationUnsuspendEvent>("github.installation.unsuspend", async (data) => {
|
||||||
if (c instanceof GitHubIssueConnection)
|
this.github?.onInstallationCreated(data.data);
|
||||||
await c.onIssueCommentCreated(data);
|
});
|
||||||
} catch (ex) {
|
this.queue.on<GitHubWebhookTypes.InstallationDeletedEvent>("github.installation.deleted", async (data) => {
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issue_comment.created:`, ex);
|
this.github?.onInstallationRemoved(data.data);
|
||||||
}
|
});
|
||||||
})
|
this.queue.on<GitHubWebhookTypes.InstallationSuspendEvent>("github.installation.suspend", async (data) => {
|
||||||
|
this.github?.onInstallationRemoved(data.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesOpenedEvent>("github.issues.opened", async ({ data }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssueCommentCreatedEvent, GitHubIssueConnection>(
|
||||||
const { repository, owner } = validateRepoIssue(data);
|
"github.issue_comment.created",
|
||||||
const connections = connManager.getConnectionsForGithubRepo(owner, repository.name);
|
(data) => {
|
||||||
connections.map(async (c) => {
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
try {
|
return connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number).filter(c => c instanceof GitHubIssueConnection) as GitHubIssueConnection[];
|
||||||
await c.onIssueCreated(data);
|
},
|
||||||
} catch (ex) {
|
(c, data) => c.onIssueCommentCreated(data),
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.opened:`, ex);
|
);
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssuesOpenedEvent, GitHubRepoConnection>(
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
"github.issues.opened",
|
||||||
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
(data) => {
|
||||||
connections.map(async (c) => {
|
const { repository, owner } = validateRepoIssue(data);
|
||||||
try {
|
return connManager.getConnectionsForGithubRepo(owner, repository.name);
|
||||||
// TODO: Needs impls
|
},
|
||||||
if (c instanceof GitHubIssueConnection /* || c instanceof GitHubRepoConnection*/)
|
(c, data) => c.onIssueCreated(data),
|
||||||
await c.onIssueEdited(data);
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.edited:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesClosedEvent>("github.issues.closed", async ({ data }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssuesEditedEvent, GitHubIssueConnection|GitHubRepoConnection>(
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
"github.issues.edited",
|
||||||
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
(data) => {
|
||||||
connections.map(async (c) => {
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
try {
|
return connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
if (c instanceof GitHubIssueConnection || c instanceof GitHubRepoConnection)
|
},
|
||||||
await c.onIssueStateChange(data);
|
(c, data) => c.onIssueEdited(data),
|
||||||
} catch (ex) {
|
);
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.closed:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesReopenedEvent>("github.issues.reopened", async ({ data }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssuesClosedEvent, GitHubIssueConnection|GitHubRepoConnection>(
|
||||||
const { repository, issue, owner } = validateRepoIssue(data);
|
"github.issues.closed",
|
||||||
const connections = connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
(data) => {
|
||||||
connections.map(async (c) => {
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
try {
|
return connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
if (c.onIssueStateChange) {
|
},
|
||||||
await c.onIssueStateChange(data);
|
(c, data) => c.onIssueStateChange(data),
|
||||||
}
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.reopened:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.IssuesEditedEvent>("github.issues.edited", async ({ data }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssuesReopenedEvent, GitHubIssueConnection|GitHubRepoConnection>(
|
||||||
const { repository, owner } = validateRepoIssue(data);
|
"github.issues.reopened",
|
||||||
const connections = connManager.getConnectionsForGithubRepo(owner, repository.name);
|
(data) => {
|
||||||
connections.map(async (c) => {
|
const { repository, issue, owner } = validateRepoIssue(data);
|
||||||
try {
|
return connManager.getConnectionsForGithubIssue(owner, repository.name, issue.number);
|
||||||
await c.onIssueEdited(data);
|
},
|
||||||
} catch (ex) {
|
(c, data) => c.onIssueStateChange(data),
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.issues.edited:`, ex);
|
);
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.PullRequestOpenedEvent>("github.pull_request.opened", async ({ data }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.IssuesEditedEvent, GitHubRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
"github.issues.edited",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name),
|
||||||
try {
|
(c, data) => c.onIssueEdited(data),
|
||||||
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 }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.PullRequestOpenedEvent, GitHubRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
"github.pull_request.opened",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name),
|
||||||
try {
|
(c, data) => c.onPROpened(data),
|
||||||
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 }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.PullRequestClosedEvent, GitHubRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
"github.pull_request.closed",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name),
|
||||||
try {
|
(c, data) => c.onPRClosed(data),
|
||||||
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 }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.PullRequestReadyForReviewEvent, GitHubRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
"github.pull_request_review.ready_for_review",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name),
|
||||||
try {
|
(c, data) => c.onPRReadyForReview(data),
|
||||||
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 }) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.PullRequestReviewSubmittedEvent, GitHubRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name);
|
"github.pull_request_review.submitted",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name),
|
||||||
try {
|
(c, data) => c.onPRReviewed(data),
|
||||||
await c.onReleaseCreated(data);
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle github.pull_request.closed:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookMREvent>("gitlab.merge_request.open", async (msg) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.ReleaseCreatedEvent, GitHubRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGitLabRepo(msg.data.project.path_with_namespace);
|
"github.release.created",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubRepo(data.repository.owner.login, data.repository.name),
|
||||||
try {
|
(c, data) => c.onReleaseCreated(data),
|
||||||
await c.onMergeRequestOpened(msg.data);
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle gitlab.merge_request.open:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookMREvent>("gitlab.tag_push", async (msg) => {
|
this.bindHandlerToQueue<IGitLabWebhookMREvent, GitLabRepoConnection>(
|
||||||
const connections = connManager.getConnectionsForGitLabRepo(msg.data.project.path_with_namespace);
|
"gitlab.merge_request.open",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGitLabRepo(data.project.path_with_namespace),
|
||||||
try {
|
(c, data) => c.onMergeRequestOpened(data),
|
||||||
await c.onMergeRequestOpened(msg.data);
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle gitlab.tag_push:`, ex);
|
this.bindHandlerToQueue<IGitLabWebhookMREvent, GitLabRepoConnection>(
|
||||||
}
|
"gitlab.merge_request.merge",
|
||||||
})
|
(data) => connManager.getConnectionsForGitLabRepo(data.project.path_with_namespace),
|
||||||
});
|
(c, data) => c.onMergeRequestReviewed(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.bindHandlerToQueue<IGitLabWebhookMREvent, GitLabRepoConnection>(
|
||||||
|
"gitlab.merge_request.merge",
|
||||||
|
(data) => connManager.getConnectionsForGitLabRepo(data.project.path_with_namespace),
|
||||||
|
(c, data) => c.onMergeRequestReviewed(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.bindHandlerToQueue<IGitLabWebhookMREvent, GitLabRepoConnection>(
|
||||||
|
"gitlab.merge_request.approved",
|
||||||
|
(data) => connManager.getConnectionsForGitLabRepo(data.project.path_with_namespace),
|
||||||
|
(c, data) => c.onMergeRequestReviewed(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.bindHandlerToQueue<IGitLabWebhookMREvent, GitLabRepoConnection>(
|
||||||
|
"gitlab.merge_request.unapproved",
|
||||||
|
(data) => connManager.getConnectionsForGitLabRepo(data.project.path_with_namespace),
|
||||||
|
(c, data) => c.onMergeRequestReviewed(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.bindHandlerToQueue<IGitLabWebhookTagPushEvent, GitLabRepoConnection>(
|
||||||
|
"gitlab.tag_push",
|
||||||
|
(data) => connManager.getConnectionsForGitLabRepo(data.project.path_with_namespace),
|
||||||
|
(c, data) => c.onGitLabTagPush(data),
|
||||||
|
);
|
||||||
|
|
||||||
this.queue.on<UserNotificationsEvent>("notifications.user.events", async (msg) => {
|
this.queue.on<UserNotificationsEvent>("notifications.user.events", async (msg) => {
|
||||||
const adminRoom = this.adminRooms.get(msg.data.roomId);
|
const adminRoom = this.adminRooms.get(msg.data.roomId);
|
||||||
@ -344,50 +320,29 @@ export class Bridge {
|
|||||||
await this.tokenStore.storeUserToken("github", adminRoom.userId, msg.data.access_token);
|
await this.tokenStore.storeUserToken("github", adminRoom.userId, msg.data.access_token);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookNoteEvent>("gitlab.note.created", async ({data}) => {
|
this.bindHandlerToQueue<IGitLabWebhookNoteEvent, GitLabIssueConnection>(
|
||||||
const connections = connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.issue.iid);
|
"gitlab.note.created",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.issue.iid),
|
||||||
try {
|
(c, data) => c.onCommentCreated(data),
|
||||||
if (c.onCommentCreated)
|
);
|
||||||
await c.onCommentCreated(data);
|
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookIssueStateEvent>("gitlab.issue.reopen", async ({data}) => {
|
this.bindHandlerToQueue<IGitLabWebhookIssueStateEvent, GitLabIssueConnection>(
|
||||||
const connections = connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid);
|
"gitlab.issue.reopen",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid),
|
||||||
try {
|
(c) => c.onIssueReopened(),
|
||||||
await c.onIssueReopened();
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<IGitLabWebhookIssueStateEvent>("gitlab.issue.close", async ({data}) => {
|
this.bindHandlerToQueue<IGitLabWebhookIssueStateEvent, GitLabIssueConnection>(
|
||||||
const connections = connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid);
|
"gitlab.issue.close",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGitLabIssueWebhook(data.repository.homepage, data.object_attributes.iid),
|
||||||
try {
|
(c) => c.onIssueClosed(),
|
||||||
await c.onIssueClosed();
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.DiscussionCommentCreatedEvent>("github.discussion_comment.created", async ({data}) => {
|
this.bindHandlerToQueue<GitHubWebhookTypes.DiscussionCommentCreatedEvent, GitHubDiscussionConnection>(
|
||||||
const connections = connManager.getConnectionsForGithubDiscussion(data.repository.owner.login, data.repository.name, data.discussion.number);
|
"github.discussion_comment.created",
|
||||||
connections.map(async (c) => {
|
(data) => connManager.getConnectionsForGithubDiscussion(data.repository.owner.login, data.repository.name, data.discussion.number),
|
||||||
try {
|
(c, data) => c.onDiscussionCommentCreated(data),
|
||||||
await c.onDiscussionCommentCreated(data);
|
);
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Connection ${c.toString()} failed to handle comment.created:`, ex);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<GitHubWebhookTypes.DiscussionCreatedEvent>("github.discussion.created", async ({data}) => {
|
this.queue.on<GitHubWebhookTypes.DiscussionCreatedEvent>("github.discussion.created", async ({data}) => {
|
||||||
if (!this.github) {
|
if (!this.github) {
|
||||||
@ -428,33 +383,18 @@ export class Bridge {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<JiraIssueEvent>("jira.issue_created", async ({data}) => {
|
|
||||||
log.info(`JIRA issue created for project ${data.issue.fields.project.id}, issue id ${data.issue.id}`);
|
|
||||||
const connections = connManager.getConnectionsForJiraProject(data.issue.fields.project, "jira.issue_created");
|
|
||||||
|
|
||||||
connections.forEach(async (c) => {
|
|
||||||
try {
|
|
||||||
await c.onJiraIssueCreated(data);
|
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Failed to handle jira.issue_created:`, ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.queue.on<JiraIssueEvent>("jira.issue_updated", async ({data}) => {
|
|
||||||
log.info(`JIRA issue created for project ${data.issue.fields.project.id}, issue id ${data.issue.id}`);
|
|
||||||
const connections = connManager.getConnectionsForJiraProject(data.issue.fields.project, "jira.issue_updated");
|
|
||||||
|
|
||||||
connections.forEach(async (c) => {
|
|
||||||
try {
|
|
||||||
await c.onJiraIssueUpdated(data as JiraIssueUpdatedEvent);
|
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Failed to handle jira.issue_updated:`, ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
this.bindHandlerToQueue<JiraIssueEvent, JiraProjectConnection>(
|
||||||
|
"jira.issue_created",
|
||||||
|
(data) => connManager.getConnectionsForJiraProject(data.issue.fields.project, "jira.issue_created"),
|
||||||
|
(c, data) => c.onJiraIssueCreated(data),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.bindHandlerToQueue<JiraIssueUpdatedEvent, JiraProjectConnection>(
|
||||||
|
"jira.issue_updated",
|
||||||
|
(data) => connManager.getConnectionsForJiraProject(data.issue.fields.project, "jira.issue_updated"),
|
||||||
|
(c, data) => c.onJiraIssueUpdated(data),
|
||||||
|
);
|
||||||
|
|
||||||
this.queue.on<OAuthRequest>("jira.oauth.response", async (msg) => {
|
this.queue.on<OAuthRequest>("jira.oauth.response", async (msg) => {
|
||||||
const adminRoom = [...this.adminRooms.values()].find((r) => r.jiraOAuthState === msg.data.state);
|
const adminRoom = [...this.adminRooms.values()].find((r) => r.jiraOAuthState === msg.data.state);
|
||||||
@ -477,18 +417,11 @@ export class Bridge {
|
|||||||
await adminRoom.sendNotice(`Logged into Jira`);
|
await adminRoom.sendNotice(`Logged into Jira`);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.queue.on<GenericWebhookEvent>("generic-webhook.event", async ({data}) => {
|
this.bindHandlerToQueue<GenericWebhookEvent, GenericHookConnection>(
|
||||||
log.info(`Incoming generic hook ${data.hookId}`);
|
"generic-webhook.event",
|
||||||
const connections = connManager.getConnectionsForGenericWebhook(data.hookId);
|
(data) => connManager.getConnectionsForGenericWebhook(data.hookId),
|
||||||
|
(c, data) => c.onGenericHook(data.hookData),
|
||||||
connections.forEach(async (c) => {
|
);
|
||||||
try {
|
|
||||||
await c.onGenericHook(data.hookData);
|
|
||||||
} catch (ex) {
|
|
||||||
log.warn(`Failed to handle generic-webhook.event:`, ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fetch all room state
|
// Fetch all room state
|
||||||
let joinedRooms: string[]|undefined;
|
let joinedRooms: string[]|undefined;
|
||||||
@ -597,6 +530,20 @@ export class Bridge {
|
|||||||
this.ready = true;
|
this.ready = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async bindHandlerToQueue<EventType, ConnType extends IConnection>(event: string, connectionFetcher: (data: EventType) => ConnType[], handler: (c: ConnType, data: EventType) => Promise<unknown>) {
|
||||||
|
this.queue.on<EventType>(event, (msg) => {
|
||||||
|
const connections = connectionFetcher.bind(this)(msg.data);
|
||||||
|
log.debug(`${event} for ${connections.map(c => c.toString()).join(', ')}`);
|
||||||
|
connections.forEach(async (c) => {
|
||||||
|
try {
|
||||||
|
await handler(c, msg.data);
|
||||||
|
} catch (ex) {
|
||||||
|
log.warn(`Connection ${c.toString()} failed to handle ${event}:`, ex);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async onRoomInvite(roomId: string, event: MatrixEvent<MatrixMemberContent>) {
|
private async onRoomInvite(roomId: string, event: MatrixEvent<MatrixMemberContent>) {
|
||||||
if (this.as.isNamespacedUser(event.sender)) {
|
if (this.as.isNamespacedUser(event.sender)) {
|
||||||
/* Do not handle invites from our users */
|
/* Do not handle invites from our users */
|
||||||
@ -762,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);
|
||||||
@ -777,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) {
|
||||||
@ -797,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);
|
||||||
@ -812,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) {
|
||||||
|
@ -20,7 +20,6 @@ export interface BridgeConfigGitHub {
|
|||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
redirect_uri: string;
|
redirect_uri: string;
|
||||||
};
|
};
|
||||||
installationId: number|string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GitLabInstance {
|
export interface GitLabInstance {
|
||||||
|
@ -35,7 +35,6 @@ export const DefaultConfig = new BridgeConfig({
|
|||||||
avatar: "mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d"
|
avatar: "mxc://half-shot.uk/2876e89ccade4cb615e210c458e2a7a6883fe17d"
|
||||||
},
|
},
|
||||||
github: {
|
github: {
|
||||||
installationId: 6854059,
|
|
||||||
auth: {
|
auth: {
|
||||||
id: 123,
|
id: 123,
|
||||||
privateKeyFile: "github-key.pem",
|
privateKeyFile: "github-key.pem",
|
||||||
|
@ -43,7 +43,6 @@ export class ConnectionManager {
|
|||||||
// NOTE: Double loop
|
// NOTE: Double loop
|
||||||
for (const connection of connections) {
|
for (const connection of connections) {
|
||||||
if (!this.connections.find((c) => c === connection)) {
|
if (!this.connections.find((c) => c === connection)) {
|
||||||
console.log("PUSH!");
|
|
||||||
this.connections.push(connection);
|
this.connections.push(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +109,7 @@ export class ConnectionManager {
|
|||||||
if (!instance) {
|
if (!instance) {
|
||||||
throw Error('Instance name not recognised');
|
throw Error('Instance name not recognised');
|
||||||
}
|
}
|
||||||
return new GitLabRepoConnection(roomId, this.as, state.content, this.tokenStore, instance);
|
return new GitLabRepoConnection(roomId, this.as, state.content, state.stateKey, this.tokenStore, instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GitLabIssueConnection.EventTypes.includes(state.type)) {
|
if (GitLabIssueConnection.EventTypes.includes(state.type)) {
|
||||||
|
@ -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,
|
||||||
|
@ -7,15 +7,15 @@ import { IConnection } from "./IConnection";
|
|||||||
import { IssuesOpenedEvent, IssuesReopenedEvent, IssuesEditedEvent, PullRequestOpenedEvent, IssuesClosedEvent, PullRequestClosedEvent, PullRequestReadyForReviewEvent, PullRequestReviewSubmittedEvent, ReleaseCreatedEvent } from "@octokit/webhooks-types";
|
import { IssuesOpenedEvent, IssuesReopenedEvent, IssuesEditedEvent, PullRequestOpenedEvent, IssuesClosedEvent, PullRequestClosedEvent, PullRequestReadyForReviewEvent, PullRequestReviewSubmittedEvent, ReleaseCreatedEvent } from "@octokit/webhooks-types";
|
||||||
import { MatrixMessageContent, MatrixEvent, MatrixReactionContent } from "../MatrixEvent";
|
import { MatrixMessageContent, MatrixEvent, MatrixReactionContent } from "../MatrixEvent";
|
||||||
import { MessageSenderClient } from "../MatrixSender";
|
import { MessageSenderClient } from "../MatrixSender";
|
||||||
import { NotLoggedInError } from "../errors";
|
import { CommandError, NotLoggedInError } from "../errors";
|
||||||
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 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();
|
||||||
|
|
||||||
@ -24,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 {
|
||||||
@ -82,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;
|
||||||
@ -96,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) {
|
||||||
@ -148,7 +149,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
|
|
||||||
constructor(roomId: string,
|
constructor(roomId: string,
|
||||||
private readonly as: Appservice,
|
private readonly as: Appservice,
|
||||||
private readonly state: GitHubRepoConnectionState,
|
private state: GitHubRepoConnectionState,
|
||||||
private readonly tokenStore: UserTokenStore,
|
private readonly tokenStore: UserTokenStore,
|
||||||
private readonly stateKey: string) {
|
private readonly stateKey: string) {
|
||||||
super(
|
super(
|
||||||
@ -168,14 +169,17 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
return this.state.repo.toLowerCase();
|
return this.state.repo.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async onStateUpdate(stateEv: MatrixEvent<unknown>) {
|
||||||
|
const state = stateEv.content as GitHubRepoConnectionState;
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
public isInterestedInStateEvent(eventType: string, stateKey: string) {
|
public isInterestedInStateEvent(eventType: string, stateKey: string) {
|
||||||
return GitHubRepoConnection.EventTypes.includes(eventType) && this.stateKey === stateKey;
|
return GitHubRepoConnection.EventTypes.includes(eventType) && this.stateKey === stateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("create", "Create an issue for this repo", ["title"], ["description", "labels"], true)
|
@botCommand("create", "Create an issue for this repo", ["title"], ["description", "labels"], true)
|
||||||
// @ts-ignore
|
public async onCreateIssue(userId: string, title: string, description?: string, labels?: string) {
|
||||||
private async onCreateIssue(userId: string, title: string, description?: string, labels?: string) {
|
|
||||||
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||||
if (!octokit) {
|
if (!octokit) {
|
||||||
throw new NotLoggedInError();
|
throw new NotLoggedInError();
|
||||||
@ -199,11 +203,10 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("assign", "Assign an issue to a user", ["number", "...users"], [], true)
|
@botCommand("assign", "Assign an issue to a user", ["number", "...users"], [], true)
|
||||||
// @ts-ignore
|
public async onAssign(userId: string, number: string, ...users: string[]) {
|
||||||
private async onAssign(userId: string, number: string, ...users: string[]) {
|
|
||||||
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||||
if (!octokit) {
|
if (!octokit) {
|
||||||
return this.as.botIntent.sendText(this.roomId, "You must login to assign an issue", "m.notice");
|
throw new NotLoggedInError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (users.length === 1) {
|
if (users.length === 1) {
|
||||||
@ -219,11 +222,10 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("close", "Close an issue", ["number"], ["comment"], true)
|
@botCommand("close", "Close an issue", ["number"], ["comment"], true)
|
||||||
// @ts-ignore
|
public async onClose(userId: string, number: string, comment?: string) {
|
||||||
private async onClose(userId: string, number: string, comment?: string) {
|
|
||||||
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||||
if (!octokit) {
|
if (!octokit) {
|
||||||
return this.as.botIntent.sendText(this.roomId, "You must login to close an issue", "m.notice");
|
throw new NotLoggedInError();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (comment) {
|
if (comment) {
|
||||||
@ -243,6 +245,58 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@botCommand("workflow run", "Run a GitHub Actions workflow. Args should be specified in \"key=value,key2='value 2'\" format.", ["name"], ["args", "ref"], true)
|
||||||
|
public async onWorkflowRun(userId: string, name: string, args?: string, ref?: string) {
|
||||||
|
const octokit = await this.tokenStore.getOctokitForUser(userId);
|
||||||
|
if (!octokit) {
|
||||||
|
throw new NotLoggedInError();
|
||||||
|
}
|
||||||
|
const workflowArgs: Record<string, string> = {};
|
||||||
|
if (args) {
|
||||||
|
args.split(',').forEach((arg) => { const [key,value] = arg.split('='); workflowArgs[key] = value || "" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const workflows = await octokit.actions.listRepoWorkflows({
|
||||||
|
repo: this.state.repo,
|
||||||
|
owner: this.state.org,
|
||||||
|
});
|
||||||
|
|
||||||
|
const workflow = workflows.data.workflows.find(w => w.name.toLowerCase().trim() === name.toLowerCase().trim());
|
||||||
|
if (!workflow) {
|
||||||
|
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");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (!ref) {
|
||||||
|
ref = (await octokit.repos.get({
|
||||||
|
repo: this.state.repo,
|
||||||
|
owner: this.state.org,
|
||||||
|
})).data.default_branch;
|
||||||
|
}
|
||||||
|
} catch (ex) {
|
||||||
|
throw new CommandError(ex.message, `Could not determine default ref (maybe pass one in)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await octokit.actions.createWorkflowDispatch({
|
||||||
|
repo: this.state.repo,
|
||||||
|
owner: this.state.org,
|
||||||
|
workflow_id: workflow.id,
|
||||||
|
ref,
|
||||||
|
inputs: workflowArgs,
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
const httpError = ex as AxiosError;
|
||||||
|
if (httpError.response?.data) {
|
||||||
|
throw new CommandError(httpError.response?.data.message, httpError.response?.data.message);
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.as.botIntent.sendText(this.roomId, `Workflow started`, "m.notice");
|
||||||
|
}
|
||||||
|
|
||||||
public async onIssueCreated(event: IssuesOpenedEvent) {
|
public async onIssueCreated(event: IssuesOpenedEvent) {
|
||||||
if (this.shouldSkipHook('issue.created', 'issue')) {
|
if (this.shouldSkipHook('issue.created', 'issue')) {
|
||||||
return;
|
return;
|
||||||
|
@ -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,19 +1,20 @@
|
|||||||
// We need to instantiate some functions which are not directly called, which confuses typescript.
|
// We need to instantiate some functions which are not directly called, which confuses typescript.
|
||||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
import { IConnection } from "./IConnection";
|
|
||||||
import { UserTokenStore } from "../UserTokenStore";
|
import { UserTokenStore } from "../UserTokenStore";
|
||||||
import { Appservice } from "matrix-bot-sdk";
|
import { Appservice } from "matrix-bot-sdk";
|
||||||
import { BotCommands, handleCommand, botCommand, compileBotCommands } from "../BotCommands";
|
import { BotCommands, botCommand, compileBotCommands } from "../BotCommands";
|
||||||
import { MatrixEvent, MatrixMessageContent } from "../MatrixEvent";
|
import { MatrixEvent, MatrixMessageContent } from "../MatrixEvent";
|
||||||
import markdown from "markdown-it";
|
import markdown from "markdown-it";
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import { GitLabInstance } from "../Config/Config";
|
import { GitLabInstance } from "../Config/Config";
|
||||||
import { IGitLabWebhookMREvent } from "../Gitlab/WebhookTypes";
|
import { IGitLabWebhookMREvent, IGitLabWebhookTagPushEvent } from "../Gitlab/WebhookTypes";
|
||||||
|
import { CommandConnection } from "./CommandConnection";
|
||||||
|
|
||||||
export interface GitLabRepoConnectionState {
|
export interface GitLabRepoConnectionState {
|
||||||
instance: string;
|
instance: string;
|
||||||
path: string;
|
path: string;
|
||||||
state: string;
|
ignoreHooks?: string[],
|
||||||
|
commandPrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const log = new LogWrapper("GitLabRepoConnection");
|
const log = new LogWrapper("GitLabRepoConnection");
|
||||||
@ -22,7 +23,7 @@ const md = new markdown();
|
|||||||
/**
|
/**
|
||||||
* Handles rooms connected to a github repo.
|
* Handles rooms connected to a github repo.
|
||||||
*/
|
*/
|
||||||
export class GitLabRepoConnection implements IConnection {
|
export class GitLabRepoConnection extends CommandConnection {
|
||||||
static readonly CanonicalEventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
static readonly CanonicalEventType = "uk.half-shot.matrix-hookshot.gitlab.repository";
|
||||||
static readonly LegacyCanonicalEventType = "uk.half-shot.matrix-github.gitlab.repository";
|
static readonly LegacyCanonicalEventType = "uk.half-shot.matrix-github.gitlab.repository";
|
||||||
|
|
||||||
@ -32,12 +33,21 @@ export class GitLabRepoConnection implements IConnection {
|
|||||||
];
|
];
|
||||||
|
|
||||||
static botCommands: BotCommands;
|
static botCommands: BotCommands;
|
||||||
|
static helpMessage: (cmdPrefix?: string | undefined) => MatrixMessageContent;
|
||||||
|
|
||||||
constructor(public readonly roomId: string,
|
constructor(public readonly roomId: string,
|
||||||
private readonly as: Appservice,
|
private readonly as: Appservice,
|
||||||
private readonly state: GitLabRepoConnectionState,
|
private state: GitLabRepoConnectionState,
|
||||||
|
private readonly stateKey: string,
|
||||||
private readonly tokenStore: UserTokenStore,
|
private readonly tokenStore: UserTokenStore,
|
||||||
private readonly instance: GitLabInstance) {
|
private readonly instance: GitLabInstance) {
|
||||||
|
super(
|
||||||
|
roomId,
|
||||||
|
as.botClient,
|
||||||
|
GitLabRepoConnection.botCommands,
|
||||||
|
GitLabRepoConnection.helpMessage,
|
||||||
|
"!gl"
|
||||||
|
)
|
||||||
if (!state.path || !state.instance) {
|
if (!state.path || !state.instance) {
|
||||||
throw Error('Invalid state, missing `path` or `instance`');
|
throw Error('Invalid state, missing `path` or `instance`');
|
||||||
}
|
}
|
||||||
@ -47,37 +57,17 @@ export class GitLabRepoConnection implements IConnection {
|
|||||||
return this.state.path?.toString();
|
return this.state.path?.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async onStateUpdate(stateEv: MatrixEvent<unknown>) {
|
||||||
public isInterestedInStateEvent() {
|
const state = stateEv.content as GitLabRepoConnectionState;
|
||||||
return false;
|
this.state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onMessageEvent(ev: MatrixEvent<MatrixMessageContent>) {
|
public isInterestedInStateEvent(eventType: string, stateKey: string) {
|
||||||
const { error, handled } = await handleCommand(ev.sender, ev.content.body, GitLabRepoConnection.botCommands, this);
|
return GitLabRepoConnection.EventTypes.includes(eventType) && this.stateKey === stateKey;
|
||||||
if (!handled) {
|
|
||||||
// Not for us.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (error) {
|
|
||||||
log.error(error);
|
|
||||||
await this.as.botIntent.sendEvent(this.roomId,{
|
|
||||||
msgtype: "m.notice",
|
|
||||||
body: "Failed to handle command",
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await this.as.botClient.sendEvent(this.roomId, "m.reaction", {
|
|
||||||
"m.relates_to": {
|
|
||||||
rel_type: "m.annotation",
|
|
||||||
event_id: ev.event_id,
|
|
||||||
key: "✅",
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("gl create", "Create an issue for this repo", ["title"], ["description", "labels"], true)
|
@botCommand("gl create", "Create an issue for this repo", ["title"], ["description", "labels"], true)
|
||||||
// @ts-ignore
|
public async onCreateIssue(userId: string, title: string, description?: string, labels?: string) {
|
||||||
private 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 login to create an issue", "m.notice");
|
||||||
@ -100,8 +90,7 @@ export class GitLabRepoConnection implements IConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@botCommand("gl close", "Close an issue", ["number"], ["comment"], true)
|
@botCommand("gl close", "Close an issue", ["number"], ["comment"], true)
|
||||||
// @ts-ignore
|
public async onClose(userId: string, number: string) {
|
||||||
private 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 login to create an issue", "m.notice");
|
||||||
@ -115,14 +104,21 @@ export class GitLabRepoConnection implements IConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async onMergeRequestOpened(event: IGitLabWebhookMREvent) {
|
private validateMREvent(event: IGitLabWebhookMREvent) {
|
||||||
log.info(`onMergeRequestOpened ${this.roomId} ${this.path} #${event.object_attributes.iid}`);
|
|
||||||
if (!event.object_attributes) {
|
if (!event.object_attributes) {
|
||||||
throw Error('No merge_request content!');
|
throw Error('No merge_request content!');
|
||||||
}
|
}
|
||||||
if (!event.project) {
|
if (!event.project) {
|
||||||
throw Error('No repository content!');
|
throw Error('No repository content!');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onMergeRequestOpened(event: IGitLabWebhookMREvent) {
|
||||||
|
log.info(`onMergeRequestOpened ${this.roomId} ${this.path} #${event.object_attributes.iid}`);
|
||||||
|
if (this.shouldSkipHook('merge_request.open')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.validateMREvent(event);
|
||||||
const orgRepoName = event.project.path_with_namespace;
|
const orgRepoName = event.project.path_with_namespace;
|
||||||
const content = `**${event.user.username}** opened a new MR [${orgRepoName}#${event.object_attributes.iid}](${event.object_attributes.url}): "${event.object_attributes.title}"`;
|
const content = `**${event.user.username}** opened a new MR [${orgRepoName}#${event.object_attributes.iid}](${event.object_attributes.url}): "${event.object_attributes.title}"`;
|
||||||
await this.as.botIntent.sendEvent(this.roomId, {
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
@ -133,12 +129,80 @@ export class GitLabRepoConnection implements IConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async onMergeRequestMerged(event: IGitLabWebhookMREvent) {
|
||||||
|
log.info(`onMergeRequestOpened ${this.roomId} ${this.path} #${event.object_attributes.iid}`);
|
||||||
|
if (this.shouldSkipHook('merge_request.merge')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.validateMREvent(event);
|
||||||
|
const orgRepoName = event.project.path_with_namespace;
|
||||||
|
const content = `**${event.user.username}** merged MR [${orgRepoName}#${event.object_attributes.iid}](${event.object_attributes.url}): "${event.object_attributes.title}"`;
|
||||||
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
|
msgtype: "m.notice",
|
||||||
|
body: content,
|
||||||
|
formatted_body: md.renderInline(content),
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onMergeRequestReviewed(event: IGitLabWebhookMREvent) {
|
||||||
|
if (this.shouldSkipHook('merge_request.review', `merge_request.${event.object_attributes.action}`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info(`onMergeRequestReviewed ${this.roomId} ${this.instance}/${this.path} ${event.object_attributes.iid}`);
|
||||||
|
this.validateMREvent(event);
|
||||||
|
if (event.object_attributes.action !== "approved" && event.object_attributes.action !== "unapproved") {
|
||||||
|
// Not interested.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const emojiForReview = {
|
||||||
|
'approved': '✅',
|
||||||
|
'unapproved': '🔴'
|
||||||
|
}[event.object_attributes.action];
|
||||||
|
const orgRepoName = event.project.path_with_namespace;
|
||||||
|
const content = `**${event.user.username}** ${emojiForReview} ${event.object_attributes.action} MR [${orgRepoName}#${event.object_attributes.iid}](${event.object_attributes.url}): "${event.object_attributes.title}"`;
|
||||||
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
|
msgtype: "m.notice",
|
||||||
|
body: content,
|
||||||
|
formatted_body: md.renderInline(content),
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onGitLabTagPush(event: IGitLabWebhookTagPushEvent) {
|
||||||
|
log.info(`onGitLabTagPush ${this.roomId} ${this.instance}/${this.path} ${event.ref}`);
|
||||||
|
if (this.shouldSkipHook('tag_push')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tagname = event.ref.replace("refs/tags/", "");
|
||||||
|
const url = `${event.project.homepage}/-/tree/${tagname}`;
|
||||||
|
const content = `**${event.user_name}** pushed tag [\`${tagname}\`](${url}) for ${event.project.path_with_namespace}`;
|
||||||
|
await this.as.botIntent.sendEvent(this.roomId, {
|
||||||
|
msgtype: "m.notice",
|
||||||
|
body: content,
|
||||||
|
formatted_body: md.renderInline(content),
|
||||||
|
format: "org.matrix.custom.html",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public toString() {
|
public toString() {
|
||||||
return `GitHubRepo`;
|
return `GitLabRepo ${this.instance}/${this.path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldSkipHook(...hookName: string[]) {
|
||||||
|
if (this.state.ignoreHooks) {
|
||||||
|
for (const name of hookName) {
|
||||||
|
if (this.state.ignoreHooks?.includes(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Typescript doesn't understand Prototypes very well yet.
|
// Typescript doesn't understand Prototypes very well yet.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
const res = compileBotCommands(GitLabRepoConnection.prototype as any);
|
const res = compileBotCommands(GitLabRepoConnection.prototype as any, CommandConnection.prototype as any);
|
||||||
|
GitLabRepoConnection.helpMessage = res.helpMessage;
|
||||||
GitLabRepoConnection.botCommands = res.botCommands;
|
GitLabRepoConnection.botCommands = res.botCommands;
|
@ -4,7 +4,8 @@ export * from "./GithubIssue";
|
|||||||
export * from "./GithubProject";
|
export * from "./GithubProject";
|
||||||
export * from "./GithubRepo";
|
export * from "./GithubRepo";
|
||||||
export * from "./GithubUserSpace";
|
export * from "./GithubUserSpace";
|
||||||
|
|
||||||
export * from "./GitlabIssue";
|
export * from "./GitlabIssue";
|
||||||
export * from "./GitlabRepo";
|
export * from "./GitlabRepo";
|
||||||
|
export * from "./JiraProject";
|
||||||
|
export * from "./GenericHook"
|
||||||
export * from "./IConnection";
|
export * from "./IConnection";
|
@ -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,12 +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,
|
||||||
installationId: parseInt(this.config.installationId as string, 10),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.internalOctokit = new Octokit({
|
this.internalOctokit = new Octokit({
|
||||||
@ -42,13 +75,45 @@ export class GithubInstance {
|
|||||||
userAgent: USER_AGENT,
|
userAgent: USER_AGENT,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
let installPageSize = 100;
|
||||||
await this.octokit.rateLimit.get();
|
let page = 1;
|
||||||
log.info("Auth check success");
|
do {
|
||||||
} catch (ex) {
|
const installations = await this.internalOctokit.apps.listInstallations({ per_page: 100, page: page++ });
|
||||||
log.info("Auth check failed:", ex);
|
for (const install of installations.data) {
|
||||||
throw Error("Attempting to verify GitHub authentication configration failed");
|
await this.addInstallation(install);
|
||||||
|
}
|
||||||
|
installPageSize = installations.data.length;
|
||||||
|
} while(installPageSize === 100)
|
||||||
|
|
||||||
|
log.info(`Found ${this.installationsCache.size} installations`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async addInstallation(install: InstallationDataType, repos?: {full_name: string}[]) {
|
||||||
|
let matchesRepository: string[] = [];
|
||||||
|
if (install.repository_selection === "all") {
|
||||||
|
matchesRepository = [`${install.account?.login}/*`.toLowerCase()];
|
||||||
|
} else if (repos) {
|
||||||
|
matchesRepository = repos.map(r => r.full_name.toLowerCase());
|
||||||
|
} else {
|
||||||
|
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;
|
||||||
|
@ -26,6 +26,7 @@ export interface IGitlabRepository {
|
|||||||
export interface IGitlabProject {
|
export interface IGitlabProject {
|
||||||
path_with_namespace: string;
|
path_with_namespace: string;
|
||||||
web_url: string;
|
web_url: string;
|
||||||
|
homepage: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGitlabIssue {
|
export interface IGitlabIssue {
|
||||||
@ -39,6 +40,11 @@ export interface IGitlabMergeRequest {
|
|||||||
iid: number;
|
iid: number;
|
||||||
author_id: number;
|
author_id: number;
|
||||||
state: 'opened'|'closed'|'merged';
|
state: 'opened'|'closed'|'merged';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGitLabMergeRequestObjectAttributes extends IGitlabMergeRequest {
|
||||||
|
action: "open"|"close"|"reopen"|"approved"|"unapproved"|"merge";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IGitLabWebhookMREvent {
|
export interface IGitLabWebhookMREvent {
|
||||||
@ -46,9 +52,27 @@ export interface IGitLabWebhookMREvent {
|
|||||||
user: IGitlabUser;
|
user: IGitlabUser;
|
||||||
project: IGitlabProject;
|
project: IGitlabProject;
|
||||||
repository: IGitlabRepository;
|
repository: IGitlabRepository;
|
||||||
object_attributes: IGitlabMergeRequest;
|
object_attributes: IGitLabMergeRequestObjectAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IGitLabWebhookTagPushEvent {
|
||||||
|
object_kind: "tag_push";
|
||||||
|
user_id: number;
|
||||||
|
ref: string;
|
||||||
|
user_name: string;
|
||||||
|
/**
|
||||||
|
* Commit hash before push
|
||||||
|
*/
|
||||||
|
before: string;
|
||||||
|
/**
|
||||||
|
* Commit hash after push
|
||||||
|
*/
|
||||||
|
after: string;
|
||||||
|
project: IGitlabProject;
|
||||||
|
repository: IGitlabRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export interface IGitLabWebhookNoteEvent {
|
export interface IGitLabWebhookNoteEvent {
|
||||||
user: IGitlabUser;
|
user: IGitlabUser;
|
||||||
project: IGitlabProject;
|
project: IGitlabProject;
|
||||||
|
@ -2,8 +2,8 @@ import axios from "axios";
|
|||||||
import { Router, Request, Response } from "express";
|
import { Router, Request, Response } from "express";
|
||||||
import { BridgeConfigJira } from "../Config/Config";
|
import { BridgeConfigJira } from "../Config/Config";
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import { MessageQueue } from "../MessageQueue/MessageQueue";
|
import { MessageQueue } from "../MessageQueue";
|
||||||
import { OAuthRequest } from "../Webhooks";
|
import { OAuthRequest } from "../WebhookTypes";
|
||||||
import { JiraOAuthResult } from "./Types";
|
import { JiraOAuthResult } from "./Types";
|
||||||
|
|
||||||
const log = new LogWrapper("JiraRouter");
|
const log = new LogWrapper("JiraRouter");
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { BridgeConfig } from "./Config/Config";
|
import { BridgeConfig } from "./Config/Config";
|
||||||
import { MessageQueue, createMessageQueue } from "./MessageQueue/MessageQueue";
|
import { MessageQueue, createMessageQueue } from "./MessageQueue";
|
||||||
import { MatrixEventContent, MatrixMessageContent } from "./MatrixEvent";
|
import { MatrixEventContent, MatrixMessageContent } from "./MatrixEvent";
|
||||||
import { Appservice, IAppserviceRegistration } from "matrix-bot-sdk";
|
import { Appservice, IAppserviceRegistration } from "matrix-bot-sdk";
|
||||||
import LogWrapper from "./LogWrapper";
|
import LogWrapper from "./LogWrapper";
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { MessageQueue, MessageQueueMessage, DEFAULT_RES_TIMEOUT } from "./MessageQueue";
|
import { MessageQueue, MessageQueueMessage, DEFAULT_RES_TIMEOUT } from "./Types";
|
||||||
import micromatch from "micromatch";
|
import micromatch from "micromatch";
|
||||||
import {v4 as uuid} from "uuid";
|
import {v4 as uuid} from "uuid";
|
||||||
|
|
||||||
|
@ -1,34 +1,11 @@
|
|||||||
import { BridgeConfig } from "../Config/Config";
|
import { BridgeConfig } from "../Config/Config";
|
||||||
import { LocalMQ } from "./LocalMQ";
|
import { LocalMQ } from "./LocalMQ";
|
||||||
import { RedisMQ } from "./RedisQueue";
|
import { RedisMQ } from "./RedisQueue";
|
||||||
|
import { MessageQueue } from "./Types";
|
||||||
export const DEFAULT_RES_TIMEOUT = 30000;
|
|
||||||
|
|
||||||
const staticLocalMq = new LocalMQ();
|
const staticLocalMq = new LocalMQ();
|
||||||
let staticRedisMq: RedisMQ|null = null;
|
let staticRedisMq: RedisMQ|null = null;
|
||||||
|
|
||||||
|
|
||||||
export interface MessageQueueMessage<T> {
|
|
||||||
sender: string;
|
|
||||||
eventName: string;
|
|
||||||
data: T;
|
|
||||||
messageId?: string;
|
|
||||||
for?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageQueueMessageOut<T> extends MessageQueueMessage<T> {
|
|
||||||
ts: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MessageQueue {
|
|
||||||
subscribe: (eventGlob: string) => void;
|
|
||||||
unsubscribe: (eventGlob: string) => void;
|
|
||||||
push: <T>(data: MessageQueueMessage<T>, single?: boolean) => Promise<void>;
|
|
||||||
pushWait: <T, X>(data: MessageQueueMessage<T>, timeout?: number, single?: boolean) => Promise<X>;
|
|
||||||
on: <T>(eventName: string, cb: (data: MessageQueueMessageOut<T>) => void) => void;
|
|
||||||
stop?(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createMessageQueue(config: BridgeConfig): MessageQueue {
|
export function createMessageQueue(config: BridgeConfig): MessageQueue {
|
||||||
if (config.queue.monolithic) {
|
if (config.queue.monolithic) {
|
||||||
return staticLocalMq;
|
return staticLocalMq;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { MessageQueue, MessageQueueMessage, DEFAULT_RES_TIMEOUT, MessageQueueMessageOut } from "./MessageQueue";
|
|
||||||
|
import { MessageQueue, MessageQueueMessage, DEFAULT_RES_TIMEOUT, MessageQueueMessageOut } from "./Types";
|
||||||
import { Redis, default as redis } from "ioredis";
|
import { Redis, default as redis } from "ioredis";
|
||||||
import { BridgeConfig } from "../Config/Config";
|
import { BridgeConfig } from "../Config/Config";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
22
src/MessageQueue/Types.ts
Normal file
22
src/MessageQueue/Types.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export interface MessageQueueMessage<T> {
|
||||||
|
sender: string;
|
||||||
|
eventName: string;
|
||||||
|
data: T;
|
||||||
|
messageId?: string;
|
||||||
|
for?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageQueueMessageOut<T> extends MessageQueueMessage<T> {
|
||||||
|
ts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MessageQueue {
|
||||||
|
subscribe: (eventGlob: string) => void;
|
||||||
|
unsubscribe: (eventGlob: string) => void;
|
||||||
|
push: <T>(data: MessageQueueMessage<T>, single?: boolean) => Promise<void>;
|
||||||
|
pushWait: <T, X>(data: MessageQueueMessage<T>, timeout?: number, single?: boolean) => Promise<X>;
|
||||||
|
on: <T>(eventName: string, cb: (data: MessageQueueMessageOut<T>) => void) => void;
|
||||||
|
stop?(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_RES_TIMEOUT = 30000;
|
2
src/MessageQueue/index.ts
Normal file
2
src/MessageQueue/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./Types";
|
||||||
|
export * from "./MessageQueue";
|
@ -1,6 +1,6 @@
|
|||||||
import { NotificationsDisableEvent, NotificationsEnableEvent } from "../Webhooks";
|
import { NotificationsDisableEvent, NotificationsEnableEvent } from "../Webhooks";
|
||||||
import LogWrapper from "../LogWrapper";
|
import LogWrapper from "../LogWrapper";
|
||||||
import { createMessageQueue, MessageQueue, MessageQueueMessage } from "../MessageQueue/MessageQueue";
|
import { createMessageQueue, MessageQueue, MessageQueueMessage } from "../MessageQueue";
|
||||||
import { MessageSenderClient } from "../MatrixSender";
|
import { MessageSenderClient } from "../MatrixSender";
|
||||||
import { NotificationWatcherTask } from "./NotificationWatcherTask";
|
import { NotificationWatcherTask } from "./NotificationWatcherTask";
|
||||||
import { GitHubWatcher } from "./GitHubWatcher";
|
import { GitHubWatcher } from "./GitHubWatcher";
|
||||||
|
3
src/WebhookTypes.ts
Normal file
3
src/WebhookTypes.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface OAuthRequest {
|
||||||
|
state: string;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { BridgeConfig } from "./Config/Config";
|
import { BridgeConfig } from "./Config/Config";
|
||||||
import { Application, default as express, Request, Response } from "express";
|
import { Application, default as express, Request, Response } from "express";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { MessageQueue, createMessageQueue } from "./MessageQueue/MessageQueue";
|
import { MessageQueue, createMessageQueue } from "./MessageQueue";
|
||||||
import LogWrapper from "./LogWrapper";
|
import LogWrapper from "./LogWrapper";
|
||||||
import qs from "querystring";
|
import qs from "querystring";
|
||||||
import { Server } from "http";
|
import { Server } from "http";
|
||||||
@ -10,6 +10,7 @@ import { IGitLabWebhookEvent } from "./Gitlab/WebhookTypes";
|
|||||||
import { EmitterWebhookEvent, Webhooks as OctokitWebhooks } from "@octokit/webhooks"
|
import { EmitterWebhookEvent, Webhooks as OctokitWebhooks } from "@octokit/webhooks"
|
||||||
import { IJiraWebhookEvent } from "./Jira/WebhookTypes";
|
import { IJiraWebhookEvent } from "./Jira/WebhookTypes";
|
||||||
import JiraRouter from "./Jira/Router";
|
import JiraRouter from "./Jira/Router";
|
||||||
|
import { OAuthRequest } from "./WebhookTypes";
|
||||||
const log = new LogWrapper("GithubWebhooks");
|
const log = new LogWrapper("GithubWebhooks");
|
||||||
|
|
||||||
export interface GenericWebhookEvent {
|
export interface GenericWebhookEvent {
|
||||||
@ -17,10 +18,6 @@ export interface GenericWebhookEvent {
|
|||||||
hookId: string;
|
hookId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OAuthRequest {
|
|
||||||
state: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GitHubOAuthTokens {
|
export interface GitHubOAuthTokens {
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
access_token: string;
|
access_token: string;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user