Add custom Matrix Python SDK. Solves #5 and #2

This commit releases a custom Matrix Python SDK. This means that the
most important classes---MatrixClient, Room, and MatrixHttpApi---are
inherited by Custom classes. This enables us to change behaviour or add
new features in a well structured and predictable way.

The first issue that is solved with this new custom SDK is the problem
that was introduced in #2.
This commit is contained in:
Dennis Potter 2019-01-20 00:41:02 +01:00
parent e4d325955c
commit 9e139a0f3e
6 changed files with 126 additions and 74 deletions

View File

@ -0,0 +1,10 @@
from matrix_client.api import MatrixHttpApi
class CustomMatrixHttpApi(MatrixHttpApi):
def __init__(
self, base_url, token=None, identity=None,
default_429_wait_ms=5000,
use_authorization_header=True
):
super().__init__(base_url, token, identity, default_429_wait_ms)

View File

@ -0,0 +1,76 @@
import os
import hjson
from matrix_client.client import MatrixClient
from .api import CustomMatrixHttpApi
from .room import CustomRoom
MESSAGES_DIR = os.path.join(os.path.dirname(__file__), 'messages')
MESSAGES_LOCATION = MESSAGES_DIR + '/messages.dutch.hjson'
class CustomMatrixClient(MatrixClient):
def __init__(self, base_url, token=None):
super().__init__(base_url, token)
self.api = CustomMatrixHttpApi(base_url, token)
# Load messages
with open(MESSAGES_LOCATION) as hjson_data:
self.messages = hjson.load(hjson_data)
def _mkroom(self, room_id):
room = CustomRoom(self, room_id)
self.rooms[room_id] = room
return self.rooms[room_id]
def create_direct_room(self, invitees=None):
content = {
"visibility": "private",
"is_direct": True,
"invite": [invitees]
}
room_dict = self.api._send("POST", "/createRoom", content)
return self._mkroom(room_dict["room_id"])
def send_message_private_public(self, room, event, message):
"""This method takes a room, event, and message and makes sure
that the message is sent in a private room and not in a public
room. If no private room exists, it will create a private room
with the sender of the event.
"""
orig_room = room
found_room = False
for room_id, room in self.rooms.items():
joined_members = room.get_joined_members()
# Check for rooms with only two members
if len(joined_members) == 2:
# Check if sender is in that room
for member in joined_members:
if event['sender'] == member.user_id:
found_room = True
if found_room:
# If the flag is set, we do not need to check further rooms
break
# Send help message to an existing room or to a new room
if found_room:
room.send_html(message)
else:
room = self.create_direct_room(event['sender']);
room.send_html(message)
if room != orig_room:
display_name_sender = self.api.get_display_name(event['sender'])
orig_room.send_text(self.messages['private_message'].format(
display_name_sender))

View File

@ -0,0 +1,6 @@
from matrix_client.room import Room
class CustomRoom(Room):
def __init__(self, client, room_id):
super().__init__(client, room_id)

View File

@ -9,17 +9,16 @@ import datetime as dt
from pip._internal import main as pipmain from pip._internal import main as pipmain
from matrix_client.client import MatrixClient from matrix_client.api import MatrixRequestError
from matrix_client.api import MatrixRequestError, MatrixHttpApi
from matrix_client.user import User
from matrix_bot_api.mregex_handler import MRegexHandler from matrix_bot_api.mregex_handler import MRegexHandler
from .custom_matrix_client.api import CustomMatrixHttpApi
from .custom_matrix_client.client import CustomMatrixClient
MESSAGES_DIR = os.path.join(os.path.dirname(__file__), 'messages') MESSAGES_DIR = os.path.join(os.path.dirname(__file__), 'messages')
DATA_LOCATION = os.path.join(os.path.dirname(__file__), '../data/bot.db') DATA_LOCATION = os.path.join(os.path.dirname(__file__), '../data/bot.db')
HELP_LOCATION = MESSAGES_DIR + '/help' HELP_LOCATION = MESSAGES_DIR + '/help'
MESSAGES_LOCATION = MESSAGES_DIR + '/messages.dutch.hjson'
def eprint(*args, **kwargs): def eprint(*args, **kwargs):
"""Print error messages to stderr""" """Print error messages to stderr"""
@ -32,7 +31,8 @@ class MatrixBotAPI:
self.username = self.config['bot_credentials']['username'] self.username = self.config['bot_credentials']['username']
# Authenticate with given credentials # Authenticate with given credentials
self.client = MatrixClient(self.config['bot_credentials']['server']) self.client = CustomMatrixClient(
self.config['bot_credentials']['server'])
try: try:
self.token = self.client.login_with_password( self.token = self.client.login_with_password(
self.config['bot_credentials']['username'], self.config['bot_credentials']['username'],
@ -41,13 +41,11 @@ class MatrixBotAPI:
print(e) print(e)
if e.code == 403: if e.code == 403:
print("Bad username/password") print("Bad username/password")
sys.exit()
except Exception as e: except Exception as e:
print("Invalid server URL") print("Invalid server URL")
traceback.print_exc() traceback.print_exc()
sys.exit()
self.api = MatrixHttpApi(
self.config['bot_credentials']['server'],
token=self.token)
# Store allowed rooms # Store allowed rooms
self.rooms = rooms self.rooms = rooms
@ -63,7 +61,7 @@ class MatrixBotAPI:
# Add all rooms we're currently in to self.rooms and add their # Add all rooms we're currently in to self.rooms and add their
# callbacks # callbacks
for room_id, room in self.client.get_rooms().items(): for room_id, room in self.client.rooms.items():
room.add_listener(self.handle_message) room.add_listener(self.handle_message)
self.rooms.append(room_id) self.rooms.append(room_id)
else: else:
@ -86,10 +84,6 @@ class MatrixBotAPI:
self.config['triggers']['help'], self.help) self.config['triggers']['help'], self.help)
self.add_handler(self.help_handler) self.add_handler(self.help_handler)
# Load messages
with open(MESSAGES_LOCATION) as hjson_data:
self.messages = hjson.load(hjson_data)
def add_plugins(self): def add_plugins(self):
"""Acquire list of plugins from configuration, load them, """Acquire list of plugins from configuration, load them,
@ -103,30 +97,31 @@ class MatrixBotAPI:
# ./plugins directory # ./plugins directory
modules = [] modules = []
#try: try:
# Loop through the available plugins, install their requirements, # Loop through the available plugins, install their requirements,
# load them as module, run their setup, append them to a list, # load them as module, run their setup, append them to a list,
# and add their handler variables # and add their handler variables
for i, plugin in enumerate(self.config['plugins']): for i, plugin in enumerate(self.config['plugins']):
# Install requirements # Install requirements
self.install_requirements(plugin) self.install_requirements(plugin)
# Dynamically load the module # Dynamically load the module
modules.append( modules.append(
importlib.import_module( importlib.import_module(
"plugins.{0}.{0}".format(plugin), package = None)) "plugins.{0}.{0}".format(plugin), package = None))
# Run the module's setup function and save that it got installed # Run the module's setup function and save that it got installed
self.setup_plugin(modules[i]) self.setup_plugin(modules[i])
# Create new instance of plugin and append to plugin_objects array # Create new instance of plugin and append to plugin_objects array
self.plugin_objects.append(modules[i].Plugin(self)) self.plugin_objects.append(modules[i].Plugin(self))
# Add handler of newly created instance to bot # Add handler of newly created instance to bot
self.add_handler(self.plugin_objects[i].handler) self.add_handler(self.plugin_objects[i].handler)
#except: except:
# eprint("Importing one or more of the plugins did not go well!") eprint("Importing one or more of the plugins did not go well!")
# sys.exit() traceback.print_exc()
sys.exit()
def check_installation_plugin(self, module_name): def check_installation_plugin(self, module_name):
"""Function returns 0 if a plugin with that name is not """Function returns 0 if a plugin with that name is not
@ -175,10 +170,10 @@ class MatrixBotAPI:
print(f"Running installation of {module_name}.") print(f"Running installation of {module_name}.")
# Run module's install method # Run module's install method
#try: try:
module.setup() module.setup()
#except: except:
# print(f"{module_name} did not specify setup(). Skipping...") print(f"{module_name} did not specify setup(). Skipping...")
# Save in database that we installed this plugin # Save in database that we installed this plugin
datetime_added = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S") datetime_added = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@ -238,42 +233,6 @@ class MatrixBotAPI:
# Do nothing, data directory already exists # Do nothing, data directory already exists
pass pass
def send_message_private_public(self, room, event, message):
"""This method takes a room, event, and message and makes sure
that the message is sent in a private room and not in a public
room. If no private room exists, it will create a private room
with the sender of the event.
"""
orig_room = room
found_room = False
for room_id, room in self.client.get_rooms().items():
joined_members = room.get_joined_members()
# Check for rooms with only two members
if len(joined_members) == 2:
# Check if sender is in that room
for member in joined_members:
if event['sender'] == member.user_id:
found_room = True
if found_room:
# If the flag is set, we do not need to check further rooms
break
# Send help message to an existing room or to a new room
if found_room:
room.send_html(message)
else:
room = self.client.create_room(invitees=[event['sender']]);
room.send_html(message)
if room != orig_room:
display_name_sender = self.api.get_display_name(event['sender'])
orig_room.send_text(self.messages['private_message'].format(
display_name_sender))
def help(self, room, event): def help(self, room, event):
"""Prints a general help message and then grabs all help """Prints a general help message and then grabs all help
messages from the different plugins messages from the different plugins
@ -288,7 +247,7 @@ class MatrixBotAPI:
# The plugin probably returned 0, ignore it # The plugin probably returned 0, ignore it
pass pass
self.send_message_private_public(room, event, help_text) self.client.send_message_private_public(room, event, help_text)
def add_handler(self, handler): def add_handler(self, handler):
try: try:
@ -327,4 +286,5 @@ class MatrixBotAPI:
def start_polling(self): def start_polling(self):
# Starts polling for messages # Starts polling for messages
self.client.start_listener_thread() self.client.start_listener_thread()
return self.client.sync_thread return self.client.sync_thread

View File

@ -146,7 +146,7 @@ class Plugin:
if row[1] in mapping[0]: if row[1] in mapping[0]:
# We got a winner! # We got a winner!
for i, room in self.bot.client.get_rooms().items(): for i, room in self.bot.client.rooms.items():
if room.display_name in mapping[1]: if room.display_name in mapping[1]:
room.send_html( room.send_html(
self.messages["new_event"].format( self.messages["new_event"].format(