mirror of
https://github.com/matrix-org/matrix-hookshot.git
synced 2025-03-10 21:19:13 +00:00
Allow users to import other people's go-neb services (#695)
* Allow room admins to import other people's go-neb services This requires us to guess what these other people's MXIDs were, so we scroll through the list of room members and make educated guesses about which of them are Scalar+go-neb bots, and which users they were set up by. * Relax our requirements for scraping others' go-neb connections * Changelog * Linting --------- Co-authored-by: Tadeusz Sośnierz <tadeusz@sosnierz.com>
This commit is contained in:
parent
76e2b53cf0
commit
0ce06c4ea7
1
changelog.d/695.feature
Normal file
1
changelog.d/695.feature
Normal file
@ -0,0 +1 @@
|
|||||||
|
Allow users to import other people's go-neb services.
|
@ -434,6 +434,7 @@ export interface BridgeConfigMetrics {
|
|||||||
export interface BridgeConfigGoNebMigrator {
|
export interface BridgeConfigGoNebMigrator {
|
||||||
apiUrl: string;
|
apiUrl: string;
|
||||||
serviceIds?: string[];
|
serviceIds?: string[];
|
||||||
|
goNebBotPrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BridgeConfigRoot {
|
export interface BridgeConfigRoot {
|
||||||
|
@ -70,6 +70,7 @@ export class BridgeWidgetApi extends ProvisioningApi {
|
|||||||
this.goNebMigrator = new GoNebMigrator(
|
this.goNebMigrator = new GoNebMigrator(
|
||||||
this.config.goNebMigrator.apiUrl,
|
this.config.goNebMigrator.apiUrl,
|
||||||
this.config.goNebMigrator.serviceIds,
|
this.config.goNebMigrator.serviceIds,
|
||||||
|
this.config.goNebMigrator.goNebBotPrefix,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +91,12 @@ export class BridgeWidgetApi extends ProvisioningApi {
|
|||||||
|
|
||||||
const botUser = await this.getBotUserInRoom(roomId);
|
const botUser = await this.getBotUserInRoom(roomId);
|
||||||
await assertUserPermissionsInRoom(req.userId, roomId, "read", botUser.intent);
|
await assertUserPermissionsInRoom(req.userId, roomId, "read", botUser.intent);
|
||||||
const connections = await this.goNebMigrator.getConnectionsForRoom(roomId, req.userId);
|
|
||||||
|
const userIds = this.goNebMigrator.getGoNebUsersFromRoomMembers(
|
||||||
|
await botUser.intent.underlyingClient.getJoinedRoomMembers(roomId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const connections = await this.goNebMigrator.getConnectionsForRoom(roomId, new Set(userIds));
|
||||||
|
|
||||||
res.send(connections);
|
res.send(connections);
|
||||||
}
|
}
|
||||||
|
@ -46,10 +46,14 @@ interface GoNebGithubWebhookService extends GoNebService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class GoNebMigrator {
|
export class GoNebMigrator {
|
||||||
|
private goNebBotPrefix: string;
|
||||||
constructor(
|
constructor(
|
||||||
private apiUrl: string,
|
private apiUrl: string,
|
||||||
private serviceIds?: string[],
|
private serviceIds?: string[],
|
||||||
) {}
|
goNebBotPrefix?: string,
|
||||||
|
) {
|
||||||
|
this.goNebBotPrefix = goNebBotPrefix ?? '@_neb_';
|
||||||
|
}
|
||||||
|
|
||||||
static convertFeeds(goNebFeeds: GoNebFeedsConfig): Map<string, FeedConnectionState[]> {
|
static convertFeeds(goNebFeeds: GoNebFeedsConfig): Map<string, FeedConnectionState[]> {
|
||||||
const feedsPerRoom = new Map<string, FeedConnectionState[]>();
|
const feedsPerRoom = new Map<string, FeedConnectionState[]>();
|
||||||
@ -83,14 +87,14 @@ export class GoNebMigrator {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getConnectionsForRoom(roomId: string, userId: string): Promise<MigratedConnections> {
|
public async getConnectionsForRoom(roomId: string, userIds: Set<string>): Promise<MigratedConnections> {
|
||||||
const feeds: MigratedFeed[] = [];
|
const feeds: MigratedFeed[] = [];
|
||||||
const github: MigratedGithub[] = [];
|
const github: MigratedGithub[] = [];
|
||||||
|
|
||||||
const serviceIds = [
|
const serviceIds = new Set([
|
||||||
...(this.serviceIds ?? []),
|
...(this.serviceIds ?? []),
|
||||||
...['rssbot', 'github'].map(type => `${type}/${strictEncodeURIComponent(userId)}/${strictEncodeURIComponent(roomId)}`),
|
...['rssbot', 'github'].flatMap(type => Array.from(userIds).map(userId => `${type}/${strictEncodeURIComponent(userId)}/${strictEncodeURIComponent(roomId)}`)),
|
||||||
];
|
]);
|
||||||
|
|
||||||
for (const id of serviceIds) {
|
for (const id of serviceIds) {
|
||||||
const endpoint = this.apiUrl + (this.apiUrl.endsWith('/') ? '' : '/') + 'admin/getService';
|
const endpoint = this.apiUrl + (this.apiUrl.endsWith('/') ? '' : '/') + 'admin/getService';
|
||||||
@ -116,7 +120,7 @@ export class GoNebMigrator {
|
|||||||
}
|
}
|
||||||
case 'github-webhook': {
|
case 'github-webhook': {
|
||||||
const service = obj as GoNebGithubWebhookService;
|
const service = obj as GoNebGithubWebhookService;
|
||||||
if (service.Config.ClientUserID === userId) {
|
if (userIds.has(service.Config.ClientUserID)) {
|
||||||
const roomRepos = service.Config.Rooms[roomId]?.Repos;
|
const roomRepos = service.Config.Rooms[roomId]?.Repos;
|
||||||
if (roomRepos) {
|
if (roomRepos) {
|
||||||
const githubConnections = GoNebMigrator.convertGithub(roomRepos);
|
const githubConnections = GoNebMigrator.convertGithub(roomRepos);
|
||||||
@ -137,6 +141,43 @@ export class GoNebMigrator {
|
|||||||
github,
|
github,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getGoNebUsersFromRoomMembers(members: string[]): string[] {
|
||||||
|
const goNebUsers = [];
|
||||||
|
|
||||||
|
for (const member of members) {
|
||||||
|
if (member.startsWith(this.goNebBotPrefix)) {
|
||||||
|
try {
|
||||||
|
const mxid = this.getUserMxid(member);
|
||||||
|
goNebUsers.push(mxid);
|
||||||
|
} catch (err: unknown) {
|
||||||
|
log.error(`${member} looks like a go-neb mxid, but we failed to extract the owner mxid from it (${err})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return goNebUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUserMxid(botMxid: string): string {
|
||||||
|
let userPart = botMxid.substring(this.goNebBotPrefix.length);
|
||||||
|
// strip the service type (before first '_') and server name (after ':')
|
||||||
|
try {
|
||||||
|
[, userPart] = userPart.match(/[^_]+_([^:]+):.*/)!;
|
||||||
|
} catch (err: unknown) {
|
||||||
|
throw new Error(`${botMxid} does not look like a Scalar-produced go-neb mxid`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode according to https://spec.matrix.org/v1.2/appendices/#mapping-from-other-character-sets,
|
||||||
|
return userPart.replace(/=\w\w/g, (match) => {
|
||||||
|
// first the lowercased string...
|
||||||
|
const code = parseInt(match.substring(1), 16);
|
||||||
|
return String.fromCharCode(code);
|
||||||
|
}).replace(/_\w/g, (match) => {
|
||||||
|
// and then reapply the uppercase where applicable
|
||||||
|
return match.substring(1).toUpperCase();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
|
// from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
|
||||||
|
Loading…
x
Reference in New Issue
Block a user