Skip to content

Commit

Permalink
Add support for automated syndication to Bluesky (#498)
Browse files Browse the repository at this point in the history
* Add support for Bluesky syndication

* Python package updates
  • Loading branch information
Apreche authored Nov 23, 2024
1 parent 0a492cd commit 2a1765f
Show file tree
Hide file tree
Showing 8 changed files with 1,158 additions and 363 deletions.
1,350 changes: 988 additions & 362 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = ""
authors = ["Scott Rubin <[email protected]>"]

[tool.poetry.dependencies]
python = "^3.11"
python = ">=3.11,<3.14"
Django = "^4.0"
django-extensions = "^3.1.5"
celery = "^5.4.0"
Expand Down Expand Up @@ -33,6 +33,7 @@ requests = "^2.28.2"
tqdm = "^4.64.1"
flower = "^2.0.0"
django-model-utils = "^4.3.1"
atproto = "^0.0.55"

[tool.poetry.group.test.dependencies]
tblib = "^3.0.0"
Expand Down
49 changes: 49 additions & 0 deletions shows/migrations/0034_add_bluesky_related_link_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from django.db import migrations
from django.utils.translation import gettext_lazy as _

from shows import models


def create_related_link_types(apps, schema_editor):
RelatedLinkType = apps.get_model("shows", "RelatedLinkType")
RelatedLinkType.objects.create(
id=models.RelatedLinkType.BLUESKY_POST,
description=_("Bluesky Post"),
plural_description=_("Bluesky Posts"),
slug="bluesky_chat",
plural_slug="bluesky_chats",
)


def delete_related_link_types(apps, schema_editor):
RelatedLinkType = apps.get_model("shows", "RelatedLinkType")
RelatedLinkType.objects.filter(
id=models.RelatedLinkType.BLUESKY_POST,
).delete()


class Migration(migrations.Migration):
dependencies = [
("shows", "0033_add_discord_related_link_type"),
]

operations = [
migrations.RunPython(
create_related_link_types,
delete_related_link_types,
),
migrations.RunSQL(
sql=[
(
"ALTER sequence shows_relatedlinktype_id_seq RESTART with %s;",
[models.RelatedLinkType.BLUESKY_POST + 1],
),
],
reverse_sql=[
(
"ALTER sequence shows_relatedlinktype_id_seq RESTART with %s;",
[models.RelatedLinkType.BLUESKY_POST],
)
],
),
]
1 change: 1 addition & 0 deletions shows/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ class RelatedLinkType(models.Model):
FORUM_THREAD = 2
PURCHASE_LINK = 3
DISCORD_CHAT = 4
BLUESKY_POST = 5

description = models.TextField(unique=True)
plural_description = models.TextField(unique=True)
Expand Down
15 changes: 15 additions & 0 deletions syndicators/admin.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
from django import forms
from django.contrib import admin

from . import models


class BlueskyForm(forms.ModelForm):
class Meta:
model = models.Bluesky
widgets = {
"password": forms.PasswordInput(),
}
fields = "__all__"


@admin.register(models.Bluesky)
class BlueskyAdmin(admin.ModelAdmin):
form = BlueskyForm


@admin.register(models.Discord)
class DiscordAdmin(admin.ModelAdmin):
pass
Expand Down
32 changes: 32 additions & 0 deletions syndicators/migrations/0003_bluesky.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 4.2.16 on 2024-11-23 04:02

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
dependencies = [
("syndicators", "0002_add_discord_syndicator"),
]

operations = [
migrations.CreateModel(
name="Bluesky",
fields=[
(
"syndicator_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="syndicators.syndicator",
),
),
("username", models.TextField()),
("password", models.TextField()),
],
bases=("syndicators.syndicator",),
),
]
2 changes: 2 additions & 0 deletions syndicators/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from .bluesky import Bluesky
from .discord import Discord
from .discourse import Discourse
from .syndication import Syndicator

__all__ = [
"Bluesky",
"Discord",
"Discourse",
"Syndicator",
Expand Down
69 changes: 69 additions & 0 deletions syndicators/models/bluesky.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import logging

import atproto
from django.contrib.sites.models import Site
from django.db import models
from django.utils.html import strip_tags

from shows import models as show_models

from .syndication import Syndicator

logger = logging.getLogger(__name__)


class Bluesky(Syndicator):
username = models.TextField()
password = models.TextField()

def __str__(self) -> str:
return self.username

def format_content(self, content):
domain = Site.objects.get_current().domain
path = content.get_absolute_url()
return f"https://{domain}{path}"

def post(self, content):
client = atproto.Client()
client.login(self.username, self.password)

# Bluesky can't intelligently embed, we need to do it ourselves
# https://github.com/MarshalX/atproto/blob/main/examples/advanced_usage/send_ogp_link_card.py

embed_data = {
"title": content.title,
"description": strip_tags(content.rendered_html),
"uri": self.format_content(content),
}

image = content.image or content.show.logo or None
if image:
image.seek(0)
image_data = image.read()
thumb = client.upload_blob(image_data).blob
embed_data["thumb"] = thumb

external = atproto.models.AppBskyEmbedExternal.External(**embed_data)
embed_external = atproto.models.AppBskyEmbedExternal.Main(external=external)
response = client.send_post(
text=content.title,
embed=embed_external,
)
return response.model_dump()

def create_related_link(self, content, post_result):
at_uri = post_result.get("uri", None)
if at_uri is None:
return
try:
post_id = at_uri.split("/")[-1]
except IndexError:
return
post_url = f"https://bsky.app/profile/{self.username}/post/{post_id}"
show_models.RelatedLink.objects.create(
content=content,
type_id=show_models.RelatedLinkType.BLUESKY_POST,
title=content.title,
url=post_url,
)

0 comments on commit 2a1765f

Please sign in to comment.