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,
|
room_id,
|
||||||
"m.room.message",
|
"m.room.message",
|
||||||
content,
|
content,
|
||||||
|
ignore_unverified_devices=True,
|
||||||
)
|
)
|
||||||
except SendRetryError:
|
except SendRetryError:
|
||||||
logger.exception(f"Unable to send message response to {room_id}")
|
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)
|
handler.setFormatter(formatter)
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
|
|
||||||
# Database setup
|
# Storage setup
|
||||||
self.database_filepath = self._get_cfg(["database", "filepath"], required=True)
|
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
|
# Matrix bot account setup
|
||||||
self.user_id = self._get_cfg(["matrix", "user_id"], required=True)
|
self.user_id = self._get_cfg(["matrix", "user_id"], required=True)
|
||||||
if not re.match("@.*:.*", self.user_id):
|
if not re.match("@.*:.*", self.user_id):
|
||||||
raise ConfigError("matrix.user_id must be in the form @name:domain")
|
raise ConfigError("matrix.user_id must be in the form @name:domain")
|
||||||
|
|
||||||
self.access_token = self._get_cfg(["matrix", "access_token"], required=True)
|
self.user_password = self._get_cfg(["matrix", "user_password"], required=True)
|
||||||
|
self.device_id = self._get_cfg(["matrix", "device_id"], required=True)
|
||||||
self.device_id = self._get_cfg(["matrix", "device_id"])
|
self.device_name = self._get_cfg(["matrix", "device_name"], default="nio-template")
|
||||||
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.homeserver_url = self._get_cfg(["matrix", "homeserver_url"], required=True)
|
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") + " "
|
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
|
ConfigError: If required is specified and the object is not found
|
||||||
(and there is no default value provided), this error will be raised
|
(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
|
# Sift through the the config until we reach our option
|
||||||
config = self.config
|
config = self.config
|
||||||
for name in path:
|
for name in path:
|
||||||
|
@ -86,7 +80,7 @@ class Config(object):
|
||||||
if config is None:
|
if config is None:
|
||||||
# Raise an error if it was required
|
# Raise an error if it was required
|
||||||
if required or not default:
|
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
|
# or return the default value
|
||||||
return default
|
return default
|
||||||
|
|
77
main.py
77
main.py
|
@ -2,17 +2,22 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from time import sleep
|
||||||
from nio import (
|
from nio import (
|
||||||
AsyncClient,
|
AsyncClient,
|
||||||
AsyncClientConfig,
|
AsyncClientConfig,
|
||||||
RoomMessageText,
|
RoomMessageText,
|
||||||
InviteEvent,
|
InviteEvent,
|
||||||
SyncError,
|
LoginError,
|
||||||
|
LocalProtocolError,
|
||||||
|
)
|
||||||
|
from aiohttp import (
|
||||||
|
ServerDisconnectedError,
|
||||||
|
ClientConnectionError,
|
||||||
)
|
)
|
||||||
from callbacks import Callbacks
|
from callbacks import Callbacks
|
||||||
from config import Config
|
from config import Config
|
||||||
from storage import Storage
|
from storage import Storage
|
||||||
from sync_token import SyncToken
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -28,41 +33,73 @@ async def main():
|
||||||
client_config = AsyncClientConfig(
|
client_config = AsyncClientConfig(
|
||||||
max_limit_exceeded=0,
|
max_limit_exceeded=0,
|
||||||
max_timeouts=0,
|
max_timeouts=0,
|
||||||
|
store_sync_tokens=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize the matrix client
|
# Initialize the matrix client
|
||||||
|
if config.enable_encryption:
|
||||||
|
store_path = config.store_filepath
|
||||||
|
else:
|
||||||
|
store_path = None
|
||||||
|
|
||||||
client = AsyncClient(
|
client = AsyncClient(
|
||||||
config.homeserver_url,
|
config.homeserver_url,
|
||||||
config.user_id,
|
config.user_id,
|
||||||
device_id=config.device_id,
|
device_id=config.device_id,
|
||||||
|
store_path=store_path,
|
||||||
config=client_config,
|
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
|
# Set up event callbacks
|
||||||
callbacks = Callbacks(client, store, config)
|
callbacks = Callbacks(client, store, config)
|
||||||
client.add_event_callback(callbacks.message, (RoomMessageText,))
|
client.add_event_callback(callbacks.message, (RoomMessageText,))
|
||||||
client.add_event_callback(callbacks.invite, (InviteEvent,))
|
client.add_event_callback(callbacks.invite, (InviteEvent,))
|
||||||
|
|
||||||
# Create a new sync token, attempting to load one from the database if it has one already
|
# Keep trying to reconnect on failure (with some time in-between)
|
||||||
sync_token = SyncToken(store)
|
|
||||||
|
|
||||||
# Sync loop
|
|
||||||
while True:
|
while True:
|
||||||
# Sync with the server
|
try:
|
||||||
sync_response = await client.sync(timeout=30000, full_state=True,
|
# Try to login with the configured username/password
|
||||||
since=sync_token.token)
|
try:
|
||||||
|
login_response = await client.login(
|
||||||
|
password=config.user_password,
|
||||||
|
device_name=config.device_name,
|
||||||
|
)
|
||||||
|
|
||||||
# Check if the sync had an error
|
# Check if login failed
|
||||||
if type(sync_response) == SyncError:
|
if type(login_response) == LoginError:
|
||||||
logger.warning("Error in client sync: %s", sync_response.message)
|
logger.error(f"Failed to login: %s", login_response.message)
|
||||||
continue
|
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
|
# Login succeeded!
|
||||||
token = sync_response.next_batch
|
|
||||||
if token:
|
# Sync encryption keys with the server
|
||||||
sync_token.update(token)
|
# 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())
|
asyncio.get_event_loop().run_until_complete(main())
|
||||||
|
|
|
@ -9,16 +9,27 @@ command_prefix: "!c"
|
||||||
matrix:
|
matrix:
|
||||||
# The Matrix User ID of the bot account
|
# The Matrix User ID of the bot account
|
||||||
user_id: "@bot:example.com"
|
user_id: "@bot:example.com"
|
||||||
# The access token of the bot account
|
# Matrix account password
|
||||||
access_token: ""
|
user_password: ""
|
||||||
# The device ID given on login
|
|
||||||
device_id: ABCDEFGHIJ
|
|
||||||
# The URL of the homeserver to connect to
|
# The URL of the homeserver to connect to
|
||||||
homeserver_url: https://example.com
|
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
|
# 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 setup
|
||||||
logging:
|
logging:
|
||||||
|
|
Loading…
Add table
Reference in a new issue