mirror of
https://github.com/hemna/aprsd-twitter-plugin.git
synced 2025-06-25 05:25:17 -04:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
6eadbd0555 | |||
4637038b8b | |||
d51347f48e | |||
185d40a06e | |||
295e35aa06 | |||
06d3664586 | |||
96e64d7bd3 | |||
7a590c5581 | |||
e82d01ae01 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal 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']
|
18
ChangeLog
Normal file
18
ChangeLog
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
CHANGES
|
||||||
|
=======
|
||||||
|
|
||||||
|
v0.3.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Added PBR Version
|
||||||
|
|
||||||
|
v0.2.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Updated command to 'tw' from 't'. Added Help
|
||||||
|
* First working version that tweets!
|
||||||
|
|
||||||
|
v0.1.0
|
||||||
|
------
|
||||||
|
|
||||||
|
* Initial commit
|
6
Makefile
6
Makefile
@ -21,7 +21,7 @@ docs: build
|
|||||||
cp Changelog docs/changelog.rst
|
cp Changelog docs/changelog.rst
|
||||||
tox -edocs
|
tox -edocs
|
||||||
|
|
||||||
clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts
|
clean: clean-build clean-pyc clean-test clean-dev ## remove all build, test, coverage and Python artifacts
|
||||||
|
|
||||||
clean-build: ## remove build artifacts
|
clean-build: ## remove build artifacts
|
||||||
rm -fr build/
|
rm -fr build/
|
||||||
@ -42,6 +42,10 @@ clean-test: ## remove test and coverage artifacts
|
|||||||
rm -fr htmlcov/
|
rm -fr htmlcov/
|
||||||
rm -fr .pytest_cache
|
rm -fr .pytest_cache
|
||||||
|
|
||||||
|
clean-dev:
|
||||||
|
rm -rf $(VENVDIR)
|
||||||
|
rm Makefile.venv
|
||||||
|
|
||||||
coverage: ## check code coverage quickly with the default Python
|
coverage: ## check code coverage quickly with the default Python
|
||||||
coverage run --source aprsd_twitter_plugin setup.py test
|
coverage run --source aprsd_twitter_plugin setup.py test
|
||||||
coverage report -m
|
coverage report -m
|
||||||
|
21
README.rst
21
README.rst
@ -36,13 +36,30 @@ Send tweet via Ham RADIO!
|
|||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* TODO
|
* Sent a tweet from your personal twitter account!
|
||||||
|
* to tweet send a message of "t Hello World #aprs #hamradio"
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* TODO
|
* This plugin requires you have a twitter account and create a developer
|
||||||
|
account with:
|
||||||
|
* api key
|
||||||
|
* api key secret
|
||||||
|
* access token
|
||||||
|
* access token secret
|
||||||
|
|
||||||
|
Add the following entries to the aprsd.yml file
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
services:
|
||||||
|
twitter:
|
||||||
|
apiKey: <your api key here>
|
||||||
|
apiKey_secret: <your api key secret here>
|
||||||
|
access_token: <your Twitter app access token>
|
||||||
|
access_token_secret: <your Twitter app access token secret>
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import pbr.version
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = pbr.version.VersionInfo("aprsd_twitter_plugin").version_string()
|
@ -1,54 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from aprsd import messaging, plugin, trace
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
|
||||||
|
|
||||||
|
|
||||||
class SendTweetPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
||||||
|
|
||||||
version = "1.0"
|
|
||||||
# Look for any command that starts with w or W
|
|
||||||
command_regex = "^[wW]"
|
|
||||||
# the command is for ?
|
|
||||||
command_name = "weather"
|
|
||||||
|
|
||||||
enabled = False
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
# Do some checks here?
|
|
||||||
self.enabled = True
|
|
||||||
|
|
||||||
def create_threads(self):
|
|
||||||
"""This allows you to create and return a custom APRSDThread object.
|
|
||||||
|
|
||||||
Create a child of the aprsd.threads.APRSDThread object and return it
|
|
||||||
It will automatically get started.
|
|
||||||
|
|
||||||
You can see an example of one here:
|
|
||||||
https://github.com/craigerl/aprsd/blob/master/aprsd/threads.py#L141
|
|
||||||
"""
|
|
||||||
if self.enabled:
|
|
||||||
# You can create a background APRSDThread object here
|
|
||||||
# Just return it for example:
|
|
||||||
# https://github.com/hemna/aprsd-weewx-plugin/blob/master/aprsd_weewx_plugin/aprsd_weewx_plugin.py#L42-L50
|
|
||||||
#
|
|
||||||
return []
|
|
||||||
|
|
||||||
@trace.trace
|
|
||||||
def process(self, packet):
|
|
||||||
|
|
||||||
"""This is called when a received packet matches self.command_regex."""
|
|
||||||
|
|
||||||
LOG.info("SendTweetPlugin Plugin")
|
|
||||||
|
|
||||||
packet.get("from")
|
|
||||||
packet.get("message_text", None)
|
|
||||||
|
|
||||||
if self.enabled:
|
|
||||||
# Now we can process
|
|
||||||
return "some reply message"
|
|
||||||
else:
|
|
||||||
LOG.warning("SendTweetPlugin is disabled.")
|
|
||||||
return messaging.NULL
|
|
7
aprsd_twitter_plugin/conf/__init__.py
Normal file
7
aprsd_twitter_plugin/conf/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from aprsd_twitter_plugin.conf import twitter
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
twitter.register_opts(CONF)
|
80
aprsd_twitter_plugin/conf/opts.py
Normal file
80
aprsd_twitter_plugin/conf/opts.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
# 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_twitter_plugin.conf." + modname)
|
||||||
|
if not hasattr(mod, LIST_OPTS_FUNC_NAME):
|
||||||
|
msg = "The module 'aprsd_twitter_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)
|
57
aprsd_twitter_plugin/conf/twitter.py
Normal file
57
aprsd_twitter_plugin/conf/twitter.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
|
||||||
|
twitter_group = cfg.OptGroup(
|
||||||
|
name="aprsd_twitter_plugin",
|
||||||
|
title="APRSD Twitter Plugin settings",
|
||||||
|
)
|
||||||
|
|
||||||
|
twitter_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
"callsign",
|
||||||
|
help="Callsign allowed to send tweets! "
|
||||||
|
"Any callsign starting with this will be allowed to tweet to"
|
||||||
|
"the configured twitter account. "
|
||||||
|
"For example, if you set this to WB4BOR then any"
|
||||||
|
"callsign starting with WB4BOR will be allowed to tweet."
|
||||||
|
"This way WB4BOR-1 can tweet from this instance.",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"apiKey",
|
||||||
|
help="Your twitter apiKey"
|
||||||
|
"Information for creating your api keys is here: "
|
||||||
|
"https://developer.twitter.com/en/docs/authentication/oauth-1-0a/api-key-and-secret",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"apiKey_secret",
|
||||||
|
help="Your twitter accounts apikey secret.",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"access_token",
|
||||||
|
help="The twitter access_token for your Twitter account",
|
||||||
|
),
|
||||||
|
cfg.StrOpt(
|
||||||
|
"access_token_secret",
|
||||||
|
help="The twitter access token secret for your Twitter account",
|
||||||
|
),
|
||||||
|
cfg.BoolOpt(
|
||||||
|
"add_aprs_hashtag",
|
||||||
|
default=True,
|
||||||
|
help="Automatically add #aprs hash tag to every tweet?",
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
ALL_OPTS = (
|
||||||
|
twitter_opts
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def register_opts(cfg):
|
||||||
|
cfg.register_group(twitter_group)
|
||||||
|
cfg.register_opts(ALL_OPTS, group=twitter_group)
|
||||||
|
|
||||||
|
|
||||||
|
def list_opts():
|
||||||
|
return {
|
||||||
|
twitter_group.name: ALL_OPTS,
|
||||||
|
}
|
134
aprsd_twitter_plugin/twitter.py
Normal file
134
aprsd_twitter_plugin/twitter.py
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import tweepy
|
||||||
|
from aprsd import conf # noqa
|
||||||
|
from aprsd import plugin
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
import aprsd_twitter_plugin
|
||||||
|
from aprsd_twitter_plugin import conf # noqa
|
||||||
|
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class SendTweetPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
|
|
||||||
|
version = aprsd_twitter_plugin.__version__
|
||||||
|
# Look for any command that starts with tw or tW or TW or Tw
|
||||||
|
# or case insensitive version of 'twitter'
|
||||||
|
command_regex = r"^([t][w]\s|twitter)"
|
||||||
|
# the command is for ?
|
||||||
|
command_name = "tweet"
|
||||||
|
|
||||||
|
enabled = False
|
||||||
|
|
||||||
|
def help(self):
|
||||||
|
_help = [
|
||||||
|
"twitter: Send a Tweet!!",
|
||||||
|
"twitter: Format 'tw <message>'",
|
||||||
|
]
|
||||||
|
return _help
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
# Do some checks here?
|
||||||
|
self.enabled = True
|
||||||
|
|
||||||
|
if not CONF.aprsd_twitter_plugin.callsign:
|
||||||
|
LOG.error(
|
||||||
|
"No aprsd_twitter_pligin.callsign is set."
|
||||||
|
" Callsign is needed to allow tweets!",
|
||||||
|
)
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
# Ensure the access token exists.
|
||||||
|
if not CONF.aprsd_twitter_plugin.apiKey:
|
||||||
|
LOG.error(
|
||||||
|
"No aprsd_twitter_plugin.apiKey is set!."
|
||||||
|
" Plugin Disabled.",
|
||||||
|
)
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
if not CONF.aprsd_twitter_plugin.apiKey_secret:
|
||||||
|
LOG.error(
|
||||||
|
"No aprsd_twitter_plugin.apiKey_secret is set."
|
||||||
|
" Plugin Disabled.",
|
||||||
|
)
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
if not CONF.aprsd_twitter_plugin.access_token:
|
||||||
|
LOG.error(
|
||||||
|
"No aprsd_twitter_plugin.access_token exists."
|
||||||
|
" Plugin Disabled.",
|
||||||
|
)
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
if not CONF.aprsd_twitter_plugin.access_token_secret:
|
||||||
|
LOG.error(
|
||||||
|
"No aprsd_twitter_plugin.access_token_secret exists."
|
||||||
|
" Plugin Disabled.",
|
||||||
|
)
|
||||||
|
self.enabled = False
|
||||||
|
|
||||||
|
def _create_client(self):
|
||||||
|
"""Create the twitter client object."""
|
||||||
|
auth = tweepy.OAuthHandler(
|
||||||
|
CONF.aprsd_twitter_plugin.apiKey,
|
||||||
|
CONF.aprsd_twitter_plugin.apiKey_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
auth.set_access_token(
|
||||||
|
CONF.aprsd_twitter_plugin.access_token,
|
||||||
|
CONF.aprsd_twitter_plugin.access_token_secret,
|
||||||
|
)
|
||||||
|
|
||||||
|
api = tweepy.API(
|
||||||
|
auth,
|
||||||
|
wait_on_rate_limit=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
api.verify_credentials()
|
||||||
|
LOG.debug("Logged in to Twitter Authentication OK")
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error("Failed to auth to Twitter")
|
||||||
|
LOG.exception(ex)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return api
|
||||||
|
|
||||||
|
def process(self, packet):
|
||||||
|
|
||||||
|
"""This is called when a received packet matches self.command_regex."""
|
||||||
|
|
||||||
|
LOG.info("SendTweetPlugin Plugin")
|
||||||
|
|
||||||
|
from_callsign = packet.from_call
|
||||||
|
message = packet.message_text
|
||||||
|
message = message.split(" ")
|
||||||
|
del message[0]
|
||||||
|
message = " ".join(message)
|
||||||
|
|
||||||
|
# Now we can process
|
||||||
|
auth_call = CONF.aprsd_twitter_plugin.callsign
|
||||||
|
|
||||||
|
# Only allow the owner of aprsd to send a tweet
|
||||||
|
if not from_callsign.startswith(auth_call):
|
||||||
|
return f"{from_callsign} not authorized to tweet!"
|
||||||
|
|
||||||
|
client = self._create_client()
|
||||||
|
if not client:
|
||||||
|
LOG.error("No twitter client!!")
|
||||||
|
return "Failed to Auth"
|
||||||
|
|
||||||
|
if CONF.aprsd_twitter_plugin.add_aprs_hashtag:
|
||||||
|
message += (
|
||||||
|
" #aprs #aprsd #hamradio "
|
||||||
|
"https://github.com/hemna/aprsd-twitter-plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Now lets tweet!
|
||||||
|
client.update_status(message)
|
||||||
|
|
||||||
|
return "Tweet sent!"
|
@ -1,2 +1,4 @@
|
|||||||
pbr
|
pbr
|
||||||
aprsd>=2.2.0
|
aprsd>=3.0.0
|
||||||
|
tweepy
|
||||||
|
oslo.config
|
||||||
|
@ -19,6 +19,10 @@ description_file =
|
|||||||
README.rst
|
README.rst
|
||||||
summary = Python APRSD plugin to send tweets
|
summary = Python APRSD plugin to send tweets
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
oslo.config.opts =
|
||||||
|
aprsd_twitter_plugin.conf = aprsd_twitter_plugin.conf.opts:list_opts
|
||||||
|
|
||||||
[global]
|
[global]
|
||||||
setup-hooks =
|
setup-hooks =
|
||||||
pbr.hooks.setup_hook
|
pbr.hooks.setup_hook
|
||||||
@ -38,3 +42,6 @@ upload-dir = doc/build/html
|
|||||||
[mypy]
|
[mypy]
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
||||||
strict = True
|
strict = True
|
||||||
|
|
||||||
|
[bdist_wheel]
|
||||||
|
universal = 1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user