Add abstract functioning for publishers, mock-up for Telegram publisher

This commit is contained in:
ComradeSlyK 2021-04-23 17:04:27 +02:00
parent d61ad2537d
commit a210574f97
4 changed files with 187 additions and 0 deletions

View File

@ -1 +1,4 @@
__version__ = '0.1.0'
from . import publishers

View File

@ -0,0 +1,40 @@
from typing import Iterable
from . import abstract
from . import telegram
# WIP
# from . import twitter
def run(publishers: Iterable[abstract.AbstractPublisher]) -> dict:
invalid_credentials, invalid_event = [], []
for p in publishers:
if not p.validate_credentials():
invalid_credentials.append(p)
if not p.validate_event():
invalid_event.append(p)
if invalid_credentials or invalid_event:
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",
}

View File

@ -0,0 +1,78 @@
import inspect
import logging
from abc import ABC, abstractmethod
logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(name)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger("Publishers")
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) -> bool:
"""
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) -> bool:
"""
Validates credentials.
:return: True or False according to whether credentials are valid.
"""
raise NotImplementedError
@abstractmethod
def validate_event(self) -> bool:
"""
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)

View File

@ -0,0 +1,66 @@
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)
)
data = self.__validate_response(res)
if data.get('__error'):
self.log_error(data['__error'])
return False
return True
def validate_credentials(self) -> bool:
chat_id = self.credentials.get('chat_id')
token = self.credentials.get('token')
username = self.credentials.get('username')
if any(a is None for a in (chat_id, token, username)):
self.log_error("Required info is missing")
return False
res = requests.get(f'https://api.telegram.org/bot{token}/getMe')
data = self.__validate_response(res)
if data.get('__error'):
self.log_error(data['__error'])
return False
if not username == data.get('result', {}).get('username'):
self.log_error("Found a different bot than the expected one!")
return False
return True
def validate_event(self) -> bool:
text = self.event.get('text')
if not (text and text.strip()):
self.log_error(f"No text was found!")
return False
return True
@staticmethod
def __validate_response(res):
try:
res.raise_for_status()
except requests.exceptions.HTTPError as e:
return {'__error': str(e)}
try:
data = res.json()
except ValueError:
return {'__error': "Server returned invalid json data"}
if not data.get('ok'):
data['__error'] = f"Invalid request (response: {data})"
return data