mmrelay 1.0.7__tar.gz → 1.0.9__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.7 → mmrelay-1.0.9}/MANIFEST.in +0 -2
- {mmrelay-1.0.7/src/mmrelay.egg-info → mmrelay-1.0.9}/PKG-INFO +24 -48
- {mmrelay-1.0.7 → mmrelay-1.0.9}/README.md +21 -45
- {mmrelay-1.0.7 → mmrelay-1.0.9}/requirements.txt +2 -2
- {mmrelay-1.0.7 → mmrelay-1.0.9}/setup.cfg +3 -3
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/__init__.py +1 -1
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/main.py +4 -1
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/matrix_utils.py +34 -4
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugin_loader.py +63 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/base_plugin.py +199 -16
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/debug_plugin.py +13 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/drop_plugin.py +2 -3
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/help_plugin.py +28 -4
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/map_plugin.py +33 -3
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/mesh_relay_plugin.py +89 -2
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/ping_plugin.py +2 -3
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/weather_plugin.py +2 -3
- {mmrelay-1.0.7 → mmrelay-1.0.9/src/mmrelay.egg-info}/PKG-INFO +24 -48
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay.egg-info/requires.txt +2 -2
- {mmrelay-1.0.7 → mmrelay-1.0.9}/LICENSE +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/pyproject.toml +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/cli.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/config.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/config_checker.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/db_utils.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/log_utils.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/meshtastic_utils.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/__init__.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/health_plugin.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/nodes_plugin.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/plugins/telemetry_plugin.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/setup_utils.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/tools/__init__.py +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/tools/mmrelay.service +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay/tools/sample_config.yaml +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay.egg-info/SOURCES.txt +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay.egg-info/dependency_links.txt +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/src/mmrelay.egg-info/entry_points.txt +0 -0
- {mmrelay-1.0.7 → mmrelay-1.0.9}/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.9
|
|
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
|
|
@@ -22,72 +22,45 @@ Requires-Dist: requests==2.32.3
|
|
|
22
22
|
Requires-Dist: markdown==3.8
|
|
23
23
|
Requires-Dist: haversine==2.9.0
|
|
24
24
|
Requires-Dist: schedule==1.2.2
|
|
25
|
-
Requires-Dist: platformdirs==4.3.
|
|
25
|
+
Requires-Dist: platformdirs==4.3.8
|
|
26
26
|
Requires-Dist: py-staticmaps>=0.4.0
|
|
27
27
|
Requires-Dist: rich==14.0.0
|
|
28
|
-
Requires-Dist: setuptools==80.
|
|
28
|
+
Requires-Dist: setuptools==80.9.0
|
|
29
29
|
Dynamic: license-file
|
|
30
30
|
|
|
31
31
|
# M<>M Relay
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## (Meshtastic <=> Matrix Relay)
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
## Documentation
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
2. Move your configuration to the new standard location (`~/.mmrelay/config.yaml`)
|
|
41
|
-
3. See [ANNOUNCEMENT.md](ANNOUNCEMENT.md) for all the exciting new features
|
|
39
|
+
Visit our [Wiki](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki) for comprehensive guides and information.
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
|
|
41
|
+
- [Installation Instructions](docs/INSTRUCTIONS.md) - Setup and configuration guide
|
|
42
|
+
- [v1.0 Release Announcement](docs/ANNOUNCEMENT.md) - New changes in v1.0
|
|
43
|
+
- [Upgrade Guide](docs/UPGRADE_TO_V1.md) - Migration guidance for existing users
|
|
48
44
|
|
|
49
45
|
---
|
|
50
46
|
|
|
51
|
-
##
|
|
47
|
+
## Quick Start
|
|
52
48
|
|
|
53
49
|
MMRelay runs on Linux, macOS, and Windows.
|
|
54
50
|
|
|
55
|
-
### Quick Installation
|
|
56
|
-
|
|
57
51
|
```bash
|
|
58
52
|
# Install using pipx for isolated installation (recommended)
|
|
59
53
|
pipx install mmrelay
|
|
60
54
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
For pipx installation instructions, see: [pipx installation guide](https://pipx.pypa.io/stable/installation/#on-linux)
|
|
55
|
+
# Generate a sample configuration file & then edit it
|
|
56
|
+
mmrelay --generate-config
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **New Users**: See [INSTRUCTIONS.md](INSTRUCTIONS.md) for setup and configuration
|
|
70
|
-
- **Existing Users**: See [UPGRADE_TO_V1.md](UPGRADE_TO_V1.md) for migration guidance
|
|
71
|
-
- **Configuration**: Review [sample_config.yaml](src/mmrelay/tools/sample_config.yaml) for examples
|
|
72
|
-
|
|
73
|
-
### Command-Line Options
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
usage: mmrelay [-h] [--config CONFIG] [--data-dir DATA_DIR] [--log-level {error,warning,info,debug}] [--logfile LOGFILE] [--version] [--generate-config] [--install-service] [--check-config]
|
|
77
|
-
|
|
78
|
-
Options:
|
|
79
|
-
-h, --help Show this help message and exit
|
|
80
|
-
--config CONFIG Path to config file
|
|
81
|
-
--data-dir DATA_DIR Base directory for all data (logs, database, plugins)
|
|
82
|
-
--log-level {error,warning,info,debug}
|
|
83
|
-
Set logging level
|
|
84
|
-
--logfile LOGFILE Path to log file (can be overridden by --data-dir)
|
|
85
|
-
--version Show version and exit
|
|
86
|
-
--generate-config Generate a sample config.yaml file
|
|
87
|
-
--install-service Install or update the systemd user service
|
|
88
|
-
--check-config Check if the configuration file is valid
|
|
58
|
+
# Start the relay (without --install-service to run manually)
|
|
59
|
+
mmrelay --install-service
|
|
89
60
|
```
|
|
90
61
|
|
|
62
|
+
For detailed installation and configuration instructions, see the [Installation Guide](docs/INSTRUCTIONS.md).
|
|
63
|
+
|
|
91
64
|
---
|
|
92
65
|
|
|
93
66
|
## Features
|
|
@@ -133,7 +106,12 @@ See the full list of core plugins [here](https://github.com/geoffwhittington/mes
|
|
|
133
106
|
|
|
134
107
|
### Community & Custom Plugins
|
|
135
108
|
|
|
136
|
-
|
|
109
|
+
MMRelay's plugin system allows you to extend functionality in two ways:
|
|
110
|
+
|
|
111
|
+
- **Custom Plugins**: Create personal plugins for your own use, stored in `~/.mmrelay/plugins/custom/`
|
|
112
|
+
- **Community Plugins**: Share your creations with others or use plugins developed by the community
|
|
113
|
+
|
|
114
|
+
Check the [Community Plugins Development Guide](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-Development-Guide) in our wiki to get started.
|
|
137
115
|
|
|
138
116
|
✨️ Visit the [Community Plugins List](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-List)!
|
|
139
117
|
|
|
@@ -151,14 +129,12 @@ community-plugins:
|
|
|
151
129
|
|
|
152
130
|
### Plugin System
|
|
153
131
|
|
|
154
|
-
MMRelay features a powerful plugin system with standardized locations:
|
|
132
|
+
Plugins make it easy to extend functionality without modifying the core program. MMRelay features a powerful plugin system with standardized locations:
|
|
155
133
|
|
|
156
134
|
- **Core Plugins**: Pre-installed with the package
|
|
157
135
|
- **Custom Plugins**: Your own plugins in `~/.mmrelay/plugins/custom/`
|
|
158
136
|
- **Community Plugins**: Third-party plugins in `~/.mmrelay/plugins/community/`
|
|
159
137
|
|
|
160
|
-
Plugins make it easy to extend functionality without modifying the core code.
|
|
161
|
-
|
|
162
138
|
---
|
|
163
139
|
|
|
164
140
|
## Getting Started with Matrix
|
|
@@ -1,63 +1,36 @@
|
|
|
1
1
|
# M<>M Relay
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## (Meshtastic <=> Matrix Relay)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Documentation
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
2. Move your configuration to the new standard location (`~/.mmrelay/config.yaml`)
|
|
11
|
-
3. See [ANNOUNCEMENT.md](ANNOUNCEMENT.md) for all the exciting new features
|
|
9
|
+
Visit our [Wiki](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki) for comprehensive guides and information.
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
|
|
11
|
+
- [Installation Instructions](docs/INSTRUCTIONS.md) - Setup and configuration guide
|
|
12
|
+
- [v1.0 Release Announcement](docs/ANNOUNCEMENT.md) - New changes in v1.0
|
|
13
|
+
- [Upgrade Guide](docs/UPGRADE_TO_V1.md) - Migration guidance for existing users
|
|
18
14
|
|
|
19
15
|
---
|
|
20
16
|
|
|
21
|
-
##
|
|
17
|
+
## Quick Start
|
|
22
18
|
|
|
23
19
|
MMRelay runs on Linux, macOS, and Windows.
|
|
24
20
|
|
|
25
|
-
### Quick Installation
|
|
26
|
-
|
|
27
21
|
```bash
|
|
28
22
|
# Install using pipx for isolated installation (recommended)
|
|
29
23
|
pipx install mmrelay
|
|
30
24
|
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
For pipx installation instructions, see: [pipx installation guide](https://pipx.pypa.io/stable/installation/#on-linux)
|
|
25
|
+
# Generate a sample configuration file & then edit it
|
|
26
|
+
mmrelay --generate-config
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
- **New Users**: See [INSTRUCTIONS.md](INSTRUCTIONS.md) for setup and configuration
|
|
40
|
-
- **Existing Users**: See [UPGRADE_TO_V1.md](UPGRADE_TO_V1.md) for migration guidance
|
|
41
|
-
- **Configuration**: Review [sample_config.yaml](src/mmrelay/tools/sample_config.yaml) for examples
|
|
42
|
-
|
|
43
|
-
### Command-Line Options
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
usage: mmrelay [-h] [--config CONFIG] [--data-dir DATA_DIR] [--log-level {error,warning,info,debug}] [--logfile LOGFILE] [--version] [--generate-config] [--install-service] [--check-config]
|
|
47
|
-
|
|
48
|
-
Options:
|
|
49
|
-
-h, --help Show this help message and exit
|
|
50
|
-
--config CONFIG Path to config file
|
|
51
|
-
--data-dir DATA_DIR Base directory for all data (logs, database, plugins)
|
|
52
|
-
--log-level {error,warning,info,debug}
|
|
53
|
-
Set logging level
|
|
54
|
-
--logfile LOGFILE Path to log file (can be overridden by --data-dir)
|
|
55
|
-
--version Show version and exit
|
|
56
|
-
--generate-config Generate a sample config.yaml file
|
|
57
|
-
--install-service Install or update the systemd user service
|
|
58
|
-
--check-config Check if the configuration file is valid
|
|
28
|
+
# Start the relay (without --install-service to run manually)
|
|
29
|
+
mmrelay --install-service
|
|
59
30
|
```
|
|
60
31
|
|
|
32
|
+
For detailed installation and configuration instructions, see the [Installation Guide](docs/INSTRUCTIONS.md).
|
|
33
|
+
|
|
61
34
|
---
|
|
62
35
|
|
|
63
36
|
## Features
|
|
@@ -103,7 +76,12 @@ See the full list of core plugins [here](https://github.com/geoffwhittington/mes
|
|
|
103
76
|
|
|
104
77
|
### Community & Custom Plugins
|
|
105
78
|
|
|
106
|
-
|
|
79
|
+
MMRelay's plugin system allows you to extend functionality in two ways:
|
|
80
|
+
|
|
81
|
+
- **Custom Plugins**: Create personal plugins for your own use, stored in `~/.mmrelay/plugins/custom/`
|
|
82
|
+
- **Community Plugins**: Share your creations with others or use plugins developed by the community
|
|
83
|
+
|
|
84
|
+
Check the [Community Plugins Development Guide](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-Development-Guide) in our wiki to get started.
|
|
107
85
|
|
|
108
86
|
✨️ Visit the [Community Plugins List](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-List)!
|
|
109
87
|
|
|
@@ -121,14 +99,12 @@ community-plugins:
|
|
|
121
99
|
|
|
122
100
|
### Plugin System
|
|
123
101
|
|
|
124
|
-
MMRelay features a powerful plugin system with standardized locations:
|
|
102
|
+
Plugins make it easy to extend functionality without modifying the core program. MMRelay features a powerful plugin system with standardized locations:
|
|
125
103
|
|
|
126
104
|
- **Core Plugins**: Pre-installed with the package
|
|
127
105
|
- **Custom Plugins**: Your own plugins in `~/.mmrelay/plugins/custom/`
|
|
128
106
|
- **Community Plugins**: Third-party plugins in `~/.mmrelay/plugins/community/`
|
|
129
107
|
|
|
130
|
-
Plugins make it easy to extend functionality without modifying the core code.
|
|
131
|
-
|
|
132
108
|
---
|
|
133
109
|
|
|
134
110
|
## Getting Started with Matrix
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = mmrelay
|
|
3
|
-
version = 1.0.
|
|
3
|
+
version = 1.0.9
|
|
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
|
|
@@ -30,10 +30,10 @@ install_requires =
|
|
|
30
30
|
markdown==3.8
|
|
31
31
|
haversine==2.9.0
|
|
32
32
|
schedule==1.2.2
|
|
33
|
-
platformdirs==4.3.
|
|
33
|
+
platformdirs==4.3.8
|
|
34
34
|
py-staticmaps>=0.4.0
|
|
35
35
|
rich==14.0.0
|
|
36
|
-
setuptools==80.
|
|
36
|
+
setuptools==80.9.0
|
|
37
37
|
include_package_data = True
|
|
38
38
|
|
|
39
39
|
[options.packages.find]
|
|
@@ -9,6 +9,7 @@ import signal
|
|
|
9
9
|
import sys
|
|
10
10
|
|
|
11
11
|
from nio import ReactionEvent, RoomMessageEmote, RoomMessageNotice, RoomMessageText
|
|
12
|
+
from nio.events.room_events import RoomMemberEvent
|
|
12
13
|
|
|
13
14
|
# Import version from package
|
|
14
15
|
# Import meshtastic_utils as a module to set event_loop
|
|
@@ -22,7 +23,7 @@ from mmrelay.db_utils import (
|
|
|
22
23
|
from mmrelay.log_utils import get_logger
|
|
23
24
|
from mmrelay.matrix_utils import connect_matrix, join_matrix_room
|
|
24
25
|
from mmrelay.matrix_utils import logger as matrix_logger
|
|
25
|
-
from mmrelay.matrix_utils import on_room_message
|
|
26
|
+
from mmrelay.matrix_utils import on_room_member, on_room_message
|
|
26
27
|
from mmrelay.meshtastic_utils import connect_meshtastic
|
|
27
28
|
from mmrelay.meshtastic_utils import logger as meshtastic_logger
|
|
28
29
|
from mmrelay.plugin_loader import load_plugins
|
|
@@ -109,6 +110,8 @@ async def main(config):
|
|
|
109
110
|
)
|
|
110
111
|
# Add ReactionEvent callback so we can handle matrix reactions
|
|
111
112
|
matrix_client.add_event_callback(on_room_message, ReactionEvent)
|
|
113
|
+
# Add RoomMemberEvent callback to track room-specific display name changes
|
|
114
|
+
matrix_client.add_event_callback(on_room_member, RoomMemberEvent)
|
|
112
115
|
|
|
113
116
|
# Set up shutdown event
|
|
114
117
|
shutdown_event = asyncio.Event()
|
|
@@ -18,6 +18,7 @@ from nio import (
|
|
|
18
18
|
UploadResponse,
|
|
19
19
|
WhoamiError,
|
|
20
20
|
)
|
|
21
|
+
from nio.events.room_events import RoomMemberEvent
|
|
21
22
|
from PIL import Image
|
|
22
23
|
|
|
23
24
|
from mmrelay.db_utils import (
|
|
@@ -517,8 +518,16 @@ async def on_room_message(
|
|
|
517
518
|
meshtastic_id, matrix_room_id, meshtastic_text_db, meshtastic_meshnet_db = (
|
|
518
519
|
orig
|
|
519
520
|
)
|
|
520
|
-
|
|
521
|
-
|
|
521
|
+
# Get room-specific display name if available, fallback to global display name
|
|
522
|
+
room_display_name = room.user_name(event.sender)
|
|
523
|
+
if room_display_name:
|
|
524
|
+
full_display_name = room_display_name
|
|
525
|
+
else:
|
|
526
|
+
# Fallback to global display name if room-specific name is not available
|
|
527
|
+
display_name_response = await matrix_client.get_displayname(
|
|
528
|
+
event.sender
|
|
529
|
+
)
|
|
530
|
+
full_display_name = display_name_response.displayname or event.sender
|
|
522
531
|
|
|
523
532
|
# If not from a remote meshnet, proceed as normal to relay back to the originating meshnet
|
|
524
533
|
short_display_name = full_display_name[:5]
|
|
@@ -578,8 +587,14 @@ async def on_room_message(
|
|
|
578
587
|
return
|
|
579
588
|
else:
|
|
580
589
|
# Normal Matrix message from a Matrix user
|
|
581
|
-
|
|
582
|
-
|
|
590
|
+
# Get room-specific display name if available, fallback to global display name
|
|
591
|
+
room_display_name = room.user_name(event.sender)
|
|
592
|
+
if room_display_name:
|
|
593
|
+
full_display_name = room_display_name
|
|
594
|
+
else:
|
|
595
|
+
# Fallback to global display name if room-specific name is not available
|
|
596
|
+
display_name_response = await matrix_client.get_displayname(event.sender)
|
|
597
|
+
full_display_name = display_name_response.displayname or event.sender
|
|
583
598
|
short_display_name = full_display_name[:5]
|
|
584
599
|
prefix = f"{short_display_name}[M]: "
|
|
585
600
|
logger.debug(f"Processing matrix message from [{full_display_name}]: {text}")
|
|
@@ -752,3 +767,18 @@ async def send_room_image(
|
|
|
752
767
|
message_type="m.room.message",
|
|
753
768
|
content={"msgtype": "m.image", "url": upload_response.content_uri, "body": ""},
|
|
754
769
|
)
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
async def on_room_member(room: MatrixRoom, event: RoomMemberEvent) -> None:
|
|
773
|
+
"""
|
|
774
|
+
Callback to handle room member events, specifically tracking room-specific display name changes.
|
|
775
|
+
This ensures we detect when users update their display names in specific rooms.
|
|
776
|
+
|
|
777
|
+
Note: This callback doesn't need to do any explicit processing since matrix-nio
|
|
778
|
+
automatically updates the room state and room.user_name() will return the
|
|
779
|
+
updated room-specific display name immediately after this event.
|
|
780
|
+
"""
|
|
781
|
+
# The callback is registered to ensure matrix-nio processes the event,
|
|
782
|
+
# but no explicit action is needed since room.user_name() automatically
|
|
783
|
+
# handles room-specific display names after the room state is updated.
|
|
784
|
+
pass
|
|
@@ -57,6 +57,28 @@ def get_community_plugin_dirs():
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def clone_or_update_repo(repo_url, ref, plugins_dir):
|
|
60
|
+
"""Clone or update a Git repository for community plugins.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
repo_url (str): Git repository URL to clone/update
|
|
64
|
+
ref (dict): Reference specification with keys:
|
|
65
|
+
- type: "tag" or "branch"
|
|
66
|
+
- value: tag name or branch name
|
|
67
|
+
plugins_dir (str): Directory where the repository should be cloned
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
bool: True if successful, False if clone/update failed
|
|
71
|
+
|
|
72
|
+
Handles complex Git operations including:
|
|
73
|
+
- Cloning new repositories with specific tags/branches
|
|
74
|
+
- Updating existing repositories and switching refs
|
|
75
|
+
- Installing requirements.txt dependencies via pip or pipx
|
|
76
|
+
- Fallback to default branches (main/master) when specified ref fails
|
|
77
|
+
- Robust error handling and logging
|
|
78
|
+
|
|
79
|
+
The function automatically installs Python dependencies if a requirements.txt
|
|
80
|
+
file is found in the repository root.
|
|
81
|
+
"""
|
|
60
82
|
# Extract the repository name from the URL
|
|
61
83
|
repo_name = os.path.splitext(os.path.basename(repo_url.rstrip("/")))[0]
|
|
62
84
|
repo_path = os.path.join(plugins_dir, repo_name)
|
|
@@ -497,6 +519,27 @@ def clone_or_update_repo(repo_url, ref, plugins_dir):
|
|
|
497
519
|
|
|
498
520
|
|
|
499
521
|
def load_plugins_from_directory(directory, recursive=False):
|
|
522
|
+
"""Load plugin classes from Python files in a directory.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
directory (str): Directory path to search for plugin files
|
|
526
|
+
recursive (bool): Whether to search subdirectories recursively
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
list: List of instantiated plugin objects found in the directory
|
|
530
|
+
|
|
531
|
+
Scans for .py files and attempts to import each as a module. Looks for
|
|
532
|
+
a 'Plugin' class in each module and instantiates it if found.
|
|
533
|
+
|
|
534
|
+
Features:
|
|
535
|
+
- Automatic dependency installation for missing imports (via pip/pipx)
|
|
536
|
+
- Compatibility layer for import paths (plugins vs mmrelay.plugins)
|
|
537
|
+
- Proper sys.path management for plugin directory imports
|
|
538
|
+
- Comprehensive error handling and logging
|
|
539
|
+
|
|
540
|
+
Skips files that don't define a Plugin class or have import errors
|
|
541
|
+
that can't be automatically resolved.
|
|
542
|
+
"""
|
|
500
543
|
plugins = []
|
|
501
544
|
if os.path.isdir(directory):
|
|
502
545
|
for root, _dirs, files in os.walk(directory):
|
|
@@ -617,6 +660,26 @@ def load_plugins_from_directory(directory, recursive=False):
|
|
|
617
660
|
|
|
618
661
|
|
|
619
662
|
def load_plugins(passed_config=None):
|
|
663
|
+
"""Load and initialize all active plugins based on configuration.
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
passed_config (dict, optional): Configuration dictionary to use.
|
|
667
|
+
If None, uses global config variable.
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
list: List of active plugin instances sorted by priority
|
|
671
|
+
|
|
672
|
+
This is the main plugin loading function that:
|
|
673
|
+
- Loads core plugins from mmrelay.plugins package
|
|
674
|
+
- Processes custom plugins from ~/.mmrelay/plugins/custom and plugins/custom
|
|
675
|
+
- Downloads and loads community plugins from configured Git repositories
|
|
676
|
+
- Filters plugins based on active status in configuration
|
|
677
|
+
- Sorts active plugins by priority and calls their start() method
|
|
678
|
+
- Sets up proper plugin configuration and channel mapping
|
|
679
|
+
|
|
680
|
+
Only plugins explicitly marked as active=true in config are loaded.
|
|
681
|
+
Custom and community plugins are cloned/updated automatically.
|
|
682
|
+
"""
|
|
620
683
|
global sorted_active_plugins
|
|
621
684
|
global plugins_loaded
|
|
622
685
|
global config
|
|
@@ -20,6 +20,27 @@ config = None
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class BasePlugin(ABC):
|
|
23
|
+
"""Abstract base class for all mmrelay plugins.
|
|
24
|
+
|
|
25
|
+
Provides common functionality for plugin development including:
|
|
26
|
+
- Configuration management and validation
|
|
27
|
+
- Database storage for plugin-specific data
|
|
28
|
+
- Channel and direct message handling
|
|
29
|
+
- Matrix message sending capabilities
|
|
30
|
+
- Scheduling support for background tasks
|
|
31
|
+
- Command matching and routing
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
plugin_name (str): Unique identifier for the plugin
|
|
35
|
+
max_data_rows_per_node (int): Maximum data rows stored per node (default: 100)
|
|
36
|
+
priority (int): Plugin execution priority (lower = higher priority, default: 10)
|
|
37
|
+
|
|
38
|
+
Subclasses must:
|
|
39
|
+
- Set plugin_name as a class attribute
|
|
40
|
+
- Implement handle_meshtastic_message() and handle_room_message()
|
|
41
|
+
- Optionally override other methods for custom behavior
|
|
42
|
+
"""
|
|
43
|
+
|
|
23
44
|
# Class-level default attributes
|
|
24
45
|
plugin_name = None # Must be overridden in subclasses
|
|
25
46
|
max_data_rows_per_node = 100
|
|
@@ -27,27 +48,53 @@ class BasePlugin(ABC):
|
|
|
27
48
|
|
|
28
49
|
@property
|
|
29
50
|
def description(self):
|
|
51
|
+
"""Get the plugin description for help text.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
str: Human-readable description of plugin functionality
|
|
55
|
+
|
|
56
|
+
Override this property in subclasses to provide meaningful help text
|
|
57
|
+
that will be displayed by the help plugin.
|
|
58
|
+
"""
|
|
30
59
|
return ""
|
|
31
60
|
|
|
32
|
-
def __init__(self) -> None:
|
|
33
|
-
|
|
34
|
-
# When creating a plugin that inherits from BasePlugin, you MUST set
|
|
35
|
-
# self.plugin_name in your __init__ method BEFORE calling super().__init__()
|
|
36
|
-
# Example:
|
|
37
|
-
# def __init__(self):
|
|
38
|
-
# self.plugin_name = "your_plugin_name" # Set this FIRST
|
|
39
|
-
# super().__init__() # Then call parent
|
|
40
|
-
#
|
|
41
|
-
# Failure to do this will cause command recognition issues and other problems.
|
|
61
|
+
def __init__(self, plugin_name=None) -> None:
|
|
62
|
+
"""Initialize the plugin with configuration and logging.
|
|
42
63
|
|
|
64
|
+
Args:
|
|
65
|
+
plugin_name (str, optional): Plugin name override. If not provided,
|
|
66
|
+
uses class-level plugin_name attribute.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
ValueError: If plugin_name is not set via parameter or class attribute
|
|
70
|
+
|
|
71
|
+
Sets up:
|
|
72
|
+
- Plugin-specific logger
|
|
73
|
+
- Configuration from global config
|
|
74
|
+
- Channel mapping and validation
|
|
75
|
+
- Response delay settings
|
|
76
|
+
"""
|
|
77
|
+
# Allow plugin_name to be passed as a parameter for simpler initialization
|
|
78
|
+
# This maintains backward compatibility while providing a cleaner API
|
|
43
79
|
super().__init__()
|
|
44
80
|
|
|
45
|
-
#
|
|
81
|
+
# If plugin_name is provided as a parameter, use it
|
|
82
|
+
if plugin_name is not None:
|
|
83
|
+
self.plugin_name = plugin_name
|
|
84
|
+
|
|
85
|
+
# For backward compatibility: if plugin_name is not provided as a parameter,
|
|
86
|
+
# check if it's set as an instance attribute (old way) or use the class attribute
|
|
46
87
|
if not hasattr(self, "plugin_name") or self.plugin_name is None:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
88
|
+
# Try to get the class-level plugin_name
|
|
89
|
+
class_plugin_name = getattr(self.__class__, "plugin_name", None)
|
|
90
|
+
if class_plugin_name is not None:
|
|
91
|
+
self.plugin_name = class_plugin_name
|
|
92
|
+
else:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
f"{self.__class__.__name__} is missing plugin_name definition. "
|
|
95
|
+
f"Either set class.plugin_name, pass plugin_name to __init__, "
|
|
96
|
+
f"or set self.plugin_name before calling super().__init__()"
|
|
97
|
+
)
|
|
51
98
|
|
|
52
99
|
self.logger = get_logger(f"Plugin:{self.plugin_name}")
|
|
53
100
|
self.config = {"active": False}
|
|
@@ -66,6 +113,8 @@ class BasePlugin(ABC):
|
|
|
66
113
|
room.get("meshtastic_channel")
|
|
67
114
|
for room in config.get("matrix_rooms", [])
|
|
68
115
|
]
|
|
116
|
+
else:
|
|
117
|
+
self.mapped_channels = []
|
|
69
118
|
|
|
70
119
|
# Get the channels specified for this plugin, or default to all mapped channels
|
|
71
120
|
self.channels = self.config.get("channels", self.mapped_channels)
|
|
@@ -91,6 +140,19 @@ class BasePlugin(ABC):
|
|
|
91
140
|
)
|
|
92
141
|
|
|
93
142
|
def start(self):
|
|
143
|
+
"""Start the plugin and set up scheduled tasks if configured.
|
|
144
|
+
|
|
145
|
+
Called automatically when the plugin is loaded. Checks plugin configuration
|
|
146
|
+
for scheduling settings and sets up background jobs accordingly.
|
|
147
|
+
|
|
148
|
+
Supported schedule formats in config:
|
|
149
|
+
- schedule.hours + schedule.at: Run every N hours at specific time
|
|
150
|
+
- schedule.minutes + schedule.at: Run every N minutes at specific time
|
|
151
|
+
- schedule.hours: Run every N hours
|
|
152
|
+
- schedule.minutes: Run every N minutes
|
|
153
|
+
|
|
154
|
+
Creates a daemon thread to run the scheduler if any schedule is configured.
|
|
155
|
+
"""
|
|
94
156
|
if "schedule" not in self.config or (
|
|
95
157
|
"at" not in self.config["schedule"]
|
|
96
158
|
and "hours" not in self.config["schedule"]
|
|
@@ -132,9 +194,27 @@ class BasePlugin(ABC):
|
|
|
132
194
|
|
|
133
195
|
# trunk-ignore(ruff/B027)
|
|
134
196
|
def background_job(self):
|
|
197
|
+
"""Background task executed on schedule.
|
|
198
|
+
|
|
199
|
+
Override this method in subclasses to implement scheduled functionality.
|
|
200
|
+
Called automatically based on schedule configuration in start().
|
|
201
|
+
|
|
202
|
+
Default implementation does nothing.
|
|
203
|
+
"""
|
|
135
204
|
pass # Implement in subclass if needed
|
|
136
205
|
|
|
137
206
|
def strip_raw(self, data):
|
|
207
|
+
"""Recursively remove 'raw' keys from data structures.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
data: Data structure (dict, list, or other) to clean
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Cleaned data structure with 'raw' keys removed
|
|
214
|
+
|
|
215
|
+
Useful for cleaning packet data before logging or storage to remove
|
|
216
|
+
binary protobuf data that's not human-readable.
|
|
217
|
+
"""
|
|
138
218
|
if isinstance(data, dict):
|
|
139
219
|
data.pop("raw", None)
|
|
140
220
|
for k, v in data.items():
|
|
@@ -144,19 +224,59 @@ class BasePlugin(ABC):
|
|
|
144
224
|
return data
|
|
145
225
|
|
|
146
226
|
def get_response_delay(self):
|
|
227
|
+
"""Get the configured response delay for meshtastic messages.
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
int: Delay in seconds before sending responses (default: 3)
|
|
231
|
+
|
|
232
|
+
Used to prevent message flooding and ensure proper radio etiquette.
|
|
233
|
+
Delay is configured via meshtastic.plugin_response_delay in config.
|
|
234
|
+
"""
|
|
147
235
|
return self.response_delay
|
|
148
236
|
|
|
149
|
-
# Modified method to accept is_direct_message parameter
|
|
150
237
|
def is_channel_enabled(self, channel, is_direct_message=False):
|
|
238
|
+
"""Check if the plugin should respond on a specific channel.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
channel: Channel identifier to check
|
|
242
|
+
is_direct_message (bool): Whether this is a direct message
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
bool: True if plugin should respond, False otherwise
|
|
246
|
+
|
|
247
|
+
Direct messages always return True if the plugin is active.
|
|
248
|
+
For channel messages, checks if channel is in plugin's configured channels list.
|
|
249
|
+
"""
|
|
151
250
|
if is_direct_message:
|
|
152
251
|
return True # Always respond to DMs if the plugin is active
|
|
153
252
|
else:
|
|
154
253
|
return channel in self.channels
|
|
155
254
|
|
|
156
255
|
def get_matrix_commands(self):
|
|
256
|
+
"""Get list of Matrix commands this plugin responds to.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
list: List of command strings (without ! prefix)
|
|
260
|
+
|
|
261
|
+
Default implementation returns [plugin_name]. Override to provide
|
|
262
|
+
custom commands or multiple command aliases.
|
|
263
|
+
"""
|
|
157
264
|
return [self.plugin_name]
|
|
158
265
|
|
|
159
266
|
async def send_matrix_message(self, room_id, message, formatted=True):
|
|
267
|
+
"""Send a message to a Matrix room.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
room_id (str): Matrix room identifier
|
|
271
|
+
message (str): Message content to send
|
|
272
|
+
formatted (bool): Whether to send as formatted HTML (default: True)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
dict: Response from Matrix API room_send
|
|
276
|
+
|
|
277
|
+
Connects to Matrix using matrix_utils and sends a room message
|
|
278
|
+
with optional HTML formatting via markdown.
|
|
279
|
+
"""
|
|
160
280
|
from mmrelay.matrix_utils import connect_matrix
|
|
161
281
|
|
|
162
282
|
matrix_client = await connect_matrix()
|
|
@@ -173,9 +293,26 @@ class BasePlugin(ABC):
|
|
|
173
293
|
)
|
|
174
294
|
|
|
175
295
|
def get_mesh_commands(self):
|
|
296
|
+
"""Get list of mesh/radio commands this plugin responds to.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
list: List of command strings (without ! prefix)
|
|
300
|
+
|
|
301
|
+
Default implementation returns empty list. Override to handle
|
|
302
|
+
commands sent over the mesh radio network.
|
|
303
|
+
"""
|
|
176
304
|
return []
|
|
177
305
|
|
|
178
306
|
def store_node_data(self, meshtastic_id, node_data):
|
|
307
|
+
"""Store data for a specific node, appending to existing data.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
meshtastic_id (str): Node identifier
|
|
311
|
+
node_data: Data to store (single item or list)
|
|
312
|
+
|
|
313
|
+
Retrieves existing data, appends new data, trims to max_data_rows_per_node,
|
|
314
|
+
and stores back to database. Use for accumulating time-series data.
|
|
315
|
+
"""
|
|
179
316
|
data = self.get_node_data(meshtastic_id=meshtastic_id)
|
|
180
317
|
data = data[-self.max_data_rows_per_node :]
|
|
181
318
|
if isinstance(node_data, list):
|
|
@@ -185,21 +322,56 @@ class BasePlugin(ABC):
|
|
|
185
322
|
store_plugin_data(self.plugin_name, meshtastic_id, data)
|
|
186
323
|
|
|
187
324
|
def set_node_data(self, meshtastic_id, node_data):
|
|
325
|
+
"""Replace all data for a specific node.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
meshtastic_id (str): Node identifier
|
|
329
|
+
node_data: Data to store (replaces existing data)
|
|
330
|
+
|
|
331
|
+
Completely replaces existing data for the node, trimming to
|
|
332
|
+
max_data_rows_per_node if needed. Use when you want to reset
|
|
333
|
+
or completely replace a node's data.
|
|
334
|
+
"""
|
|
188
335
|
node_data = node_data[-self.max_data_rows_per_node :]
|
|
189
336
|
store_plugin_data(self.plugin_name, meshtastic_id, node_data)
|
|
190
337
|
|
|
191
338
|
def delete_node_data(self, meshtastic_id):
|
|
339
|
+
"""Delete all stored data for a specific node.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
meshtastic_id (str): Node identifier
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
bool: True if deletion succeeded, False otherwise
|
|
346
|
+
"""
|
|
192
347
|
return delete_plugin_data(self.plugin_name, meshtastic_id)
|
|
193
348
|
|
|
194
349
|
def get_node_data(self, meshtastic_id):
|
|
350
|
+
"""Retrieve stored data for a specific node.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
meshtastic_id (str): Node identifier
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
list: Stored data for the node (JSON deserialized)
|
|
357
|
+
"""
|
|
195
358
|
return get_plugin_data_for_node(self.plugin_name, meshtastic_id)
|
|
196
359
|
|
|
197
360
|
def get_data(self):
|
|
361
|
+
"""Retrieve all stored data for this plugin across all nodes.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
list: List of tuples containing raw data entries
|
|
365
|
+
|
|
366
|
+
Returns raw data without JSON deserialization. Use get_node_data()
|
|
367
|
+
for individual node data that's automatically deserialized.
|
|
368
|
+
"""
|
|
198
369
|
return get_plugin_data(self.plugin_name)
|
|
199
370
|
|
|
200
371
|
def get_plugin_data_dir(self, subdir=None):
|
|
201
372
|
"""
|
|
202
373
|
Returns the directory for storing plugin-specific data files.
|
|
374
|
+
|
|
203
375
|
Creates the directory if it doesn't exist.
|
|
204
376
|
|
|
205
377
|
Args:
|
|
@@ -225,6 +397,17 @@ class BasePlugin(ABC):
|
|
|
225
397
|
return plugin_dir
|
|
226
398
|
|
|
227
399
|
def matches(self, event):
|
|
400
|
+
"""Check if a Matrix event matches this plugin's commands.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
event: Matrix room event to check
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
bool: True if event matches plugin commands, False otherwise
|
|
407
|
+
|
|
408
|
+
Uses bot_command() utility to check if the event contains any of
|
|
409
|
+
the plugin's matrix commands with proper bot command syntax.
|
|
410
|
+
"""
|
|
228
411
|
from mmrelay.matrix_utils import bot_command
|
|
229
412
|
|
|
230
413
|
# Pass the entire event to bot_command
|
|
@@ -2,6 +2,19 @@ from mmrelay.plugins.base_plugin import BasePlugin
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class Plugin(BasePlugin):
|
|
5
|
+
"""Debug plugin for logging packet information.
|
|
6
|
+
|
|
7
|
+
A low-priority plugin that logs all received meshtastic packets
|
|
8
|
+
for debugging and development purposes. Strips raw binary data
|
|
9
|
+
before logging to keep output readable.
|
|
10
|
+
|
|
11
|
+
Configuration:
|
|
12
|
+
priority: 1 (runs first, before other plugins)
|
|
13
|
+
|
|
14
|
+
Never intercepts messages (always returns False) so other plugins
|
|
15
|
+
can still process the same packets.
|
|
16
|
+
"""
|
|
17
|
+
|
|
5
18
|
plugin_name = "debug"
|
|
6
19
|
priority = 1
|
|
7
20
|
|
|
@@ -10,9 +10,8 @@ class Plugin(BasePlugin):
|
|
|
10
10
|
plugin_name = "drop"
|
|
11
11
|
special_node = "!NODE_MSGS!"
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
super().__init__()
|
|
13
|
+
# No __init__ method needed with the simplified plugin system
|
|
14
|
+
# The BasePlugin will automatically use the class-level plugin_name
|
|
16
15
|
|
|
17
16
|
def get_position(self, meshtastic_client, node_id):
|
|
18
17
|
for _node, info in meshtastic_client.nodes.items():
|
|
@@ -5,14 +5,28 @@ from mmrelay.plugins.base_plugin import BasePlugin
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Plugin(BasePlugin):
|
|
8
|
-
|
|
8
|
+
"""Help command plugin for listing available commands.
|
|
9
|
+
|
|
10
|
+
Provides users with information about available relay commands
|
|
11
|
+
and plugin functionality.
|
|
12
|
+
|
|
13
|
+
Commands:
|
|
14
|
+
!help: List all available commands
|
|
15
|
+
!help <command>: Show detailed help for a specific command
|
|
16
|
+
|
|
17
|
+
Dynamically discovers available commands from all loaded plugins
|
|
18
|
+
and their descriptions.
|
|
19
|
+
"""
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
self.plugin_name = "help"
|
|
12
|
-
super().__init__()
|
|
21
|
+
plugin_name = "help"
|
|
13
22
|
|
|
14
23
|
@property
|
|
15
24
|
def description(self):
|
|
25
|
+
"""Get plugin description.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
str: Description of help functionality
|
|
29
|
+
"""
|
|
16
30
|
return "List supported relay commands"
|
|
17
31
|
|
|
18
32
|
async def handle_meshtastic_message(
|
|
@@ -21,9 +35,19 @@ class Plugin(BasePlugin):
|
|
|
21
35
|
return False
|
|
22
36
|
|
|
23
37
|
def get_matrix_commands(self):
|
|
38
|
+
"""Get Matrix commands handled by this plugin.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
list: List containing the help command
|
|
42
|
+
"""
|
|
24
43
|
return [self.plugin_name]
|
|
25
44
|
|
|
26
45
|
def get_mesh_commands(self):
|
|
46
|
+
"""Get mesh commands handled by this plugin.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
list: Empty list (help only works via Matrix)
|
|
50
|
+
"""
|
|
27
51
|
return []
|
|
28
52
|
|
|
29
53
|
async def handle_room_message(self, room, event, full_message):
|
|
@@ -156,6 +156,19 @@ class TextLabel(staticmaps.Object):
|
|
|
156
156
|
|
|
157
157
|
|
|
158
158
|
def anonymize_location(lat, lon, radius=1000):
|
|
159
|
+
"""Add random offset to GPS coordinates for privacy protection.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
lat (float): Original latitude
|
|
163
|
+
lon (float): Original longitude
|
|
164
|
+
radius (int): Maximum offset distance in meters (default: 1000)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
tuple: (new_lat, new_lon) with random offset applied
|
|
168
|
+
|
|
169
|
+
Adds random offset within specified radius to obscure exact locations
|
|
170
|
+
while maintaining general geographic area for mapping purposes.
|
|
171
|
+
"""
|
|
159
172
|
# Generate random offsets for latitude and longitude
|
|
160
173
|
lat_offset = random.uniform(-radius / 111320, radius / 111320)
|
|
161
174
|
lon_offset = random.uniform(
|
|
@@ -233,11 +246,28 @@ async def send_image(client: AsyncClient, room_id: str, image: Image.Image):
|
|
|
233
246
|
|
|
234
247
|
|
|
235
248
|
class Plugin(BasePlugin):
|
|
249
|
+
"""Static map generation plugin for mesh node locations.
|
|
250
|
+
|
|
251
|
+
Generates static maps showing positions of mesh nodes with labeled markers.
|
|
252
|
+
Supports customizable zoom levels, image sizes, and privacy features.
|
|
253
|
+
|
|
254
|
+
Commands:
|
|
255
|
+
!map: Generate map with default settings
|
|
256
|
+
!map zoom=N: Set zoom level (0-30)
|
|
257
|
+
!map size=W,H: Set image dimensions (max 1000x1000)
|
|
258
|
+
|
|
259
|
+
Configuration:
|
|
260
|
+
zoom (int): Default zoom level (default: 8)
|
|
261
|
+
image_width/image_height (int): Default image size (default: 1000x1000)
|
|
262
|
+
anonymize (bool): Whether to offset coordinates for privacy (default: true)
|
|
263
|
+
radius (int): Anonymization offset radius in meters (default: 1000)
|
|
264
|
+
|
|
265
|
+
Uploads generated maps as images to Matrix rooms.
|
|
266
|
+
"""
|
|
236
267
|
plugin_name = "map"
|
|
237
268
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
super().__init__()
|
|
269
|
+
# No __init__ method needed with the simplified plugin system
|
|
270
|
+
# The BasePlugin will automatically use the class-level plugin_name
|
|
241
271
|
|
|
242
272
|
@property
|
|
243
273
|
def description(self):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# Note: This plugin was experimental and is not functional.
|
|
2
|
+
|
|
1
3
|
import base64
|
|
2
4
|
import json
|
|
3
5
|
import re
|
|
@@ -8,12 +10,36 @@ from mmrelay.plugins.base_plugin import BasePlugin, config
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
class Plugin(BasePlugin):
|
|
13
|
+
"""Core mesh-to-Matrix relay plugin.
|
|
14
|
+
|
|
15
|
+
Handles bidirectional message relay between Meshtastic mesh network
|
|
16
|
+
and Matrix chat rooms. Processes radio packets and forwards them
|
|
17
|
+
to configured Matrix rooms, and vice versa.
|
|
18
|
+
|
|
19
|
+
This plugin is fundamental to the relay's core functionality and
|
|
20
|
+
typically runs with high priority to ensure messages are properly
|
|
21
|
+
bridged between the two networks.
|
|
22
|
+
|
|
23
|
+
Configuration:
|
|
24
|
+
max_data_rows_per_node: 50 (reduced storage for performance)
|
|
25
|
+
"""
|
|
26
|
+
|
|
11
27
|
plugin_name = "mesh_relay"
|
|
12
28
|
max_data_rows_per_node = 50
|
|
13
29
|
|
|
14
30
|
def normalize(self, dict_obj):
|
|
15
|
-
"""
|
|
16
|
-
|
|
31
|
+
"""Normalize packet data to consistent dictionary format.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
dict_obj: Packet data (dict, JSON string, or plain string)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
dict: Normalized packet dictionary with raw data stripped
|
|
38
|
+
|
|
39
|
+
Handles various packet formats:
|
|
40
|
+
- Dict objects (passed through)
|
|
41
|
+
- JSON strings (parsed)
|
|
42
|
+
- Plain strings (wrapped in TEXT_MESSAGE_APP format)
|
|
17
43
|
"""
|
|
18
44
|
if not isinstance(dict_obj, dict):
|
|
19
45
|
try:
|
|
@@ -24,6 +50,17 @@ class Plugin(BasePlugin):
|
|
|
24
50
|
return self.strip_raw(dict_obj)
|
|
25
51
|
|
|
26
52
|
def process(self, packet):
|
|
53
|
+
"""Process and prepare packet data for relay.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
packet: Raw packet data to process
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
dict: Processed packet with base64-encoded binary payloads
|
|
60
|
+
|
|
61
|
+
Normalizes packet format and encodes binary payloads as base64
|
|
62
|
+
for JSON serialization and Matrix transmission.
|
|
63
|
+
"""
|
|
27
64
|
packet = self.normalize(packet)
|
|
28
65
|
|
|
29
66
|
if "decoded" in packet and "payload" in packet["decoded"]:
|
|
@@ -35,14 +72,39 @@ class Plugin(BasePlugin):
|
|
|
35
72
|
return packet
|
|
36
73
|
|
|
37
74
|
def get_matrix_commands(self):
|
|
75
|
+
"""Get Matrix commands handled by this plugin.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
list: Empty list (this plugin handles all traffic, not specific commands)
|
|
79
|
+
"""
|
|
38
80
|
return []
|
|
39
81
|
|
|
40
82
|
def get_mesh_commands(self):
|
|
83
|
+
"""Get mesh commands handled by this plugin.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
list: Empty list (this plugin handles all traffic, not specific commands)
|
|
87
|
+
"""
|
|
41
88
|
return []
|
|
42
89
|
|
|
43
90
|
async def handle_meshtastic_message(
|
|
44
91
|
self, packet, formatted_message, longname, meshnet_name
|
|
45
92
|
):
|
|
93
|
+
"""Handle incoming meshtastic message and relay to Matrix.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
packet: Raw packet data (dict or JSON) to relay
|
|
97
|
+
formatted_message (str): Human-readable message extracted from packet
|
|
98
|
+
longname (str): Long name of the sender node
|
|
99
|
+
meshnet_name (str): Name of the mesh network
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
bool: Always returns False to allow other plugins to process the same packet
|
|
103
|
+
|
|
104
|
+
Processes the packet by normalizing and preparing it, connects to the Matrix client,
|
|
105
|
+
checks if the meshtastic channel is mapped to a Matrix room based on config,
|
|
106
|
+
and sends the packet to the appropriate Matrix room.
|
|
107
|
+
"""
|
|
46
108
|
from mmrelay.matrix_utils import connect_matrix
|
|
47
109
|
|
|
48
110
|
packet = self.process(packet)
|
|
@@ -80,6 +142,17 @@ class Plugin(BasePlugin):
|
|
|
80
142
|
return False
|
|
81
143
|
|
|
82
144
|
def matches(self, event):
|
|
145
|
+
"""Check if Matrix event is a relayed radio packet.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
event: Matrix room event object
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
bool: True if event contains embedded meshtastic packet JSON
|
|
152
|
+
|
|
153
|
+
Identifies Matrix messages that contain embedded meshtastic packet
|
|
154
|
+
data by matching the default relay message format "Processed <portnum> radio packet".
|
|
155
|
+
"""
|
|
83
156
|
# Check for the presence of necessary keys in the event
|
|
84
157
|
content = event.source.get("content", {})
|
|
85
158
|
body = content.get("body", "")
|
|
@@ -90,6 +163,20 @@ class Plugin(BasePlugin):
|
|
|
90
163
|
return False
|
|
91
164
|
|
|
92
165
|
async def handle_room_message(self, room, event, full_message):
|
|
166
|
+
"""Handle incoming Matrix room message and relay to meshtastic mesh.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
room: Matrix Room object where message was received
|
|
170
|
+
event: Matrix room event containing the message
|
|
171
|
+
full_message (str): Raw message body text
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
bool: True if packet relaying succeeded, False otherwise
|
|
175
|
+
|
|
176
|
+
Checks if the Matrix event matches the expected embedded packet format,
|
|
177
|
+
retrieves the packet JSON, decodes it, reconstructs a MeshPacket,
|
|
178
|
+
connects to the meshtastic client, and sends the packet via the radio.
|
|
179
|
+
"""
|
|
93
180
|
# Use the event for matching instead of full_message
|
|
94
181
|
if not self.matches(event):
|
|
95
182
|
return False
|
|
@@ -17,9 +17,8 @@ class Plugin(BasePlugin):
|
|
|
17
17
|
plugin_name = "ping"
|
|
18
18
|
punctuation = string.punctuation
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
super().__init__()
|
|
20
|
+
# No __init__ method needed with the simplified plugin system
|
|
21
|
+
# The BasePlugin will automatically use the class-level plugin_name
|
|
23
22
|
|
|
24
23
|
@property
|
|
25
24
|
def description(self):
|
|
@@ -9,9 +9,8 @@ from mmrelay.plugins.base_plugin import BasePlugin
|
|
|
9
9
|
class Plugin(BasePlugin):
|
|
10
10
|
plugin_name = "weather"
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
super().__init__()
|
|
12
|
+
# No __init__ method needed with the simplified plugin system
|
|
13
|
+
# The BasePlugin will automatically use the class-level plugin_name
|
|
15
14
|
|
|
16
15
|
@property
|
|
17
16
|
def description(self):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mmrelay
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.9
|
|
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
|
|
@@ -22,72 +22,45 @@ Requires-Dist: requests==2.32.3
|
|
|
22
22
|
Requires-Dist: markdown==3.8
|
|
23
23
|
Requires-Dist: haversine==2.9.0
|
|
24
24
|
Requires-Dist: schedule==1.2.2
|
|
25
|
-
Requires-Dist: platformdirs==4.3.
|
|
25
|
+
Requires-Dist: platformdirs==4.3.8
|
|
26
26
|
Requires-Dist: py-staticmaps>=0.4.0
|
|
27
27
|
Requires-Dist: rich==14.0.0
|
|
28
|
-
Requires-Dist: setuptools==80.
|
|
28
|
+
Requires-Dist: setuptools==80.9.0
|
|
29
29
|
Dynamic: license-file
|
|
30
30
|
|
|
31
31
|
# M<>M Relay
|
|
32
32
|
|
|
33
|
-
##
|
|
33
|
+
## (Meshtastic <=> Matrix Relay)
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
## Documentation
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
2. Move your configuration to the new standard location (`~/.mmrelay/config.yaml`)
|
|
41
|
-
3. See [ANNOUNCEMENT.md](ANNOUNCEMENT.md) for all the exciting new features
|
|
39
|
+
Visit our [Wiki](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki) for comprehensive guides and information.
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
A powerful and easy-to-use relay between Meshtastic devices and Matrix chat rooms, allowing seamless communication across platforms. This opens the door for bridging Meshtastic devices to [many other platforms](https://matrix.org/bridges/).
|
|
41
|
+
- [Installation Instructions](docs/INSTRUCTIONS.md) - Setup and configuration guide
|
|
42
|
+
- [v1.0 Release Announcement](docs/ANNOUNCEMENT.md) - New changes in v1.0
|
|
43
|
+
- [Upgrade Guide](docs/UPGRADE_TO_V1.md) - Migration guidance for existing users
|
|
48
44
|
|
|
49
45
|
---
|
|
50
46
|
|
|
51
|
-
##
|
|
47
|
+
## Quick Start
|
|
52
48
|
|
|
53
49
|
MMRelay runs on Linux, macOS, and Windows.
|
|
54
50
|
|
|
55
|
-
### Quick Installation
|
|
56
|
-
|
|
57
51
|
```bash
|
|
58
52
|
# Install using pipx for isolated installation (recommended)
|
|
59
53
|
pipx install mmrelay
|
|
60
54
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
For pipx installation instructions, see: [pipx installation guide](https://pipx.pypa.io/stable/installation/#on-linux)
|
|
55
|
+
# Generate a sample configuration file & then edit it
|
|
56
|
+
mmrelay --generate-config
|
|
66
57
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
- **New Users**: See [INSTRUCTIONS.md](INSTRUCTIONS.md) for setup and configuration
|
|
70
|
-
- **Existing Users**: See [UPGRADE_TO_V1.md](UPGRADE_TO_V1.md) for migration guidance
|
|
71
|
-
- **Configuration**: Review [sample_config.yaml](src/mmrelay/tools/sample_config.yaml) for examples
|
|
72
|
-
|
|
73
|
-
### Command-Line Options
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
usage: mmrelay [-h] [--config CONFIG] [--data-dir DATA_DIR] [--log-level {error,warning,info,debug}] [--logfile LOGFILE] [--version] [--generate-config] [--install-service] [--check-config]
|
|
77
|
-
|
|
78
|
-
Options:
|
|
79
|
-
-h, --help Show this help message and exit
|
|
80
|
-
--config CONFIG Path to config file
|
|
81
|
-
--data-dir DATA_DIR Base directory for all data (logs, database, plugins)
|
|
82
|
-
--log-level {error,warning,info,debug}
|
|
83
|
-
Set logging level
|
|
84
|
-
--logfile LOGFILE Path to log file (can be overridden by --data-dir)
|
|
85
|
-
--version Show version and exit
|
|
86
|
-
--generate-config Generate a sample config.yaml file
|
|
87
|
-
--install-service Install or update the systemd user service
|
|
88
|
-
--check-config Check if the configuration file is valid
|
|
58
|
+
# Start the relay (without --install-service to run manually)
|
|
59
|
+
mmrelay --install-service
|
|
89
60
|
```
|
|
90
61
|
|
|
62
|
+
For detailed installation and configuration instructions, see the [Installation Guide](docs/INSTRUCTIONS.md).
|
|
63
|
+
|
|
91
64
|
---
|
|
92
65
|
|
|
93
66
|
## Features
|
|
@@ -133,7 +106,12 @@ See the full list of core plugins [here](https://github.com/geoffwhittington/mes
|
|
|
133
106
|
|
|
134
107
|
### Community & Custom Plugins
|
|
135
108
|
|
|
136
|
-
|
|
109
|
+
MMRelay's plugin system allows you to extend functionality in two ways:
|
|
110
|
+
|
|
111
|
+
- **Custom Plugins**: Create personal plugins for your own use, stored in `~/.mmrelay/plugins/custom/`
|
|
112
|
+
- **Community Plugins**: Share your creations with others or use plugins developed by the community
|
|
113
|
+
|
|
114
|
+
Check the [Community Plugins Development Guide](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-Development-Guide) in our wiki to get started.
|
|
137
115
|
|
|
138
116
|
✨️ Visit the [Community Plugins List](https://github.com/geoffwhittington/meshtastic-matrix-relay/wiki/Community-Plugin-List)!
|
|
139
117
|
|
|
@@ -151,14 +129,12 @@ community-plugins:
|
|
|
151
129
|
|
|
152
130
|
### Plugin System
|
|
153
131
|
|
|
154
|
-
MMRelay features a powerful plugin system with standardized locations:
|
|
132
|
+
Plugins make it easy to extend functionality without modifying the core program. MMRelay features a powerful plugin system with standardized locations:
|
|
155
133
|
|
|
156
134
|
- **Core Plugins**: Pre-installed with the package
|
|
157
135
|
- **Custom Plugins**: Your own plugins in `~/.mmrelay/plugins/custom/`
|
|
158
136
|
- **Community Plugins**: Third-party plugins in `~/.mmrelay/plugins/community/`
|
|
159
137
|
|
|
160
|
-
Plugins make it easy to extend functionality without modifying the core code.
|
|
161
|
-
|
|
162
138
|
---
|
|
163
139
|
|
|
164
140
|
## Getting Started with Matrix
|
|
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
|