diff --git a/README.md b/README.md index ff48c2979c1c0c91025981b389211a3ac4ce9552..c81a04249273efada791fa48e6531f42730f36e5 100644 --- a/README.md +++ b/README.md @@ -174,6 +174,7 @@ Přes CRON je třeba na pozadí spouštět Django `manage.py` commandy: * `publish_scheduled_pages` - publikuje naplánované stránky (každou hodinu) * `update_callendars` - stáhne a aktualizuje kalendáře (několikrát denně) * `update_main_timeline_articles` - aktualizuje články na `pirati.cz` z `https://piratipracuji.cz/api/` +* `update_mastodon_feed` - aktualizuje tooty podle nastavení na různých aplikacích * `update_redmine_issues` - aktualizuje programované body MS a KS stránek napojených na Redmine (několikrát denně) * `update_tweets` - aktualizuje tweety podle nastavení na Homepage pirati.cz - vyžaduje mít v .env TWITTER_BEARER_TOKEN, parametr --days určuje stáří tweetů (default 1) * `update_instagram` - aktualizuje Instagramové posty na Homepage pirati.cz - vyžaduje mít v .env `INSTAGRAM_APP_ID` a `INSTAGRAM_APP_SECRET`. diff --git a/shared/management/comands/update_mastodon_feed.py b/shared/management/comands/update_mastodon_feed.py new file mode 100644 index 0000000000000000000000000000000000000000..ed66096baef302fe4e972d1378b76173131c3127 --- /dev/null +++ b/shared/management/comands/update_mastodon_feed.py @@ -0,0 +1,11 @@ +from django.core.management.base import BaseCommand + +from shared.services import MastodonFeedDownloadService + + +class Command(BaseCommand): + def handle(self, *args, **options): + ads = MastodonFeedDownloadService() + ads.perform_update() + + self.stdout.write("\nUpdate of mastodon finished!") diff --git a/shared/models.py b/shared/models.py index bb652dfd77351d0fc132dc7b3e12daf87e73138e..99fd4165964e2d8ba83dc9c825ce96f015599243 100644 --- a/shared/models.py +++ b/shared/models.py @@ -1,5 +1,5 @@ +import hashlib import logging -from time import time from attr import dataclass from django.core.files.temp import NamedTemporaryFile @@ -15,15 +15,12 @@ from wagtail.models import Page from shared.blocks import ( DEFAULT_CONTENT_BLOCKS, FooterLinksBlock, - ImageCreatorMixin, MenuItemBlock, MenuParentBlock, ) logger = logging.getLogger(__name__) -FIVE_MINUTES_IN_SECONDS = 300 - @dataclass class MastodonImage: @@ -66,12 +63,26 @@ class MastodonUser: preferredUsername: str -class Mastodon(ImageCreatorMixin, models.Model): +class MastodonImageMixin(models.Model): + image = models.ImageField(null=True, blank=True, upload_to="mastodon/") + + def download_image(self, url, name): + response = request("GET", url, stream=True) + response.raw.decode_content = True + # Taken from: https://gist.github.com/anderser/2172888 + temporary_stored_image = NamedTemporaryFile(delete=True) + temporary_stored_image.write(response.content) + temporary_stored_image.flush() + self.image.save(name, temporary_stored_image) + + class Meta: + abstract = True + + +class Mastodon(MastodonImageMixin, models.Model): url = models.URLField() toots = models.JSONField(encoder=DjangoJSONEncoder, null=True) summary = models.TextField(null=True, blank=True) - user_image = models.ImageField(null=True, blank=True, upload_to="mastodon_users/") - timestamp = models.FloatField() def download_toots(self): """ @@ -89,31 +100,41 @@ class Mastodon(ImageCreatorMixin, models.Model): full_url = self.url + ".json" return request("GET", full_url).json() - def parse_url(self, now: float): + def parse_url(self): toots: MastodonToots = self.download_toots() user: MastodonUser = self.download_user() - # Taken from: https://gist.github.com/anderser/2172888 - temporary_stored_image = NamedTemporaryFile(delete=True) - temporary_stored_image.write(user.icon.url) - temporary_stored_image.flush() - self.user_image.save(f"{user.preferredUsername}.jpg", temporary_stored_image) + self.download_image(f"{user.preferredUsername}.jpg", user["icon"]["url"]) + + for toot in toots["orderedItems"]: + if toot["attachment"] != None: + for attachment in toot["attachment"]: + mastodon_attachment: MastodonAttachment = ( + MastodonAttachment.objects.create(mastodon=self) + ) + mastodon_attachment.download_image( + attachment["url"], + hashlib.md5(attachment["url"].encode("ascii")).hexdigest(), + ) + attachment["url"] = mastodon_attachment.image.url self.toots = toots.orderedItems self.summary = user.summary - self.timestamp = now def refresh_toots(self): - now = time() - if self.timestamp != None and now - self.timestamp < FIVE_MINUTES_IN_SECONDS: - return try: - self.parse_url(now) + self.parse_url() self.save() except: logger.error("Mastodon refresh failed for %s", self.url, exc_info=True) +class MastodonAttachment(MastodonImageMixin, models.Model): + mastodon = models.ForeignKey( + Mastodon, null=True, blank=True, on_delete=models.PROTECT + ) + + class MastodonFeedMixin(models.Model): mastodon_feed = models.URLField( verbose_name="Mastodon feed", @@ -132,7 +153,7 @@ class MastodonFeedMixin(models.Model): if self.mastodon is not None else Mastodon.objects.create(url=self.calendar_url) ) - mastodon.parse_url(time()) + mastodon.parse_url() except: raise ValidationError("Update mastodonu se nepovedl") diff --git a/shared/services.py b/shared/services.py new file mode 100644 index 0000000000000000000000000000000000000000..533439504f595b7621ffada83d4764e45038027a --- /dev/null +++ b/shared/services.py @@ -0,0 +1,15 @@ +import logging +from typing import TYPE_CHECKING + +from shared.models import Mastodon + +if TYPE_CHECKING: + pass + +logger = logging.getLogger() + + +class MastodonFeedDownloadService: + def perform_update(self): + for mastodon in Mastodon.objects.all(): + mastodon.refresh_toots()