mirror of
https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare.git
synced 2025-02-15 03:00:44 +01:00
twitter (#77)
* added basic recap feature (no error handling) * introduced abstractpublication * extracted base reports * added error report to recap * added test * added docs * implemented publisher and formatter * fixed API for recap * removed redundant config validation * added config sample
This commit is contained in:
parent
5e171216d2
commit
c14cdfb67f
@ -83,6 +83,7 @@ Currently the following publishers are supported:
|
||||
|
||||
* Telegram
|
||||
* Zulip
|
||||
* Twitter
|
||||
|
||||
### Notifier
|
||||
|
||||
|
@ -12,7 +12,11 @@ subject="xxx"
|
||||
bot_token="xxx"
|
||||
bot_email="xxx"
|
||||
[default.publisher.twitter]
|
||||
active=false
|
||||
active=true
|
||||
api_key="xxx"
|
||||
api_key_secret="xxx"
|
||||
access_token="xxx"
|
||||
access_secret="xxx"
|
||||
[default.publisher.mastodon]
|
||||
active=false
|
||||
|
||||
@ -28,6 +32,10 @@ subject="xxx"
|
||||
bot_token="xxx"
|
||||
bot_email="xxx"
|
||||
[default.notifier.twitter]
|
||||
active=false
|
||||
active=true
|
||||
api_key="xxx"
|
||||
api_key_secret="xxx"
|
||||
access_token="xxx"
|
||||
access_secret="xxx"
|
||||
[default.notifier.mastodon]
|
||||
active=false
|
||||
|
@ -14,7 +14,12 @@ zulip_validators = [
|
||||
Validator("publisher.zulip.bot_email", must_exist=True),
|
||||
]
|
||||
mastodon_validators = []
|
||||
twitter_validators = []
|
||||
twitter_validators = [
|
||||
Validator("publisher.twitter.api_key", must_exist=True),
|
||||
Validator("publisher.twitter.api_key_secret", must_exist=True),
|
||||
Validator("publisher.twitter.access_token", must_exist=True),
|
||||
Validator("publisher.twitter.access_secret", must_exist=True),
|
||||
]
|
||||
|
||||
notifier_name_to_validators = {
|
||||
"telegram": telegram_validators,
|
||||
|
@ -17,7 +17,14 @@ zulip_validators = [
|
||||
Validator("publisher.zulip.bot_email", must_exist=True),
|
||||
]
|
||||
mastodon_validators = []
|
||||
twitter_validators = []
|
||||
twitter_validators = [
|
||||
Validator("publisher.twitter.msg_template_path", must_exist=True, default=None),
|
||||
Validator("publisher.twitter.recap_template_path", must_exist=True, default=None),
|
||||
Validator("publisher.twitter.api_key", must_exist=True),
|
||||
Validator("publisher.twitter.api_key_secret", must_exist=True),
|
||||
Validator("publisher.twitter.access_token", must_exist=True),
|
||||
Validator("publisher.twitter.access_secret", must_exist=True),
|
||||
]
|
||||
facebook_validators = []
|
||||
|
||||
publisher_name_to_validators = {
|
||||
|
@ -3,6 +3,11 @@ from mobilizon_reshare.publishers.platforms.telegram import (
|
||||
TelegramFormatter,
|
||||
TelegramNotifier,
|
||||
)
|
||||
from mobilizon_reshare.publishers.platforms.twitter import (
|
||||
TwitterPublisher,
|
||||
TwitterFormatter,
|
||||
TwitterNotifier,
|
||||
)
|
||||
from mobilizon_reshare.publishers.platforms.zulip import (
|
||||
ZulipPublisher,
|
||||
ZulipFormatter,
|
||||
@ -12,14 +17,17 @@ from mobilizon_reshare.publishers.platforms.zulip import (
|
||||
name_to_publisher_class = {
|
||||
"telegram": TelegramPublisher,
|
||||
"zulip": ZulipPublisher,
|
||||
"twitter": TwitterPublisher,
|
||||
}
|
||||
name_to_formatter_class = {
|
||||
"telegram": TelegramFormatter,
|
||||
"zulip": ZulipFormatter,
|
||||
"twitter": TwitterFormatter,
|
||||
}
|
||||
name_to_notifier_class = {
|
||||
"telegram": TelegramNotifier,
|
||||
"zulip": ZulipNotifier,
|
||||
"twitter": TwitterNotifier,
|
||||
}
|
||||
|
||||
|
||||
|
@ -10,7 +10,6 @@ from mobilizon_reshare.publishers.abstract import (
|
||||
)
|
||||
from mobilizon_reshare.publishers.exceptions import (
|
||||
InvalidBot,
|
||||
InvalidCredentials,
|
||||
InvalidEvent,
|
||||
InvalidResponse,
|
||||
PublisherError,
|
||||
@ -63,26 +62,10 @@ class TelegramPlatform(AbstractPlatform):
|
||||
return TelegramFormatter.escape_message(message)
|
||||
|
||||
def validate_credentials(self):
|
||||
conf = self.conf
|
||||
chat_id = conf.chat_id
|
||||
token = conf.token
|
||||
username = conf.username
|
||||
err = []
|
||||
if not chat_id:
|
||||
err.append("chat ID")
|
||||
if not token:
|
||||
err.append("token")
|
||||
if not username:
|
||||
err.append("username")
|
||||
if err:
|
||||
self._log_error(
|
||||
", ".join(err) + " is/are missing", raise_error=InvalidCredentials,
|
||||
)
|
||||
|
||||
res = requests.get(f"https://api.telegram.org/bot{token}/getMe")
|
||||
res = requests.get(f"https://api.telegram.org/bot{self.conf.token}/getMe")
|
||||
data = self._validate_response(res)
|
||||
|
||||
if not username == data.get("result", {}).get("username"):
|
||||
if not self.conf.username == data.get("result", {}).get("username"):
|
||||
self._log_error(
|
||||
"Found a different bot than the expected one", raise_error=InvalidBot,
|
||||
)
|
||||
|
76
mobilizon_reshare/publishers/platforms/twitter.py
Normal file
76
mobilizon_reshare/publishers/platforms/twitter.py
Normal file
@ -0,0 +1,76 @@
|
||||
import pkg_resources
|
||||
from tweepy import OAuthHandler, API
|
||||
from tweepy.models import Status
|
||||
|
||||
from mobilizon_reshare.event.event import MobilizonEvent
|
||||
from mobilizon_reshare.publishers.abstract import (
|
||||
AbstractPlatform,
|
||||
AbstractEventFormatter,
|
||||
)
|
||||
from mobilizon_reshare.publishers.exceptions import (
|
||||
InvalidCredentials,
|
||||
InvalidEvent,
|
||||
PublisherError,
|
||||
)
|
||||
|
||||
|
||||
class TwitterFormatter(AbstractEventFormatter):
|
||||
|
||||
_conf = ("publisher", "twitter")
|
||||
default_template_path = pkg_resources.resource_filename(
|
||||
"mobilizon_reshare.publishers.templates", "twitter.tmpl.j2"
|
||||
)
|
||||
|
||||
default_recap_template_path = pkg_resources.resource_filename(
|
||||
"mobilizon_reshare.publishers.templates", "twitter_recap.tmpl.j2"
|
||||
)
|
||||
|
||||
def validate_event(self, event: MobilizonEvent) -> None:
|
||||
text = event.description
|
||||
if not (text and text.strip()):
|
||||
self._log_error("No text was found", raise_error=InvalidEvent)
|
||||
|
||||
def validate_message(self, message) -> None:
|
||||
if len(message.encode("utf-8")) > 140:
|
||||
raise PublisherError("Message is too long")
|
||||
|
||||
|
||||
class TwitterPlatform(AbstractPlatform):
|
||||
"""
|
||||
Twitter publisher class.
|
||||
"""
|
||||
|
||||
_conf = ("publisher", "twitter")
|
||||
|
||||
def _get_api(self):
|
||||
|
||||
api_key = self.conf.api_key
|
||||
api_key_secret = self.conf.api_key_secret
|
||||
access_token = self.conf.access_token
|
||||
access_secret = self.conf.access_secret
|
||||
auth = OAuthHandler(api_key, api_key_secret)
|
||||
auth.set_access_token(access_token, access_secret)
|
||||
return API(auth)
|
||||
|
||||
def _send(self, message: str) -> Status:
|
||||
return self._get_api().update_status(message)
|
||||
|
||||
def validate_credentials(self):
|
||||
if not self._get_api().verify_credentials():
|
||||
self._log_error(
|
||||
"Invalid Twitter credentials. Authentication Failed",
|
||||
raise_error=InvalidCredentials,
|
||||
)
|
||||
|
||||
def _validate_response(self, res: Status) -> dict:
|
||||
pass
|
||||
|
||||
|
||||
class TwitterPublisher(TwitterPlatform):
|
||||
|
||||
_conf = ("publisher", "twitter")
|
||||
|
||||
|
||||
class TwitterNotifier(TwitterPlatform):
|
||||
|
||||
_conf = ("notifier", "twitter")
|
@ -11,7 +11,6 @@ from mobilizon_reshare.publishers.abstract import (
|
||||
)
|
||||
from mobilizon_reshare.publishers.exceptions import (
|
||||
InvalidBot,
|
||||
InvalidCredentials,
|
||||
InvalidEvent,
|
||||
InvalidResponse,
|
||||
ZulipError,
|
||||
@ -81,20 +80,6 @@ class ZulipPlatform(AbstractPlatform):
|
||||
|
||||
def validate_credentials(self):
|
||||
conf = self.conf
|
||||
chat_id = conf.chat_id
|
||||
bot_token = conf.bot_token
|
||||
bot_email = conf.bot_email
|
||||
err = []
|
||||
if not chat_id:
|
||||
err.append("chat ID")
|
||||
if not bot_token:
|
||||
err.append("bot token")
|
||||
if not bot_email:
|
||||
err.append("bot email")
|
||||
if err:
|
||||
self._log_error(
|
||||
", ".join(err) + " is/are missing", raise_error=InvalidCredentials,
|
||||
)
|
||||
|
||||
res = requests.get(
|
||||
auth=HTTPBasicAuth(self.conf.bot_email, self.conf.bot_token),
|
||||
@ -107,11 +92,11 @@ class ZulipPlatform(AbstractPlatform):
|
||||
"These user is not a bot", raise_error=InvalidBot,
|
||||
)
|
||||
|
||||
if not bot_email == data["email"]:
|
||||
if not conf.bot_email == data["email"]:
|
||||
self._log_error(
|
||||
"Found a different bot than the expected one"
|
||||
f"\n\tfound: {data['email']}"
|
||||
f"\n\texpected: {bot_email}",
|
||||
f"\n\texpected: {conf.bot_email}",
|
||||
raise_error=InvalidBot,
|
||||
)
|
||||
|
||||
|
8
mobilizon_reshare/publishers/templates/twitter.tmpl.j2
Normal file
8
mobilizon_reshare/publishers/templates/twitter.tmpl.j2
Normal file
@ -0,0 +1,8 @@
|
||||
# {{ name }}
|
||||
|
||||
🕒 {{ begin_datetime.format('DD MMMM, HH:mm') }} - {{ end_datetime.format('DD MMMM, HH:mm') }}
|
||||
|
||||
{% if location %}
|
||||
📍 {{ location }}
|
||||
|
||||
{% endif %}
|
@ -0,0 +1,5 @@
|
||||
# {{ name }}
|
||||
🕒 {{ begin_datetime.format('DD MMMM, HH:mm') }} - {{ end_datetime.format('DD MMMM, HH:mm') }}
|
||||
{% if location %}
|
||||
📍 {{ location }}
|
||||
{% endif %}
|
61
poetry.lock
generated
61
poetry.lock
generated
@ -186,6 +186,19 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.1.1"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["cryptography (>=3.0.0,<4)"]
|
||||
signals = ["blinker (>=1.4.0)"]
|
||||
signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.0"
|
||||
@ -305,6 +318,21 @@ urllib3 = ">=1.21.1,<1.27"
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
|
||||
|
||||
[[package]]
|
||||
name = "requests-oauthlib"
|
||||
version = "1.3.0"
|
||||
description = "OAuthlib authentication support for Requests."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
oauthlib = ">=3.0.0"
|
||||
requests = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "responses"
|
||||
version = "0.13.4"
|
||||
@ -365,6 +393,24 @@ asyncmy = ["asyncmy"]
|
||||
asyncpg = ["asyncpg"]
|
||||
accel = ["ciso8601 (>=2.1.2,<3.0.0)", "python-rapidjson", "uvloop (>=0.14.0,<0.15.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "tweepy"
|
||||
version = "4.1.0"
|
||||
description = "Twitter library for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
requests = ">=2.11.1,<3"
|
||||
requests-oauthlib = ">=1.0.0,<2"
|
||||
|
||||
[package.extras]
|
||||
async = ["aiohttp (>=3.7.3,<4)", "oauthlib (>=3.1.0,<4)"]
|
||||
dev = ["coveralls (>=2.1.0)", "tox (>=3.14.0)"]
|
||||
socks = ["requests[socks] (>=2.11.1,<3)"]
|
||||
test = ["vcrpy (>=1.10.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "3.10.0.2"
|
||||
@ -389,7 +435,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "1a9b33d624371bf57e4988339fe52e8e89269d6c4e699aec1f16504ce4520d16"
|
||||
content-hash = "4fe276575784de9ed9d3ee66eef2c75355dd66e7717bcc277c38755dd489b4ac"
|
||||
|
||||
[metadata.files]
|
||||
aiosqlite = [
|
||||
@ -497,6 +543,10 @@ markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
|
||||
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
|
||||
]
|
||||
oauthlib = [
|
||||
{file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
|
||||
{file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"},
|
||||
{file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"},
|
||||
@ -537,6 +587,11 @@ requests = [
|
||||
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
|
||||
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
|
||||
]
|
||||
requests-oauthlib = [
|
||||
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
|
||||
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
|
||||
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
||||
]
|
||||
responses = [
|
||||
{file = "responses-0.13.4-py2.py3-none-any.whl", hash = "sha256:d8d0f655710c46fd3513b9202a7f0dcedd02ca0f8cf4976f27fa8ab5b81e656d"},
|
||||
{file = "responses-0.13.4.tar.gz", hash = "sha256:9476775d856d3c24ae660bbebe29fb6d789d4ad16acd723efbfb6ee20990b899"},
|
||||
@ -557,6 +612,10 @@ tortoise-orm = [
|
||||
{file = "tortoise-orm-0.17.7.tar.gz", hash = "sha256:74d5c6341fc0e2d4ef7a321f460107715da2679a2b3e2a48e32e276ed7bd3fc0"},
|
||||
{file = "tortoise_orm-0.17.7-py3-none-any.whl", hash = "sha256:36bb0d1f9bd800d3b91d5490cfc13a598c0e0f570a638b185bc4976abfabd135"},
|
||||
]
|
||||
tweepy = [
|
||||
{file = "tweepy-4.1.0-py2.py3-none-any.whl", hash = "sha256:42c63f5ee2210a8afc7178c74a6d800ef5911b007ad19e774d75dec4b777993e"},
|
||||
{file = "tweepy-4.1.0.tar.gz", hash = "sha256:88e2938de5ac7043c9ba8b8358996fbc5806059d63c96269d22527a40ca7d511"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
|
||||
{file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
|
||||
|
@ -20,6 +20,7 @@ click = "^8.0"
|
||||
beautifulsoup4 = "^4.9"
|
||||
markdownify = "^0.9"
|
||||
appdirs = "^1.4"
|
||||
tweepy = "^4.1.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
responses = "^0.13"
|
||||
|
Loading…
x
Reference in New Issue
Block a user