mirror of
https://github.com/Tech-Workers-Coalition-Italia/mobilizon-reshare.git
synced 2025-02-11 17:20:46 +01:00
Merge pull request #2 from Tech-Workers-Coalition-Italia/publishers-dev
Publishers dev
This commit is contained in:
commit
5c9f5d37cd
2
.gitignore
vendored
2
.gitignore
vendored
@ -171,4 +171,4 @@ com_crashlytics_export_strings.xml
|
|||||||
crashlytics.properties
|
crashlytics.properties
|
||||||
crashlytics-build.properties
|
crashlytics-build.properties
|
||||||
fabric.properties
|
fabric.properties
|
||||||
|
.idea
|
||||||
|
@ -1 +1,4 @@
|
|||||||
__version__ = '0.1.0'
|
__version__ = '0.1.0'
|
||||||
|
|
||||||
|
|
||||||
|
from . import publishers
|
||||||
|
37
mobilizon_bots/publishers/__init__.py
Normal file
37
mobilizon_bots/publishers/__init__.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from typing import Iterable
|
||||||
|
|
||||||
|
from mobilizon_bots.publishers import abstract
|
||||||
|
|
||||||
|
|
||||||
|
def run(publishers: Iterable[abstract.AbstractPublisher]) -> dict:
|
||||||
|
invalid_credentials, invalid_event = [], []
|
||||||
|
for p in publishers:
|
||||||
|
if not p.are_credetials_valid():
|
||||||
|
invalid_credentials.append(p)
|
||||||
|
if not p.is_event_valid():
|
||||||
|
invalid_event.append(p)
|
||||||
|
if invalid_credentials or invalid_event:
|
||||||
|
# TODO: consider to use exceptions or data class if necessary
|
||||||
|
return {
|
||||||
|
"status": "fail",
|
||||||
|
"description": "Validation failed for at least 1 publisher",
|
||||||
|
"invalid_credentials": invalid_credentials,
|
||||||
|
"invalid_event": invalid_event,
|
||||||
|
}
|
||||||
|
failed_publishers, successful_publishers = [], []
|
||||||
|
for p in publishers:
|
||||||
|
if p.post():
|
||||||
|
successful_publishers.append(p)
|
||||||
|
else:
|
||||||
|
failed_publishers.append(p)
|
||||||
|
if failed_publishers:
|
||||||
|
return {
|
||||||
|
"status": "fail",
|
||||||
|
"description": "Posting failed for at least 1 publisher",
|
||||||
|
"failed_publishers": failed_publishers,
|
||||||
|
"successful_publishers": successful_publishers,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"status": "success",
|
||||||
|
"description": "https://www.youtube.com/watch?v=2lHgmC6PBBE",
|
||||||
|
}
|
92
mobilizon_bots/publishers/abstract.py
Normal file
92
mobilizon_bots/publishers/abstract.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AbstractPublisher(ABC):
|
||||||
|
"""
|
||||||
|
Generic publisher class.
|
||||||
|
|
||||||
|
Shall be inherited from specific subclasses that will manage validation
|
||||||
|
process for events and credentials, text formatting, posting, etc.
|
||||||
|
|
||||||
|
Class attributes:
|
||||||
|
- ``credentials``: a ``dict`` containing every useful info that the
|
||||||
|
current publisher will need to correctly login to its platform
|
||||||
|
- ``event``: a ``dict`` containing every useful info from the event
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: will the actual event be managed by its own class?
|
||||||
|
def __init__(self, credentials: dict, event: dict):
|
||||||
|
self.credentials = credentials
|
||||||
|
self.event = event
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return type(self).__name__
|
||||||
|
|
||||||
|
__str__ = __repr__
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def post(self) -> dict:
|
||||||
|
"""
|
||||||
|
Publishes the actual post on social media using ``data`` info.
|
||||||
|
:return: True or False according to whether publisher was able to
|
||||||
|
complete its task
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def validate_credentials(self) -> dict:
|
||||||
|
"""
|
||||||
|
Validates credentials.
|
||||||
|
:return: True or False according to whether credentials are valid.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def are_credentials_valid(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.validate_credentials()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_event_valid(self) -> bool:
|
||||||
|
try:
|
||||||
|
self.validate_event()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def validate_event(self) -> dict:
|
||||||
|
"""
|
||||||
|
Validates publisher's event.
|
||||||
|
:return: True or False according to whether event is valid.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def _log_debug(self, msg, *args, **kwargs):
|
||||||
|
self.__log(logging.DEBUG, msg, *args, **kwargs)
|
||||||
|
|
||||||
|
def _log_info(self, msg, *args, **kwargs):
|
||||||
|
self.__log(logging.INFO, msg, *args, **kwargs)
|
||||||
|
|
||||||
|
def _log_warning(self, msg, *args, **kwargs):
|
||||||
|
self.__log(logging.WARNING, msg, *args, **kwargs)
|
||||||
|
|
||||||
|
def _log_error(self, msg, *args, **kwargs):
|
||||||
|
self.__log(logging.ERROR, msg, *args, **kwargs)
|
||||||
|
|
||||||
|
def _log_critical(self, msg, *args, **kwargs):
|
||||||
|
self.__log(logging.CRITICAL, msg, *args, **kwargs)
|
||||||
|
|
||||||
|
def __log(self, level, msg, *args, **kwargs):
|
||||||
|
method = inspect.currentframe().f_back.f_back.f_code.co_name
|
||||||
|
logger.log(level, f"{self}.{method}(): {msg}", *args, **kwargs)
|
||||||
|
|
||||||
|
def _log_error_and_raise(self, message):
|
||||||
|
self._log_error(message)
|
||||||
|
raise ValueError(message)
|
50
mobilizon_bots/publishers/telegram.py
Normal file
50
mobilizon_bots/publishers/telegram.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
from .abstract import AbstractPublisher
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramPublisher(AbstractPublisher):
|
||||||
|
def post(self) -> bool:
|
||||||
|
chat_id = self.credentials["chat_id"]
|
||||||
|
text = self.event["text"]
|
||||||
|
token = self.credentials["token"]
|
||||||
|
post_params_kwargs = self.event.get("post_params_kwargs")
|
||||||
|
res = requests.post(
|
||||||
|
url=f"https://api.telegram.org/bot{token}/sendMessage",
|
||||||
|
params=dict(chat_id=chat_id, text=text, **post_params_kwargs),
|
||||||
|
)
|
||||||
|
return self._validate_response(res)
|
||||||
|
|
||||||
|
def validate_credentials(self) -> bool:
|
||||||
|
chat_id = self.credentials.get("chat_id")
|
||||||
|
token = self.credentials.get("token")
|
||||||
|
username = self.credentials.get("username")
|
||||||
|
if all([chat_id, token, username]):
|
||||||
|
# TODO: add composable errors to highlight which credentials are missing
|
||||||
|
self._log_error_and_raise("Some credentials are missing")
|
||||||
|
|
||||||
|
res = requests.get(f"https://api.telegram.org/bot{token}/getMe")
|
||||||
|
data = self._validate_response(res)
|
||||||
|
|
||||||
|
if not username == data.get("result", {}).get("username"):
|
||||||
|
self._log_error_and_raise("Found a different bot than the expected one")
|
||||||
|
return data
|
||||||
|
|
||||||
|
def validate_event(self) -> bool:
|
||||||
|
text = self.event.get("text")
|
||||||
|
if not (text and text.strip()):
|
||||||
|
self._log_error_and_raise("No text was found. Invalid event")
|
||||||
|
|
||||||
|
def _validate_response(self, res):
|
||||||
|
res.raise_for_status()
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = res.json()
|
||||||
|
except ValueError as e:
|
||||||
|
self._log_error("Server returned invalid json data")
|
||||||
|
raise ValueError from e
|
||||||
|
|
||||||
|
if not data.get("ok"):
|
||||||
|
raise ValueError(f"Invalid request (response: {data})")
|
||||||
|
|
||||||
|
return data
|
0
tests/publishers/__init__.py
Normal file
0
tests/publishers/__init__.py
Normal file
49
tests/publishers/test_abstract_predicates.py
Normal file
49
tests/publishers/test_abstract_predicates.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from mobilizon_bots.publishers.abstract import AbstractPublisher
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_publisher_valid():
|
||||||
|
class MockPublisher(AbstractPublisher):
|
||||||
|
def validate_credentials(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def validate_event(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def post(self) -> dict:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return MockPublisher({}, {})
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_publisher_invalid():
|
||||||
|
class MockPublisher(AbstractPublisher):
|
||||||
|
def validate_credentials(self) -> dict:
|
||||||
|
raise ValueError("Error")
|
||||||
|
|
||||||
|
def validate_event(self) -> dict:
|
||||||
|
raise ValueError("Error")
|
||||||
|
|
||||||
|
def post(self) -> dict:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
return MockPublisher({}, {})
|
||||||
|
|
||||||
|
|
||||||
|
def test_are_credentials_valid(mock_publisher_valid):
|
||||||
|
assert mock_publisher_valid.are_credentials_valid()
|
||||||
|
|
||||||
|
|
||||||
|
def test_are_credentials_valid_false(mock_publisher_invalid):
|
||||||
|
assert not mock_publisher_invalid.are_credentials_valid()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_event_valid(mock_publisher_valid):
|
||||||
|
assert mock_publisher_valid.is_event_valid()
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_event_valid_false(mock_publisher_invalid):
|
||||||
|
assert not mock_publisher_invalid.is_event_valid()
|
@ -1,5 +0,0 @@
|
|||||||
from mobilizon_bots import __version__
|
|
||||||
|
|
||||||
|
|
||||||
def test_version():
|
|
||||||
assert __version__ == '0.1.0'
|
|
Loading…
x
Reference in New Issue
Block a user