Early ban list processing (parsing, specifically)

This commit is contained in:
Travis Ralston 2019-09-27 13:57:36 -06:00
parent 05e86a972b
commit 23c6c20768
5 changed files with 140 additions and 21 deletions

View File

@ -9,3 +9,11 @@ dataPath: "/data/storage"
# Whether the bot should autojoin rooms it is invited to or not
autojoin: true
# The room ID where people can use the bot. The bot has no access controls, so
# anyone in this room can use the bot - secure your room!
managementRoom: "#moderators:example.org"
# A list of ban lists to follow (matrix.to URLs)
banLists:
- "https://matrix.to/#/#sample-ban-list:t2bot.io" # S.A.M.P.L.E.

View File

@ -14,27 +14,53 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixClient, Permalinks } from "matrix-bot-sdk";
import { MatrixClient } from "matrix-bot-sdk";
import { ListRule } from "./ListRule";
export const RULE_USER = "m.room.rule.user";
export const RULE_ROOM = "m.room.rule.room";
export const RULE_SERVER = "m.room.rule.server";
export const USER_RULE_TYPES = [RULE_USER, "org.matrix.mjolnir.rule.user"];
export const ROOM_RULE_TYPES = [RULE_ROOM, "org.matrix.mjolnir.rule.room"];
export const SERVER_RULE_TYPES = [RULE_SERVER, "org.matrix.mjolnir.rule.server"];
export const ALL_RULE_TYPES = [...USER_RULE_TYPES, ...ROOM_RULE_TYPES, ...SERVER_RULE_TYPES];
export default class BanList {
private viaServers: string[] = [];
public rules: ListRule[] = [];
constructor(private roomRef: string, private client: MatrixClient) {
if (this.roomRef.startsWith("https")) {
const parts = Permalinks.parseUrl(this.roomRef);
this.roomRef = parts.roomIdOrAlias;
constructor(public readonly roomId: string, public readonly roomRef, private client: MatrixClient) {
}
if (parts.viaServers) this.viaServers = parts.viaServers;
public async updateList() {
this.rules = [];
const state = await this.client.getRoomState(this.roomId);
for (const event of state) {
if (event['state_key'] === '' || !ALL_RULE_TYPES.includes(event['type'])) {
continue;
}
// It's a rule - parse it
const content = event['content'];
if (!content) continue;
const entity = content['entity'];
const recommendation = content['recommendation'];
const reason = content['reason'];
if (!entity || !recommendation || !reason) {
continue;
}
this.rules.push(new ListRule(entity, recommendation, reason));
}
client.resolveRoom(this.roomRef).then(roomId => this.roomRef = roomId);
}
public async ensureJoined(): Promise<void> {
await this.client.joinRoom(this.roomRef, this.viaServers);
public getRuleFor(entity: string): ListRule {
for (const rule of this.rules) {
if (rule.isMatch(entity)) return rule;
}
return null;
}
// TODO: Update list
// TODO: Match checking
}

View File

@ -14,17 +14,24 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { MatrixGlob } from "matrix-bot-sdk/lib/MatrixGlob";
export const RECOMMENDATION_BAN = "m.ban";
export const RECOMMENDATION_BAN_TYPES = [RECOMMENDATION_BAN, "org.matrix.mjolnir.ban"];
export class ListRule {
constructor(public entity: string, private action: string, public reason: string) {
// TODO: Convert entity to regex
private glob: MatrixGlob;
constructor(public readonly entity: string, private action: string, public readonly reason: string) {
this.glob = new MatrixGlob(entity);
}
public get recommendation(): string {
if (RECOMMENDATION_BAN_TYPES.includes(this.action)) return RECOMMENDATION_BAN;
}
// TODO: Match checking functions
public isMatch(entity: string): boolean {
return this.glob.test(entity);
}
}

View File

@ -21,6 +21,8 @@ interface IConfig {
accessToken: string;
dataPath: string;
autojoin: boolean;
managementRoom: string;
banLists: string[]; // matrix.to urls
}
export default <IConfig>config;

View File

@ -19,27 +19,103 @@ import {
AutojoinRoomsMixin,
LogService,
MatrixClient,
Permalinks,
RichConsoleLogger,
SimpleFsStorageProvider
} from "matrix-bot-sdk";
import config from "./config";
import BanList, { ALL_RULE_TYPES } from "./BanList";
LogService.setLogger(new RichConsoleLogger());
const storage = new SimpleFsStorageProvider(path.join(config.dataPath, "bot.json"));
const client = new MatrixClient(config.homeserverUrl, config.accessToken, storage);
const lists: BanList[] = [];
let managementRoomId = "";
if (config.autojoin) {
AutojoinRoomsMixin.setupOnClient(client);
}
client.on("room.event", async (roomId, event) => {
if (!event['state_key']) return; // we also don't do anything with state events that have no state key
if (ALL_RULE_TYPES.includes(event['type'])) {
for (const list of lists) {
if (list.roomId !== roomId) continue;
await list.updateList();
// TODO: Re-apply ACLs as needed
}
} else if (event['type'] === "m.room.member") {
// TODO: Check membership against ban lists
}
});
client.on("room.message", async (roomId, event) => {
if (!event['content']) return;
const content = event['content'];
if (content['msgtype'] === 'm.text' && content['body'] === '!mjolnir') {
await client.sendNotice(roomId, "Hello world!");
if (content['msgtype'] === "m.text" && content['body'] === "!mjolnir") {
await client.sendReadReceipt(roomId, event['event_id']);
return printStatus(roomId);
}
});
client.start().then(() => LogService.info("index", "Bot started!"));
(async function () {
// Ensure we're in all the rooms we expect to be in
const joinedRooms = await client.getJoinedRooms();
for (const roomRef of config.banLists) {
const permalink = Permalinks.parseUrl(roomRef);
if (!permalink.roomIdOrAlias) continue;
const roomId = await client.resolveRoom(permalink.roomIdOrAlias);
if (!joinedRooms.includes(roomId)) {
await client.joinRoom(permalink.roomIdOrAlias, permalink.viaServers);
}
const list = new BanList(roomId, roomRef, client);
await list.updateList();
lists.push(list);
}
// Ensure we're also in the management room
managementRoomId = await client.joinRoom(config.managementRoom);
await client.sendNotice(managementRoomId, "Mjolnir is starting up. Use !mjolnir to query status.");
// TODO: Check permissions for mjolnir in protected rooms
// TODO: Complain about permission changes in protected rooms (including after power levels change)
await client.start();
LogService.info("index", "Bot started!")
})();
async function printStatus(roomId: string) {
const rooms = await client.getJoinedRooms();
let html = "";
let text = "";
// Append header information first
html += "<b>Running: </b>✅<br/>";
text += "Running: ✅\n";
html += `<b>Protected rooms: </b> ${rooms.length}<br/>`;
text += `Protected rooms: ${rooms.length}\n`;
// Append list information
html += "<b>Subscribed lists:</b><br><ul>";
text += "Subscribed lists:\n";
for (const list of lists) {
html += `<li><a href="${list.roomRef}">${list.roomId}</a> (${list.rules.length} rules)</li>`;
text += `${list.roomRef} (${list.rules.length} rules)\n`;
}
html += "</ul>";
const message = {
msgtype: "m.notice",
body: text,
format: "org.matrix.custom.html",
formatted_body: html,
};
return client.sendMessage(roomId, message);
}