Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mastodon job publisher #582

Merged
merged 3 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions joboffers/management/commands/test_mastodon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from joboffers.management.commands import TestPublishCommand
from joboffers.publishers.mastodon import MastodonPublisher


class Command(TestPublishCommand):
help = 'Test sending a post to Mastodon.'

def handle(self, *args, **options):
"""Post a message to Mastodon."""
self._handle_publish(options, MastodonPublisher)
48 changes: 48 additions & 0 deletions joboffers/publishers/mastodon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import logging

from django.conf import settings
from mastodon import Mastodon, errors

from joboffers.utils import hash_secret
from joboffers.publishers import Publisher


def _repr_credentials():
"""Show a string representation of mastodon credentials."""
# Need to convert to string, in case they are strings or they are not set
credentials_repr = (
f' MASTODON_AUTH_TOKEN: {hash_secret(settings.MASTODON_AUTH_TOKEN)} '
f' MASTODON_API_BASE_URL: {hash_secret(settings.MASTODON_API_BASE_URL)} '
)
return credentials_repr


ERROR_LOG_MESSAGE = (
'Falló al querer tootear con las siguientes credenciales (hasheadas): %s - Error: %s'
)


class MastodonPublisher(Publisher):
"""Mastodon Publisher."""

name = 'Mastodon'

def _push_to_api(self, message: str, title: str, link: str):
"""Publish a message to mastodon."""
mastodon = Mastodon(
access_token=settings.MASTODON_AUTH_TOKEN,
api_base_url=settings.MASTODON_API_BASE_URL,
)

try:
mastodon.status_post(message)
except errors.MastodonUnauthorizedError as err:
status = None
logging.error(ERROR_LOG_MESSAGE, _repr_credentials(), err)
except Exception as err:
status = None
logging.error("Unknown error when tooting: %s", repr(err))
else:
status = 200

return status
3 changes: 3 additions & 0 deletions joboffers/publishers/mastodon/template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Oferta:
{{ job_offer.short_description }}
{{ job_offer.get_full_url }}
20 changes: 12 additions & 8 deletions joboffers/tests/test_joboffer_publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from ..publishers.facebook import FacebookPublisher
from ..publishers.telegram import TelegramPublisher
from ..publishers.twitter import TwitterPublisher
from ..publishers.mastodon import MastodonPublisher
from ..models import OfferState
from .factories import JobOfferFactory

Expand Down Expand Up @@ -78,21 +79,24 @@ def test_publisher_publish_error():
@pytest.mark.django_db
@patch('joboffers.publishers.publish_offer')
def test_publisher_to_all_social_networks_works_ok(publish_offer_function, settings):
"""
Test that publish_to_all_social_networks() uses all the condifured publishers
"""
"""Test that publish_to_all_social_networks() uses all the configured publishers."""
joboffer = JobOfferFactory.create(state=OfferState.ACTIVE)
settings.SOCIAL_NETWORKS_PUBLISHERS = [
'joboffers.publishers.discourse.DiscoursePublisher',
'joboffers.publishers.facebook.FacebookPublisher',
'joboffers.publishers.telegram.TelegramPublisher',
'joboffers.publishers.twitter.TwitterPublisher'
'joboffers.publishers.discourse.DiscoursePublisher',
'joboffers.publishers.facebook.FacebookPublisher',
'joboffers.publishers.telegram.TelegramPublisher',
'joboffers.publishers.twitter.TwitterPublisher',
'joboffers.publishers.mastodon.MastodonPublisher',
]

publish_to_all_social_networks(joboffer)

expected_publishers = [
DiscoursePublisher, FacebookPublisher, TelegramPublisher, TwitterPublisher
DiscoursePublisher,
FacebookPublisher,
TelegramPublisher,
TwitterPublisher,
MastodonPublisher,
]

assert publish_offer_function.called
Expand Down
60 changes: 60 additions & 0 deletions joboffers/tests/test_mastodon_publisher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from unittest.mock import patch

import mastodon

from ..publishers.mastodon import MastodonPublisher

DUMMY_MESSAGE = 'message'
DUMMY_TITLE = 'title'
DUMMY_LINK = 'https://example.com'


class DummyAPIBad:
def __init__(self, to_raise):
self.to_raise = to_raise

def status_post(self, *args, **kwargs):
raise self.to_raise


class DummyAPIOK:
def status_post(*args, **kwargs):
return


@patch('joboffers.publishers.mastodon.Mastodon')
def test_push_to_api_bad_credentials(mock_api, settings, caplog):
"""Test exception when the credentials are wrong."""
mock_api.return_value = DummyAPIBad(mastodon.errors.MastodonUnauthorizedError("bad auth"))
settings.MASTODON_AUTH_TOKEN = "wrong"
settings.MASTODON_API_BASE_URL = "creds"

status = MastodonPublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)
assert status is None

expected_error_message = "Falló al querer tootear con las siguientes credenciales (hasheadas)"
assert expected_error_message in caplog.text


@patch('joboffers.publishers.mastodon.Mastodon')
def test_push_to_api_generic_error(mock_api, settings, caplog):
"""Something went wrong."""
mock_api.return_value = DummyAPIBad(ValueError("boom"))
settings.MASTODON_AUTH_TOKEN = "good"
settings.MASTODON_API_BASE_URL = "creds"

status = MastodonPublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)
assert status is None

expected_error_message = "Unknown error when tooting: ValueError"
assert expected_error_message in caplog.text


@patch('joboffers.publishers.mastodon.Mastodon')
def test_push_to_api_ok(mock_api, settings):
mock_api.return_value = DummyAPIOK
settings.MASTODON_AUTH_TOKEN = "good"
settings.MASTODON_API_BASE_URL = "creds"

status = MastodonPublisher()._push_to_api(DUMMY_MESSAGE, DUMMY_TITLE, DUMMY_LINK)
assert status == 200
13 changes: 9 additions & 4 deletions pyarweb/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,17 +277,22 @@
TWITTER_CONSUMER_KEY = os.environ.get('TWITTER_CONSUMER_KEY')
TWITTER_CONSUMER_SECRET = os.environ.get('TWITTER_CONSUMER_SECRET')

# Mastodon constants
MASTODON_AUTH_TOKEN = os.environ.get('MASTODON_AUTH_TOKEN')
MASTODON_API_BASE_URL = os.environ.get('MASTODON_API_BASE_URL')

# Discourse constants
DISCOURSE_HOST = os.environ.get('DISCOURSE_HOST')
DISCOURSE_API_KEY = os.environ.get('DISCOURSE_API_KEY')
DISCOURSE_USERNAME = os.environ.get('DISCOURSE_USERNAME')
DISCOURSE_CATEGORY = os.environ.get('DISCOURSE_CATEGORY')

SOCIAL_NETWORKS_PUBLISHERS = [
'joboffers.publishers.discourse.DiscoursePublisher',
'joboffers.publishers.facebook.FacebookPublisher',
'joboffers.publishers.telegram.TelegramPublisher',
'joboffers.publishers.twitter.TwitterPublisher'
'joboffers.publishers.discourse.DiscoursePublisher',
'joboffers.publishers.facebook.FacebookPublisher',
'joboffers.publishers.telegram.TelegramPublisher',
'joboffers.publishers.twitter.TwitterPublisher',
'joboffers.publishers.mastodon.MastodonPublisher',
]

DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ django-tagging==0.5.0
django-taggit==1.5.1
django-taggit-autosuggest==0.3.8
lxml==4.9.1
Mastodon.py==1.8.1
plotly==5.7.0
psycopg2-binary==2.9.1
tweepy==4.5.0
Expand Down
Loading