Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
for (const userRule of list.userRules) {
if (userRule.isMatch(member.userId)) {
// User needs to be banned
// We specifically use sendNotice to avoid having to escape HTML
await logMessage(LogLevel.DEBUG, "ApplyBan", `Banning ${member.userId} in ${roomId} for: ${userRule.reason}`);
if (!config.noop) {
// Always prioritize redactions above bans
if (mjolnir.automaticRedactGlobs.find(g => g.test(userRule.reason.toLowerCase()))) {
await redactUserMessagesIn(mjolnir.client, member.userId, [roomId]);
}
await mjolnir.client.banUser(member.userId, roomId, userRule.reason);
} else {
await logMessage(LogLevel.WARN, "ApplyBan", `Tried to ban ${member.userId} in ${roomId} but Mjolnir is running in no-op mode`);
}
bansApplied++;
banned = true;
break;
}
}
if (banned) break;
}
}
} catch (e) {
const message = e.message || (e.body ? e.body.error : '');
errors.push({
roomId,
errorMessage: message,
errorKind: message.includes("You don't have permission to ban") ? ERROR_KIND_PERMISSION : ERROR_KIND_FATAL,
this.recentlyBanned.push(event['sender']); // flag to reduce spam
// Redact all the things the user said too
if (!config.noop) {
for (const eventId of forUser.map(e => e.eventId)) {
await mjolnir.client.redactEvent(roomId, eventId, "spam");
}
} else {
await logMessage(LogLevel.WARN, "BasicFlooding", `Tried to redact messages for ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`);
}
await logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`);
}
// Free up some memory now that we're ready to handle it elsewhere
forUser = forRoom[event['sender']] = []; // reset the user's list
}
// Trim the oldest messages off the user's history if it's getting large
if (forUser.length > MAX_PER_MINUTE * 2) {
forUser.splice(0, forUser.length - (MAX_PER_MINUTE * 2) - 1);
}
}
}
if (this.recentlyBanned.includes(event['sender'])) return; // already handled (will be redacted)
mjolnir.redactionHandler.addUser(event['sender']);
this.recentlyBanned.push(event['sender']); // flag to reduce spam
// Redact the event
if (!config.noop) {
await mjolnir.client.redactEvent(roomId, event['event_id'], "spam");
} else {
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to redact ${event['event_id']} in ${roomId} but Mjolnir is running in no-op mode`);
}
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Banning ${event['sender']} for posting an image as the first thing after joining in ${roomId}.`);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await logMessage(LogLevel.WARN, "FirstMessageIsImage", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`);
}
}
}
const idx = this.justJoined[roomId].indexOf(event['sender']);
if (idx >= 0) {
LogService.info("FirstMessageIsImage", `${event['sender']} is no longer considered suspect`);
this.justJoined[roomId].splice(idx, 1);
}
}
}
}
if (messageCount >= MAX_PER_MINUTE) {
// Prioritize redaction over ban - we can always keep redacting what the user said.
if (this.recentlyBanned.includes(event['sender'])) return; // already handled (will be redacted)
mjolnir.redactionHandler.addUser(event['sender']);
this.recentlyBanned.push(event['sender']); // flag to reduce spam
// Redact all the things the user said too
if (!config.noop) {
for (const eventId of forUser.map(e => e.eventId)) {
await mjolnir.client.redactEvent(roomId, eventId, "spam");
}
} else {
await logMessage(LogLevel.WARN, "BasicFlooding", `Tried to redact messages for ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`);
}
await logMessage(LogLevel.WARN, "BasicFlooding", `Banning ${event['sender']} in ${roomId} for flooding (${messageCount} messages in the last minute)`);
if (!config.noop) {
await mjolnir.client.banUser(event['sender'], roomId, "spam");
} else {
await logMessage(LogLevel.WARN, "BasicFlooding", `Tried to ban ${event['sender']} in ${roomId} but Mjolnir is running in no-op mode`);
}
// Free up some memory now that we're ready to handle it elsewhere
forUser = forRoom[event['sender']] = []; // reset the user's list
}
// Trim the oldest messages off the user's history if it's getting large
if (forUser.length > MAX_PER_MINUTE * 2) {
forUser.splice(0, forUser.length - (MAX_PER_MINUTE * 2) - 1);
const isMedia = msgtype === 'm.image' || msgtype === 'm.video' || formattedBody.toLowerCase().includes('= 0) {
LogService.info("FirstMessageIsImage", `${event['sender']} is no longer considered suspect`);
this.justJoined[roomId].splice(idx, 1);
}
}
}
if (USER_RULE_TYPES.includes(bits.ruleType) && parts.length > 5 && parts[5] === 'true') {
const rule = new MatrixGlob(bits.entity);
await logMessage(LogLevel.INFO, "UnbanBanCommand", "Unbanning users that match glob: " + bits.entity);
let unbannedSomeone = false;
for (const protectedRoomId of Object.keys(mjolnir.protectedRooms)) {
const members = await mjolnir.client.getRoomMembers(protectedRoomId, null, ['ban'], null);
for (const member of members) {
const victim = member['state_key'];
if (!member['content'] || member['content']['membership'] !== 'ban') continue;
if (rule.test(victim)) {
await logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Unbanning ${victim} in ${protectedRoomId}`);
if (!config.noop) {
await mjolnir.client.unbanUser(victim, protectedRoomId);
} else {
await logMessage(LogLevel.WARN, "UnbanBanCommand", `Attempted to unban ${victim} in ${protectedRoomId} but Mjolnir is running in no-op mode`);
}
unbannedSomeone = true;
}
}
}
if (unbannedSomeone) {
await logMessage(LogLevel.DEBUG, "UnbanBanCommand", `Syncing lists to ensure no users were accidentally unbanned`);
await mjolnir.syncLists(config.verboseLogging);
}
}
await mjolnir.client.unstableApis.addReactionToEvent(roomId, event['event_id'], '✅');
}
export async function redactUserMessagesIn(client: MatrixClient, userIdOrGlob: string, targetRoomIds: string[]) {
for (const targetRoomId of targetRoomIds) {
await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Fetching sent messages for ${userIdOrGlob} in ${targetRoomId} to redact...`);
const eventsToRedact = await getMessagesByUserSinceLastJoin(client, userIdOrGlob, targetRoomId);
for (const victimEvent of eventsToRedact) {
await logMessage(LogLevel.DEBUG, "utils#redactUserMessagesIn", `Redacting ${victimEvent['event_id']} in ${targetRoomId}`);
if (!config.noop) {
await client.redactEvent(targetRoomId, victimEvent['event_id']);
} else {
await logMessage(LogLevel.WARN, "utils#redactUserMessagesIn", `Tried to redact ${victimEvent['event_id']} in ${targetRoomId} but Mjolnir is running in no-op mode`);
}
}
}
}
public async handleEvent(roomId: string, event: any, mjolnirClient: MatrixClient) {
if (this.isUserQueued(event['sender'])) {
const permalink = Permalinks.forEvent(roomId, event['event_id']);
try {
LogService.info("AutomaticRedactionQueue", `Redacting event because the user is listed as bad: ${permalink}`)
if (!config.noop) {
await mjolnirClient.redactEvent(roomId, event['event_id']);
} else {
await logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Tried to redact ${permalink} but Mjolnir is running in no-op mode`);
}
} catch (e) {
logMessage(LogLevel.WARN, "AutomaticRedactionQueue", `Unable to redact message: ${permalink}`);
LogService.warn("AutomaticRedactionQueue", e);
}
}
}
}