Skip to content

Commit

Permalink
feat: add listusers command
Browse files Browse the repository at this point in the history
Signed-off-by: Seth Falco <[email protected]>
  • Loading branch information
SethFalco committed Sep 27, 2022
1 parent ac5180e commit 8738ebd
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog.d/854.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add listusers command to Discord bot to list the users on the Matrix side. Thanks to @SethFalco!
2 changes: 1 addition & 1 deletion src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export class DiscordBot {
this.mxEventProcessor = new MatrixEventProcessor(
new MatrixEventProcessorOpts(config, bridge, this, store),
);
this.discordCommandHandler = new DiscordCommandHandler(bridge, this);
this.discordCommandHandler = new DiscordCommandHandler(bridge, this, config);
// init vars
this.sentMessages = [];
this.discordMessageQueue = {};
Expand Down
129 changes: 120 additions & 9 deletions src/discordcommandhandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ import { DiscordBot } from "./bot";
import * as Discord from "better-discord.js";
import { Util, ICommandActions, ICommandParameters, CommandPermissonCheck } from "./util";
import { Log } from "./log";
import { Appservice } from "matrix-bot-sdk";
import { Appservice, Presence } from "matrix-bot-sdk";
import { DiscordBridgeConfig } from './config';

const log = new Log("DiscordCommandHandler");

export class DiscordCommandHandler {
constructor(
private bridge: Appservice,
private discord: DiscordBot,
private config: DiscordBridgeConfig,
) { }

public async Process(msg: Discord.Message) {
Expand All @@ -50,15 +52,15 @@ export class DiscordCommandHandler {
permission: "MANAGE_WEBHOOKS",
run: async () => {
if (await this.discord.Provisioner.MarkApproved(chan, discordMember, true)) {
return "Thanks for your response! The matrix bridge has been approved.";
return "Thanks for your response! The Matrix bridge has been approved.";
} else {
return "Thanks for your response, however" +
" it has arrived after the deadline - sorry!";
}
},
},
ban: {
description: "Bans a user on the matrix side",
description: "Bans a user on the Matrix side",
params: ["name"],
permission: "BAN_MEMBERS",
run: this.ModerationActionGenerator(chan, "ban"),
Expand All @@ -69,36 +71,42 @@ export class DiscordCommandHandler {
permission: "MANAGE_WEBHOOKS",
run: async () => {
if (await this.discord.Provisioner.MarkApproved(chan, discordMember, false)) {
return "Thanks for your response! The matrix bridge has been declined.";
return "Thanks for your response! The Matrix bridge has been declined.";
} else {
return "Thanks for your response, however" +
" it has arrived after the deadline - sorry!";
}
},
},
kick: {
description: "Kicks a user on the matrix side",
description: "Kicks a user on the Matrix side",
params: ["name"],
permission: "KICK_MEMBERS",
run: this.ModerationActionGenerator(chan, "kick"),
},
unban: {
description: "Unbans a user on the matrix side",
description: "Unbans a user on the Matrix side",
params: ["name"],
permission: "BAN_MEMBERS",
run: this.ModerationActionGenerator(chan, "unban"),
},
unbridge: {
description: "Unbridge matrix rooms from this channel",
description: "Unbridge Matrix rooms from this channel",
params: [],
permission: ["MANAGE_WEBHOOKS", "MANAGE_CHANNELS"],
run: async () => this.UnbridgeChannel(chan),
},
listusers: {
description: "List users on the Matrix side of the bridge",
params: [],
permission: [],
run: async () => this.ListMatrixMembers(chan)
}
};

const parameters: ICommandParameters = {
name: {
description: "The display name or mxid of a matrix user",
description: "The display name or mxid of a Matrix user",
get: async (name) => {
const channelMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(msg.channel);
const mxUserId = await Util.GetMxidFromName(intent, name, channelMxids);
Expand Down Expand Up @@ -156,12 +164,115 @@ export class DiscordCommandHandler {
return "This channel has been unbridged";
} catch (err) {
if (err.message === "Channel is not bridged") {
return "This channel is not bridged to a plumbed matrix room";
return "This channel is not bridged to a plumbed Matrix room";
}
log.error("Error while unbridging room " + channel.id);
log.error(err);
return "There was an error unbridging this room. " +
"Please try again later or contact the bridge operator.";
}
}

private async ListMatrixMembers(channel: Discord.TextChannel): Promise<string> {
const chanMxids = await this.discord.ChannelSyncroniser.GetRoomIdsFromChannel(channel);
const members: {
mxid: string;
displayName?: string;
presence?: Presence;
}[] = [];
const errorMessages: string[] = [];

await Promise.all(chanMxids.map(async (chanMxid) => {
const { underlyingClient } = this.bridge.botIntent;

try {
const memberProfiles = await underlyingClient.getJoinedRoomMembersWithProfiles(chanMxid);
const userProfiles = Object.keys(memberProfiles)
.filter((mxid) => !this.bridge.isNamespacedUser(mxid))
.map((mxid) => {
return {
mxid,
displayName: memberProfiles[mxid].display_name
};
});

members.push(...userProfiles);
} catch (e) {
errorMessages.push(`Couldn't get members from ${chanMxid}`);
}
}));

if (errorMessages.length) {
const errorMessage = errorMessages.join('\n');
throw Error(errorMessage);
}

if (!this.config.bridge.disablePresence) {
await Promise.all(members.map(async (member) => {
const { botClient } = this.bridge;
try {
const presence = await botClient.getPresenceStatusFor(member.mxid);
member.presence = presence;
return presence;
} catch (e) {
errorMessages.push(`Couldn't get presence for ${member.mxid}`);
}
}));
}

if (errorMessages.length) {
const errorMessage = errorMessages.join('\n');
throw Error(errorMessage);
}

const length = members.length;
const formatter = new Intl.NumberFormat('en-US');
const formattedTotalMembers = formatter.format(length);
let userCount: string;

if (length === 1) {
userCount = `is **1** user`;
} else {
userCount = `are **${formattedTotalMembers}** users`;
}

const userCountMessage = `There ${userCount} on the Matrix side.`;

if (length === 0) {
return userCountMessage;
}

const disclaimer = `Matrix users in ${channel.toString()} may not necessarily be in the other bridged channels in the server.`;
/** Reserve characters for the worst-case "and x others…" line at the end if there are too many members. */
const reservedChars = `\n_and ${formattedTotalMembers} others…_`.length;

let message = `${userCountMessage} ${disclaimer}\n`;

for (let i = 0; i < length; i++) {
const member = members[i];
const hasDisplayName = !!member.displayName;
let line = "• ";

if (hasDisplayName) {
line += `${member.displayName} (${member.mxid})`;
} else {
line += member.mxid;
}

if (member.presence) {
const { state } = member.presence;
line += ` - ${state.charAt(0).toUpperCase() + state.slice(1)}`;
}

if (2000 - message.length - reservedChars < line.length) {
const remaining = length - i;
message += `\n_and ${formatter.format(remaining)} others…_`;
return message;
}

message += `\n${line}`;
}

return message;
}
}

0 comments on commit 8738ebd

Please sign in to comment.