Browse Source

Implement quiet mode (not responding to everything)
More README content
"Fixing" auto-joining

Kumi 1 year ago
parent
commit
85a04c4188
4 changed files with 102 additions and 27 deletions
  1. 44 8
      README.md
  2. 25 5
      classes/bot.py
  3. 1 1
      commands/newroom.py
  4. 32 13
      commands/roomsettings.py

+ 44 - 8
README.md

@@ -9,20 +9,20 @@ probably add more in the future, so the name is a bit misleading.
 
 ## Features
 
-- AI-generated responses to all messages in a Matrix room (chatbot)
+- AI-generated responses to messages in a Matrix room (chatbot)
   - Currently supports OpenAI (tested with `gpt-3.5-turbo` and `gpt-4`)
 - AI-generated pictures via the `!gptbot imagine` command
   - Currently supports OpenAI (DALL-E)
 - Mathematical calculations via the `!gptbot calculate` command
   - Currently supports WolframAlpha
+- Automatic classification of messages (for `imagine`, `calculate`, etc.)
+  - Beta feature, see Usage section for details
 - Really useful commands like `!gptbot help` and `!gptbot coin`
 - DuckDB database to store room context
 
 ## Planned features
 
 - End-to-end encryption support (partly implemented, but not yet working)
-- Automatic classification of messages (for `imagine`, `calculate`, etc.)
-  - Beta feature, enable for a room using `!gptbot roomsettings classification true`
 
 ## Installation
 
@@ -57,12 +57,48 @@ to messages. If you want to create a new room, you can use the `!gptbot newroom`
 command at any time, which will cause the bot to create a new room and invite
 you to it. You may also specify a room name, e.g. `!gptbot newroom My new room`.
 
-Note that the bot will currently respond to _all_ messages in the room. So you
-shouldn't invite it to a room with other people in it.
+### Reply generation
+
+Note that the bot will respond to _all_ messages in the room by default. If you
+don't want this, for example because you want to use the bot in a room with
+other people, you can use the `!gptbot roomsettings` command to change the
+settings for the current room. For example, you can disable response generation
+with `!gptbot roomsettings always_reply false`.
+
+With this setting, the bot will only be triggered if a message begins with
+`!gptbot chat`. For example, `!gptbot chat Hello, how are you?` will cause the
+bot to generate a response to the message `Hello, how are you?`. The bot will
+still get previous messages in the room as context for generating the response.
+
+### Commands
+
+There are a few commands that you can use to interact with the bot. For example,
+if you want to generate an image from a text prompt, you can use the
+`!gptbot imagine` command. For example, `!gptbot imagine a cat` will cause the
+bot to generate an image of a cat.
+
+To learn more about the available commands, `!gptbot help` will print a list of
+available commands.
+
+### Automatic classification
+
+As a beta feature, the bot can automatically classify messages and use the
+appropriate API to generate a response. For example, if you send a message
+like "Draw me a picture of a cat", the bot will automatically use the
+`imagine` command to generate an image of a cat.
+
+This feature is disabled by default. To enable it, use the `!gptbot roomsettings`
+command to change the settings for the current room. `!gptbot roomsettings classification true`
+will enable automatic classification, and `!gptbot roomsettings classification false`
+will disable it again.
+
+Note that this feature is still in beta and may not work as expected. You can
+always use the commands manually if the automatic classification doesn't work
+for you (including `!gptbot chat` for a regular chat message).
 
-It also supports the `!gptbot help` command, which will print a list of available
-commands. Messages starting with `!` are considered commands and will not be
-considered for response generation.
+Also note that this feature conflicts with the `always_reply false` setting -
+or rather, it doesn't make sense then because you already have to explicitly
+specify the command to use.
 
 ## Troubleshooting
 

+ 25 - 5
classes/bot.py

@@ -22,7 +22,9 @@ from nio import (
     RoomMessageText,
     RoomSendResponse,
     SyncResponse,
-    RoomMessageNotice
+    RoomMessageNotice,
+    JoinError,
+    RoomLeaveError,
 )
 from nio.crypto import Olm
 
@@ -58,6 +60,7 @@ class GPTBot:
     image_api: Optional[OpenAI] = None
     classification_api: Optional[OpenAI] = None
     operator: Optional[str] = None
+    room_ignore_list: List[str] = [] # List of rooms to ignore invites from
 
     @classmethod
     def from_config(cls, config: ConfigParser):
@@ -276,8 +279,25 @@ class GPTBot:
         invites = self.matrix_client.invited_rooms
 
         for invite in invites.keys():
+            if invite in self.room_ignore_list:
+                self.logger.log(
+                    f"Ignoring invite to room {invite} (room is in ignore list)")
+                continue
+
             self.logger.log(f"Accepting invite to room {invite}")
-            await self.matrix_client.join(invite)
+
+            response = await self.matrix_client.join(invite)
+
+            if isinstance(response, JoinError):
+                self.logger.log(
+                    f"Error joining room {invite}: {response.message}. Not trying again.", "error")
+
+                leave_response = await self.matrix_client.room_leave(invite)
+
+                if isinstance(leave_response, RoomLeaveError):
+                    self.logger.log(
+                        f"Error leaving room {invite}: {leave_response.message}", "error")
+                    self.room_ignore_list.append(invite)
 
     async def send_image(self, room: MatrixRoom, image: bytes, message: Optional[str] = None):
         """Send an image to a room.
@@ -487,13 +507,13 @@ class GPTBot:
             await self.matrix_client.sync(timeout=30000)
 
     def respond_to_room_messages(self, room: MatrixRoom | str) -> bool:
-        """Check whether the bot should respond to messages sent in a room.
+        """Check whether the bot should respond to all messages sent in a room.
 
         Args:
             room (MatrixRoom | str): The room to check.
 
         Returns:
-            bool: Whether the bot should respond to messages sent in the room.
+            bool: Whether the bot should respond to all messages sent in the room.
         """
 
         if isinstance(room, MatrixRoom):
@@ -501,7 +521,7 @@ class GPTBot:
 
         with self.database.cursor() as cursor:
             cursor.execute(
-                "SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "respond_to_messages"))
+                "SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "always_reply"))
             result = cursor.fetchone()
 
         return True if not result else bool(int(result[0]))

+ 1 - 1
commands/newroom.py

@@ -28,4 +28,4 @@ async def command_newroom(room: MatrixRoom, event: RoomMessageText, bot):
 
     await bot.matrix_client.joined_rooms()
     await bot.send_message(room, f"Alright, I've created a new room called '{room_name}' and invited you to it. You can find it at {new_room.room_id}", True)
-    await bot.send_message(new_room.room_id, f"Welcome to the new room! What can I do for you?")
+    await bot.send_message(new_room, f"Welcome to the new room! What can I do for you?")

+ 32 - 13
commands/roomsettings.py

@@ -3,10 +3,15 @@ from nio.rooms import MatrixRoom
 
 
 async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
-    setting = event.body.split()[2]
+    setting = event.body.split()[2] if len(event.body.split()) > 2 else None
     value = " ".join(event.body.split()[3:]) if len(
         event.body.split()) > 3 else None
 
+    if setting == "classification":
+        setting = "use_classification"
+    if setting == "systemmessage":
+        setting = "system_message"
+
     if setting == "system_message":
         if value:
             bot.logger.log("Adding system message...")
@@ -28,38 +33,52 @@ async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
         await bot.send_message(room, f"The current system message is: '{system_message}'.", True)
         return
 
-    if setting == "classification":
+    if setting in ("use_classification", "always_reply"):
         if value:
             if value.lower() in ["true", "false"]:
                 value = value.lower() == "true"
 
-                bot.logger.log("Setting classification status...")
+                bot.logger.log(f"Setting {setting} status for {room.room_id} to {value}...")
 
                 with bot.database.cursor() as cur:
                     cur.execute(
                         """INSERT INTO room_settings (room_id, setting, value) VALUES (?, ?, ?)
                         ON CONFLICT (room_id, setting) DO UPDATE SET value = ?;""",
-                        (room.room_id, "use_classification", "1" if value else "0", "1" if value else "0")
+                        (room.room_id, setting, "1" if value else "0", "1" if value else "0")
                     )
 
-                await bot.send_message(room, f"Alright, I've set use_classification to: '{value}'.", True)
+                await bot.send_message(room, f"Alright, I've set {setting} to: '{value}'.", True)
                 return
 
             await bot.send_message(room, "You need to provide a boolean value (true/false).", True)
             return
 
-        bot.logger.log("Retrieving classification status...")
+        bot.logger.log(f"Retrieving {setting} status for {room.room_id}...")
+
+        with bot.database.cursor() as cur:
+            cur.execute(
+                """SELECT value FROM room_settings WHERE room_id = ? AND setting = ?;""",
+                (room.room_id, setting)
+            )
+
+            value = cur.fetchone()[0]
 
-        use_classification = bot.room_uses_classification(room)
+            if not value:
+                if setting == "use_classification":
+                    value = False
+                elif setting == "always_reply":
+                    value = True
+            else:
+                value = bool(int(value))
 
-        await bot.send_message(room, f"The current classification status is: '{use_classification}'.", True)
+        await bot.send_message(room, f"The current {setting} status is: '{value}'.", True)
         return
 
-    message = f"""
-    The following settings are available:
+    message = f"""The following settings are available:
 
-    - system_message [message]: Get or set the system message to be sent to the chat model
-    - classification [true/false]: Get or set whether the room uses classification
-    """
+- system_message [message]: Get or set the system message to be sent to the chat model
+- classification [true/false]: Get or set whether the room uses classification
+- always_reply [true/false]: Get or set whether the bot should reply to all messages (if false, only reply to mentions and commands)
+"""
 
     await bot.send_message(room, message, True)