feat: improve bot functionality and logging setup
Enhanced the bot's functionality with several improvements: - Added YAML dependency for configuration loading - Introduced detailed logging for better monitoring - Implemented invite event handling and operator check - Updated error handling and user feedback messages - Ensured the bot ignores its own messages Increased the version to 0.1.1 to reflect these changes.
This commit is contained in:
parent
6447362d04
commit
14e8d44999
3 changed files with 80 additions and 21 deletions
|
@ -7,7 +7,7 @@ allow-direct-references = true
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "matrix-supportbot"
|
name = "matrix-supportbot"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
|
|
||||||
authors = [{ name = "Private.coffee Team", email = "support@private.coffee" }]
|
authors = [{ name = "Private.coffee Team", email = "support@private.coffee" }]
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ classifiers = [
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matrix-nio>=0.24.0",
|
"matrix-nio>=0.24.0",
|
||||||
|
"pyyaml"
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|
|
@ -6,33 +6,48 @@ from nio import (
|
||||||
RoomMessageMedia,
|
RoomMessageMedia,
|
||||||
LoginResponse,
|
LoginResponse,
|
||||||
RoomGetStateEventResponse,
|
RoomGetStateEventResponse,
|
||||||
RoomMemberEvent,
|
InviteMemberEvent,
|
||||||
|
JoinedMembersResponse,
|
||||||
|
RoomPutStateResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class SupportBot:
|
class SupportBot:
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.client = AsyncClient(config["homeserver"])
|
self.client = AsyncClient(config["homeserver"], config["username"])
|
||||||
self.username = config["username"]
|
self.username = config["username"]
|
||||||
self.password = config["password"]
|
self.password = config["password"]
|
||||||
self.operator_room_id = config["operator_room_id"]
|
self.operator_room_id = config["operator_room_id"]
|
||||||
|
|
||||||
async def login(self):
|
async def login(self):
|
||||||
response = await self.client.login(self.username, self.password)
|
response = await self.client.login(self.password)
|
||||||
if isinstance(response, LoginResponse):
|
if isinstance(response, LoginResponse):
|
||||||
print("Logged in successfully")
|
logging.info("Logged in successfully")
|
||||||
else:
|
else:
|
||||||
print("Failed to log in")
|
logging.fatal("Failed to log in")
|
||||||
|
|
||||||
async def is_operator(self, user_id):
|
async def is_operator(self, user_id):
|
||||||
|
logging.info(f"Checking if {user_id} is an operator")
|
||||||
|
|
||||||
response = await self.client.joined_members(self.operator_room_id)
|
response = await self.client.joined_members(self.operator_room_id)
|
||||||
if isinstance(response, RoomMemberEvent):
|
|
||||||
return user_id in response.members
|
if not isinstance(response, JoinedMembersResponse):
|
||||||
|
logging.error(f"Failed to get members in operator room: {response}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for member in response.members:
|
||||||
|
if member.user_id == user_id:
|
||||||
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
|
await self.client.sync(timeout=30000)
|
||||||
self.client.add_event_callback(self.message_callback, RoomMessageText)
|
self.client.add_event_callback(self.message_callback, RoomMessageText)
|
||||||
self.client.add_event_callback(self.message_callback, RoomMessageMedia)
|
self.client.add_event_callback(self.message_callback, RoomMessageMedia)
|
||||||
|
self.client.add_event_callback(self.invite_callback, InviteMemberEvent)
|
||||||
await self.client.sync_forever(timeout=30000)
|
await self.client.sync_forever(timeout=30000)
|
||||||
|
|
||||||
async def message_callback(self, room: MatrixRoom, event):
|
async def message_callback(self, room: MatrixRoom, event):
|
||||||
|
@ -44,9 +59,24 @@ class SupportBot:
|
||||||
else:
|
else:
|
||||||
await self.relay_message(room, sender, event)
|
await self.relay_message(room, sender, event)
|
||||||
|
|
||||||
|
async def invite_callback(self, room: MatrixRoom, event: InviteMemberEvent):
|
||||||
|
logging.info(f"Received invite event: {event}")
|
||||||
|
|
||||||
|
if event.membership == "invite" and event.state_key == self.client.user_id:
|
||||||
|
await self.client.join(room.room_id)
|
||||||
|
await self.client.room_send(
|
||||||
|
room.room_id,
|
||||||
|
"m.room.message",
|
||||||
|
{
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": "Hello! To open a support ticket, please type `!supportbot openticket`.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def handle_command(self, room, sender, command):
|
async def handle_command(self, room, sender, command):
|
||||||
if command == "!supportbot openticket":
|
if command == "!supportbot openticket":
|
||||||
await self.open_ticket(room, sender)
|
await self.open_ticket(room, sender)
|
||||||
|
return
|
||||||
elif await self.is_operator(sender):
|
elif await self.is_operator(sender):
|
||||||
if command.startswith("!supportbot invite"):
|
if command.startswith("!supportbot invite"):
|
||||||
await self.invite_operator(room, sender, command)
|
await self.invite_operator(room, sender, command)
|
||||||
|
@ -54,13 +84,14 @@ class SupportBot:
|
||||||
await self.close_ticket(room, sender, command)
|
await self.close_ticket(room, sender, command)
|
||||||
elif command == "!supportbot list":
|
elif command == "!supportbot list":
|
||||||
await self.list_tickets(room)
|
await self.list_tickets(room)
|
||||||
else:
|
return
|
||||||
|
|
||||||
await self.client.room_send(
|
await self.client.room_send(
|
||||||
room.room_id,
|
room.room_id,
|
||||||
"m.room.message",
|
"m.room.message",
|
||||||
{
|
{
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
"body": "You are not authorized to use this command.",
|
"body": "Sorry, I do not know this command, or you are not authorized to use it.",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -91,13 +122,19 @@ class SupportBot:
|
||||||
"status": "open",
|
"status": "open",
|
||||||
}
|
}
|
||||||
|
|
||||||
await self.client.room_put_state(
|
state_event_response = await self.client.room_put_state(
|
||||||
room_id=self.operator_room_id,
|
room_id=self.operator_room_id,
|
||||||
event_type="m.room.custom.ticket",
|
event_type="m.room.custom.ticket",
|
||||||
state_key=state_event_key,
|
state_key=state_event_key,
|
||||||
content=state_event_content,
|
content=state_event_content,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not isinstance(state_event_response, RoomPutStateResponse):
|
||||||
|
logging.error(
|
||||||
|
f"Failed to update state in operator room: {state_event_response}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
# Inform the operator room
|
# Inform the operator room
|
||||||
await self.client.room_send(
|
await self.client.room_send(
|
||||||
self.operator_room_id,
|
self.operator_room_id,
|
||||||
|
@ -118,6 +155,15 @@ class SupportBot:
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await self.client.room_send(
|
||||||
|
room.room_id,
|
||||||
|
"m.room.message",
|
||||||
|
{
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": f"Ticket #{ticket_id} has been created. Please check your DMs.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
async def invite_operator(self, room, sender, command):
|
async def invite_operator(self, room, sender, command):
|
||||||
ticket_id = command.split()[2]
|
ticket_id = command.split()[2]
|
||||||
state_event_key = f"ticket_{ticket_id}"
|
state_event_key = f"ticket_{ticket_id}"
|
||||||
|
@ -164,7 +210,10 @@ class SupportBot:
|
||||||
await self.client.room_send(
|
await self.client.room_send(
|
||||||
customer_room_id,
|
customer_room_id,
|
||||||
"m.room.message",
|
"m.room.message",
|
||||||
{"msgtype": "m.text", "body": f"Ticket #{ticket_id} has been closed."},
|
{
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"body": f"Ticket #{ticket_id} has been closed. If you need further assistance, please open a new ticket.",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
await self.client.room_send(
|
await self.client.room_send(
|
||||||
operator_room_id,
|
operator_room_id,
|
||||||
|
@ -212,6 +261,10 @@ class SupportBot:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def relay_message(self, room, sender, event):
|
async def relay_message(self, room, sender, event):
|
||||||
|
# Ignore any messages from the bot itself
|
||||||
|
if sender == self.client.user_id:
|
||||||
|
return
|
||||||
|
|
||||||
ticket_id = await self.get_ticket_id_from_room(room.room_id)
|
ticket_id = await self.get_ticket_id_from_room(room.room_id)
|
||||||
if ticket_id:
|
if ticket_id:
|
||||||
state_event_key = f"ticket_{ticket_id}"
|
state_event_key = f"ticket_{ticket_id}"
|
||||||
|
@ -229,5 +282,5 @@ class SupportBot:
|
||||||
|
|
||||||
# Relay the entire event content to the target room
|
# Relay the entire event content to the target room
|
||||||
await self.client.room_send(
|
await self.client.room_send(
|
||||||
target_room_id, event.type, event.source["content"]
|
target_room_id, "m.room.message", event.source["content"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from .bot import SupportBot
|
from .classes.bot import SupportBot
|
||||||
|
|
||||||
def load_config(config_file):
|
def load_config(config_file):
|
||||||
with open(config_file, 'r') as file:
|
with open(config_file, 'r') as file:
|
||||||
|
@ -11,6 +12,10 @@ def load_config(config_file):
|
||||||
def main():
|
def main():
|
||||||
config = load_config("config.yaml")
|
config = load_config("config.yaml")
|
||||||
bot = SupportBot(config)
|
bot = SupportBot(config)
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
logging.getLogger("nio").setLevel(logging.WARNING)
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(bot.login())
|
asyncio.get_event_loop().run_until_complete(bot.login())
|
||||||
asyncio.get_event_loop().run_until_complete(bot.start())
|
asyncio.get_event_loop().run_until_complete(bot.start())
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue