added facebook publisher (#99)
* added facebook publisher * mobilizon-reshare.git: [propagated-inputs]: Add python-sdk-facebook. This package definition has been generated with `guix import pypi -r facebook-sdk`. * mobilizon-reshare.git: [propagated-inputs]: Use python-facebook-sdk.git. Co-authored-by: Giacomo Leidi <goodoldpaul@autistici.org>
This commit is contained in:
parent
a91e72c3ef
commit
2476686c33
|
@ -335,6 +335,49 @@ development, testing, production]};
|
|||
(description "Twitter library for Python")
|
||||
(license license:expat)))
|
||||
|
||||
(define-public python-facebook-sdk
|
||||
(package
|
||||
(name "python-facebook-sdk")
|
||||
(version "3.1.0")
|
||||
(source
|
||||
(origin
|
||||
(method url-fetch)
|
||||
(uri (pypi-uri "facebook-sdk" version))
|
||||
(sha256
|
||||
(base32 "138grz0n6plzdqgi4h6hhszf58bsvx9v76cwj51g1nd3kvkd5g6a"))))
|
||||
(build-system python-build-system)
|
||||
(propagated-inputs `(("python-requests" ,python-requests)))
|
||||
(home-page "https://facebook-sdk.readthedocs.io")
|
||||
(synopsis
|
||||
"Facebook Graph API client in Python")
|
||||
(description
|
||||
"This client library is designed to support the Facebook Graph API and
|
||||
the official Facebook JavaScript SDK, which is the canonical way to implement
|
||||
Facebook authentication.")
|
||||
(license license:asl2.0)))
|
||||
|
||||
(define-public python-facebook-sdk.git
|
||||
(let ((version (package-version python-facebook-sdk))
|
||||
(revision "0")
|
||||
(commit "3fa89fec6a20dd070ccf57968c6f89256f237f54"))
|
||||
(package (inherit python-facebook-sdk)
|
||||
(name "python-facebook-sdk.git")
|
||||
(version (git-version version revision commit))
|
||||
(source
|
||||
(origin
|
||||
(method git-fetch)
|
||||
(uri
|
||||
(git-reference
|
||||
(url "https://github.com/mobolic/facebook-sdk")
|
||||
(commit commit)))
|
||||
(file-name (git-file-name name version))
|
||||
(sha256
|
||||
(base32
|
||||
"0vayxkg6p8wdj63qvzr24dj3q7rkyhr925b31z2qv2mnbas01dmg"))))
|
||||
(arguments
|
||||
;; Tests depend on network access.
|
||||
`(#:tests? #false)))))
|
||||
|
||||
(define-public mobilizon-reshare.git
|
||||
(let ((source-version (with-input-from-file
|
||||
(string-append %source-dir
|
||||
|
@ -389,6 +432,7 @@ development, testing, production]};
|
|||
("python-beautifulsoup4" ,python-beautifulsoup4)
|
||||
("python-click" ,python-click)
|
||||
("python-dynaconf" ,python-dynaconf)
|
||||
("python-facebook-sdk" ,python-facebook-sdk.git)
|
||||
("python-jinja2" ,python-jinja2)
|
||||
("python-markdownify" ,python-markdownify)
|
||||
("python-requests" ,python-requests)
|
||||
|
|
|
@ -3,8 +3,6 @@ active=true
|
|||
chat_id="xxx"
|
||||
token="xxx"
|
||||
username="xxx"
|
||||
[default.publisher.facebook]
|
||||
active=false
|
||||
[default.publisher.zulip]
|
||||
active=true
|
||||
instance="xxx"
|
||||
|
@ -25,6 +23,11 @@ token="xxx"
|
|||
name="xxx"
|
||||
toot_length=500
|
||||
|
||||
[default.publisher.facebook]
|
||||
|
||||
active=true
|
||||
page_access_token="xxx"
|
||||
|
||||
[default.notifier.telegram]
|
||||
active=true
|
||||
chat_id="xxx"
|
||||
|
@ -45,3 +48,7 @@ access_token="xxx"
|
|||
access_secret="xxx"
|
||||
[default.notifier.mastodon]
|
||||
active=false
|
||||
|
||||
[default.notifier.facebook]
|
||||
active=false
|
||||
page_access_token="xxx"
|
|
@ -26,7 +26,11 @@ twitter_validators = [
|
|||
Validator("publisher.twitter.access_secret", must_exist=True),
|
||||
]
|
||||
|
||||
facebook_validators = [
|
||||
Validator("publisher.facebook.page_access_token", must_exist=True),
|
||||
]
|
||||
notifier_name_to_validators = {
|
||||
"facebook": facebook_validators,
|
||||
"telegram": telegram_validators,
|
||||
"twitter": twitter_validators,
|
||||
"mastodon": mastodon_validators,
|
||||
|
|
|
@ -45,7 +45,14 @@ twitter_validators = [
|
|||
Validator("publisher.twitter.access_token", must_exist=True),
|
||||
Validator("publisher.twitter.access_secret", must_exist=True),
|
||||
]
|
||||
facebook_validators = []
|
||||
facebook_validators = [
|
||||
Validator("publisher.facebook.msg_template_path", must_exist=True, default=None),
|
||||
Validator("publisher.facebook.recap_template_path", must_exist=True, default=None),
|
||||
Validator(
|
||||
"publisher.facebook.recap_header_template_path", must_exist=True, default=None
|
||||
),
|
||||
Validator("publisher.facebook.page_access_token", must_exist=True),
|
||||
]
|
||||
|
||||
publisher_name_to_validators = {
|
||||
"telegram": telegram_validators,
|
||||
|
|
|
@ -10,9 +10,7 @@ from jinja2 import Environment, FileSystemLoader, Template
|
|||
|
||||
from mobilizon_reshare.config.config import get_settings
|
||||
from mobilizon_reshare.event.event import MobilizonEvent
|
||||
from mobilizon_reshare.models.publication import (
|
||||
Publication as PublicationModel,
|
||||
)
|
||||
from mobilizon_reshare.models.publication import Publication as PublicationModel
|
||||
from .exceptions import PublisherError, InvalidAttribute
|
||||
|
||||
JINJA_ENV = Environment(loader=FileSystemLoader("/"))
|
||||
|
@ -86,15 +84,15 @@ class AbstractPlatform(ABC, LoggerMixin, ConfLoaderMixin):
|
|||
pass
|
||||
|
||||
@abstractmethod
|
||||
def _send(self, message: str):
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None):
|
||||
raise NotImplementedError # pragma: no cover
|
||||
|
||||
def send(self, message: str):
|
||||
def send(self, message: str, event: Optional[MobilizonEvent] = None):
|
||||
"""
|
||||
Sends a message to the target channel
|
||||
"""
|
||||
message = self._preprocess_message(message)
|
||||
response = self._send(message)
|
||||
response = self._send(message, event)
|
||||
self._validate_response(response)
|
||||
|
||||
def _preprocess_message(self, message: str):
|
||||
|
|
|
@ -85,7 +85,7 @@ class PublisherCoordinator:
|
|||
message = publication.formatter.get_message_from_event(
|
||||
publication.event
|
||||
)
|
||||
publication.publisher.send(message)
|
||||
publication.publisher.send(message, publication.event)
|
||||
reports.append(
|
||||
EventPublicationReport(
|
||||
status=PublicationStatus.COMPLETED,
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
from typing import Optional
|
||||
|
||||
import facebook
|
||||
import pkg_resources
|
||||
|
||||
from mobilizon_reshare.event.event import MobilizonEvent
|
||||
from mobilizon_reshare.publishers.abstract import (
|
||||
AbstractPlatform,
|
||||
AbstractEventFormatter,
|
||||
)
|
||||
from mobilizon_reshare.publishers.exceptions import (
|
||||
InvalidCredentials,
|
||||
InvalidEvent,
|
||||
)
|
||||
|
||||
|
||||
class FacebookFormatter(AbstractEventFormatter):
|
||||
|
||||
_conf = ("publisher", "facebook")
|
||||
default_template_path = pkg_resources.resource_filename(
|
||||
"mobilizon_reshare.publishers.templates", "facebook.tmpl.j2"
|
||||
)
|
||||
|
||||
default_recap_template_path = pkg_resources.resource_filename(
|
||||
"mobilizon_reshare.publishers.templates", "facebook_recap.tmpl.j2"
|
||||
)
|
||||
|
||||
default_recap_header_template_path = pkg_resources.resource_filename(
|
||||
"mobilizon_reshare.publishers.templates", "facebook_recap_header.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:
|
||||
pass
|
||||
|
||||
|
||||
class FacebookPlatform(AbstractPlatform):
|
||||
"""
|
||||
Facebook publisher class.
|
||||
"""
|
||||
|
||||
name = "facebook"
|
||||
|
||||
def _get_api(self):
|
||||
return facebook.GraphAPI(
|
||||
access_token=self.conf["page_access_token"], version="8.0"
|
||||
)
|
||||
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None):
|
||||
self._get_api().put_object(
|
||||
parent_object="me",
|
||||
connection_name="feed",
|
||||
message=message,
|
||||
link=event.mobilizon_link if event else None,
|
||||
)
|
||||
|
||||
def validate_credentials(self):
|
||||
|
||||
try:
|
||||
self._get_api().get_object(id="me", field="name")
|
||||
except Exception:
|
||||
self._log_error(
|
||||
"Invalid Facebook credentials. Authentication Failed",
|
||||
raise_error=InvalidCredentials,
|
||||
)
|
||||
|
||||
def _validate_response(self, response):
|
||||
pass
|
||||
|
||||
|
||||
class FacebookPublisher(FacebookPlatform):
|
||||
|
||||
_conf = ("publisher", "facebook")
|
||||
|
||||
|
||||
class FacebookNotifier(FacebookPlatform):
|
||||
|
||||
_conf = ("notifier", "facebook")
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pkg_resources
|
||||
|
@ -52,7 +53,7 @@ class MastodonPlatform(AbstractPlatform):
|
|||
api_uri = "api/v1/"
|
||||
name = "mastodon"
|
||||
|
||||
def _send(self, message: str) -> Response:
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Response:
|
||||
"""
|
||||
Send messages
|
||||
"""
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
from mobilizon_reshare.publishers.platforms.facebook import (
|
||||
FacebookPublisher,
|
||||
FacebookFormatter,
|
||||
FacebookNotifier,
|
||||
)
|
||||
from mobilizon_reshare.publishers.platforms.mastodon import (
|
||||
MastodonPublisher,
|
||||
MastodonFormatter,
|
||||
|
@ -29,18 +34,21 @@ name_to_publisher_class = {
|
|||
"telegram": TelegramPublisher,
|
||||
"zulip": ZulipPublisher,
|
||||
"twitter": TwitterPublisher,
|
||||
"facebook": FacebookPublisher,
|
||||
}
|
||||
name_to_formatter_class = {
|
||||
"mastodon": MastodonFormatter,
|
||||
"telegram": TelegramFormatter,
|
||||
"zulip": ZulipFormatter,
|
||||
"twitter": TwitterFormatter,
|
||||
"facebook": FacebookFormatter,
|
||||
}
|
||||
name_to_notifier_class = {
|
||||
"mastodon": MastodonNotifier,
|
||||
"telegram": TelegramNotifier,
|
||||
"zulip": ZulipNotifier,
|
||||
"twitter": TwitterNotifier,
|
||||
"facebook": FacebookNotifier,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import re
|
||||
from typing import Optional
|
||||
|
||||
import pkg_resources
|
||||
import requests
|
||||
|
@ -98,7 +99,7 @@ class TelegramPlatform(AbstractPlatform):
|
|||
"Found a different bot than the expected one", raise_error=InvalidBot,
|
||||
)
|
||||
|
||||
def _send(self, message) -> Response:
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Response:
|
||||
return requests.post(
|
||||
url=f"https://api.telegram.org/bot{self.conf.token}/sendMessage",
|
||||
json={
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Optional
|
||||
|
||||
import pkg_resources
|
||||
from tweepy import OAuthHandler, API, TweepyException
|
||||
from tweepy.models import Status
|
||||
|
@ -59,7 +61,7 @@ class TwitterPlatform(AbstractPlatform):
|
|||
auth.set_access_token(access_token, access_secret)
|
||||
return API(auth)
|
||||
|
||||
def _send(self, message: str) -> Status:
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Status:
|
||||
try:
|
||||
return self._get_api().update_status(message)
|
||||
except TweepyException as e:
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Optional
|
||||
from urllib.parse import urljoin
|
||||
|
||||
import pkg_resources
|
||||
|
@ -59,7 +60,9 @@ class ZulipPlatform(AbstractPlatform):
|
|||
api_uri = "api/v1/"
|
||||
name = "zulip"
|
||||
|
||||
def _send_private(self, message: str) -> Response:
|
||||
def _send_private(
|
||||
self, message: str, event: Optional[MobilizonEvent] = None
|
||||
) -> Response:
|
||||
"""
|
||||
Send private messages
|
||||
"""
|
||||
|
@ -69,7 +72,7 @@ class ZulipPlatform(AbstractPlatform):
|
|||
data={"type": "private", "to": f"[{self.user_id}]", "content": message},
|
||||
)
|
||||
|
||||
def _send(self, message: str) -> Response:
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Response:
|
||||
"""
|
||||
Send stream messages
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# {{ name }}
|
||||
|
||||
🕒 {{ begin_datetime.format('DD MMMM, HH:mm') }} - {{ end_datetime.format('DD MMMM, HH:mm') }}
|
||||
|
||||
{% if location %}
|
||||
📍 {{ location }}
|
||||
|
||||
{% endif %}
|
||||
{{ description }}
|
|
@ -0,0 +1,9 @@
|
|||
# {{ name }}
|
||||
|
||||
🕒 {{ begin_datetime.format('DD MMMM, HH:mm') }} - {{ end_datetime.format('DD MMMM, HH:mm') }}
|
||||
|
||||
{% if location %}
|
||||
📍 {{ location }}
|
||||
|
||||
{% endif %}
|
||||
🔗 {{mobilizon_link}}
|
|
@ -0,0 +1 @@
|
|||
Upcoming events
|
|
@ -128,6 +128,24 @@ toml = ["toml"]
|
|||
vault = ["hvac"]
|
||||
yaml = ["ruamel.yaml"]
|
||||
|
||||
[[package]]
|
||||
name = "facebook-sdk"
|
||||
version = "4.0.0rc0"
|
||||
description = "This client library is designed to support the Facebook Graph API and the official Facebook JavaScript SDK, which is the canonical way to implement Facebook authentication."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
develop = false
|
||||
|
||||
[package.dependencies]
|
||||
requests = "*"
|
||||
|
||||
[package.source]
|
||||
type = "git"
|
||||
url = "https://github.com/mobolic/facebook-sdk.git"
|
||||
reference = "master"
|
||||
resolved_reference = "3fa89fec6a20dd070ccf57968c6f89256f237f54"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.2"
|
||||
|
@ -446,7 +464,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "763106b0d68a1b95c690e2ad828a4e847ad2532a3b13354c227f35b70f1c8ad7"
|
||||
content-hash = "eb95c081f2b389a4702ee0a144a29d3bb0a9d7e26174c2c650acaa14b0f980a7"
|
||||
|
||||
[metadata.files]
|
||||
aiosqlite = [
|
||||
|
@ -498,6 +516,7 @@ dynaconf = [
|
|||
{file = "dynaconf-3.1.5-py2.py3-none-any.whl", hash = "sha256:98f0e5d861e945c1b06ed33844b9341e6412bc121c1a9ea88668df7891d8f308"},
|
||||
{file = "dynaconf-3.1.5.tar.gz", hash = "sha256:40979cc454d8533fada490dc0945a1dbf5ea96f95dc9113f8a3b3053670fd569"},
|
||||
]
|
||||
facebook-sdk = []
|
||||
idna = [
|
||||
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
|
||||
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
|
||||
|
|
|
@ -21,6 +21,7 @@ beautifulsoup4 = "^4.9"
|
|||
markdownify = "^0.9"
|
||||
appdirs = "^1.4"
|
||||
tweepy = "^4.1.0"
|
||||
facebook-sdk = {git = "https://github.com/mobolic/facebook-sdk.git"}
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
responses = "^0.13"
|
||||
|
|
|
@ -206,7 +206,7 @@ def mock_publisher_class(message_collector):
|
|||
class MockPublisher(AbstractPlatform):
|
||||
name = "mock"
|
||||
|
||||
def _send(self, message):
|
||||
def _send(self, message, event):
|
||||
message_collector.append(message)
|
||||
|
||||
def _validate_response(self, response):
|
||||
|
@ -281,7 +281,7 @@ def mock_publisher_invalid_class(message_collector):
|
|||
|
||||
name = "mock"
|
||||
|
||||
def _send(self, message):
|
||||
def _send(self, message, event):
|
||||
message_collector.append(message)
|
||||
|
||||
def _validate_response(self, response):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
import arrow
|
||||
|
@ -48,7 +49,7 @@ def mock_publisher_invalid(message_collector):
|
|||
|
||||
name = "mock"
|
||||
|
||||
def _send(self, message):
|
||||
def _send(self, message: str, event: Optional[MobilizonEvent] = None):
|
||||
message_collector.append(message)
|
||||
|
||||
def _validate_response(self, response):
|
||||
|
@ -66,7 +67,7 @@ def mock_publisher_invalid_response(message_collector):
|
|||
|
||||
name = "mock"
|
||||
|
||||
def _send(self, message):
|
||||
def _send(self, message, event):
|
||||
message_collector.append(message)
|
||||
|
||||
def _validate_response(self, response):
|
||||
|
|
Loading…
Reference in New Issue