mmrelay 1.1.4__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mmrelay might be problematic. Click here for more details.
- mmrelay/__init__.py +1 -1
- mmrelay/cli.py +1003 -76
- mmrelay/cli_utils.py +696 -0
- mmrelay/config.py +578 -17
- mmrelay/constants/app.py +12 -0
- mmrelay/constants/config.py +6 -1
- mmrelay/constants/messages.py +10 -1
- mmrelay/constants/network.py +7 -0
- mmrelay/e2ee_utils.py +392 -0
- mmrelay/log_utils.py +39 -5
- mmrelay/main.py +96 -26
- mmrelay/matrix_utils.py +1059 -84
- mmrelay/meshtastic_utils.py +192 -40
- mmrelay/plugin_loader.py +76 -44
- mmrelay/plugins/base_plugin.py +16 -4
- mmrelay/plugins/weather_plugin.py +108 -11
- mmrelay/tools/sample-docker-compose-prebuilt.yaml +80 -0
- mmrelay/tools/sample-docker-compose.yaml +34 -8
- mmrelay/tools/sample_config.yaml +29 -4
- {mmrelay-1.1.4.dist-info → mmrelay-1.2.0.dist-info}/METADATA +19 -48
- mmrelay-1.2.0.dist-info/RECORD +45 -0
- mmrelay/config_checker.py +0 -162
- mmrelay-1.1.4.dist-info/RECORD +0 -43
- {mmrelay-1.1.4.dist-info → mmrelay-1.2.0.dist-info}/WHEEL +0 -0
- {mmrelay-1.1.4.dist-info → mmrelay-1.2.0.dist-info}/entry_points.txt +0 -0
- {mmrelay-1.1.4.dist-info → mmrelay-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {mmrelay-1.1.4.dist-info → mmrelay-1.2.0.dist-info}/top_level.txt +0 -0
mmrelay/main.py
CHANGED
|
@@ -5,16 +5,22 @@ It uses Meshtastic-python and Matrix nio client library to interface with the ra
|
|
|
5
5
|
|
|
6
6
|
import asyncio
|
|
7
7
|
import concurrent.futures
|
|
8
|
-
import logging
|
|
9
8
|
import signal
|
|
10
9
|
import sys
|
|
11
10
|
|
|
12
|
-
from nio import
|
|
11
|
+
from nio import (
|
|
12
|
+
MegolmEvent,
|
|
13
|
+
ReactionEvent,
|
|
14
|
+
RoomMessageEmote,
|
|
15
|
+
RoomMessageNotice,
|
|
16
|
+
RoomMessageText,
|
|
17
|
+
)
|
|
13
18
|
from nio.events.room_events import RoomMemberEvent
|
|
14
19
|
|
|
15
20
|
# Import version from package
|
|
16
21
|
# Import meshtastic_utils as a module to set event_loop
|
|
17
22
|
from mmrelay import __version__, meshtastic_utils
|
|
23
|
+
from mmrelay.cli_utils import msg_suggest_check_config, msg_suggest_generate_config
|
|
18
24
|
from mmrelay.constants.app import APP_DISPLAY_NAME, WINDOWS_PLATFORM
|
|
19
25
|
from mmrelay.db_utils import (
|
|
20
26
|
initialize_database,
|
|
@@ -23,9 +29,16 @@ from mmrelay.db_utils import (
|
|
|
23
29
|
wipe_message_map,
|
|
24
30
|
)
|
|
25
31
|
from mmrelay.log_utils import get_logger
|
|
26
|
-
from mmrelay.matrix_utils import
|
|
32
|
+
from mmrelay.matrix_utils import (
|
|
33
|
+
connect_matrix,
|
|
34
|
+
join_matrix_room,
|
|
35
|
+
)
|
|
27
36
|
from mmrelay.matrix_utils import logger as matrix_logger
|
|
28
|
-
from mmrelay.matrix_utils import
|
|
37
|
+
from mmrelay.matrix_utils import (
|
|
38
|
+
on_decryption_failure,
|
|
39
|
+
on_room_member,
|
|
40
|
+
on_room_message,
|
|
41
|
+
)
|
|
29
42
|
from mmrelay.meshtastic_utils import connect_meshtastic
|
|
30
43
|
from mmrelay.meshtastic_utils import logger as meshtastic_logger
|
|
31
44
|
from mmrelay.message_queue import (
|
|
@@ -39,20 +52,22 @@ from mmrelay.plugin_loader import load_plugins
|
|
|
39
52
|
# Initialize logger
|
|
40
53
|
logger = get_logger(name=APP_DISPLAY_NAME)
|
|
41
54
|
|
|
42
|
-
# Set the logging level for 'nio' to ERROR to suppress warnings
|
|
43
|
-
logging.getLogger("nio").setLevel(logging.ERROR)
|
|
44
|
-
|
|
45
55
|
|
|
46
56
|
# Flag to track if banner has been printed
|
|
47
57
|
_banner_printed = False
|
|
48
58
|
|
|
49
59
|
|
|
50
60
|
def print_banner():
|
|
51
|
-
"""
|
|
61
|
+
"""
|
|
62
|
+
Log the MMRelay startup banner with version information once.
|
|
63
|
+
|
|
64
|
+
This records an informational message "Starting MMRelay version <version>" via the module logger
|
|
65
|
+
the first time it is called and sets a module-level flag to prevent subsequent prints.
|
|
66
|
+
"""
|
|
52
67
|
global _banner_printed
|
|
53
68
|
# Only print the banner once
|
|
54
69
|
if not _banner_printed:
|
|
55
|
-
logger.info(f"Starting MMRelay
|
|
70
|
+
logger.info(f"Starting MMRelay version {__version__}")
|
|
56
71
|
_banner_printed = True
|
|
57
72
|
|
|
58
73
|
|
|
@@ -109,6 +124,13 @@ async def main(config):
|
|
|
109
124
|
# Connect to Matrix
|
|
110
125
|
matrix_client = await connect_matrix(passed_config=config)
|
|
111
126
|
|
|
127
|
+
# Check if Matrix connection was successful
|
|
128
|
+
if matrix_client is None:
|
|
129
|
+
# The error is logged by connect_matrix, so we can just raise here.
|
|
130
|
+
raise ConnectionError(
|
|
131
|
+
"Failed to connect to Matrix. Cannot continue without Matrix client."
|
|
132
|
+
)
|
|
133
|
+
|
|
112
134
|
# Join the rooms specified in the config.yaml
|
|
113
135
|
for room in matrix_rooms:
|
|
114
136
|
await join_matrix_room(matrix_client, room["id"])
|
|
@@ -116,12 +138,14 @@ async def main(config):
|
|
|
116
138
|
# Register the message callback for Matrix
|
|
117
139
|
matrix_logger.info("Listening for inbound Matrix messages...")
|
|
118
140
|
matrix_client.add_event_callback(
|
|
119
|
-
on_room_message,
|
|
141
|
+
on_room_message,
|
|
142
|
+
(RoomMessageText, RoomMessageNotice, RoomMessageEmote, ReactionEvent),
|
|
120
143
|
)
|
|
121
|
-
# Add
|
|
122
|
-
|
|
144
|
+
# Add E2EE callbacks - MegolmEvent only goes to decryption failure handler
|
|
145
|
+
# Successfully decrypted messages will be converted to RoomMessageText etc. by matrix-nio
|
|
146
|
+
matrix_client.add_event_callback(on_decryption_failure, (MegolmEvent,))
|
|
123
147
|
# Add RoomMemberEvent callback to track room-specific display name changes
|
|
124
|
-
matrix_client.add_event_callback(on_room_member, RoomMemberEvent)
|
|
148
|
+
matrix_client.add_event_callback(on_room_member, (RoomMemberEvent,))
|
|
125
149
|
|
|
126
150
|
# Set up shutdown event
|
|
127
151
|
shutdown_event = asyncio.Event()
|
|
@@ -171,15 +195,33 @@ async def main(config):
|
|
|
171
195
|
[sync_task, shutdown_task],
|
|
172
196
|
return_when=asyncio.FIRST_COMPLETED,
|
|
173
197
|
)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
198
|
+
|
|
199
|
+
# Cancel any pending tasks
|
|
200
|
+
for task in pending:
|
|
201
|
+
task.cancel()
|
|
177
202
|
try:
|
|
178
|
-
await
|
|
203
|
+
await task
|
|
179
204
|
except asyncio.CancelledError:
|
|
180
205
|
pass
|
|
206
|
+
|
|
207
|
+
if shutdown_event.is_set():
|
|
208
|
+
matrix_logger.info("Shutdown event detected. Stopping sync loop...")
|
|
181
209
|
break
|
|
182
210
|
|
|
211
|
+
# Check if sync_task completed with an exception
|
|
212
|
+
if sync_task in done:
|
|
213
|
+
try:
|
|
214
|
+
# This will raise the exception if the task failed
|
|
215
|
+
sync_task.result()
|
|
216
|
+
# If we get here, sync completed normally (shouldn't happen with sync_forever)
|
|
217
|
+
matrix_logger.warning(
|
|
218
|
+
"Matrix sync_forever completed unexpectedly"
|
|
219
|
+
)
|
|
220
|
+
except Exception as e:
|
|
221
|
+
# Log the exception and continue to retry
|
|
222
|
+
matrix_logger.error(f"Matrix sync failed: {e}")
|
|
223
|
+
# The outer try/catch will handle the retry logic
|
|
224
|
+
|
|
183
225
|
except Exception as e:
|
|
184
226
|
if shutdown_event.is_set():
|
|
185
227
|
break
|
|
@@ -246,13 +288,16 @@ async def main(config):
|
|
|
246
288
|
|
|
247
289
|
|
|
248
290
|
def run_main(args):
|
|
249
|
-
"""
|
|
291
|
+
"""
|
|
292
|
+
Run the application's top-level startup sequence and invoke the main async runner.
|
|
293
|
+
|
|
294
|
+
Performs initial setup (prints banner, optionally sets a custom data directory, loads and applies configuration and logging overrides), validates that required configuration sections are present (required keys differ if credentials.json is present), then runs the main coroutine. Returns an exit code: 0 for successful run or user interrupt, 1 for configuration errors or unhandled exceptions.
|
|
250
295
|
|
|
251
|
-
|
|
252
|
-
args:
|
|
296
|
+
Parameters:
|
|
297
|
+
args: Parsed command-line arguments (may be None). Recognized options used here include `data_dir` and `log_level`.
|
|
253
298
|
|
|
254
299
|
Returns:
|
|
255
|
-
int: Exit code (0
|
|
300
|
+
int: Exit code (0 on success or user-initiated interrupt, 1 on failure such as invalid config or runtime error).
|
|
256
301
|
"""
|
|
257
302
|
# Print the banner at startup
|
|
258
303
|
print_banner()
|
|
@@ -317,7 +362,17 @@ def run_main(args):
|
|
|
317
362
|
config_rich_logger.info(f"Log file location: {log_file_path}")
|
|
318
363
|
|
|
319
364
|
# Check if config exists and has the required keys
|
|
320
|
-
|
|
365
|
+
# Note: matrix section is optional if credentials.json exists
|
|
366
|
+
from mmrelay.config import load_credentials
|
|
367
|
+
|
|
368
|
+
credentials = load_credentials()
|
|
369
|
+
|
|
370
|
+
if credentials:
|
|
371
|
+
# With credentials.json, only meshtastic and matrix_rooms are required
|
|
372
|
+
required_keys = ["meshtastic", "matrix_rooms"]
|
|
373
|
+
else:
|
|
374
|
+
# Without credentials.json, all sections are required
|
|
375
|
+
required_keys = ["matrix", "meshtastic", "matrix_rooms"]
|
|
321
376
|
|
|
322
377
|
# Check each key individually for better debugging
|
|
323
378
|
for key in required_keys:
|
|
@@ -327,10 +382,21 @@ def run_main(args):
|
|
|
327
382
|
if not config or not all(key in config for key in required_keys):
|
|
328
383
|
# Exit with error if no config exists
|
|
329
384
|
missing_keys = [key for key in required_keys if key not in config]
|
|
330
|
-
|
|
331
|
-
f"Configuration is missing required keys: {missing_keys}
|
|
332
|
-
"
|
|
333
|
-
|
|
385
|
+
if credentials:
|
|
386
|
+
logger.error(f"Configuration is missing required keys: {missing_keys}")
|
|
387
|
+
logger.error("Matrix authentication will use credentials.json")
|
|
388
|
+
logger.error("Next steps:")
|
|
389
|
+
logger.error(
|
|
390
|
+
f" • Create a valid config.yaml file or {msg_suggest_generate_config()}"
|
|
391
|
+
)
|
|
392
|
+
logger.error(f" • {msg_suggest_check_config()}")
|
|
393
|
+
else:
|
|
394
|
+
logger.error(f"Configuration is missing required keys: {missing_keys}")
|
|
395
|
+
logger.error("Next steps:")
|
|
396
|
+
logger.error(
|
|
397
|
+
f" • Create a valid config.yaml file or {msg_suggest_generate_config()}"
|
|
398
|
+
)
|
|
399
|
+
logger.error(f" • {msg_suggest_check_config()}")
|
|
334
400
|
return 1
|
|
335
401
|
|
|
336
402
|
try:
|
|
@@ -340,7 +406,11 @@ def run_main(args):
|
|
|
340
406
|
logger.info("Interrupted by user. Exiting.")
|
|
341
407
|
return 0
|
|
342
408
|
except Exception as e:
|
|
409
|
+
import traceback
|
|
410
|
+
|
|
343
411
|
logger.error(f"Error running main functionality: {e}")
|
|
412
|
+
logger.error("Full traceback:")
|
|
413
|
+
logger.error(traceback.format_exc())
|
|
344
414
|
return 1
|
|
345
415
|
|
|
346
416
|
|