From 19415bbb4d797ba47b33b824e56db81ff7fd5a03 Mon Sep 17 00:00:00 2001 From: Jonathan Dahan Date: Thu, 16 Mar 2023 17:29:29 -0400 Subject: [PATCH 1/2] initial, untested power level filter --- .env.example | 2 ++ README.md | 1 + src/env.ts | 2 ++ src/handlers.ts | 22 +++++++++++++++++++--- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index 2aba99e..851e7aa 100644 --- a/.env.example +++ b/.env.example @@ -38,6 +38,8 @@ MATRIX_DEFAULT_PREFIX_REPLY=false MATRIX_BLACKLIST= # `MATRIX_WHITELIST` is overriden by `MATRIX_BLACKLIST` if they contain same entry MATRIX_WHITELIST= +# Minimum power level to reply to messages +MATRIX_POWER_LEVEL=0 # Matrix Feature Flags (optional) MATRIX_AUTOJOIN=true diff --git a/README.md b/README.md index e18e6fd..02fe653 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Adjust all required settings in the `.env` file before running. Optional setting - By default, anyone that knows the name of your bot can invite it to rooms or chat with it. - Restrict access with `MATRIX_BLACKLIST` or `MATRIX_WHITELIST` - Restrict access with `MATRIX_BLACKLIST_ROOMS` or `MATRIX_WHITELIST_ROOMS` +- Restrict access with `MATRIX_POWER_LEVEL` (defaults to 0) - When using a self-hosted setup, you could wildcard all your users with `MATRIX_WHITELIST=:yourhomeserver.example`. ### OpenAI / ChatGPT diff --git a/src/env.ts b/src/env.ts index 464971b..e7a6303 100644 --- a/src/env.ts +++ b/src/env.ts @@ -25,6 +25,7 @@ export const { MATRIX_WHITELIST, MATRIX_ROOM_BLACKLIST, MATRIX_ROOM_WHITELIST, + MATRIX_POWER_LEVEL, /** Matrix Bot Runtime Config */ MATRIX_DEFAULT_PREFIX, MATRIX_DEFAULT_PREFIX_REPLY, @@ -57,6 +58,7 @@ export const { MATRIX_WHITELIST: { schema: z.string().optional(), description: "Set to a spaces separated string of 'user:homeserver' or a wildcard like ':anotherhomeserver.example' to whitelist users or domains" }, MATRIX_ROOM_BLACKLIST: { schema: z.string().optional(), description: "Set to a spaces separated string of 'user:homeserver' or a wildcard like ':anotherhomeserver.example' to blacklist rooms or domains" }, MATRIX_ROOM_WHITELIST: { schema: z.string().optional(), description: "Set to a spaces separated string of 'user:homeserver' or a wildcard like ':anotherhomeserver.example' to whitelist rooms or domains" }, + MATRIX_POWER_LEVEL: { schema: z.number().default(0), description: "Set to the minimum required power level - in default server config user is 0, Mod is 50, Admin is 100. Default is 0." }, /** Matrix Bot Runtime Config */ MATRIX_DEFAULT_PREFIX: { schema: z.string().default(""), description: "Set to a string if you want the bot to respond only when messages start with this prefix. Trailing space matters. Empty for no prefix." }, MATRIX_DEFAULT_PREFIX_REPLY: { schema: z.boolean().default(false), description: "Set to false if you want the bot to answer to all messages in a thread/conversation" }, diff --git a/src/handlers.ts b/src/handlers.ts index d418e0e..b476bc9 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -1,6 +1,6 @@ import ChatGPTClient from '@waylaidwanderer/chatgpt-api'; import { LogService, MatrixClient, UserID } from "matrix-bot-sdk"; -import { CHATGPT_CONTEXT, CHATGPT_TIMEOUT, CHATGPT_IGNORE_MEDIA, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX, MATRIX_BLACKLIST, MATRIX_WHITELIST, MATRIX_RICH_TEXT, MATRIX_PREFIX_DM, MATRIX_THREADS, MATRIX_ROOM_BLACKLIST, MATRIX_ROOM_WHITELIST } from "./env.js"; +import { CHATGPT_CONTEXT, CHATGPT_TIMEOUT, CHATGPT_IGNORE_MEDIA, MATRIX_DEFAULT_PREFIX_REPLY, MATRIX_DEFAULT_PREFIX, MATRIX_BLACKLIST, MATRIX_WHITELIST, MATRIX_POWER_LEVEL, MATRIX_RICH_TEXT, MATRIX_PREFIX_DM, MATRIX_THREADS, MATRIX_ROOM_BLACKLIST, MATRIX_ROOM_WHITELIST } from "./env.js"; import { RelatesTo, MessageEvent, StoredConversation, StoredConversationConfig } from "./interfaces.js"; import { sendChatGPTMessage, sendError, sendReply } from "./utils.js"; @@ -29,12 +29,28 @@ export default class CommandHandler { } } - private shouldIgnore(event: MessageEvent, roomId: string): boolean { + private async userBelowPowerLevel(userId: string, roomId: string) { + const powerLevelsEvent = await this.client.getRoomStateEvent(roomId, "m.room.power_levels", ""); + if (!powerLevelsEvent) { + LogService.warn("No power level event found"); + } + + let requiredPower = MATRIX_POWER_LEVEL + + let userPower = 0; + if (Number.isFinite(powerLevelsEvent["users_default"])) userPower = powerLevelsEvent["users_default"]; + if (Number.isFinite(powerLevelsEvent["users"]?.[userId])) userPower = powerLevelsEvent["users"][userId]; + + return userPower < requiredPower; + } + + private async shouldIgnore(event: MessageEvent, roomId: string): Promise { if (event.sender === this.userId) return true; // Ignore ourselves if (MATRIX_BLACKLIST && MATRIX_BLACKLIST.split(" ").find(b => event.sender.endsWith(b))) return true; // Ignore if on blacklist if set if (MATRIX_WHITELIST && !MATRIX_WHITELIST.split(" ").find(w => event.sender.endsWith(w))) return true; // Ignore if not on whitelist if set if (MATRIX_ROOM_BLACKLIST && MATRIX_ROOM_BLACKLIST.split(" ").find(b => roomId.endsWith(b))) return true; // Ignore if on room blacklist if set if (MATRIX_ROOM_WHITELIST && !MATRIX_ROOM_WHITELIST.split(" ").find(w => roomId.endsWith(w))) return true; // Ignore if not on room whitelist if set + if (await this.userBelowPowerLevel(event.sender, roomId)) return true; // Ignore if power level too low if (Date.now() - event.origin_server_ts > 10000) return true; // Ignore old messages if (event.content["m.relates_to"]?.["rel_type"] === "m.replace") return true; // Ignore edits if (CHATGPT_IGNORE_MEDIA && event.content.msgtype !== "m.text") return true; // Ignore everything which is not text if set @@ -104,7 +120,7 @@ export default class CommandHandler { */ private async onMessage(roomId: string, event: MessageEvent) { try { - if (this.shouldIgnore(event, roomId)) return; + if (await this.shouldIgnore(event, roomId)) return; const storageKey = this.getStorageKey(event, roomId); const storedConversation = await this.getStoredConversation(storageKey, roomId); From 27a2eb6c96e8e3202608433e2269373a222f6880 Mon Sep 17 00:00:00 2001 From: Jonathan Dahan Date: Thu, 16 Mar 2023 17:31:26 -0400 Subject: [PATCH 2/2] missing semicolon --- src/handlers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers.ts b/src/handlers.ts index b476bc9..ea0c010 100644 --- a/src/handlers.ts +++ b/src/handlers.ts @@ -35,7 +35,7 @@ export default class CommandHandler { LogService.warn("No power level event found"); } - let requiredPower = MATRIX_POWER_LEVEL + let requiredPower = MATRIX_POWER_LEVEL; let userPower = 0; if (Number.isFinite(powerLevelsEvent["users_default"])) userPower = powerLevelsEvent["users_default"];