mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
Add !mjolnir rules matching <entity>
to search watched lists. (#307)
* Add `!mjolnir rules matching <entity> to search watched lists. Lists all the rules that will match the entity.
This commit is contained in:
parent
b03d81dcc4
commit
941cc32ddd
@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
import { Mjolnir } from "../Mjolnir";
|
import { Mjolnir } from "../Mjolnir";
|
||||||
import { execStatusCommand } from "./StatusCommand";
|
import { execStatusCommand } from "./StatusCommand";
|
||||||
import { execBanCommand, execUnbanCommand } from "./UnbanBanCommand";
|
import { execBanCommand, execUnbanCommand } from "./UnbanBanCommand";
|
||||||
import { execDumpRulesCommand } from "./DumpRulesCommand";
|
import { execDumpRulesCommand, execRulesMatchingCommand } from "./DumpRulesCommand";
|
||||||
import { extractRequestError, LogService, RichReply } from "matrix-bot-sdk";
|
import { extractRequestError, LogService, RichReply } from "matrix-bot-sdk";
|
||||||
import { htmlEscape } from "../utils";
|
import { htmlEscape } from "../utils";
|
||||||
import { execSyncCommand } from "./SyncCommand";
|
import { execSyncCommand } from "./SyncCommand";
|
||||||
@ -59,6 +59,8 @@ export async function handleCommand(roomId: string, event: { content: { body: st
|
|||||||
return await execBanCommand(roomId, event, mjolnir, parts);
|
return await execBanCommand(roomId, event, mjolnir, parts);
|
||||||
} else if (parts[1] === 'unban' && parts.length > 2) {
|
} else if (parts[1] === 'unban' && parts.length > 2) {
|
||||||
return await execUnbanCommand(roomId, event, mjolnir, parts);
|
return await execUnbanCommand(roomId, event, mjolnir, parts);
|
||||||
|
} else if (parts[1] === 'rules' && parts.length === 4 && parts[2] === 'matching') {
|
||||||
|
return await execRulesMatchingCommand(roomId, event, mjolnir, parts[3])
|
||||||
} else if (parts[1] === 'rules') {
|
} else if (parts[1] === 'rules') {
|
||||||
return await execDumpRulesCommand(roomId, event, mjolnir);
|
return await execDumpRulesCommand(roomId, event, mjolnir);
|
||||||
} else if (parts[1] === 'sync') {
|
} else if (parts[1] === 'sync') {
|
||||||
@ -133,6 +135,7 @@ export async function handleCommand(roomId: string, event: { content: { body: st
|
|||||||
"!mjolnir redact <event permalink> - Redacts a message by permalink\n" +
|
"!mjolnir redact <event permalink> - Redacts a message by permalink\n" +
|
||||||
"!mjolnir kick <glob> [room alias/ID] [reason] - Kicks a user or all of those matching a glob in a particular room or all protected rooms\n" +
|
"!mjolnir kick <glob> [room alias/ID] [reason] - Kicks a user or all of those matching a glob in a particular room or all protected rooms\n" +
|
||||||
"!mjolnir rules - Lists the rules currently in use by Mjolnir\n" +
|
"!mjolnir rules - Lists the rules currently in use by Mjolnir\n" +
|
||||||
|
"!mjolnir rules matching <user|room|server> - Lists the rules in use that will match this entity e.g. `!rules matching @foo:example.com` will show all the user and server rules, including globs, that match this user." +
|
||||||
"!mjolnir sync - Force updates of all lists and re-apply rules\n" +
|
"!mjolnir sync - Force updates of all lists and re-apply rules\n" +
|
||||||
"!mjolnir verify - Ensures Mjolnir can moderate all your rooms\n" +
|
"!mjolnir verify - Ensures Mjolnir can moderate all your rooms\n" +
|
||||||
"!mjolnir list create <shortcode> <alias localpart> - Creates a new ban list with the given shortcode and alias\n" +
|
"!mjolnir list create <shortcode> <alias localpart> - Creates a new ban list with the given shortcode and alias\n" +
|
||||||
|
@ -14,10 +14,63 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Mjolnir } from "../Mjolnir";
|
|
||||||
import { RichReply } from "matrix-bot-sdk";
|
import { RichReply } from "matrix-bot-sdk";
|
||||||
|
import { Mjolnir } from "../Mjolnir";
|
||||||
|
import { RULE_ROOM, RULE_SERVER, RULE_USER } from "../models/BanList";
|
||||||
import { htmlEscape } from "../utils";
|
import { htmlEscape } from "../utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all of the rules that match a given entity.
|
||||||
|
* The reason why you want to test against all rules and not just e.g. user or server is because
|
||||||
|
* there are situations where rules of different types can ban other entities e.g. server ACL can cause users to be banned.
|
||||||
|
* @param roomId The room the command is from.
|
||||||
|
* @param event The event containing the command.
|
||||||
|
* @param mjolnir A mjolnir to fetch the watched lists from.
|
||||||
|
* @param entity a user, room id or server.
|
||||||
|
* @returns When a response has been sent to the command.
|
||||||
|
*/
|
||||||
|
export async function execRulesMatchingCommand(roomId: string, event: any, mjolnir: Mjolnir, entity: string) {
|
||||||
|
let html = "";
|
||||||
|
let text = "";
|
||||||
|
for (const list of mjolnir.lists) {
|
||||||
|
const matches = list.rulesMatchingEntity(entity)
|
||||||
|
|
||||||
|
if (matches.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchesInfo = `Found ${matches.length} ` + (matches.length === 1 ? 'match:' : 'matches:');
|
||||||
|
const shortcodeInfo = list.listShortcode ? ` (shortcode: ${htmlEscape(list.listShortcode)})` : '';
|
||||||
|
|
||||||
|
html += `<a href="${htmlEscape(list.roomRef)}">${htmlEscape(list.roomId)}</a>${shortcodeInfo} ${matchesInfo}<br/><ul>`;
|
||||||
|
text += `${list.roomRef}${shortcodeInfo} ${matchesInfo}:\n`;
|
||||||
|
|
||||||
|
for (const rule of matches) {
|
||||||
|
// If we know the rule kind, we will give it a readable name, otherwise just use its name.
|
||||||
|
let ruleKind: string = rule.kind;
|
||||||
|
if (ruleKind === RULE_USER) {
|
||||||
|
ruleKind = 'user';
|
||||||
|
} else if (ruleKind === RULE_SERVER) {
|
||||||
|
ruleKind = 'server';
|
||||||
|
} else if (ruleKind === RULE_ROOM) {
|
||||||
|
ruleKind = 'room';
|
||||||
|
}
|
||||||
|
html += `<li>${htmlEscape(ruleKind)} (<code>${htmlEscape(rule.recommendation ?? "")}</code>): <code>${htmlEscape(rule.entity)}</code> (${htmlEscape(rule.reason)})</li>`;
|
||||||
|
text += `* ${ruleKind} (${rule.recommendation}): ${rule.entity} (${rule.reason})\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.length === 0) {
|
||||||
|
html += `No results for ${htmlEscape(entity)}`;
|
||||||
|
text += `No results for ${entity}`;
|
||||||
|
}
|
||||||
|
const reply = RichReply.createFor(roomId, event, text, html);
|
||||||
|
reply["msgtype"] = "m.notice";
|
||||||
|
return mjolnir.client.sendMessage(roomId, reply);
|
||||||
|
}
|
||||||
|
|
||||||
// !mjolnir rules
|
// !mjolnir rules
|
||||||
export async function execDumpRulesCommand(roomId: string, event: any, mjolnir: Mjolnir) {
|
export async function execDumpRulesCommand(roomId: string, event: any, mjolnir: Mjolnir) {
|
||||||
let html = "<b>Rules currently in use:</b><br/>";
|
let html = "<b>Rules currently in use:</b><br/>";
|
||||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { extractRequestError, LogService, MatrixClient } from "matrix-bot-sdk";
|
import { extractRequestError, LogService, MatrixClient, UserID } from "matrix-bot-sdk";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { ListRule, RECOMMENDATION_BAN } from "./ListRule";
|
import { ListRule, RECOMMENDATION_BAN } from "./ListRule";
|
||||||
|
|
||||||
@ -182,6 +182,38 @@ class BanList extends EventEmitter {
|
|||||||
return [...this.serverRules, ...this.userRules, ...this.roomRules];
|
return [...this.serverRules, ...this.userRules, ...this.roomRules];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all of the rules in this list that will match the provided entity.
|
||||||
|
* If the entity is a user, then we match the domain part against server rules too.
|
||||||
|
* @param ruleKind The type of rule for the entity e.g. `RULE_USER`.
|
||||||
|
* @param entity The entity to test e.g. the user id, server name or a room id.
|
||||||
|
* @returns All of the rules that match this entity.
|
||||||
|
*/
|
||||||
|
public rulesMatchingEntity(entity: string, ruleKind?: string): ListRule[] {
|
||||||
|
const ruleTypeOf: (entityPart: string) => string = (entityPart: string) => {
|
||||||
|
if (ruleKind) {
|
||||||
|
return ruleKind;
|
||||||
|
} else if (entityPart.startsWith("#") || entityPart.startsWith("#")) {
|
||||||
|
return RULE_ROOM;
|
||||||
|
} else if (entity.startsWith("@")) {
|
||||||
|
return RULE_USER;
|
||||||
|
} else {
|
||||||
|
return RULE_SERVER;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ruleTypeOf(entity) === RULE_USER) {
|
||||||
|
// We special case because want to see whether a server ban is preventing this user from participating too.
|
||||||
|
const userId = new UserID(entity);
|
||||||
|
return [
|
||||||
|
...this.userRules.filter(rule => rule.isMatch(entity)),
|
||||||
|
...this.serverRules.filter(rule => rule.isMatch(userId.domain))
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
return this.rulesOfKind(ruleTypeOf(entity)).filter(rule => rule.isMatch(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all rules in the banList for this entity that have the same state key (as when we ban them)
|
* Remove all rules in the banList for this entity that have the same state key (as when we ban them)
|
||||||
* by searching for rules that have legacy state types.
|
* by searching for rules that have legacy state types.
|
||||||
|
Loading…
Reference in New Issue
Block a user