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 {
|
||||
apiUrl: string;
|
||||
serviceIds?: string[];
|
||||
goNebBotPrefix?: string;
|
||||
}
|
||||
|
||||
export interface BridgeConfigRoot {
|
||||
|
@ -70,6 +70,7 @@ export class BridgeWidgetApi extends ProvisioningApi {
|
||||
this.goNebMigrator = new GoNebMigrator(
|
||||
this.config.goNebMigrator.apiUrl,
|
||||
this.config.goNebMigrator.serviceIds,
|
||||
this.config.goNebMigrator.goNebBotPrefix,
|
||||
);
|
||||
}
|
||||
|
||||
@ -90,7 +91,12 @@ export class BridgeWidgetApi extends ProvisioningApi {
|
||||
|
||||
const botUser = await this.getBotUserInRoom(roomId);
|
||||
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);
|
||||
}
|
||||
|
@ -46,10 +46,14 @@ interface GoNebGithubWebhookService extends GoNebService {
|
||||
}
|
||||
|
||||
export class GoNebMigrator {
|
||||
private goNebBotPrefix: string;
|
||||
constructor(
|
||||
private apiUrl: string,
|
||||
private serviceIds?: string[],
|
||||
) {}
|
||||
goNebBotPrefix?: string,
|
||||
) {
|
||||
this.goNebBotPrefix = goNebBotPrefix ?? '@_neb_';
|
||||
}
|
||||
|
||||
static convertFeeds(goNebFeeds: GoNebFeedsConfig): 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 github: MigratedGithub[] = [];
|
||||
|
||||
const serviceIds = [
|
||||
const serviceIds = new Set([
|
||||
...(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) {
|
||||
const endpoint = this.apiUrl + (this.apiUrl.endsWith('/') ? '' : '/') + 'admin/getService';
|
||||
@ -116,7 +120,7 @@ export class GoNebMigrator {
|
||||
}
|
||||
case 'github-webhook': {
|
||||
const service = obj as GoNebGithubWebhookService;
|
||||
if (service.Config.ClientUserID === userId) {
|
||||
if (userIds.has(service.Config.ClientUserID)) {
|
||||
const roomRepos = service.Config.Rooms[roomId]?.Repos;
|
||||
if (roomRepos) {
|
||||
const githubConnections = GoNebMigrator.convertGithub(roomRepos);
|
||||
@ -137,6 +141,43 @@ export class GoNebMigrator {
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user