mmrelay 1.0.9__tar.gz → 1.0.11__tar.gz
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-1.0.9/src/mmrelay.egg-info → mmrelay-1.0.11}/PKG-INFO +3 -3
- {mmrelay-1.0.9 → mmrelay-1.0.11}/requirements.txt +2 -2
- {mmrelay-1.0.9 → mmrelay-1.0.11}/setup.cfg +3 -3
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/__init__.py +5 -6
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/db_utils.py +61 -9
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/matrix_utils.py +321 -61
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/meshtastic_utils.py +101 -10
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/tools/sample_config.yaml +4 -1
- {mmrelay-1.0.9 → mmrelay-1.0.11/src/mmrelay.egg-info}/PKG-INFO +3 -3
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay.egg-info/requires.txt +2 -2
- {mmrelay-1.0.9 → mmrelay-1.0.11}/LICENSE +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/MANIFEST.in +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/README.md +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/pyproject.toml +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/cli.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/config.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/config_checker.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/log_utils.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/main.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugin_loader.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/__init__.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/base_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/debug_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/drop_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/health_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/help_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/map_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/mesh_relay_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/nodes_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/ping_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/telemetry_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/plugins/weather_plugin.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/setup_utils.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/tools/__init__.py +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay/tools/mmrelay.service +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay.egg-info/SOURCES.txt +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay.egg-info/dependency_links.txt +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay.egg-info/entry_points.txt +0 -0
- {mmrelay-1.0.9 → mmrelay-1.0.11}/src/mmrelay.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmrelay
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.11
|
|
4
4
|
Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
|
|
5
5
|
Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
|
|
6
6
|
Author: Geoff Whittington, Jeremiah K., and contributors
|
|
@@ -18,8 +18,8 @@ Requires-Dist: meshtastic
|
|
|
18
18
|
Requires-Dist: Pillow==11.2.1
|
|
19
19
|
Requires-Dist: matrix-nio==0.25.2
|
|
20
20
|
Requires-Dist: matplotlib==3.10.1
|
|
21
|
-
Requires-Dist: requests==2.32.
|
|
22
|
-
Requires-Dist: markdown==3.8
|
|
21
|
+
Requires-Dist: requests==2.32.4
|
|
22
|
+
Requires-Dist: markdown==3.8.2
|
|
23
23
|
Requires-Dist: haversine==2.9.0
|
|
24
24
|
Requires-Dist: schedule==1.2.2
|
|
25
25
|
Requires-Dist: platformdirs==4.3.8
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = mmrelay
|
|
3
|
-
version = 1.0.
|
|
3
|
+
version = 1.0.11
|
|
4
4
|
author = Geoff Whittington, Jeremiah K., and contributors
|
|
5
5
|
author_email = jeremiahk@gmx.com
|
|
6
6
|
description = Bridge between Meshtastic mesh networks and Matrix chat rooms
|
|
@@ -26,8 +26,8 @@ install_requires =
|
|
|
26
26
|
Pillow==11.2.1
|
|
27
27
|
matrix-nio==0.25.2
|
|
28
28
|
matplotlib==3.10.1
|
|
29
|
-
requests==2.32.
|
|
30
|
-
markdown==3.8
|
|
29
|
+
requests==2.32.4
|
|
30
|
+
markdown==3.8.2
|
|
31
31
|
haversine==2.9.0
|
|
32
32
|
schedule==1.2.2
|
|
33
33
|
platformdirs==4.3.8
|
|
@@ -3,16 +3,15 @@ Meshtastic Matrix Relay - Bridge between Meshtastic mesh networks and Matrix cha
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
-
|
|
7
|
-
import pkg_resources
|
|
6
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
7
|
|
|
9
8
|
# First try to get version from environment variable (GitHub tag)
|
|
10
9
|
if "GITHUB_REF_NAME" in os.environ:
|
|
11
10
|
__version__ = os.environ.get("GITHUB_REF_NAME")
|
|
12
11
|
else:
|
|
13
|
-
# Fall back to
|
|
12
|
+
# Fall back to package metadata using importlib.metadata (modern replacement for pkg_resources)
|
|
14
13
|
try:
|
|
15
|
-
__version__ =
|
|
16
|
-
except
|
|
14
|
+
__version__ = version("mmrelay")
|
|
15
|
+
except PackageNotFoundError:
|
|
17
16
|
# If all else fails, use hardcoded version
|
|
18
|
-
__version__ = "1.0.
|
|
17
|
+
__version__ = "1.0.11"
|
|
@@ -8,17 +8,57 @@ from mmrelay.log_utils import get_logger
|
|
|
8
8
|
# Global config variable that will be set from main.py
|
|
9
9
|
config = None
|
|
10
10
|
|
|
11
|
+
# Cache for database path to avoid repeated logging and path resolution
|
|
12
|
+
_cached_db_path = None
|
|
13
|
+
_db_path_logged = False
|
|
14
|
+
_cached_config_hash = None
|
|
15
|
+
|
|
11
16
|
logger = get_logger(name="db_utils")
|
|
12
17
|
|
|
13
18
|
|
|
19
|
+
def clear_db_path_cache():
|
|
20
|
+
"""Clear the cached database path to force re-resolution on next call.
|
|
21
|
+
|
|
22
|
+
This is useful for testing or if the application supports runtime
|
|
23
|
+
configuration changes.
|
|
24
|
+
"""
|
|
25
|
+
global _cached_db_path, _db_path_logged, _cached_config_hash
|
|
26
|
+
_cached_db_path = None
|
|
27
|
+
_db_path_logged = False
|
|
28
|
+
_cached_config_hash = None
|
|
29
|
+
|
|
30
|
+
|
|
14
31
|
# Get the database path
|
|
15
32
|
def get_db_path():
|
|
16
33
|
"""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
Resolve and return the file path to the SQLite database, using configuration overrides if provided.
|
|
35
|
+
|
|
36
|
+
By default, returns the path to `meshtastic.sqlite` in the standard data directory (`~/.mmrelay/data`).
|
|
37
|
+
If a custom path is specified in the configuration under `database.path` (preferred) or `db.path` (legacy),
|
|
38
|
+
that path is used instead. The resolved path is cached for subsequent calls, and the directory is created
|
|
39
|
+
if it does not exist. Cache is automatically invalidated if the relevant configuration changes.
|
|
20
40
|
"""
|
|
21
|
-
global config
|
|
41
|
+
global config, _cached_db_path, _db_path_logged, _cached_config_hash
|
|
42
|
+
|
|
43
|
+
# Create a hash of the relevant config sections to detect changes
|
|
44
|
+
current_config_hash = None
|
|
45
|
+
if config is not None:
|
|
46
|
+
# Hash only the database-related config sections
|
|
47
|
+
db_config = {
|
|
48
|
+
"database": config.get("database", {}),
|
|
49
|
+
"db": config.get("db", {}), # Legacy format
|
|
50
|
+
}
|
|
51
|
+
current_config_hash = hash(str(sorted(db_config.items())))
|
|
52
|
+
|
|
53
|
+
# Check if cache is valid (path exists and config hasn't changed)
|
|
54
|
+
if _cached_db_path is not None and current_config_hash == _cached_config_hash:
|
|
55
|
+
return _cached_db_path
|
|
56
|
+
|
|
57
|
+
# Config changed or first call - clear cache and re-resolve
|
|
58
|
+
if current_config_hash != _cached_config_hash:
|
|
59
|
+
_cached_db_path = None
|
|
60
|
+
_db_path_logged = False
|
|
61
|
+
_cached_config_hash = current_config_hash
|
|
22
62
|
|
|
23
63
|
# Check if config is available
|
|
24
64
|
if config is not None:
|
|
@@ -30,7 +70,12 @@ def get_db_path():
|
|
|
30
70
|
db_dir = os.path.dirname(custom_path)
|
|
31
71
|
if db_dir:
|
|
32
72
|
os.makedirs(db_dir, exist_ok=True)
|
|
33
|
-
|
|
73
|
+
|
|
74
|
+
# Cache the path and log only once
|
|
75
|
+
_cached_db_path = custom_path
|
|
76
|
+
if not _db_path_logged:
|
|
77
|
+
logger.info(f"Using database path from config: {custom_path}")
|
|
78
|
+
_db_path_logged = True
|
|
34
79
|
return custom_path
|
|
35
80
|
|
|
36
81
|
# Check legacy format (db section)
|
|
@@ -41,13 +86,20 @@ def get_db_path():
|
|
|
41
86
|
db_dir = os.path.dirname(custom_path)
|
|
42
87
|
if db_dir:
|
|
43
88
|
os.makedirs(db_dir, exist_ok=True)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
89
|
+
|
|
90
|
+
# Cache the path and log only once
|
|
91
|
+
_cached_db_path = custom_path
|
|
92
|
+
if not _db_path_logged:
|
|
93
|
+
logger.warning(
|
|
94
|
+
"Using 'db.path' configuration (legacy). 'database.path' is now the preferred format and 'db.path' will be deprecated in a future version."
|
|
95
|
+
)
|
|
96
|
+
_db_path_logged = True
|
|
47
97
|
return custom_path
|
|
48
98
|
|
|
49
99
|
# Use the standard data directory
|
|
50
|
-
|
|
100
|
+
default_path = os.path.join(get_data_dir(), "meshtastic.sqlite")
|
|
101
|
+
_cached_db_path = default_path
|
|
102
|
+
return default_path
|
|
51
103
|
|
|
52
104
|
|
|
53
105
|
# Initialize SQLite database
|
|
@@ -31,6 +31,56 @@ from mmrelay.log_utils import get_logger
|
|
|
31
31
|
# Do not import plugin_loader here to avoid circular imports
|
|
32
32
|
from mmrelay.meshtastic_utils import connect_meshtastic
|
|
33
33
|
|
|
34
|
+
|
|
35
|
+
def get_interaction_settings(config):
|
|
36
|
+
"""
|
|
37
|
+
Returns a dictionary indicating whether message reactions and replies are enabled based on the provided configuration.
|
|
38
|
+
|
|
39
|
+
Supports both the new `message_interactions` structure and the legacy `relay_reactions` flag for backward compatibility. Defaults to disabling both features if not specified.
|
|
40
|
+
"""
|
|
41
|
+
if config is None:
|
|
42
|
+
return {"reactions": False, "replies": False}
|
|
43
|
+
|
|
44
|
+
meshtastic_config = config.get("meshtastic", {})
|
|
45
|
+
|
|
46
|
+
# Check for new structured configuration first
|
|
47
|
+
if "message_interactions" in meshtastic_config:
|
|
48
|
+
interactions = meshtastic_config["message_interactions"]
|
|
49
|
+
return {
|
|
50
|
+
"reactions": interactions.get("reactions", False),
|
|
51
|
+
"replies": interactions.get("replies", False),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Fall back to legacy relay_reactions setting
|
|
55
|
+
if "relay_reactions" in meshtastic_config:
|
|
56
|
+
enabled = meshtastic_config["relay_reactions"]
|
|
57
|
+
logger.warning(
|
|
58
|
+
"Configuration setting 'relay_reactions' is deprecated. "
|
|
59
|
+
"Please use 'message_interactions: {reactions: bool, replies: bool}' instead. "
|
|
60
|
+
"Legacy mode: enabling reactions only."
|
|
61
|
+
)
|
|
62
|
+
return {
|
|
63
|
+
"reactions": enabled,
|
|
64
|
+
"replies": False,
|
|
65
|
+
} # Only reactions for legacy compatibility
|
|
66
|
+
|
|
67
|
+
# Default to privacy-first (both disabled)
|
|
68
|
+
return {"reactions": False, "replies": False}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def message_storage_enabled(interactions):
|
|
72
|
+
"""
|
|
73
|
+
Return True if message storage is required for enabled message interactions.
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
interactions (dict): Dictionary with 'reactions' and 'replies' boolean flags.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
bool: True if reactions or replies are enabled; otherwise, False.
|
|
80
|
+
"""
|
|
81
|
+
return interactions["reactions"] or interactions["replies"]
|
|
82
|
+
|
|
83
|
+
|
|
34
84
|
# Global config variable that will be set from config.py
|
|
35
85
|
config = None
|
|
36
86
|
|
|
@@ -198,16 +248,29 @@ async def matrix_relay(
|
|
|
198
248
|
meshtastic_text=None,
|
|
199
249
|
emote=False,
|
|
200
250
|
emoji=False,
|
|
251
|
+
reply_to_event_id=None,
|
|
201
252
|
):
|
|
202
253
|
"""
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
254
|
+
Relays a message from Meshtastic to a Matrix room, supporting replies and message mapping for interactions.
|
|
255
|
+
|
|
256
|
+
If `reply_to_event_id` is provided, sends the message as a Matrix reply, formatting the content to include quoted original text and appropriate Matrix reply relations. When message interactions (reactions or replies) are enabled in the configuration, stores a mapping between the Meshtastic message ID and the resulting Matrix event ID to enable cross-referencing for future interactions. Prunes old message mappings based on configuration to limit storage.
|
|
257
|
+
|
|
258
|
+
Parameters:
|
|
259
|
+
room_id (str): The Matrix room ID to send the message to.
|
|
260
|
+
message (str): The message content to relay.
|
|
261
|
+
longname (str): The sender's long display name from Meshtastic.
|
|
262
|
+
shortname (str): The sender's short display name from Meshtastic.
|
|
263
|
+
meshnet_name (str): The originating meshnet name.
|
|
264
|
+
portnum (int): The Meshtastic port number.
|
|
265
|
+
meshtastic_id (str, optional): The Meshtastic message ID for mapping.
|
|
266
|
+
meshtastic_replyId (str, optional): The Meshtastic message ID being replied to, if any.
|
|
267
|
+
meshtastic_text (str, optional): The original Meshtastic message text.
|
|
268
|
+
emote (bool, optional): Whether to send the message as an emote.
|
|
269
|
+
emoji (bool, optional): Whether the message is an emoji reaction.
|
|
270
|
+
reply_to_event_id (str, optional): The Matrix event ID being replied to, if sending a reply.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
None
|
|
211
274
|
"""
|
|
212
275
|
global config
|
|
213
276
|
|
|
@@ -221,8 +284,9 @@ async def matrix_relay(
|
|
|
221
284
|
logger.error("No configuration available. Cannot relay message to Matrix.")
|
|
222
285
|
return
|
|
223
286
|
|
|
224
|
-
#
|
|
225
|
-
|
|
287
|
+
# Get interaction settings
|
|
288
|
+
interactions = get_interaction_settings(config)
|
|
289
|
+
storage_enabled = message_storage_enabled(interactions)
|
|
226
290
|
|
|
227
291
|
# Retrieve db config for message_map pruning
|
|
228
292
|
# Check database config for message map settings (preferred format)
|
|
@@ -263,6 +327,40 @@ async def matrix_relay(
|
|
|
263
327
|
if emoji:
|
|
264
328
|
content["meshtastic_emoji"] = 1
|
|
265
329
|
|
|
330
|
+
# Add Matrix reply formatting if this is a reply
|
|
331
|
+
if reply_to_event_id:
|
|
332
|
+
content["m.relates_to"] = {"m.in_reply_to": {"event_id": reply_to_event_id}}
|
|
333
|
+
# For Matrix replies, we need to format the body with quoted content
|
|
334
|
+
# Get the original message details for proper quoting
|
|
335
|
+
try:
|
|
336
|
+
orig = get_message_map_by_matrix_event_id(reply_to_event_id)
|
|
337
|
+
if orig:
|
|
338
|
+
# orig = (meshtastic_id, matrix_room_id, meshtastic_text, meshtastic_meshnet)
|
|
339
|
+
_, _, original_text, original_meshnet = orig
|
|
340
|
+
|
|
341
|
+
# Use the relay bot's user ID for attribution (this is correct for relay messages)
|
|
342
|
+
bot_user_id = matrix_client.user_id
|
|
343
|
+
original_sender_display = f"{longname}/{original_meshnet}"
|
|
344
|
+
|
|
345
|
+
# Create the quoted reply format
|
|
346
|
+
quoted_text = f"> <@{bot_user_id}> [{original_sender_display}]: {original_text}"
|
|
347
|
+
content["body"] = f"{quoted_text}\n\n{message}"
|
|
348
|
+
content["format"] = "org.matrix.custom.html"
|
|
349
|
+
|
|
350
|
+
# Create formatted HTML version with better readability
|
|
351
|
+
reply_link = f"https://matrix.to/#/{room_id}/{reply_to_event_id}"
|
|
352
|
+
bot_link = f"https://matrix.to/#/@{bot_user_id}"
|
|
353
|
+
blockquote_content = f'<a href="{reply_link}">In reply to</a> <a href="{bot_link}">@{bot_user_id}</a><br>[{original_sender_display}]: {original_text}'
|
|
354
|
+
content["formatted_body"] = (
|
|
355
|
+
f"<mx-reply><blockquote>{blockquote_content}</blockquote></mx-reply>{message}"
|
|
356
|
+
)
|
|
357
|
+
else:
|
|
358
|
+
logger.warning(
|
|
359
|
+
f"Could not find original message for reply_to_event_id: {reply_to_event_id}"
|
|
360
|
+
)
|
|
361
|
+
except Exception as e:
|
|
362
|
+
logger.error(f"Error formatting Matrix reply: {e}")
|
|
363
|
+
|
|
266
364
|
try:
|
|
267
365
|
# Ensure matrix_client is not None
|
|
268
366
|
if not matrix_client:
|
|
@@ -292,10 +390,10 @@ async def matrix_relay(
|
|
|
292
390
|
logger.error(f"Error sending message to Matrix room {room_id}: {e}")
|
|
293
391
|
return
|
|
294
392
|
|
|
295
|
-
# Only store message map if
|
|
296
|
-
#
|
|
393
|
+
# Only store message map if any interactions are enabled and conditions are met
|
|
394
|
+
# This enables reactions and/or replies functionality based on configuration
|
|
297
395
|
if (
|
|
298
|
-
|
|
396
|
+
storage_enabled
|
|
299
397
|
and meshtastic_id is not None
|
|
300
398
|
and not emote
|
|
301
399
|
and hasattr(response, "event_id")
|
|
@@ -337,30 +435,190 @@ def truncate_message(text, max_bytes=227):
|
|
|
337
435
|
|
|
338
436
|
def strip_quoted_lines(text: str) -> str:
|
|
339
437
|
"""
|
|
340
|
-
|
|
341
|
-
|
|
438
|
+
Removes lines starting with '>' from the input text.
|
|
439
|
+
|
|
440
|
+
This is typically used to exclude quoted content from Matrix replies, such as when processing reaction text.
|
|
342
441
|
"""
|
|
343
442
|
lines = text.splitlines()
|
|
344
443
|
filtered = [line for line in lines if not line.strip().startswith(">")]
|
|
345
444
|
return " ".join(filtered).strip()
|
|
346
445
|
|
|
347
446
|
|
|
447
|
+
async def get_user_display_name(room, event):
|
|
448
|
+
"""
|
|
449
|
+
Retrieve the display name of a Matrix user, preferring the room-specific name if available.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
str: The user's display name, or their Matrix ID if no display name is set.
|
|
453
|
+
"""
|
|
454
|
+
room_display_name = room.user_name(event.sender)
|
|
455
|
+
if room_display_name:
|
|
456
|
+
return room_display_name
|
|
457
|
+
|
|
458
|
+
display_name_response = await matrix_client.get_displayname(event.sender)
|
|
459
|
+
return display_name_response.displayname or event.sender
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def format_reply_message(full_display_name, text):
|
|
463
|
+
"""
|
|
464
|
+
Format a reply message by prefixing a truncated display name and removing quoted lines.
|
|
465
|
+
|
|
466
|
+
The resulting message is prefixed with the first five characters of the user's display name followed by "[M]: ", has quoted lines removed, and is truncated to fit within the allowed message length.
|
|
467
|
+
|
|
468
|
+
Parameters:
|
|
469
|
+
full_display_name (str): The user's full display name to be truncated for the prefix.
|
|
470
|
+
text (str): The reply text, possibly containing quoted lines.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
str: The formatted and truncated reply message.
|
|
474
|
+
"""
|
|
475
|
+
short_display_name = full_display_name[:5]
|
|
476
|
+
prefix = f"{short_display_name}[M]: "
|
|
477
|
+
|
|
478
|
+
# Strip quoted content from the reply text
|
|
479
|
+
clean_text = strip_quoted_lines(text)
|
|
480
|
+
reply_message = f"{prefix}{clean_text}"
|
|
481
|
+
return truncate_message(reply_message)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
async def send_reply_to_meshtastic(
|
|
485
|
+
reply_message,
|
|
486
|
+
full_display_name,
|
|
487
|
+
room_config,
|
|
488
|
+
room,
|
|
489
|
+
event,
|
|
490
|
+
text,
|
|
491
|
+
storage_enabled,
|
|
492
|
+
local_meshnet_name,
|
|
493
|
+
reply_id=None,
|
|
494
|
+
):
|
|
495
|
+
"""
|
|
496
|
+
Sends a reply message from Matrix to Meshtastic and optionally stores the message mapping for future interactions.
|
|
497
|
+
|
|
498
|
+
If message storage is enabled and the message is successfully sent, stores a mapping between the Meshtastic packet ID and the Matrix event ID, after removing quoted lines from the reply text. Prunes old message mappings based on configuration to limit storage size. Logs errors if sending fails.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
reply_id: If provided, sends as a structured Meshtastic reply to this message ID
|
|
502
|
+
"""
|
|
503
|
+
meshtastic_interface = connect_meshtastic()
|
|
504
|
+
from mmrelay.meshtastic_utils import logger as meshtastic_logger
|
|
505
|
+
from mmrelay.meshtastic_utils import sendTextReply
|
|
506
|
+
|
|
507
|
+
meshtastic_channel = room_config["meshtastic_channel"]
|
|
508
|
+
|
|
509
|
+
if config["meshtastic"]["broadcast_enabled"]:
|
|
510
|
+
try:
|
|
511
|
+
if reply_id is not None:
|
|
512
|
+
# Send as a structured reply using our custom function
|
|
513
|
+
sent_packet = sendTextReply(
|
|
514
|
+
meshtastic_interface,
|
|
515
|
+
text=reply_message,
|
|
516
|
+
reply_id=reply_id,
|
|
517
|
+
channelIndex=meshtastic_channel,
|
|
518
|
+
)
|
|
519
|
+
meshtastic_logger.info(
|
|
520
|
+
f"Relaying Matrix reply from {full_display_name} to radio broadcast as structured reply to message {reply_id}"
|
|
521
|
+
)
|
|
522
|
+
else:
|
|
523
|
+
# Send as regular message (fallback for when no reply_id is available)
|
|
524
|
+
sent_packet = meshtastic_interface.sendText(
|
|
525
|
+
text=reply_message, channelIndex=meshtastic_channel
|
|
526
|
+
)
|
|
527
|
+
meshtastic_logger.info(
|
|
528
|
+
f"Relaying Matrix reply from {full_display_name} to radio broadcast as regular message"
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
# Store the reply in message map if storage is enabled
|
|
532
|
+
if storage_enabled and sent_packet and hasattr(sent_packet, "id"):
|
|
533
|
+
# Strip quoted lines from text before storing to prevent issues with reactions to replies
|
|
534
|
+
cleaned_text = strip_quoted_lines(text)
|
|
535
|
+
store_message_map(
|
|
536
|
+
sent_packet.id,
|
|
537
|
+
event.event_id,
|
|
538
|
+
room.room_id,
|
|
539
|
+
cleaned_text,
|
|
540
|
+
meshtastic_meshnet=local_meshnet_name,
|
|
541
|
+
)
|
|
542
|
+
# Prune old messages if configured
|
|
543
|
+
database_config = config.get("database", {})
|
|
544
|
+
msg_map_config = database_config.get("msg_map", {})
|
|
545
|
+
if not msg_map_config:
|
|
546
|
+
db_config = config.get("db", {})
|
|
547
|
+
legacy_msg_map_config = db_config.get("msg_map", {})
|
|
548
|
+
if legacy_msg_map_config:
|
|
549
|
+
msg_map_config = legacy_msg_map_config
|
|
550
|
+
msgs_to_keep = msg_map_config.get("msgs_to_keep", 500)
|
|
551
|
+
if msgs_to_keep > 0:
|
|
552
|
+
prune_message_map(msgs_to_keep)
|
|
553
|
+
|
|
554
|
+
except Exception as e:
|
|
555
|
+
meshtastic_logger.error(f"Error sending Matrix reply to Meshtastic: {e}")
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
async def handle_matrix_reply(
|
|
559
|
+
room,
|
|
560
|
+
event,
|
|
561
|
+
reply_to_event_id,
|
|
562
|
+
text,
|
|
563
|
+
room_config,
|
|
564
|
+
storage_enabled,
|
|
565
|
+
local_meshnet_name,
|
|
566
|
+
):
|
|
567
|
+
"""
|
|
568
|
+
Relays a Matrix reply to the corresponding Meshtastic message if a mapping exists.
|
|
569
|
+
|
|
570
|
+
Looks up the original Meshtastic message using the Matrix event ID being replied to. If found, formats and sends the reply to Meshtastic, preserving conversational context. Returns True if the reply was successfully handled; otherwise, returns False to allow normal message processing.
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
bool: True if the reply was relayed to Meshtastic, False otherwise.
|
|
574
|
+
"""
|
|
575
|
+
# Look up the original message in the message map
|
|
576
|
+
orig = get_message_map_by_matrix_event_id(reply_to_event_id)
|
|
577
|
+
if not orig:
|
|
578
|
+
logger.debug(
|
|
579
|
+
f"Original message for Matrix reply not found in DB: {reply_to_event_id}"
|
|
580
|
+
)
|
|
581
|
+
return False # Continue processing as normal message if original not found
|
|
582
|
+
|
|
583
|
+
# Extract the original meshtastic_id to use as reply_id
|
|
584
|
+
# orig = (meshtastic_id, matrix_room_id, meshtastic_text, meshtastic_meshnet)
|
|
585
|
+
original_meshtastic_id = orig[0]
|
|
586
|
+
|
|
587
|
+
# Get user display name
|
|
588
|
+
full_display_name = await get_user_display_name(room, event)
|
|
589
|
+
|
|
590
|
+
# Format the reply message
|
|
591
|
+
reply_message = format_reply_message(full_display_name, text)
|
|
592
|
+
|
|
593
|
+
logger.info(
|
|
594
|
+
f"Relaying Matrix reply from {full_display_name} to Meshtastic as reply to message {original_meshtastic_id}"
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
# Send the reply to Meshtastic with the original message ID as reply_id
|
|
598
|
+
await send_reply_to_meshtastic(
|
|
599
|
+
reply_message,
|
|
600
|
+
full_display_name,
|
|
601
|
+
room_config,
|
|
602
|
+
room,
|
|
603
|
+
event,
|
|
604
|
+
text,
|
|
605
|
+
storage_enabled,
|
|
606
|
+
local_meshnet_name,
|
|
607
|
+
reply_id=original_meshtastic_id,
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
return True # Reply was handled, stop further processing
|
|
611
|
+
|
|
612
|
+
|
|
348
613
|
# Callback for new messages in Matrix room
|
|
349
614
|
async def on_room_message(
|
|
350
615
|
room: MatrixRoom,
|
|
351
616
|
event: Union[RoomMessageText, RoomMessageNotice, ReactionEvent, RoomMessageEmote],
|
|
352
617
|
) -> None:
|
|
353
618
|
"""
|
|
354
|
-
|
|
355
|
-
to Meshtastic, we always apply our local meshnet_name to outgoing events.
|
|
356
|
-
|
|
357
|
-
We must be careful not to relay reactions to reactions (reaction-chains),
|
|
358
|
-
especially remote reactions that got relayed into the room as m.emote events,
|
|
359
|
-
as we do not store them in the database. If we can't find the original message in the DB,
|
|
360
|
-
it likely means it's a reaction to a reaction, and we stop there.
|
|
619
|
+
Handles incoming Matrix room messages, reactions, and replies, relaying them to Meshtastic as appropriate.
|
|
361
620
|
|
|
362
|
-
|
|
363
|
-
if relay_reactions is True. If it's False, none of these mappings are stored or used.
|
|
621
|
+
Processes events from Matrix rooms, including text messages, reactions, and replies. Relays supported messages to Meshtastic if broadcasting is enabled, applying message mapping for cross-referencing when reactions or replies are enabled. Prevents relaying of reactions to reactions and avoids processing messages from the bot itself or messages sent before the bot started. Integrates with plugins for command and message handling, and ensures that only supported messages are forwarded to Meshtastic.
|
|
364
622
|
"""
|
|
365
623
|
# Importing here to avoid circular imports and to keep logic consistent
|
|
366
624
|
# Note: We do not call store_message_map directly here for inbound matrix->mesh messages.
|
|
@@ -403,8 +661,9 @@ async def on_room_message(
|
|
|
403
661
|
logger.error("No configuration available. Cannot process Matrix message.")
|
|
404
662
|
return
|
|
405
663
|
|
|
406
|
-
#
|
|
407
|
-
|
|
664
|
+
# Get interaction settings
|
|
665
|
+
interactions = get_interaction_settings(config)
|
|
666
|
+
storage_enabled = message_storage_enabled(interactions)
|
|
408
667
|
|
|
409
668
|
# Check if this is a Matrix ReactionEvent (usually m.reaction)
|
|
410
669
|
if isinstance(event, ReactionEvent):
|
|
@@ -441,17 +700,26 @@ async def on_room_message(
|
|
|
441
700
|
if suppress:
|
|
442
701
|
return
|
|
443
702
|
|
|
444
|
-
# If this is a reaction and
|
|
445
|
-
if is_reaction and not
|
|
703
|
+
# If this is a reaction and reactions are disabled, do nothing
|
|
704
|
+
if is_reaction and not interactions["reactions"]:
|
|
446
705
|
logger.debug(
|
|
447
|
-
"Reaction event encountered but
|
|
706
|
+
"Reaction event encountered but reactions are disabled. Doing nothing."
|
|
448
707
|
)
|
|
449
708
|
return
|
|
450
709
|
|
|
451
710
|
local_meshnet_name = config["meshtastic"]["meshnet_name"]
|
|
452
711
|
|
|
453
|
-
#
|
|
454
|
-
|
|
712
|
+
# Check if this is a Matrix reply (not a reaction)
|
|
713
|
+
is_reply = False
|
|
714
|
+
reply_to_event_id = None
|
|
715
|
+
if not is_reaction and relates_to and "m.in_reply_to" in relates_to:
|
|
716
|
+
reply_to_event_id = relates_to["m.in_reply_to"].get("event_id")
|
|
717
|
+
if reply_to_event_id:
|
|
718
|
+
is_reply = True
|
|
719
|
+
logger.debug(f"Processing Matrix reply to event: {reply_to_event_id}")
|
|
720
|
+
|
|
721
|
+
# If this is a reaction and reactions are enabled, attempt to relay it
|
|
722
|
+
if is_reaction and interactions["reactions"]:
|
|
455
723
|
# Check if we need to relay a reaction from a remote meshnet to our local meshnet.
|
|
456
724
|
# If meshnet_name != local_meshnet_name and meshtastic_replyId is present and this is an emote,
|
|
457
725
|
# it's a remote reaction that needs to be forwarded as a text message describing the reaction.
|
|
@@ -566,6 +834,20 @@ async def on_room_message(
|
|
|
566
834
|
)
|
|
567
835
|
return
|
|
568
836
|
|
|
837
|
+
# Handle Matrix replies to Meshtastic messages (only if replies are enabled)
|
|
838
|
+
if is_reply and reply_to_event_id and interactions["replies"]:
|
|
839
|
+
reply_handled = await handle_matrix_reply(
|
|
840
|
+
room,
|
|
841
|
+
event,
|
|
842
|
+
reply_to_event_id,
|
|
843
|
+
text,
|
|
844
|
+
room_config,
|
|
845
|
+
storage_enabled,
|
|
846
|
+
local_meshnet_name,
|
|
847
|
+
)
|
|
848
|
+
if reply_handled:
|
|
849
|
+
return
|
|
850
|
+
|
|
569
851
|
# For Matrix->Mesh messages from a remote meshnet, rewrite the message format
|
|
570
852
|
if longname and meshnet_name:
|
|
571
853
|
# Always include the meshnet_name in the full display name.
|
|
@@ -661,33 +943,8 @@ async def on_room_message(
|
|
|
661
943
|
channelIndex=meshtastic_channel,
|
|
662
944
|
portNum=meshtastic.protobuf.portnums_pb2.PortNum.DETECTION_SENSOR_APP,
|
|
663
945
|
)
|
|
664
|
-
#
|
|
665
|
-
#
|
|
666
|
-
if relay_reactions and sent_packet and hasattr(sent_packet, "id"):
|
|
667
|
-
store_message_map(
|
|
668
|
-
sent_packet.id,
|
|
669
|
-
event.event_id,
|
|
670
|
-
room.room_id,
|
|
671
|
-
text,
|
|
672
|
-
meshtastic_meshnet=local_meshnet_name,
|
|
673
|
-
)
|
|
674
|
-
# Check database config for message map settings (preferred format)
|
|
675
|
-
database_config = config.get("database", {})
|
|
676
|
-
msg_map_config = database_config.get("msg_map", {})
|
|
677
|
-
|
|
678
|
-
# If not found in database config, check legacy db config
|
|
679
|
-
if not msg_map_config:
|
|
680
|
-
db_config = config.get("db", {})
|
|
681
|
-
legacy_msg_map_config = db_config.get("msg_map", {})
|
|
682
|
-
|
|
683
|
-
if legacy_msg_map_config:
|
|
684
|
-
msg_map_config = legacy_msg_map_config
|
|
685
|
-
logger.warning(
|
|
686
|
-
"Using 'db.msg_map' configuration (legacy). 'database.msg_map' is now the preferred format and 'db.msg_map' will be deprecated in a future version."
|
|
687
|
-
)
|
|
688
|
-
msgs_to_keep = msg_map_config.get("msgs_to_keep", 500)
|
|
689
|
-
if msgs_to_keep > 0:
|
|
690
|
-
prune_message_map(msgs_to_keep)
|
|
946
|
+
# Note: Detection sensor messages are not stored in message_map because they are never replied to
|
|
947
|
+
# Only TEXT_MESSAGE_APP messages need to be stored for reaction handling
|
|
691
948
|
else:
|
|
692
949
|
meshtastic_logger.debug(
|
|
693
950
|
f"Detection sensor packet received from {full_display_name}, but detection sensor processing is disabled."
|
|
@@ -704,13 +961,16 @@ async def on_room_message(
|
|
|
704
961
|
except Exception as e:
|
|
705
962
|
meshtastic_logger.error(f"Error sending message to Meshtastic: {e}")
|
|
706
963
|
return
|
|
707
|
-
# Store message_map only if
|
|
708
|
-
|
|
964
|
+
# Store message_map only if storage is enabled and only for TEXT_MESSAGE_APP
|
|
965
|
+
# (these are the only messages that can be replied to and thus need reaction handling)
|
|
966
|
+
if storage_enabled and sent_packet and hasattr(sent_packet, "id"):
|
|
967
|
+
# Strip quoted lines from text before storing to prevent issues with reactions to replies
|
|
968
|
+
cleaned_text = strip_quoted_lines(text)
|
|
709
969
|
store_message_map(
|
|
710
970
|
sent_packet.id,
|
|
711
971
|
event.event_id,
|
|
712
972
|
room.room_id,
|
|
713
|
-
|
|
973
|
+
cleaned_text,
|
|
714
974
|
meshtastic_meshnet=local_meshnet_name,
|
|
715
975
|
)
|
|
716
976
|
# Check database config for message map settings (preferred format)
|
|
@@ -12,6 +12,7 @@ import meshtastic.tcp_interface
|
|
|
12
12
|
import serial # For serial port exceptions
|
|
13
13
|
import serial.tools.list_ports # Import serial tools for port listing
|
|
14
14
|
from bleak.exc import BleakDBusError, BleakError
|
|
15
|
+
from meshtastic.protobuf import mesh_pb2, portnums_pb2
|
|
15
16
|
from pubsub import pub
|
|
16
17
|
|
|
17
18
|
from mmrelay.db_utils import (
|
|
@@ -322,9 +323,9 @@ async def reconnect():
|
|
|
322
323
|
|
|
323
324
|
def on_meshtastic_message(packet, interface):
|
|
324
325
|
"""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
326
|
+
Processes incoming Meshtastic messages and relays them to Matrix rooms or plugins based on message type and interaction settings.
|
|
327
|
+
|
|
328
|
+
Handles reactions and replies by relaying them to Matrix if enabled in the interaction settings. Normal text messages are relayed to all mapped Matrix rooms unless handled by a plugin or directed to the relay node. Non-text messages are passed to plugins for processing. Filters out messages from unmapped channels or disabled detection sensors, and ensures sender information is retrieved or stored as needed.
|
|
328
329
|
"""
|
|
329
330
|
global config, matrix_rooms
|
|
330
331
|
|
|
@@ -341,15 +342,24 @@ def on_meshtastic_message(packet, interface):
|
|
|
341
342
|
logger.error("No configuration available. Cannot process Meshtastic message.")
|
|
342
343
|
return
|
|
343
344
|
|
|
344
|
-
#
|
|
345
|
-
|
|
345
|
+
# Import the configuration helpers
|
|
346
|
+
from mmrelay.matrix_utils import get_interaction_settings, message_storage_enabled
|
|
347
|
+
|
|
348
|
+
# Get interaction settings
|
|
349
|
+
interactions = get_interaction_settings(config)
|
|
350
|
+
message_storage_enabled(interactions)
|
|
346
351
|
|
|
347
|
-
#
|
|
352
|
+
# Filter packets based on interaction settings
|
|
348
353
|
if packet.get("decoded", {}).get("portnum") == "TEXT_MESSAGE_APP":
|
|
349
354
|
decoded = packet.get("decoded", {})
|
|
350
|
-
|
|
355
|
+
# Filter out reactions if reactions are disabled
|
|
356
|
+
if (
|
|
357
|
+
not interactions["reactions"]
|
|
358
|
+
and "emoji" in decoded
|
|
359
|
+
and decoded.get("emoji") == 1
|
|
360
|
+
):
|
|
351
361
|
logger.debug(
|
|
352
|
-
"Filtered out reaction
|
|
362
|
+
"Filtered out reaction packet due to reactions being disabled."
|
|
353
363
|
)
|
|
354
364
|
return
|
|
355
365
|
|
|
@@ -391,8 +401,8 @@ def on_meshtastic_message(packet, interface):
|
|
|
391
401
|
meshnet_name = config["meshtastic"]["meshnet_name"]
|
|
392
402
|
|
|
393
403
|
# Reaction handling (Meshtastic -> Matrix)
|
|
394
|
-
# If replyId and emoji_flag are present and
|
|
395
|
-
if replyId and emoji_flag and
|
|
404
|
+
# If replyId and emoji_flag are present and reactions are enabled, we relay as text reactions in Matrix
|
|
405
|
+
if replyId and emoji_flag and interactions["reactions"]:
|
|
396
406
|
longname = get_longname(sender) or str(sender)
|
|
397
407
|
shortname = get_shortname(sender) or str(sender)
|
|
398
408
|
orig = get_message_map_by_meshtastic_id(replyId)
|
|
@@ -432,6 +442,42 @@ def on_meshtastic_message(packet, interface):
|
|
|
432
442
|
logger.debug("Original message for reaction not found in DB.")
|
|
433
443
|
return
|
|
434
444
|
|
|
445
|
+
# Reply handling (Meshtastic -> Matrix)
|
|
446
|
+
# If replyId is present but emoji is not (or not 1), this is a reply
|
|
447
|
+
if replyId and not emoji_flag and interactions["replies"]:
|
|
448
|
+
longname = get_longname(sender) or str(sender)
|
|
449
|
+
shortname = get_shortname(sender) or str(sender)
|
|
450
|
+
orig = get_message_map_by_meshtastic_id(replyId)
|
|
451
|
+
if orig:
|
|
452
|
+
# orig = (matrix_event_id, matrix_room_id, meshtastic_text, meshtastic_meshnet)
|
|
453
|
+
matrix_event_id, matrix_room_id, meshtastic_text, meshtastic_meshnet = orig
|
|
454
|
+
|
|
455
|
+
# Format the reply message for Matrix
|
|
456
|
+
full_display_name = f"{longname}/{meshnet_name}"
|
|
457
|
+
formatted_message = f"[{full_display_name}]: {text}"
|
|
458
|
+
|
|
459
|
+
logger.info(f"Relaying Meshtastic reply from {longname} to Matrix")
|
|
460
|
+
|
|
461
|
+
# Relay the reply to Matrix with proper reply formatting
|
|
462
|
+
asyncio.run_coroutine_threadsafe(
|
|
463
|
+
matrix_relay(
|
|
464
|
+
matrix_room_id,
|
|
465
|
+
formatted_message,
|
|
466
|
+
longname,
|
|
467
|
+
shortname,
|
|
468
|
+
meshnet_name,
|
|
469
|
+
decoded.get("portnum"),
|
|
470
|
+
meshtastic_id=packet.get("id"),
|
|
471
|
+
meshtastic_replyId=replyId,
|
|
472
|
+
meshtastic_text=text,
|
|
473
|
+
reply_to_event_id=matrix_event_id,
|
|
474
|
+
),
|
|
475
|
+
loop=loop,
|
|
476
|
+
)
|
|
477
|
+
else:
|
|
478
|
+
logger.debug("Original message for reply not found in DB.")
|
|
479
|
+
return
|
|
480
|
+
|
|
435
481
|
# Normal text messages or detection sensor messages
|
|
436
482
|
if text:
|
|
437
483
|
# Determine the channel for this message
|
|
@@ -615,6 +661,51 @@ async def check_connection():
|
|
|
615
661
|
await asyncio.sleep(30) # Check connection every 30 seconds
|
|
616
662
|
|
|
617
663
|
|
|
664
|
+
def sendTextReply(
|
|
665
|
+
interface,
|
|
666
|
+
text: str,
|
|
667
|
+
reply_id: int,
|
|
668
|
+
destinationId=meshtastic.BROADCAST_ADDR,
|
|
669
|
+
wantAck: bool = False,
|
|
670
|
+
channelIndex: int = 0,
|
|
671
|
+
):
|
|
672
|
+
"""
|
|
673
|
+
Send a text message as a reply to a previous message.
|
|
674
|
+
|
|
675
|
+
This function creates a proper Meshtastic reply by setting the reply_id field
|
|
676
|
+
in the Data protobuf message, which the standard sendText() method doesn't support.
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
interface: The Meshtastic interface to send through
|
|
680
|
+
text: The text message to send
|
|
681
|
+
reply_id: The ID of the message this is replying to
|
|
682
|
+
destinationId: Where to send the message (default: broadcast)
|
|
683
|
+
wantAck: Whether to request acknowledgment
|
|
684
|
+
channelIndex: Which channel to send on
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
The sent packet with populated ID field
|
|
688
|
+
"""
|
|
689
|
+
logger.debug(f"Sending text reply: '{text}' replying to message ID {reply_id}")
|
|
690
|
+
|
|
691
|
+
# Create the Data protobuf message with reply_id set
|
|
692
|
+
data_msg = mesh_pb2.Data()
|
|
693
|
+
data_msg.portnum = portnums_pb2.PortNum.TEXT_MESSAGE_APP
|
|
694
|
+
data_msg.payload = text.encode("utf-8")
|
|
695
|
+
data_msg.reply_id = reply_id
|
|
696
|
+
|
|
697
|
+
# Create the MeshPacket
|
|
698
|
+
mesh_packet = mesh_pb2.MeshPacket()
|
|
699
|
+
mesh_packet.channel = channelIndex
|
|
700
|
+
mesh_packet.decoded.CopyFrom(data_msg)
|
|
701
|
+
mesh_packet.id = interface._generatePacketId()
|
|
702
|
+
|
|
703
|
+
# Send the packet using the existing infrastructure
|
|
704
|
+
return interface._sendPacket(
|
|
705
|
+
mesh_packet, destinationId=destinationId, wantAck=wantAck
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
|
|
618
709
|
if __name__ == "__main__":
|
|
619
710
|
# If running this standalone (normally the main.py does the loop), just try connecting and run forever.
|
|
620
711
|
meshtastic_client = connect_meshtastic()
|
|
@@ -18,7 +18,10 @@ meshtastic:
|
|
|
18
18
|
broadcast_enabled: true # Must be set to true to enable Matrix to Meshtastic messages
|
|
19
19
|
detection_sensor: true # Must be set to true to forward messages of Meshtastic's detection sensor module
|
|
20
20
|
plugin_response_delay: 3 # Default response delay in seconds for plugins that respond on the mesh;
|
|
21
|
-
|
|
21
|
+
message_interactions: # Configure reactions and replies (both require message storage in database)
|
|
22
|
+
reactions: false # Enable reaction relaying between platforms
|
|
23
|
+
replies: false # Enable reply relaying between platforms
|
|
24
|
+
# Note: Legacy 'relay_reactions' setting is deprecated but still supported
|
|
22
25
|
|
|
23
26
|
logging:
|
|
24
27
|
level: info
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmrelay
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.11
|
|
4
4
|
Summary: Bridge between Meshtastic mesh networks and Matrix chat rooms
|
|
5
5
|
Home-page: https://github.com/geoffwhittington/meshtastic-matrix-relay
|
|
6
6
|
Author: Geoff Whittington, Jeremiah K., and contributors
|
|
@@ -18,8 +18,8 @@ Requires-Dist: meshtastic
|
|
|
18
18
|
Requires-Dist: Pillow==11.2.1
|
|
19
19
|
Requires-Dist: matrix-nio==0.25.2
|
|
20
20
|
Requires-Dist: matplotlib==3.10.1
|
|
21
|
-
Requires-Dist: requests==2.32.
|
|
22
|
-
Requires-Dist: markdown==3.8
|
|
21
|
+
Requires-Dist: requests==2.32.4
|
|
22
|
+
Requires-Dist: markdown==3.8.2
|
|
23
23
|
Requires-Dist: haversine==2.9.0
|
|
24
24
|
Requires-Dist: schedule==1.2.2
|
|
25
25
|
Requires-Dist: platformdirs==4.3.8
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|