generated from 8go/nio-template
-
Notifications
You must be signed in to change notification settings - Fork 15
/
main.py
executable file
·180 lines (156 loc) · 6.3 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python3
r"""main.py.
0123456789012345678901234567890123456789012345678901234567890123456789012345678
0000000000111111111122222222223333333333444444444455555555556666666666777777777
# main.py
This file implements the following
- create a Matrix client device if necessary
- logs into Matrix as client
- sets up event managers for messages, invites, emoji verification
- enters the event loop
Don't change tabbing, spacing, or formating as the
file is automatically linted and beautified.
"""
import asyncio
import logging
import sys
import traceback
from time import sleep
from nio import (
AsyncClient,
AsyncClientConfig,
RoomMessageText,
RoomMessageAudio,
RoomEncryptedAudio,
InviteMemberEvent,
LoginError,
LocalProtocolError,
UpdateDeviceError,
KeyVerificationEvent,
)
from aiohttp import (
ServerDisconnectedError,
ClientConnectionError
)
from callbacks import Callbacks
from config import Config
from storage import Storage
logger = logging.getLogger(__name__)
async def main(): # noqa
"""Create bot as Matrix client and enter event loop."""
# Read config file
# A different config file path can be specified
# as the first command line argument
if len(sys.argv) > 1:
config_filepath = sys.argv[1]
else:
config_filepath = "config.yaml"
config = Config(config_filepath)
# Configure the database
store = Storage(config.database_filepath)
# Configuration options for the AsyncClient
client_config = AsyncClientConfig(
max_limit_exceeded=0,
max_timeouts=0,
store_sync_tokens=True,
encryption_enabled=True,
)
# Initialize the matrix client
client = AsyncClient(
config.homeserver_url,
config.user_id,
device_id=config.device_id,
store_path=config.store_filepath,
config=client_config,
)
# Set up event callbacks
callbacks = Callbacks(client, store, config)
client.add_event_callback(callbacks.message, (RoomMessageText,))
if config.accept_invitations:
client.add_event_callback(callbacks.invite, (InviteMemberEvent,))
if config.process_audio:
client.add_event_callback(callbacks.audio, (RoomMessageAudio,RoomEncryptedAudio))
client.add_to_device_callback(
callbacks.to_device_cb, (KeyVerificationEvent,))
# Keep trying to reconnect on failure (with some time in-between)
while True:
try:
# Try to login with the configured username/password
try:
if config.access_token:
logger.debug("Using access token from config file to log "
f"in. access_token={config.access_token}")
client.restore_login(
user_id=config.user_id,
device_id=config.device_id,
access_token=config.access_token
)
else:
logger.debug("Using password from config file to log in.")
login_response = await client.login(
password=config.user_password,
device_name=config.device_name,
)
# Check if login failed
if type(login_response) == LoginError:
logger.error("Failed to login: "
f"{login_response.message}")
return False
logger.info((f"access_token of device {config.device_name}"
f" is: \"{login_response.access_token}\""))
except LocalProtocolError as e:
# There's an edge case here where the user hasn't installed
# the correct C dependencies. In that case, a
# LocalProtocolError is raised on login.
logger.fatal(
"Failed to login. "
"Have you installed the correct dependencies? "
"https://github.com/poljar/matrix-nio#installation "
"Error: %s", e
)
return False
# Login succeeded!
logger.debug(f"Logged in successfully as user {config.user_id} "
f"with device {config.device_id}.")
# Sync encryption keys with the server
# Required for participating in encrypted rooms
if client.should_upload_keys:
await client.keys_upload()
if config.change_device_name:
content = {"display_name": config.device_name}
resp = await client.update_device(config.device_id,
content)
if isinstance(resp, UpdateDeviceError):
logger.debug(f"update_device failed with {resp}")
else:
logger.debug(f"update_device successful with {resp}")
if config.trust_own_devices:
await client.sync(timeout=30000, full_state=True)
# Trust your own devices automatically.
# Log it so it can be manually checked
for device_id, olm_device in client.device_store[
config.user_id].items():
logger.debug("My other devices are: "
f"device_id={device_id}, "
f"olm_device={olm_device}.")
logger.info("Setting up trust for my own "
f"device {device_id} and session key "
f"{olm_device.keys['ed25519']}.")
client.verify_device(olm_device)
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()
try:
asyncio.run(main())
except Exception:
logger.debug(traceback.format_exc())
sys.exit(1)
except KeyboardInterrupt:
logger.debug("Received keyboard interrupt.")
sys.exit(1)