Minor grammar corrections in code and documentation (#267)

* setupWidget -> setUpWidget

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

* setupAdminRoom -> setUpAdminRoom

* it's -> its

* Create 267.feature

* log in to; Currently, ; full stops

* Improve copy of error messages

* Update 267.feature

* Rename 267.feature to 267.misc

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

View File

@ -17,7 +17,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Login to Docker Hub
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}

View File

@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v2
- name: Get release tag
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Login to Docker Hub
- name: Log in to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}

View File

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

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

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

View File

@ -108,11 +108,11 @@ Each permission set can have a services. The `service` field can be:
- `*`, for any service.
The `level` can be:
- `commands` Can run commands within connected rooms, but NOT log into the bridge.
- `login` All the above, and can also log into the bridge.
- `commands` Can run commands within connected rooms, but NOT log in to the bridge.
- `login` All the above, and can also log in to the bridge.
- `notifications` All the above, and can also bridge their notifications.
- `manageConnections` All the above, and can create and delete connections (either via the provisioner, setup commands, or state events).
- `admin` All permissions. Currently there are no admin features so this exists as a placeholder.
- `admin` All permissions. Currently, there are no admin features so this exists as a placeholder.
When permissions are checked, if a user matches any of the permission set and one
of those grants the right level for a service, they are allowed access. If none of the
@ -134,7 +134,7 @@ permissions:
services:
- service: github
level: manageConnections
# Allow users on this domain to login to jira and github.
# Allow users on this domain to log in to jira and github.
- actor: support.example.com
services:
- service: jira

View File

@ -29,7 +29,7 @@ The `accessToken` should be the personal access token for your account.
The `passcode` should be a randomly generated code which is used to authenticate requests from Figma.
The bridge will automatically setup a webhook on Figma for you upon startup, and will automatically reconfigure that webhook if the `publicUrl` or `passcode` changes.
The bridge will automatically set up a webhook on Figma for you upon startup, and will automatically reconfigure that webhook if the `publicUrl` or `passcode` changes.
## Next steps

View File

@ -25,7 +25,7 @@ jira:
... # See below
```
You can omit the `oauth` section if you are not planning to allow users to login and use interactive features (i.e. webhook only mode).
You can omit the `oauth` section if you are not planning to allow users to log in and use interactive features (i.e. webhook only mode).
## Connecting Matrix users to JIRA
@ -85,7 +85,7 @@ jira:
# The path to your webhooks listener on the "/jira/oauth" path.
redirect_uri: http://localhost:5065/jira/oauth
```
To start with, setup your JIRA instance to support OAuth.
To start with, set up your JIRA instance to support OAuth.
1. Open the **Administration** page for your JIRA instance.
@ -104,7 +104,7 @@ To start with, setup your JIRA instance to support OAuth.
1. Enter your `consumerKey` from the config file above.
2. The `consumerName` can be anything, but will be visible to users of your app. You could use something like `Matrix`, `Hookshot` or anything else.
3. The `publicKey` can be generated by running `openssl rsa -in jira_privatekey.pem -pubout` on the key you created earlier.
7. Congratulations, you now have OAuth setup.
7. Congratulations, you now have OAuth set up.
## Next steps

View File

@ -38,7 +38,7 @@ To authenticate with a personal access token:
You can authenticate with GitLab by supplying a Personal Access Token. OAuth-style authentication isn't supported
yet.
- You will need to have configured a GitLab instance in your config.yml for the instance you want to login to.
- You will need to have configured a GitLab instance in your config.yml for the instance you want to log in to.
- Open https://%instance%/-/profile/personal_access_tokens (GitLab > User Settings > Access Tokens), where instance is your GitLab instance address.
- For the public GitLab server, this would be "gitlab.com"
- Give it a good name, and a sensible expiration date. For scopes you will need to tick `api`.
@ -49,7 +49,7 @@ yet.
## JIRA
You can login to JIRA via OAuth. This means you will need to have configured OAuth support in your `config.yml`, and
You can log in to JIRA via OAuth. This means you will need to have configured OAuth support in your `config.yml`, and
have the endpoints required accessible from the internet. Authentication is required when trying to bridge JIRA resources into rooms.
- Say `jira login` to get the URL to authenticate via.

View File

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

View File

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

View File

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

View File

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

View File

@ -514,7 +514,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
const workflow = workflows.data.workflows.find(w => w.name.toLowerCase().trim() === name.toLowerCase().trim());
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");
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 {
@ -544,7 +544,7 @@ export class GitHubRepoConnection extends CommandConnection implements IConnecti
throw ex;
}
await this.as.botIntent.sendText(this.roomId, `Workflow started`, "m.notice");
await this.as.botIntent.sendText(this.roomId, `Workflow started.`, "m.notice");
}
public async onIssueCreated(event: IssuesOpenedEvent) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -76,7 +76,7 @@ export class UserTokenStore {
public async storeUserToken(type: TokenType, userId: string, token: string, instanceUrl?: string): Promise<void> {
if (!this.config.checkPermission(userId, type, BridgePermissionLevel.login)) {
throw new ApiError('User does not have permission to login to service', ErrCode.ForbiddenUser);
throw new ApiError('User does not have permission to log in to service', ErrCode.ForbiddenUser);
}
const key = tokenKey(type, userId, false, instanceUrl);
const tokenParts: string[] = [];

View File

@ -55,7 +55,7 @@ export async function ensureFigmaWebhooks(figmaConfig: BridgeConfigFigma, matrix
webhookDefinition = res.data as FigmaWebhookDefinition;
await matrixClient.setAccountData(accountDataKey, {webhookId: webhookDefinition.id});
}
// Webhook is ready and setup
// Webhook is ready and set up
}
}

View File

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

View File

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