Optional encryption support
This commit is contained in:
parent
f40373837c
commit
1eacb2926e
4 changed files with 83 additions and 40 deletions
|
@ -46,6 +46,7 @@ async def send_text_to_room(
|
|||
room_id,
|
||||
"m.room.message",
|
||||
content,
|
||||
ignore_unverified_devices=True,
|
||||
)
|
||||
except SendRetryError:
|
||||
logger.exception(f"Unable to send message response to {room_id}")
|
||||
|
|
22
config.py
22
config.py
|
@ -41,24 +41,20 @@ class Config(object):
|
|||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
# Database setup
|
||||
self.database_filepath = self._get_cfg(["database", "filepath"], required=True)
|
||||
# Storage setup
|
||||
self.database_filepath = self._get_cfg(["storage", "database_filepath"], required=True)
|
||||
self.store_filepath = self._get_cfg(["storage", "store_filepath"], required=True)
|
||||
|
||||
# Matrix bot account setup
|
||||
self.user_id = self._get_cfg(["matrix", "user_id"], required=True)
|
||||
if not re.match("@.*:.*", self.user_id):
|
||||
raise ConfigError("matrix.user_id must be in the form @name:domain")
|
||||
|
||||
self.access_token = self._get_cfg(["matrix", "access_token"], required=True)
|
||||
|
||||
self.device_id = self._get_cfg(["matrix", "device_id"])
|
||||
if not self.device_id:
|
||||
logger.warning(
|
||||
"Config option matrix.device_id is not provided, which means "
|
||||
"that end-to-end encryption won't work correctly"
|
||||
)
|
||||
|
||||
self.user_password = self._get_cfg(["matrix", "user_password"], required=True)
|
||||
self.device_id = self._get_cfg(["matrix", "device_id"], required=True)
|
||||
self.device_name = self._get_cfg(["matrix", "device_name"], default="nio-template")
|
||||
self.homeserver_url = self._get_cfg(["matrix", "homeserver_url"], required=True)
|
||||
self.enable_encryption = self._get_cfg(["matrix", "enable_encryption"], default=False)
|
||||
|
||||
self.command_prefix = self._get_cfg(["command_prefix"], default="!c") + " "
|
||||
|
||||
|
@ -75,8 +71,6 @@ class Config(object):
|
|||
ConfigError: If required is specified and the object is not found
|
||||
(and there is no default value provided), this error will be raised
|
||||
"""
|
||||
path_str = '.'.join(path)
|
||||
|
||||
# Sift through the the config until we reach our option
|
||||
config = self.config
|
||||
for name in path:
|
||||
|
@ -86,7 +80,7 @@ class Config(object):
|
|||
if config is None:
|
||||
# Raise an error if it was required
|
||||
if required or not default:
|
||||
raise ConfigError(f"Config option {path_str} is required")
|
||||
raise ConfigError(f"Config option {'.'.join(path)} is required")
|
||||
|
||||
# or return the default value
|
||||
return default
|
||||
|
|
77
main.py
77
main.py
|
@ -2,17 +2,22 @@
|
|||
|
||||
import logging
|
||||
import asyncio
|
||||
from time import sleep
|
||||
from nio import (
|
||||
AsyncClient,
|
||||
AsyncClientConfig,
|
||||
RoomMessageText,
|
||||
InviteEvent,
|
||||
SyncError,
|
||||
LoginError,
|
||||
LocalProtocolError,
|
||||
)
|
||||
from aiohttp import (
|
||||
ServerDisconnectedError,
|
||||
ClientConnectionError,
|
||||
)
|
||||
from callbacks import Callbacks
|
||||
from config import Config
|
||||
from storage import Storage
|
||||
from sync_token import SyncToken
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -28,41 +33,73 @@ async def main():
|
|||
client_config = AsyncClientConfig(
|
||||
max_limit_exceeded=0,
|
||||
max_timeouts=0,
|
||||
store_sync_tokens=True,
|
||||
)
|
||||
|
||||
# Initialize the matrix client
|
||||
if config.enable_encryption:
|
||||
store_path = config.store_filepath
|
||||
else:
|
||||
store_path = None
|
||||
|
||||
client = AsyncClient(
|
||||
config.homeserver_url,
|
||||
config.user_id,
|
||||
device_id=config.device_id,
|
||||
store_path=store_path,
|
||||
config=client_config,
|
||||
)
|
||||
|
||||
# Assign an access token to the bot instead of logging in and creating a new device
|
||||
client.access_token = config.access_token
|
||||
|
||||
# Set up event callbacks
|
||||
callbacks = Callbacks(client, store, config)
|
||||
client.add_event_callback(callbacks.message, (RoomMessageText,))
|
||||
client.add_event_callback(callbacks.invite, (InviteEvent,))
|
||||
|
||||
# Create a new sync token, attempting to load one from the database if it has one already
|
||||
sync_token = SyncToken(store)
|
||||
|
||||
# Sync loop
|
||||
# Keep trying to reconnect on failure (with some time in-between)
|
||||
while True:
|
||||
# Sync with the server
|
||||
sync_response = await client.sync(timeout=30000, full_state=True,
|
||||
since=sync_token.token)
|
||||
try:
|
||||
# Try to login with the configured username/password
|
||||
try:
|
||||
login_response = await client.login(
|
||||
password=config.user_password,
|
||||
device_name=config.device_name,
|
||||
)
|
||||
|
||||
# Check if the sync had an error
|
||||
if type(sync_response) == SyncError:
|
||||
logger.warning("Error in client sync: %s", sync_response.message)
|
||||
continue
|
||||
# Check if login failed
|
||||
if type(login_response) == LoginError:
|
||||
logger.error(f"Failed to login: %s", login_response.message)
|
||||
return False
|
||||
except LocalProtocolError as e:
|
||||
# There's an edge case here where the user enables encryption but hasn't installed
|
||||
# the correct C dependencies. In that case, a LocalProtocolError is raised on login.
|
||||
# Warn the user if these conditions are met.
|
||||
if config.enable_encryption:
|
||||
logger.fatal(
|
||||
"Failed to login and encryption is enabled. Have you installed the correct dependencies? "
|
||||
"https://github.com/poljar/matrix-nio#installation"
|
||||
)
|
||||
return False
|
||||
else:
|
||||
# We don't know why this was raised. Throw it at the user
|
||||
logger.fatal("Error logging in: %s", e)
|
||||
|
||||
# Save the latest sync token to the database
|
||||
token = sync_response.next_batch
|
||||
if token:
|
||||
sync_token.update(token)
|
||||
# Login succeeded!
|
||||
|
||||
# Sync encryption keys with the server
|
||||
# Required for participating in encrypted rooms
|
||||
if client.should_upload_keys:
|
||||
await client.keys_upload()
|
||||
|
||||
logger.info(f"Logged in as {config.user_id}")
|
||||
await client.sync_forever(timeout=30000, full_state=True)
|
||||
|
||||
except (ClientConnectionError, ServerDisconnectedError):
|
||||
logger.warning("Unable to connect to homeserver, retrying in 15s...")
|
||||
|
||||
# Sleep so we don't bombard the server with login requests
|
||||
sleep(15)
|
||||
finally:
|
||||
# Make sure to close the client connection on disconnect
|
||||
await client.close()
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
|
|
|
@ -9,16 +9,27 @@ command_prefix: "!c"
|
|||
matrix:
|
||||
# The Matrix User ID of the bot account
|
||||
user_id: "@bot:example.com"
|
||||
# The access token of the bot account
|
||||
access_token: ""
|
||||
# The device ID given on login
|
||||
device_id: ABCDEFGHIJ
|
||||
# Matrix account password
|
||||
user_password: ""
|
||||
# The URL of the homeserver to connect to
|
||||
homeserver_url: https://example.com
|
||||
# The device ID that is **non pre-existing** device
|
||||
# If this device ID already exists, messages will be dropped silently in encrypted rooms
|
||||
device_id: ABCDEFGHIJ
|
||||
# What to name the logged in device
|
||||
device_name: nio-template
|
||||
# End-to-end encryption support
|
||||
#
|
||||
# Enabling this requires installing the matrix-nio encryption dependencies
|
||||
# as described here: https://github.com/poljar/matrix-nio#installation
|
||||
enable_encryption: true
|
||||
|
||||
database:
|
||||
storage:
|
||||
# The path to the database
|
||||
filepath: "bot.db"
|
||||
database_filepath: "bot.db"
|
||||
# The path to a directory for internal bot storage
|
||||
# containing encryption keys, sync tokens, etc.
|
||||
store_filepath: "./store"
|
||||
|
||||
# Logging setup
|
||||
logging:
|
||||
|
|
Loading…
Reference in a new issue