Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

initial, untested power level filter #141

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_ROOM_BLACKLIST` or `MATRIX_ROOM_WHITELIST`
- 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
Expand Down
2 changes: 2 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -60,6 +61,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" },
Expand Down
22 changes: 19 additions & 3 deletions src/handlers.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<boolean> {
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
Expand Down Expand Up @@ -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);
Expand Down