Compare commits

...

35 Commits
v1.0 ... master

Author SHA1 Message Date
Hemna 849639767d force requirements.txt 2023-07-12 13:44:11 -04:00
Hemna 931771fc7b generic aprsd 2023-07-10 14:26:00 -04:00
Hemna 62e3532dd2 >=3.0.0 2023-07-10 13:08:19 -04:00
Hemna bc3595d7a2 update 2023-01-10 14:19:20 -05:00
Hemna e74585f4e7 Update for aprsd 3.0.0
This patch adds the new conf settings for slack plugin
2023-01-10 14:10:46 -05:00
Hemna 8367cc8fd2 Updated regex for message plugin
This regex will match on
s
s foo
slack foo
2022-12-29 14:41:39 -05:00
Hemna 7c28bb9886 Removed import of aprsd.messaging 2022-12-29 12:14:20 -05:00
Hemna 3bfc0188c0 Fixed missing import for config 2022-12-29 10:58:22 -05:00
Hemna 63726e426e Update for APRSD 3.0.0 release.
This provides the new config options defined in the plugin code
itself.
2022-12-29 09:23:32 -05:00
Hemna b624055d06 Update for aprsd >= 2.7.0
the new aprsd changes the packets to a dataclass object, so access
to the attributes is different.
2022-12-20 17:12:20 -05:00
Hemna 53d45e9e74 1.0.5 2022-12-18 13:35:10 -05:00
Hemna 8cef8fbdbf cleanup 2022-12-18 13:18:32 -05:00
Hemna c18823cf88 fix tox runs 2022-12-18 13:18:32 -05:00
Hemna 41b7c27a63 More cleanup 2022-12-18 13:18:32 -05:00
Walter A. Boring IV f6a456a586
Create FUNDING.yml 2021-12-14 11:15:57 -05:00
Hemna 0c63bc5fcf update for new aprsd 2021-10-06 15:58:34 -04:00
Hemna 7fa7304423 update requirements.txt so it doesn't have so many 2021-10-06 15:58:34 -04:00
Hemna d4010d7868 fixed build issues 2021-10-06 15:58:34 -04:00
Hemna 8bc5afe1ed Updated to work with 2.0.0 2021-10-06 15:58:34 -04:00
Hemna 3ce8471cb0 Added new notification and location plugin
This is an update to support the new version of aprsd
2021-10-06 15:58:32 -04:00
Hemna 41468d5467 Fixed to work with utils_refactor 2021-10-06 15:55:53 -04:00
Hemna 0a1d2b3e0e Update for new plugin interface. Add Notify plugin
This patch adds the notify plugin to be used with the new
notification plugins interface for aprsd.  This sends a notification
to a slack channel when a HAM callsign is seen on the APRS-IS network
after a specified timeout/age.
2021-07-15 20:34:51 -04:00
Hemna d983e1a824 Added geo description in location.
Unfortunately, this currently only works in the US.  need a global
reverse geolocation service that provides an area description
"6 miles northeast of X"
2021-07-09 11:24:46 -04:00
Hemna bcc605dc2c fixed tests for tox 2021-06-17 13:37:07 -04:00
Hemna d5d76a1a02 fixed tests after refactoring 2021-06-17 13:32:55 -04:00
Hemna 9b4f4b5a28 prep for release 2021-06-17 13:26:31 -04:00
Hemna fc15ffd6eb new updated location plugin 2021-06-17 13:25:00 -04:00
Hemna 488b83a157 Updated slack formatting 2021-06-15 17:28:35 -04:00
Hemna 812c9b3d43 Updates for v1.0.4 release 2021-01-15 14:16:44 -05:00
Hemna 36b2eff71c Added Makefile, pre-commit. Also update for 1.5.0
This patch Adds the Makefile for setting up a dev environment
as well as fixing the plugin for the 1.5.0 release of aprsd.
2021-01-12 16:00:18 -05:00
Hemna 2a7167745b Plugin gets the version from pbr version now 2020-12-20 15:24:45 -05:00
Hemna 94e2101b46 cleaned up dev reqs 2020-12-20 15:14:19 -05:00
Hemna 45a1e35e73 Reduced the shit 2020-12-20 12:47:34 -05:00
Hemna b308285b28 Added some config checking at setup time
This patch ensures we check to make sure the required config
keys are available from the aprsd.yml config prior to accessing them.
The plugin won't work without them.  This prevents the plugin from
throwing stack dumps in aprsd.
2020-12-15 09:50:33 -05:00
Hemna 2f944f703a remote the pinning of aprsd 1.0.0 2020-12-15 08:46:05 -05:00
29 changed files with 985 additions and 228 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: wb4bor
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
python-version: [3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
*.egg-info
.tox
.idea
__pycache__
.coverage
.coverage.*
htmlcov
*.pyc
docs/_build
venv
dist
.pytest_cache
.mypy_cache
build

23
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,23 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: detect-private-key
- id: check-merge-conflict
- id: check-case-conflict
- id: check-docstring-first
- id: check-builtin-literals
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v1.16.0
hooks:
- id: setup-cfg-fmt
- repo: https://github.com/dizballanze/gray
rev: v0.10.1
hooks:
- id: gray

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
Hemna <waboring@hemna.com>
Walter A. Boring IV <waboring@hemna.com>

View File

@ -1,9 +1,65 @@
CHANGES
=======
* Make aprsd a requirement
v1.2.0
------
* update
* Update for aprsd 3.0.0
v1.1.0
------
* Updated regex for message plugin
* Removed import of aprsd.messaging
* Fixed missing import for config
* Update for APRSD 3.0.0 release
* Update for aprsd >= 2.7.0
v1.0.5
------
* 1.0.5
* cleanup
* fix tox runs
* More cleanup
* Create FUNDING.yml
* update for new aprsd
* update requirements.txt so it doesn't have so many
* fixed build issues
* Updated to work with 2.0.0
* Added new notification and location plugin
* Fixed to work with utils\_refactor
* Update for new plugin interface. Add Notify plugin
* Added geo description in location
* fixed tests for tox
* fixed tests after refactoring
* prep for release
* new updated location plugin
* Updated slack formatting
* Updates for v1.0.4 release
* Added Makefile, pre-commit. Also update for 1.5.0
v1.0.3
------
* Plugin gets the version from pbr version now
v1.0.2
------
* cleaned up dev reqs
* Reduced the shit
v1.0.1
------
* Added some config checking at setup time
* remote the pinning of aprsd 1.0.0
v1.0
----
* Updates for release
* Make aprsd a requirement
* Initial commit

View File

@ -173,4 +173,3 @@
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

73
Makefile Normal file
View File

@ -0,0 +1,73 @@
REQUIREMENTS_TXT ?= requirements.txt requirements-dev.txt
.DEFAULT_GOAL := help
.PHONY: help dev test
include Makefile.venv
Makefile.venv:
curl \
-o Makefile.fetched \
-L "https://github.com/sio/Makefile.venv/raw/v2020.08.14/Makefile.venv"
echo "5afbcf51a82f629cd65ff23185acde90ebe4dec889ef80bbdc12562fbd0b2611 *Makefile.fetched" \
| sha256sum --check - \
&& mv Makefile.fetched Makefile.venv
help: # Help for the Makefile
@egrep -h '\s##\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
dev: venv ## Create the virtualenv with all the requirements installed
docs: build
cp README.rst docs/readme.rst
cp Changelog docs/changelog.rst
tox -edocs
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
clean-build: ## remove build artifacts
rm -fr build/
rm -fr dist/
rm -fr .eggs/
find . -name '*.egg-info' -exec rm -fr {} +
find . -name '*.egg' -exec rm -f {} +
clean-pyc: ## remove Python file artifacts
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
find . -name '__pycache__' -exec rm -fr {} +
clean-test: ## remove test and coverage artifacts
rm -fr .tox/
rm -f .coverage
rm -fr htmlcov/
rm -fr .pytest_cache
coverage: ## check code coverage quickly with the default Python
coverage run --source aprsd_repeat_plugins setup.py test
coverage report -m
coverage html
$(BROWSER) htmlcov/index.html
test: dev ## Run all the tox tests
tox -p all
build: test ## Make the build artifact prior to doing an upload
$(VENV)/python3 setup.py sdist bdist_wheel
$(VENV)/twine check dist/*
upload: build ## Upload a new version of the plugin
$(VENV)/twine upload dist/*
check: dev ## Code format check with tox and pep8
tox -epep8
fix: dev ## fixes code formatting with gray
tox -efmt
update-requirements: dev ## Update the requirements.txt and dev-requirements.txt files
rm requirements.txt
rm requirements-dev.txt
touch requirements.txt
touch requirements-dev.txt
$(VENV)/pip-compile --resolver backtracking --annotation-style line requirements.in
$(VENV)/pip-compile --resolver backtracking --annotation-style line requirements-dev.in

183
Makefile.venv Normal file
View File

@ -0,0 +1,183 @@
#
# SEAMLESSLY MANAGE PYTHON VIRTUAL ENVIRONMENT WITH A MAKEFILE
#
# https://github.com/sio/Makefile.venv v2020.08.14
#
#
# Insert `include Makefile.venv` at the bottom of your Makefile to enable these
# rules.
#
# When writing your Makefile use '$(VENV)/python' to refer to the Python
# interpreter within virtual environment and '$(VENV)/executablename' for any
# other executable in venv.
#
# This Makefile provides the following targets:
# venv
# Use this as a dependency for any target that requires virtual
# environment to be created and configured
# python, ipython
# Use these to launch interactive Python shell within virtual environment
# shell, bash, zsh
# Launch interactive command line shell. "shell" target launches the
# default shell Makefile executes its rules in (usually /bin/sh).
# "bash" and "zsh" can be used to refer to the specific desired shell.
# show-venv
# Show versions of Python and pip, and the path to the virtual environment
# clean-venv
# Remove virtual environment
# $(VENV)/executable_name
# Install `executable_name` with pip. Only packages with names matching
# the name of the corresponding executable are supported.
# Use this as a lightweight mechanism for development dependencies
# tracking. E.g. for one-off tools that are not required in every
# developer's environment, therefore are not included into
# requirements.txt or setup.py.
# Note:
# Rules using such target or dependency MUST be defined below
# `include` directive to make use of correct $(VENV) value.
# Example:
# codestyle: $(VENV)/pyflakes
# $(VENV)/pyflakes .
# See `ipython` target below for another example.
#
# This Makefile can be configured via following variables:
# PY
# Command name for system Python interpreter. It is used only initialy to
# create the virtual environment
# Default: python3
# REQUIREMENTS_TXT
# Space separated list of paths to requirements.txt files.
# Paths are resolved relative to current working directory.
# Default: requirements.txt
# WORKDIR
# Parent directory for the virtual environment.
# Default: current working directory.
# VENVDIR
# Python virtual environment directory.
# Default: $(WORKDIR)/.venv
#
# This Makefile was written for GNU Make and may not work with other make
# implementations.
#
#
# Copyright (c) 2019-2020 Vitaly Potyarkin
#
# Licensed under the Apache License, Version 2.0
# <http://www.apache.org/licenses/LICENSE-2.0>
#
#
# Configuration variables
#
PY?=python3
WORKDIR?=.
VENVDIR?=$(WORKDIR)/.venv
REQUIREMENTS_TXT?=$(wildcard requirements.txt) # Multiple paths are supported (space separated)
MARKER=.initialized-with-Makefile.venv
#
# Internal variable resolution
#
VENV=$(VENVDIR)/bin
EXE=
# Detect windows
ifeq (win32,$(shell $(PY) -c "import __future__, sys; print(sys.platform)"))
VENV=$(VENVDIR)/Scripts
EXE=.exe
endif
#
# Virtual environment
#
.PHONY: venv
venv: $(VENV)/$(MARKER)
.PHONY: clean-venv
clean-venv:
-$(RM) -r "$(VENVDIR)"
.PHONY: show-venv
show-venv: venv
@$(VENV)/python -c "import sys; print('Python ' + sys.version.replace('\n',''))"
@$(VENV)/pip --version
@echo venv: $(VENVDIR)
.PHONY: debug-venv
debug-venv:
@$(MAKE) --version
$(info PY="$(PY)")
$(info REQUIREMENTS_TXT="$(REQUIREMENTS_TXT)")
$(info VENVDIR="$(VENVDIR)")
$(info VENVDEPENDS="$(VENVDEPENDS)")
$(info WORKDIR="$(WORKDIR)")
#
# Dependencies
#
ifneq ($(strip $(REQUIREMENTS_TXT)),)
VENVDEPENDS+=$(REQUIREMENTS_TXT)
endif
ifneq ($(wildcard setup.py),)
VENVDEPENDS+=setup.py
endif
ifneq ($(wildcard setup.cfg),)
VENVDEPENDS+=setup.cfg
endif
$(VENV):
$(PY) -m venv $(VENVDIR)
$(VENV)/python -m pip install --upgrade pip setuptools wheel
$(VENV)/$(MARKER): $(VENVDEPENDS) | $(VENV)
ifneq ($(strip $(REQUIREMENTS_TXT)),)
$(VENV)/pip install $(foreach path,$(REQUIREMENTS_TXT),-r $(path))
endif
ifneq ($(wildcard setup.py),)
$(VENV)/pip install -e .
endif
touch $(VENV)/$(MARKER)
#
# Interactive shells
#
.PHONY: python
python: venv
exec $(VENV)/python
.PHONY: ipython
ipython: $(VENV)/ipython
exec $(VENV)/ipython
.PHONY: shell
shell: venv
. $(VENV)/activate && exec $(notdir $(SHELL))
.PHONY: bash zsh
bash zsh: venv
. $(VENV)/activate && exec $@
#
# Commandline tools (wildcard rule, executable name must match package name)
#
ifneq ($(EXE),)
$(VENV)/%: $(VENV)/%$(EXE) ;
.PHONY: $(VENV)/%
.PRECIOUS: $(VENV)/%$(EXE)
endif
$(VENV)/%$(EXE): $(VENV)/$(MARKER)
$(VENV)/pip install --upgrade $*
touch $@

View File

@ -10,7 +10,7 @@ aprsd_slack_plugin
.. image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336
:target: https://timothycrosley.github.io/isort/
This project is a python plugin for the APRSD server daemon written by
This project is a python plugin for the APRSD server daemon written by
Craig Lamparter. The plugin looks for location APRS commands from a ham
radio, then reports that location to a slack channel. This is basically a
location proxy.

View File

@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at

View File

@ -1,79 +0,0 @@
import logging
from aprsd import plugin
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
LOG = logging.getLogger("APRSD")
class SlackCommandPlugin(plugin.APRSDPluginBase):
"""SlackCommandPlugin.
This APRSD plugin looks for the location command comming in
to aprsd, then fetches the caller's location, and then reports
that location string to the configured slack channel.
To use this:
Create a slack bot for your workspace at api.slack.com.
A good source of information on how to create the app
and the tokens and permissions and install the app in your
workspace is here:
https://api.slack.com/start/building/bolt-python
You will need the signing secret from the
Basic Information -> App Credentials form.
You will also need the Bot User OAuth Access Token from
OAuth & Permissions -> OAuth Tokens for Your Team ->
Bot User OAuth Access Token.
Install the app/bot into your workspace.
Edit your ~/.config/aprsd/aprsd.yml and add the section
slack:
signing_secret: <signing secret token here>
bot_token: <Bot User OAuth Access Token here>
channel: <channel name here>
"""
version = "1.0"
# matches any string starting with h or H
command_regex = "^[lL]"
command_name = "location-slack"
def _setup_slack(self):
"""Create the slack require client from config."""
# signing_secret = self.config["slack"]["signing_secret"]
bot_token = self.config["slack"]["bot_token"]
self.swc = WebClient(token=bot_token)
self.slack_channel = self.config["slack"]["channel"]
def command(self, fromcall, message, ack):
LOG.info("SlackCommandPlugin")
self._setup_slack()
# now call the location plugin to get the location info
location_plugin = plugin.LocationPlugin(self.config)
location = location_plugin.command(fromcall, message, ack)
if location:
reply = location
LOG.debug("Sending '{}' to slack channel '{}'".format(reply, self.slack_channel))
try:
self.swc.chat_postMessage(channel=self.slack_channel, text=reply)
except SlackApiError as e:
LOG.error(
"Failed to send message to channel '{}' because '{}'".format(
self.slack_channel, str(e)
)
)
else:
LOG.debug("SlackCommandPlugin couldn't get location for '{}'".format(fromcall))
return None

View File

@ -0,0 +1,70 @@
import logging
from oslo_config import cfg
from slack_sdk import WebClient
import aprsd_slack_plugin
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class SlackPluginBase:
"""SlackCommandPlugin.
This APRSD plugin looks for the location command comming in
to aprsd, then fetches the caller's location, and then reports
that location string to the configured slack channel.
To use this:
Create a slack bot for your workspace at api.slack.com.
A good source of information on how to create the app
and the tokens and permissions and install the app in your
workspace is here:
https://api.slack.com/start/building/bolt-python
You will need the signing secret from the
Basic Information -> App Credentials form.
You will also need the Bot User OAuth Access Token from
OAuth & Permissions -> OAuth Tokens for Your Team ->
Bot User OAuth Access Token.
Install the app/bot into your workspace.
Edit your ~/.config/aprsd/aprsd.yml and add the section
slack:
signing_secret: <signing secret token here>
bot_token: <Bot User OAuth Access Token here>
channel: <channel name here>
"""
version = aprsd_slack_plugin.__version__
swc = None
slack_channels = None
def setup_slack(self):
"""Create the slack require client from config."""
if not CONF.aprsd_slack_plugin.signing_secret:
LOG.error("Failed to find config aprsd_slack_plugin.signing_secret")
return "No slack signing_secret found"
if not CONF.aprsd_slack_plugin.bot_token:
LOG.error(
"APRSD config is missing aprsd_slack_plugin.bot_token. "
"Please install the slack app and get the "
"Bot User OAth Access Token.",
)
return False
if not CONF.aprsd_slack_plugin.channels:
LOG.error("aprsd_slack_plugin.channels is missing")
return False
self.swc = WebClient(token=CONF.aprsd_slack_plugin.bot_token)
self.slack_channels = CONF.aprsd_slack_plugin.channels
return True

View File

@ -0,0 +1,7 @@
from oslo_config import cfg
from aprsd_slack_plugin.conf import slack
CONF = cfg.CONF
slack.register_opts(CONF)

View File

@ -0,0 +1,81 @@
# Copyright 2015 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
This is the single point of entry to generate the sample configuration
file for Nova. It collects all the necessary info from the other modules
in this package. It is assumed that:
* every other module in this package has a 'list_opts' function which
return a dict where
* the keys are strings which are the group names
* the value of each key is a list of config options for that group
* the nova.conf package doesn't have further packages with config options
* this module is only used in the context of sample file generation
"""
import collections
import importlib
import os
import pkgutil
LIST_OPTS_FUNC_NAME = "list_opts"
def _tupleize(dct):
"""Take the dict of options and convert to the 2-tuple format."""
return [(key, val) for key, val in dct.items()]
def list_opts():
opts = collections.defaultdict(list)
module_names = _list_module_names()
imported_modules = _import_modules(module_names)
_append_config_options(imported_modules, opts)
return _tupleize(opts)
def _list_module_names():
module_names = []
package_path = os.path.dirname(os.path.abspath(__file__))
for _, modname, ispkg in pkgutil.iter_modules(path=[package_path]):
if modname == "opts" or ispkg:
continue
else:
module_names.append(modname)
return module_names
def _import_modules(module_names):
imported_modules = []
for modname in module_names:
mod = importlib.import_module("aprsd_slack_plugin.conf." + modname)
if not hasattr(mod, LIST_OPTS_FUNC_NAME):
msg = (
"The module 'aprsd_slack_plugin.conf.%s' should have a '%s' "
"function which returns the config options." % (modname, LIST_OPTS_FUNC_NAME)
)
raise Exception(msg)
else:
imported_modules.append(mod)
return imported_modules
def _append_config_options(imported_modules, config_options):
for mod in imported_modules:
configs = mod.list_opts()
for key, val in configs.items():
config_options[key].extend(val)

View File

@ -0,0 +1,41 @@
from oslo_config import cfg
slack_group = cfg.OptGroup(
name="aprsd_slack_plugin",
title="APRSD Slack Plugin settings",
)
slack_opts = [
cfg.StrOpt(
"signing_secret",
default=None,
help="Your Slack account signing secret"
"You have to create a slack bot account first. "
"https://api.slack.com/start/building/bolt-python",
),
cfg.StrOpt(
"bot_token",
default=None,
help="Your Slack bot's token",
),
cfg.ListOpt(
"channels",
default=None,
help="The channels you want messages sent to. This is a CSV list"
"of slack channel names.",
),
]
ALL_OPTS = slack_opts
def register_opts(cfg):
cfg.register_group(slack_group)
cfg.register_opts(ALL_OPTS, group=slack_group)
def list_opts():
return {
slack_group.name: slack_opts,
}

View File

@ -0,0 +1,168 @@
import logging
import re
import time
from aprsd import packets, plugin, plugin_utils
from oslo_config import cfg
import aprsd_slack_plugin
from aprsd_slack_plugin import base_plugin
from aprsd_slack_plugin import conf # noqa
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class SlackLocationPlugin(
base_plugin.SlackPluginBase,
plugin.APRSDRegexCommandPluginBase,
plugin.APRSFIKEYMixin,
):
"""SlackCommandPlugin.
This APRSD plugin looks for the location command comming in
to aprsd, then fetches the caller's location, and then reports
that location string to the configured slack channel.
To use this:
Create a slack bot for your workspace at api.slack.com.
A good source of information on how to create the app
and the tokens and permissions and install the app in your
workspace is here:
https://api.slack.com/start/building/bolt-python
You will need the signing secret from the
Basic Information -> App Credentials form.
You will also need the Bot User OAuth Access Token from
OAuth & Permissions -> OAuth Tokens for Your Team ->
Bot User OAuth Access Token.
Install the app/bot into your workspace.
Edit your ~/.config/aprsd/aprsd.conf and add the section
slack:
signing_secret: <signing secret token here>
bot_token: <Bot User OAuth Access Token here>
channel: <channel name here>
"""
version = aprsd_slack_plugin.__version__
# matches any string starting with h or H
command_regex = "^[lL]"
command_name = "location-slack"
def setup(self):
self.ensure_aprs_fi_key()
if self.enabled:
config_set = self.setup_slack()
if not config_set:
self.enabled = False
def process(self, packet):
LOG.info("SlackCommandPlugin")
fromcall = packet.from_call
message = packet.message_text
# get last location of a callsign, get descriptive name from weather service
api_key = CONF.aprs_fi.apiKey
# optional second argument is a callsign to search
a = re.search(r"^.*\s+(.*)", message)
if a is not None:
searchcall = a.group(1)
searchcall = searchcall.upper()
else:
# if no second argument, search for calling station
searchcall = fromcall
try:
aprs_data = plugin_utils.get_aprs_fi(api_key, searchcall)
except Exception as ex:
LOG.error(f"Failed to fetch aprs.fi '{ex}'")
return "Failed to fetch aprs.fi location"
LOG.debug(f"LocationPlugin: aprs_data = {aprs_data}")
if not len(aprs_data["entries"]):
LOG.error("Didn't get any entries from aprs.fi")
return "Failed to fetch aprs.fi location"
lat = aprs_data["entries"][0]["lat"]
lon = aprs_data["entries"][0]["lng"]
try: # altitude not always provided
alt = float(aprs_data["entries"][0]["altitude"])
except Exception:
alt = 0
altfeet = int(alt * 3.28084)
aprs_lasttime_seconds = aprs_data["entries"][0]["lasttime"]
delta_seconds = time.time() - int(aprs_lasttime_seconds)
delta_hours = delta_seconds / 60 / 60
wx_data = None
try:
wx_data = plugin_utils.get_weather_gov_for_gps(lat, lon)
except Exception:
LOG.warning("Couldn't fetch forecast.weather.gov")
callsign_url = f"<http://aprs.fi/info/a/{searchcall}|{searchcall}>"
aprs_url = "<http://aprs.fi/#!mt=roadmap&z=15&lat={}&lng={}|" " http://aprs.fi/>".format(
lat,
lon,
)
message = {}
message["username"] = "APRSD - Slack Location Plugin"
message["icon_emoji"] = ":satellite_antenna:"
message["attachments"] = [{}]
message["text"] = f"{callsign_url} - Location"
message["channel"] = "#random"
attachment = message["attachments"][0]
attachment["fallback"] = message["text"]
attachment["fields"] = []
# if the coordinates are outside of the US, we don't get this
# aread description
if wx_data and "location" in wx_data and "areaDescription" in wx_data["location"]:
attachment["fields"].append(
{
"title": "Location",
"value": wx_data["location"]["areaDescription"],
"short": True,
},
)
attachment["fields"].append(
{"title": "Map Location", "value": aprs_url, "short": True},
)
attachment["fields"].append(
{
"title": "Altitude",
"value": altfeet,
"short": True,
"fallback": f"Altitude - {altfeet}",
},
)
attachment["fields"].append(
{
"title": "Time",
"value": f"{round(delta_hours, 1)} h ago",
"short": True,
"fallback": f"Time {round(delta_hours, 1)} h ago",
},
)
LOG.debug(message)
# self.swc.chat_postMessage(**message)
for channel in self.slack_channels:
message["channel"] = channel
self.swc.chat_postMessage(**message)
# Don't have aprsd try and send a reply
return packets.NULL_MESSAGE

View File

@ -0,0 +1,87 @@
import logging
import re
from aprsd import packets
from oslo_config import cfg
import aprsd_slack_plugin
from aprsd_slack_plugin import base_plugin
from aprsd_slack_plugin import conf # noqa
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class SlackMessagePlugin(base_plugin.SlackPluginBase):
"""SlackMessagePlugin.
This APRSD plugin looks for the slack msg command comming in
to aprsd, then forwards the message to the configured slack channel.
To use this:
Create a slack bot for your workspace at api.slack.com.
A good source of information on how to create the app
and the tokens and permissions and install the app in your
workspace is here:
https://api.slack.com/start/building/bolt-python
You will need the signing secret from the
Basic Information -> App Credentials form.
You will also need the Bot User OAuth Access Token from
OAuth & Permissions -> OAuth Tokens for Your Team ->
Bot User OAuth Access Token.
Install the app/bot into your workspace.
Edit your ~/.config/aprsd/aprsd.yml and add the section
slack:
signing_secret: <signing secret token here>
bot_token: <Bot User OAuth Access Token here>
channel: <channel name here>
"""
version = aprsd_slack_plugin.__version__
# matches any string starting with h or H
command_regex = r"^([s]|[s]\s|slack)"
command_name = "message-slack"
def setup(self):
config_set = self.setup_slack()
if not config_set:
self.enabled = False
else:
self.enabled = True
def command(self, packet):
message = packet.message_text
fromcall = packet.from_call
LOG.info(f"SlackMessagePlugin '{message}'")
# optional second argument is a callsign to search
a = re.search(r"^.*\s+(.*)", message)
if a is not None:
searchcall = a.group(1)
searchcall = searchcall.upper()
else:
# if no second argument, search for calling station
searchcall = fromcall
slack_message = {}
slack_message["username"] = "APRSD - Slack Message Plugin"
slack_message["icon_emoji"] = ":satellite_antenna:"
slack_message["text"] = f"{fromcall} says {message}"
slack_message["channel"] = "#random"
LOG.debug(slack_message)
self.swc.chat_postMessage(**slack_message)
# for channel in self.slack_channels:
# message["channel"] = channel
# self.swc.chat_postMessage(**message)
# Don't have aprsd try and send a reply
return packets.NULL_MESSAGE

View File

@ -0,0 +1,56 @@
import logging
from aprsd import packets, plugin
from oslo_config import cfg
import aprsd_slack_plugin
from aprsd_slack_plugin import base_plugin
from aprsd_slack_plugin import conf # noqa
CONF = cfg.CONF
LOG = logging.getLogger("APRSD")
class SlackNotifyPlugin(
base_plugin.SlackPluginBase,
plugin.APRSDWatchListPluginBase,
):
"""SlackNotifyPlugin."""
version = aprsd_slack_plugin.__version__
def setup(self):
config_set = self.setup_slack()
if not config_set:
self.enabled = False
else:
self.enabled = True
def process(self, packet):
LOG.info("SlackCommandPlugin")
fromcall = packet.from_call
# message = packet["message_text"]
wl = packets.WatchList()
if wl.is_old(fromcall):
# get last location of a callsign, get descriptive name from weather service
callsign_url = f"<http://aprs.fi/info/a/{fromcall}|{fromcall}>"
message = {}
message["username"] = "APRSD - Slack Notification Plugin"
message["icon_emoji"] = ":satellite_antenna:"
message["attachments"] = [{}]
message["text"] = f"{callsign_url} - Is now on APRS"
message["channel"] = "#hemna"
LOG.debug(message)
# self.swc.chat_postMessage(**message)
for channel in self.slack_channels:
message["channel"] = channel
self.swc.chat_postMessage(**message)
# Don't have aprsd try and send a reply
return packets.NULL_MESSAGE

View File

@ -1,67 +0,0 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile dev-requirements.in
#
alabaster==0.7.12 # via sphinx
appdirs==1.4.4 # via black, virtualenv
aprsd==1.0.0 # via -r dev-requirements.in
attrs==20.3.0 # via pytest
babel==2.9.0 # via sphinx
black==20.8b1 # via -r dev-requirements.in
certifi==2020.12.5 # via requests
chardet==3.0.4 # via requests
click-completion==0.5.2 # via aprsd
click==7.1.2 # via aprsd, black, click-completion
coverage==5.3 # via pytest-cov
distlib==0.3.1 # via virtualenv
docutils==0.16 # via sphinx
filelock==3.0.12 # via tox, virtualenv
flake8-polyfill==1.0.2 # via pep8-naming
flake8==3.8.4 # via -r dev-requirements.in, flake8-polyfill
idna==2.10 # via requests
imagesize==1.2.0 # via sphinx
imapclient==2.1.0 # via aprsd
iniconfig==1.1.1 # via pytest
isort==5.6.4 # via -r dev-requirements.in
jinja2==2.11.2 # via click-completion, sphinx
markupsafe==1.1.1 # via jinja2
mccabe==0.6.1 # via flake8
mypy-extensions==0.4.3 # via black, mypy
mypy==0.790 # via -r dev-requirements.in
packaging==20.8 # via pytest, sphinx, tox
pathspec==0.8.1 # via black
pbr==5.5.1 # via -r dev-requirements.in, aprsd
pep8-naming==0.11.1 # via -r dev-requirements.in
pluggy==0.13.1 # via aprsd, pytest, tox
py==1.10.0 # via pytest, tox
pycodestyle==2.6.0 # via flake8
pyflakes==2.2.0 # via flake8
pygments==2.7.3 # via sphinx
pyparsing==2.4.7 # via packaging
pytest-cov==2.10.1 # via -r dev-requirements.in
pytest==6.2.0 # via -r dev-requirements.in, pytest-cov
pytz==2020.4 # via babel
pyyaml==5.3.1 # via aprsd
regex==2020.11.13 # via black
requests==2.25.0 # via aprsd, sphinx
shellingham==1.3.2 # via click-completion
six==1.15.0 # via aprsd, click-completion, imapclient, tox, virtualenv
snowballstemmer==2.0.0 # via sphinx
sphinx==3.3.1 # via -r dev-requirements.in
sphinxcontrib-applehelp==1.0.2 # via sphinx
sphinxcontrib-devhelp==1.0.2 # via sphinx
sphinxcontrib-htmlhelp==1.0.3 # via sphinx
sphinxcontrib-jsmath==1.0.1 # via sphinx
sphinxcontrib-qthelp==1.0.3 # via sphinx
sphinxcontrib-serializinghtml==1.1.4 # via sphinx
toml==0.10.2 # via black, pytest, tox
tox==3.20.1 # via -r dev-requirements.in
typed-ast==1.4.1 # via black, mypy
typing-extensions==3.7.4.3 # via black, mypy
urllib3==1.26.2 # via requests
virtualenv==20.2.2 # via tox
# The following packages are considered to be unsafe in a requirements file:
# setuptools

View File

@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
#
# Configuration file for the Sphinx documentation builder.
#

2
gray.conf Normal file
View File

@ -0,0 +1,2 @@
formatters = add-trailing-comma,autoflake,fixit,isort,pyupgrade,unify
min-python-version = 3.8

View File

@ -9,3 +9,5 @@ isort
pbr
Sphinx
aprsd
pre-commit
pip-tools

76
requirements-dev.txt Normal file
View File

@ -0,0 +1,76 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --annotation-style=line --resolver=backtracking requirements-dev.in
#
alabaster==0.7.13 # via sphinx
aprsd==1.0.0 # via -r requirements-dev.in
babel==2.12.1 # via sphinx
black==23.3.0 # via -r requirements-dev.in
build==0.10.0 # via pip-tools
cachetools==5.3.1 # via tox
certifi==2023.5.7 # via requests
cfgv==3.3.1 # via pre-commit
chardet==5.1.0 # via tox
charset-normalizer==3.2.0 # via requests
click==8.1.4 # via aprsd, black, click-completion, pip-tools
click-completion==0.5.2 # via aprsd
colorama==0.4.6 # via tox
coverage[toml]==7.2.7 # via pytest-cov
distlib==0.3.6 # via virtualenv
docutils==0.20.1 # via sphinx
exceptiongroup==1.1.2 # via pytest
filelock==3.12.2 # via tox, virtualenv
flake8==6.0.0 # via -r requirements-dev.in, pep8-naming
identify==2.5.24 # via pre-commit
idna==3.4 # via requests
imagesize==1.4.1 # via sphinx
imapclient==2.3.1 # via aprsd
importlib-metadata==6.8.0 # via sphinx
iniconfig==2.0.0 # via pytest
isort==5.12.0 # via -r requirements-dev.in
jinja2==3.1.2 # via click-completion, sphinx
markupsafe==2.1.3 # via jinja2
mccabe==0.7.0 # via flake8
mypy==1.4.1 # via -r requirements-dev.in
mypy-extensions==1.0.0 # via black, mypy
nodeenv==1.8.0 # via pre-commit
packaging==23.1 # via black, build, pyproject-api, pytest, sphinx, tox
pathspec==0.11.1 # via black
pbr==5.11.1 # via -r requirements-dev.in, aprsd
pep8-naming==0.13.3 # via -r requirements-dev.in
pip-tools==6.14.0 # via -r requirements-dev.in
platformdirs==3.8.1 # via black, tox, virtualenv
pluggy==1.2.0 # via aprsd, pytest, tox
pre-commit==3.3.3 # via -r requirements-dev.in
pycodestyle==2.10.0 # via flake8
pyflakes==3.0.1 # via flake8
pygments==2.15.1 # via sphinx
pyproject-api==1.5.3 # via tox
pyproject-hooks==1.0.0 # via build
pytest==7.4.0 # via -r requirements-dev.in, pytest-cov
pytest-cov==4.1.0 # via -r requirements-dev.in
pyyaml==6.0 # via aprsd, pre-commit
requests==2.31.0 # via aprsd, sphinx
shellingham==1.5.0.post1 # via click-completion
six==1.16.0 # via aprsd, click-completion, imapclient
snowballstemmer==2.2.0 # via sphinx
sphinx==7.0.1 # via -r requirements-dev.in
sphinxcontrib-applehelp==1.0.4 # via sphinx
sphinxcontrib-devhelp==1.0.2 # via sphinx
sphinxcontrib-htmlhelp==2.0.1 # via sphinx
sphinxcontrib-jsmath==1.0.1 # via sphinx
sphinxcontrib-qthelp==1.0.3 # via sphinx
sphinxcontrib-serializinghtml==1.1.5 # via sphinx
tomli==2.0.1 # via black, build, coverage, mypy, pip-tools, pyproject-api, pyproject-hooks, pytest, tox
tox==4.6.4 # via -r requirements-dev.in
typing-extensions==4.7.1 # via black, mypy
urllib3==2.0.3 # via requests
virtualenv==20.23.1 # via pre-commit, tox
wheel==0.40.0 # via pip-tools
zipp==3.16.0 # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file:
# pip
# setuptools

View File

@ -1,4 +0,0 @@
pbr
slack_sdk>=3.0
slackeventsapi>=2.1.0
aprsd

View File

@ -1,28 +1,5 @@
#
# This file is autogenerated by pip-compile
# To update, run:
#
# pip-compile
#
aprsd==1.0.0 # via -r requirements.in
certifi==2020.12.5 # via requests
chardet==3.0.4 # via requests
click-completion==0.5.2 # via aprsd
click==7.1.2 # via aprsd, click-completion, flask
flask==1.1.2 # via slackeventsapi
idna==2.10 # via requests
imapclient==2.1.0 # via aprsd
itsdangerous==1.1.0 # via flask, slackeventsapi
jinja2==2.11.2 # via click-completion, flask, slackeventsapi
markupsafe==1.1.1 # via jinja2, slackeventsapi
pbr==5.5.1 # via -r requirements.in, aprsd
pluggy==0.13.1 # via aprsd
pyee==7.0.4 # via slackeventsapi
pyyaml==5.3.1 # via aprsd
requests==2.25.0 # via aprsd
shellingham==1.3.2 # via click-completion
six==1.15.0 # via aprsd, click-completion, imapclient
slack-sdk==3.1.0 # via -r requirements.in
slackeventsapi==2.2.1 # via -r requirements.in
urllib3==1.26.2 # via requests
werkzeug==1.0.1 # via flask
pbr
slack_sdk>=3.0
slackeventsapi>=2.1.0
aprsd
oslo_config

View File

@ -1,11 +1,10 @@
[metadata]
name = aprsd_slack_plugin
summary = Amateur radio APRS daemon which listens for messages and responds
description-file =
README.rst
long-description-content-type = text/x-rst; charset=UTF-8
long_description = file: README.rst
long_description_content_type = text/x-rst
author = Walter A. Boring IV
author-email = something@somewhere.com
author_email = something@somewhere.com
license_file = LICENSE.txt
classifier =
Topic :: Communications :: Ham Radio
Operating System :: POSIX :: Linux
@ -14,6 +13,13 @@ classifier =
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
description_file =
README.rst
summary = Amateur radio APRS daemon which listens for messages and responds
[options.entry_points]
oslo.config.opts =
aprsd_slack_plugin.conf = aprsd_slack_plugin.conf.opts:list_opts
[global]
setup-hooks =

View File

@ -1,22 +1,17 @@
import sys
import unittest
from unittest import mock
from aprsd_slack_plugin import aprsd_slack_plugin as slack_plugin
from aprsd import conf # noqa
if sys.version_info >= (3, 2):
from unittest import mock
else:
import mock
from aprsd_slack_plugin import conf as plugin_conf # noqa
from aprsd_slack_plugin import location_plugin
class TestPlugin(unittest.TestCase):
@mock.patch.object(slack_plugin.SlackCommandPlugin, "command")
@mock.patch.object(location_plugin.SlackLocationPlugin, "filter")
def test_plugin(self, mock_command):
mock_command.return_value = ""
config = {
"slack": {"signing_secret": "something", "bot_token": "sometoken", "channel": "hemna"}
}
p = slack_plugin.SlackCommandPlugin(config)
p.command("KM6LYW", "location", 1)
p = location_plugin.SlackLocationPlugin()
packet = {"from": "WB4BOR", "message_text": "location"}
p.filter(packet)

30
tox.ini
View File

@ -11,18 +11,14 @@ extend-exclude =
# This section is not needed if not using GitHub Actions for CI.
[gh-actions]
python =
3.6: py36
3.7: py37
3.8: py38, fmt-check, lint
3.9: py39
[tox]
# These are the default environments that will be run
# when ``tox`` is run without arguments.
envlist =
fmt-check
lint
py{36,37,38,39}
py{39}
skip_missing_interpreters = true
# Activate isolated build environment. tox will use a virtual environment
@ -33,7 +29,7 @@ isolated_build = true
[testenv]
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/dev-requirements.txt
-r{toxinidir}/requirements-dev.txt
commands =
# Use -bb to enable BytesWarnings as error to catch str/bytes misuse.
# Use -Werror to treat warnings as errors.
@ -43,14 +39,14 @@ commands =
skip_install = true
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/dev-requirements.txt
-r{toxinidir}/requirements-dev.txt
commands =
mypy src tests
[testenv:lint]
skip_install = true
deps =
-r{toxinidir}/dev-requirements.txt
-r{toxinidir}/requirements-dev.txt
commands =
flake8 aprsd_slack_plugin tests
@ -58,29 +54,13 @@ commands =
skip_install = true
deps =
-r{toxinidir}/requirements.txt
-r{toxinidir}/dev-requirements.txt
-r{toxinidir}/requirements-dev.txt
changedir = {toxinidir}/docs
commands =
{envpython} clean_docs.py
sphinx-apidoc --force --output-dir apidoc {toxinidir}/aprsd_slack_plugin
sphinx-build -a -W . _build
[testenv:fmt]
skip_install = true
deps =
-r{toxinidir}/dev-requirements.txt
commands =
isort aprsd_slack_plugin tests
black aprsd_slack_plugin tests
[testenv:fmt-check]
skip_install = true
deps =
-r{toxinidir}/dev-requirements.txt
commands =
isort --check-only aprsd_slack_plugin tests
black --check aprsd_slack_plugin tests
[testenv:licenses]
skip_install = true
recreate = true