aprsd 3.2.2__py2.py3-none-any.whl → 3.3.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aprsd/cli_helper.py +35 -1
- aprsd/client.py +3 -3
- aprsd/clients/aprsis.py +0 -1
- aprsd/cmds/dev.py +31 -2
- aprsd/cmds/fetch_stats.py +5 -1
- aprsd/cmds/list_plugins.py +87 -10
- aprsd/cmds/server.py +10 -1
- aprsd/cmds/webchat.py +245 -45
- aprsd/conf/common.py +68 -1
- aprsd/conf/log.py +8 -10
- aprsd/conf/plugin_common.py +108 -0
- aprsd/log/log.py +74 -66
- aprsd/main.py +11 -11
- aprsd/packets/__init__.py +2 -2
- aprsd/packets/core.py +20 -1
- aprsd/plugin_utils.py +1 -0
- aprsd/plugins/fortune.py +4 -1
- aprsd/plugins/location.py +88 -6
- aprsd/plugins/weather.py +7 -3
- aprsd/threads/__init__.py +1 -1
- aprsd/threads/registry.py +56 -0
- aprsd/threads/rx.py +13 -5
- aprsd/threads/tx.py +40 -0
- aprsd/utils/__init__.py +19 -0
- aprsd/utils/objectstore.py +7 -1
- aprsd/web/chat/static/images/globe.svg +3 -0
- aprsd/web/chat/static/js/send-message.js +107 -29
- aprsd/web/chat/templates/index.html +6 -2
- aprsd/wsgi.py +4 -39
- {aprsd-3.2.2.dist-info → aprsd-3.3.1.dist-info}/METADATA +106 -98
- {aprsd-3.2.2.dist-info → aprsd-3.3.1.dist-info}/RECORD +36 -35
- {aprsd-3.2.2.dist-info → aprsd-3.3.1.dist-info}/WHEEL +1 -1
- aprsd-3.3.1.dist-info/pbr.json +1 -0
- aprsd/log/rich.py +0 -160
- aprsd-3.2.2.dist-info/pbr.json +0 -1
- {aprsd-3.2.2.dist-info → aprsd-3.3.1.dist-info}/LICENSE +0 -0
- {aprsd-3.2.2.dist-info → aprsd-3.3.1.dist-info}/entry_points.txt +0 -0
- {aprsd-3.2.2.dist-info → aprsd-3.3.1.dist-info}/top_level.txt +0 -0
aprsd/cli_helper.py
CHANGED
@@ -50,6 +50,40 @@ common_options = [
|
|
50
50
|
]
|
51
51
|
|
52
52
|
|
53
|
+
class AliasedGroup(click.Group):
|
54
|
+
def command(self, *args, **kwargs):
|
55
|
+
"""A shortcut decorator for declaring and attaching a command to
|
56
|
+
the group. This takes the same arguments as :func:`command` but
|
57
|
+
immediately registers the created command with this instance by
|
58
|
+
calling into :meth:`add_command`.
|
59
|
+
Copied from `click` and extended for `aliases`.
|
60
|
+
"""
|
61
|
+
def decorator(f):
|
62
|
+
aliases = kwargs.pop("aliases", [])
|
63
|
+
cmd = click.decorators.command(*args, **kwargs)(f)
|
64
|
+
self.add_command(cmd)
|
65
|
+
for alias in aliases:
|
66
|
+
self.add_command(cmd, name=alias)
|
67
|
+
return cmd
|
68
|
+
return decorator
|
69
|
+
|
70
|
+
def group(self, *args, **kwargs):
|
71
|
+
"""A shortcut decorator for declaring and attaching a group to
|
72
|
+
the group. This takes the same arguments as :func:`group` but
|
73
|
+
immediately registers the created command with this instance by
|
74
|
+
calling into :meth:`add_command`.
|
75
|
+
Copied from `click` and extended for `aliases`.
|
76
|
+
"""
|
77
|
+
def decorator(f):
|
78
|
+
aliases = kwargs.pop("aliases", [])
|
79
|
+
cmd = click.decorators.group(*args, **kwargs)(f)
|
80
|
+
self.add_command(cmd)
|
81
|
+
for alias in aliases:
|
82
|
+
self.add_command(cmd, name=alias)
|
83
|
+
return cmd
|
84
|
+
return decorator
|
85
|
+
|
86
|
+
|
53
87
|
def add_options(options):
|
54
88
|
def _add_options(func):
|
55
89
|
for option in reversed(options):
|
@@ -104,7 +138,7 @@ def process_standard_options_no_config(f: F) -> F:
|
|
104
138
|
ctx.obj["loglevel"] = kwargs["loglevel"]
|
105
139
|
ctx.obj["config_file"] = kwargs["config_file"]
|
106
140
|
ctx.obj["quiet"] = kwargs["quiet"]
|
107
|
-
log.
|
141
|
+
log.setup_logging(
|
108
142
|
ctx.obj["loglevel"],
|
109
143
|
ctx.obj["quiet"],
|
110
144
|
)
|
aprsd/client.py
CHANGED
@@ -25,7 +25,7 @@ TRANSPORT_FAKE = "fake"
|
|
25
25
|
factory = None
|
26
26
|
|
27
27
|
|
28
|
-
class Client
|
28
|
+
class Client:
|
29
29
|
"""Singleton client class that constructs the aprslib connection."""
|
30
30
|
|
31
31
|
_instance = None
|
@@ -90,7 +90,7 @@ class Client(metaclass=trace.TraceWrapperMetaclass):
|
|
90
90
|
pass
|
91
91
|
|
92
92
|
|
93
|
-
class APRSISClient(Client
|
93
|
+
class APRSISClient(Client):
|
94
94
|
|
95
95
|
_client = None
|
96
96
|
|
@@ -175,7 +175,7 @@ class APRSISClient(Client, metaclass=trace.TraceWrapperMetaclass):
|
|
175
175
|
return aprs_client
|
176
176
|
|
177
177
|
|
178
|
-
class KISSClient(Client
|
178
|
+
class KISSClient(Client):
|
179
179
|
|
180
180
|
_client = None
|
181
181
|
|
aprsd/clients/aprsis.py
CHANGED
aprsd/cmds/dev.py
CHANGED
@@ -125,8 +125,37 @@ def test_plugin(
|
|
125
125
|
LOG.info(f"P'{plugin_path}' F'{fromcall}' C'{message}'")
|
126
126
|
|
127
127
|
for x in range(number):
|
128
|
-
|
128
|
+
replies = pm.run(packet)
|
129
129
|
# Plugin might have threads, so lets stop them so we can exit.
|
130
130
|
# obj.stop_threads()
|
131
|
-
|
131
|
+
for reply in replies:
|
132
|
+
if isinstance(reply, list):
|
133
|
+
# one of the plugins wants to send multiple messages
|
134
|
+
for subreply in reply:
|
135
|
+
if isinstance(subreply, packets.Packet):
|
136
|
+
LOG.info(subreply)
|
137
|
+
else:
|
138
|
+
LOG.info(
|
139
|
+
packets.MessagePacket(
|
140
|
+
from_call=CONF.callsign,
|
141
|
+
to_call=fromcall,
|
142
|
+
message_text=subreply,
|
143
|
+
),
|
144
|
+
)
|
145
|
+
elif isinstance(reply, packets.Packet):
|
146
|
+
# We have a message based object.
|
147
|
+
LOG.info(reply)
|
148
|
+
else:
|
149
|
+
# A plugin can return a null message flag which signals
|
150
|
+
# us that they processed the message correctly, but have
|
151
|
+
# nothing to reply with, so we avoid replying with a
|
152
|
+
# usage string
|
153
|
+
if reply is not packets.NULL_MESSAGE:
|
154
|
+
LOG.info(
|
155
|
+
packets.MessagePacket(
|
156
|
+
from_call=CONF.callsign,
|
157
|
+
to_call=fromcall,
|
158
|
+
message_text=reply,
|
159
|
+
),
|
160
|
+
)
|
132
161
|
pm.stop()
|
aprsd/cmds/fetch_stats.py
CHANGED
@@ -58,7 +58,11 @@ def fetch_stats(ctx, host, port, magic_word):
|
|
58
58
|
with console.status(msg):
|
59
59
|
client = rpc_client.RPCClient(host, port, magic_word)
|
60
60
|
stats = client.get_stats_dict()
|
61
|
-
|
61
|
+
if stats:
|
62
|
+
console.print_json(data=stats)
|
63
|
+
else:
|
64
|
+
LOG.error(f"Failed to fetch stats via RPC aprsd server at {host}:{port}")
|
65
|
+
return
|
62
66
|
aprsd_title = (
|
63
67
|
"APRSD "
|
64
68
|
f"[bold cyan]v{stats['aprsd']['version']}[/] "
|
aprsd/cmds/list_plugins.py
CHANGED
@@ -26,6 +26,7 @@ from aprsd.plugins import (
|
|
26
26
|
|
27
27
|
|
28
28
|
LOG = logging.getLogger("APRSD")
|
29
|
+
PYPI_URL = "https://pypi.org/search/"
|
29
30
|
|
30
31
|
|
31
32
|
def onerror(name):
|
@@ -89,18 +90,35 @@ def get_module_info(package_name, module_name, module_path):
|
|
89
90
|
return obj_list
|
90
91
|
|
91
92
|
|
92
|
-
def
|
93
|
+
def _get_installed_aprsd_items():
|
93
94
|
# installed plugins
|
94
|
-
|
95
|
+
plugins = {}
|
96
|
+
extensions = {}
|
95
97
|
for finder, name, ispkg in pkgutil.iter_modules():
|
96
98
|
if name.startswith("aprsd_"):
|
99
|
+
print(f"Found aprsd_ module: {name}")
|
97
100
|
if ispkg:
|
98
101
|
module = importlib.import_module(name)
|
99
102
|
pkgs = walk_package(module)
|
100
103
|
for pkg in pkgs:
|
101
104
|
pkg_info = get_module_info(module.__name__, pkg.name, module.__path__[0])
|
102
|
-
|
103
|
-
|
105
|
+
if "plugin" in name:
|
106
|
+
plugins[name] = pkg_info
|
107
|
+
elif "extension" in name:
|
108
|
+
extensions[name] = pkg_info
|
109
|
+
return plugins, extensions
|
110
|
+
|
111
|
+
|
112
|
+
def get_installed_plugins():
|
113
|
+
# installed plugins
|
114
|
+
plugins, extensions = _get_installed_aprsd_items()
|
115
|
+
return plugins
|
116
|
+
|
117
|
+
|
118
|
+
def get_installed_extensions():
|
119
|
+
# installed plugins
|
120
|
+
plugins, extensions = _get_installed_aprsd_items()
|
121
|
+
return extensions
|
104
122
|
|
105
123
|
|
106
124
|
def show_built_in_plugins(console):
|
@@ -144,22 +162,27 @@ def show_built_in_plugins(console):
|
|
144
162
|
console.print(table)
|
145
163
|
|
146
164
|
|
147
|
-
def
|
165
|
+
def _get_pypi_packages():
|
148
166
|
query = "aprsd"
|
149
|
-
api_url = "https://pypi.org/search/"
|
150
167
|
snippets = []
|
151
168
|
s = requests.Session()
|
152
169
|
for page in range(1, 3):
|
153
170
|
params = {"q": query, "page": page}
|
154
|
-
r = s.get(
|
171
|
+
r = s.get(PYPI_URL, params=params)
|
155
172
|
soup = BeautifulSoup(r.text, "html.parser")
|
156
173
|
snippets += soup.select('a[class*="snippet"]')
|
157
174
|
if not hasattr(s, "start_url"):
|
158
175
|
s.start_url = r.url.rsplit("&page", maxsplit=1).pop(0)
|
159
176
|
|
177
|
+
return snippets
|
178
|
+
|
179
|
+
|
180
|
+
def show_pypi_plugins(installed_plugins, console):
|
181
|
+
snippets = _get_pypi_packages()
|
182
|
+
|
160
183
|
title = Text.assemble(
|
161
184
|
("Pypi.org APRSD Installable Plugin Packages\n\n", "bold magenta"),
|
162
|
-
("Install any of the following plugins with
|
185
|
+
("Install any of the following plugins with\n", "bold yellow"),
|
163
186
|
("'pip install ", "bold white"),
|
164
187
|
("<Plugin Package Name>'", "cyan"),
|
165
188
|
)
|
@@ -171,7 +194,7 @@ def show_pypi_plugins(installed_plugins, console):
|
|
171
194
|
table.add_column("Released", style="bold green", justify="center")
|
172
195
|
table.add_column("Installed?", style="red", justify="center")
|
173
196
|
for snippet in snippets:
|
174
|
-
link = urljoin(
|
197
|
+
link = urljoin(PYPI_URL, snippet.get("href"))
|
175
198
|
package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
|
176
199
|
version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
|
177
200
|
created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
|
@@ -194,7 +217,47 @@ def show_pypi_plugins(installed_plugins, console):
|
|
194
217
|
|
195
218
|
console.print("\n")
|
196
219
|
console.print(table)
|
197
|
-
|
220
|
+
|
221
|
+
|
222
|
+
def show_pypi_extensions(installed_extensions, console):
|
223
|
+
snippets = _get_pypi_packages()
|
224
|
+
|
225
|
+
title = Text.assemble(
|
226
|
+
("Pypi.org APRSD Installable Extension Packages\n\n", "bold magenta"),
|
227
|
+
("Install any of the following extensions by running\n", "bold yellow"),
|
228
|
+
("'pip install ", "bold white"),
|
229
|
+
("<Plugin Package Name>'", "cyan"),
|
230
|
+
)
|
231
|
+
table = Table(title=title)
|
232
|
+
table.add_column("Extension Package Name", style="cyan", no_wrap=True)
|
233
|
+
table.add_column("Description", style="yellow")
|
234
|
+
table.add_column("Version", style="yellow", justify="center")
|
235
|
+
table.add_column("Released", style="bold green", justify="center")
|
236
|
+
table.add_column("Installed?", style="red", justify="center")
|
237
|
+
for snippet in snippets:
|
238
|
+
link = urljoin(PYPI_URL, snippet.get("href"))
|
239
|
+
package = re.sub(r"\s+", " ", snippet.select_one('span[class*="name"]').text.strip())
|
240
|
+
version = re.sub(r"\s+", " ", snippet.select_one('span[class*="version"]').text.strip())
|
241
|
+
created = re.sub(r"\s+", " ", snippet.select_one('span[class*="created"]').text.strip())
|
242
|
+
description = re.sub(r"\s+", " ", snippet.select_one('p[class*="description"]').text.strip())
|
243
|
+
emoji = ":open_file_folder:"
|
244
|
+
|
245
|
+
if "aprsd-" not in package or "-extension" not in package:
|
246
|
+
continue
|
247
|
+
|
248
|
+
under = package.replace("-", "_")
|
249
|
+
if under in installed_extensions:
|
250
|
+
installed = "Yes"
|
251
|
+
else:
|
252
|
+
installed = "No"
|
253
|
+
|
254
|
+
table.add_row(
|
255
|
+
f"[link={link}]{emoji}[/link] {package}",
|
256
|
+
description, version, created, installed,
|
257
|
+
)
|
258
|
+
|
259
|
+
console.print("\n")
|
260
|
+
console.print(table)
|
198
261
|
|
199
262
|
|
200
263
|
def show_installed_plugins(installed_plugins, console):
|
@@ -240,3 +303,17 @@ def list_plugins(ctx):
|
|
240
303
|
|
241
304
|
status.update("Looking for installed APRSD plugins")
|
242
305
|
show_installed_plugins(installed_plugins, console)
|
306
|
+
|
307
|
+
|
308
|
+
@cli.command()
|
309
|
+
@cli_helper.add_options(cli_helper.common_options)
|
310
|
+
@click.pass_context
|
311
|
+
@cli_helper.process_standard_options_no_config
|
312
|
+
def list_extensions(ctx):
|
313
|
+
"""List the built in plugins available to APRSD."""
|
314
|
+
console = Console()
|
315
|
+
|
316
|
+
with console.status("Show APRSD Extensions") as status:
|
317
|
+
status.update("Fetching pypi.org APRSD Extensions")
|
318
|
+
installed_extensions = get_installed_extensions()
|
319
|
+
show_pypi_extensions(installed_extensions, console)
|
aprsd/cmds/server.py
CHANGED
@@ -11,7 +11,7 @@ from aprsd import main as aprsd_main
|
|
11
11
|
from aprsd import packets, plugin, threads, utils
|
12
12
|
from aprsd.main import cli
|
13
13
|
from aprsd.rpc import server as rpc_server
|
14
|
-
from aprsd.threads import rx
|
14
|
+
from aprsd.threads import registry, rx, tx
|
15
15
|
|
16
16
|
|
17
17
|
CONF = cfg.CONF
|
@@ -107,6 +107,15 @@ def server(ctx, flush):
|
|
107
107
|
process_thread.start()
|
108
108
|
|
109
109
|
packets.PacketTrack().restart()
|
110
|
+
if CONF.enable_beacon:
|
111
|
+
LOG.info("Beacon Enabled. Starting Beacon thread.")
|
112
|
+
bcn_thread = tx.BeaconSendThread()
|
113
|
+
bcn_thread.start()
|
114
|
+
|
115
|
+
if CONF.aprs_registry.enabled:
|
116
|
+
LOG.info("Registry Enabled. Starting Registry thread.")
|
117
|
+
registry_thread = registry.APRSRegistryThread()
|
118
|
+
registry_thread.start()
|
110
119
|
|
111
120
|
if CONF.rpc_settings.enabled:
|
112
121
|
rpc = rpc_server.APRSDRPCThread()
|