bluesky post
This commit is contained in:
parent
bf92528331
commit
9ec737c8df
7 changed files with 81 additions and 35 deletions
|
@ -229,7 +229,7 @@ ENABLE_LOCAL_ONLY = env("NEODB_ENABLE_LOCAL_ONLY")
|
|||
|
||||
# Timeout of requests to Mastodon, in seconds
|
||||
MASTODON_TIMEOUT = env("NEODB_LOGIN_MASTODON_TIMEOUT", default=5) # type: ignore
|
||||
THREADS_TIMEOUT = 10 # Threads is really slow when publishing post
|
||||
THREADS_TIMEOUT = 30 # Threads is really slow when publishing post
|
||||
TAKAHE_REMOTE_TIMEOUT = MASTODON_TIMEOUT
|
||||
|
||||
NEODB_USER_AGENT = f"NeoDB/{NEODB_VERSION} (+{SITE_INFO.get('site_url', 'undefined')})"
|
||||
|
@ -389,6 +389,8 @@ LOGGING = {
|
|||
|
||||
logging.getLogger("requests").setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
|
||||
if SLACK_TOKEN:
|
||||
INSTALLED_APPS += [
|
||||
|
|
|
@ -3,6 +3,7 @@ import uuid
|
|||
from abc import abstractmethod
|
||||
from datetime import datetime
|
||||
from functools import cached_property
|
||||
from operator import pos
|
||||
from typing import TYPE_CHECKING, Any, Self
|
||||
|
||||
import django_rq
|
||||
|
@ -279,9 +280,7 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
def get_crosspost_params(self):
|
||||
d = {
|
||||
"visibility": self.visibility,
|
||||
"update_id": (self.metadata if hasattr(self, "metadata") else {}).get(
|
||||
"mastodon_id"
|
||||
),
|
||||
"update_ids": self.metadata.copy() if hasattr(self, "metadata") else {},
|
||||
}
|
||||
d.update(self.to_crosspost_params())
|
||||
return d
|
||||
|
@ -293,20 +292,51 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
)
|
||||
|
||||
def _sync_to_social_accounts(self, update_mode: int):
|
||||
def params_for_platform(params, platform):
|
||||
p = params.copy()
|
||||
for k in ["update_id", "reply_to_id"]:
|
||||
ks = k + "s"
|
||||
if ks in p:
|
||||
d = p.pop(ks)
|
||||
v = d.get(platform + "_id")
|
||||
if v:
|
||||
p[k] = v
|
||||
return p
|
||||
|
||||
activate_language_for_user(self.owner.user)
|
||||
params = self.get_crosspost_params()
|
||||
metadata = self.metadata.copy()
|
||||
self.sync_to_mastodon(params, update_mode)
|
||||
self.sync_to_threads(params, update_mode)
|
||||
# TODO migrate
|
||||
params = self.get_crosspost_params()
|
||||
self.sync_to_mastodon(params_for_platform(params, "mastodon"), update_mode)
|
||||
self.sync_to_threads(params_for_platform(params, "threads"), update_mode)
|
||||
self.sync_to_bluesky(params_for_platform(params, "bluesky"), update_mode)
|
||||
if self.metadata != metadata:
|
||||
self.save(update_fields=["metadata"])
|
||||
|
||||
def sync_to_bluesky(self, params, update_mode):
|
||||
# skip non-public post as Bluesky does not support it
|
||||
# update_mode 0 will act like 1 as bsky.app does not support edit
|
||||
bluesky = self.owner.user.bluesky
|
||||
if params["visibility"] != 0 or not bluesky:
|
||||
return False
|
||||
if update_mode in [0, 1]:
|
||||
post_id = self.metadata.get("bluesky_id")
|
||||
if post_id:
|
||||
try:
|
||||
bluesky.delete_post(post_id)
|
||||
except Exception as e:
|
||||
logger.warning(f"Delete {bluesky} post {post_id} error {e}")
|
||||
r = bluesky.post(**params)
|
||||
self.metadata.update({"bluesky_" + k: v for k, v in r.items()})
|
||||
return True
|
||||
|
||||
def sync_to_threads(self, params, update_mode):
|
||||
# skip non-public post as Threads does not support it
|
||||
# update_mode will be ignored as update/delete are not supported either
|
||||
threads = self.owner.user.threads
|
||||
# return
|
||||
if params["visibility"] != 0 or not threads:
|
||||
return
|
||||
return False
|
||||
try:
|
||||
r = threads.post(**params)
|
||||
except RequestAborted:
|
||||
|
@ -319,13 +349,13 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
def sync_to_mastodon(self, params, update_mode):
|
||||
mastodon = self.owner.user.mastodon
|
||||
if not mastodon:
|
||||
return
|
||||
return False
|
||||
if self.owner.user.preference.mastodon_repost_mode == 1:
|
||||
if update_mode == 1:
|
||||
toot_id = self.metadata.pop("mastodon_id", None)
|
||||
if toot_id:
|
||||
self.metadata.pop("mastodon_url", None)
|
||||
mastodon.delete(toot_id)
|
||||
mastodon.delete_post(toot_id)
|
||||
elif update_mode == 1:
|
||||
params.pop("update_id", None)
|
||||
return self.crosspost_to_mastodon(params)
|
||||
|
@ -333,6 +363,7 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
mastodon.boost(self.latest_post.url)
|
||||
else:
|
||||
logger.warning("No post found for piece")
|
||||
return True
|
||||
|
||||
def crosspost_to_mastodon(self, params):
|
||||
mastodon = self.owner.user.mastodon
|
||||
|
|
|
@ -173,10 +173,8 @@ class Note(Content):
|
|||
"spoiler_text": self.title,
|
||||
"content": self.content + footer,
|
||||
"sensitive": self.sensitive,
|
||||
"reply_to_id": (
|
||||
(self.shelfmember.metadata or {}).get("mastodon_id")
|
||||
if self.shelfmember
|
||||
else None
|
||||
"reply_to_ids": (
|
||||
self.shelfmember.metadata.copy() if self.shelfmember else {}
|
||||
),
|
||||
}
|
||||
if self.latest_post:
|
||||
|
|
|
@ -374,9 +374,7 @@ class ShelfMember(ListMember):
|
|||
else:
|
||||
spoiler, txt = None, ""
|
||||
stars = (
|
||||
self.owner.user.mastodon.rating_to_emoji(self.sibling_rating.grade)
|
||||
if self.sibling_rating and self.owner.user.mastodon
|
||||
else ""
|
||||
render_rating(self.sibling_rating.grade, 1) if self.sibling_rating else ""
|
||||
)
|
||||
content = f"{action} {stars} \n{self.item.absolute_url}\n{txt}\n{self.get_crosspost_postfix()}"
|
||||
params = {"content": content, "spoiler_text": spoiler}
|
||||
|
@ -472,7 +470,6 @@ class ShelfMember(ListMember):
|
|||
def link_post_id(self, post_id: int):
|
||||
if self.local:
|
||||
self.ensure_log_entry().link_post_id(post_id)
|
||||
print(self.ensure_log_entry(), post_id)
|
||||
return super().link_post_id(post_id)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from functools import cached_property
|
||||
from operator import pos
|
||||
|
||||
from atproto import Client, SessionEvent, client_utils
|
||||
from atproto_client import models
|
||||
from django.utils import timezone
|
||||
from loguru import logger
|
||||
|
||||
|
@ -45,7 +47,6 @@ class BlueskyAccount(SocialAccount):
|
|||
)
|
||||
|
||||
def on_session_change(self, event, session) -> None:
|
||||
logger.debug("Bluesky session changed:", event, repr(session))
|
||||
if event in (SessionEvent.CREATE, SessionEvent.REFRESH):
|
||||
session_string = session.export()
|
||||
if session_string != self.session_string:
|
||||
|
@ -84,8 +85,21 @@ class BlueskyAccount(SocialAccount):
|
|||
]
|
||||
)
|
||||
|
||||
def post(self, content):
|
||||
def post(self, content, reply_to_id=None, **kwargs):
|
||||
reply_to = None
|
||||
if reply_to_id:
|
||||
posts = self._client.get_posts([reply_to_id]).posts
|
||||
if posts:
|
||||
root_post_ref = models.create_strong_ref(posts[0])
|
||||
reply_to = models.AppBskyFeedPost.ReplyRef(
|
||||
parent=root_post_ref, root=root_post_ref
|
||||
)
|
||||
text = client_utils.TextBuilder().text(content)
|
||||
# todo OpenGraph
|
||||
# .link("Python SDK", "https://atproto.blue")
|
||||
post = self._client.send_post(text)
|
||||
return {"id": post.cid, "url": post.uri}
|
||||
post = self._client.send_post(text, reply_to=reply_to)
|
||||
# return AT uri as id since it's used as so.
|
||||
return {"cid": post.cid, "id": post.uri}
|
||||
|
||||
def delete_post(self, post_uri):
|
||||
self._client.delete_post(post_uri)
|
||||
|
|
|
@ -127,10 +127,14 @@ class Threads:
|
|||
return data
|
||||
|
||||
@staticmethod
|
||||
def post_single(token: str, user_id: str, text: str):
|
||||
def post_single(token: str, user_id: str, text: str, reply_to_id=None):
|
||||
url = f"https://graph.threads.net/v1.0/{user_id}/threads?media_type=TEXT&access_token={token}&text={quote(text)}"
|
||||
# TODO waiting for Meta to confirm it's bug or permission issue
|
||||
# if reply_to_id:
|
||||
# url += "&reply_to_id=" + reply_to_id
|
||||
response = post(url)
|
||||
if response.status_code != 200:
|
||||
logger.debug(f"Error {url} {response.status_code} {response.content}")
|
||||
return None
|
||||
media_container_id = (response.json() or {}).get("id")
|
||||
if not media_container_id:
|
||||
|
@ -138,8 +142,10 @@ class Threads:
|
|||
url = f"https://graph.threads.net/v1.0/{user_id}/threads_publish?creation_id={media_container_id}&access_token={token}"
|
||||
response = post(url)
|
||||
if response.status_code != 200:
|
||||
logger.debug(f"Error {url} {response.status_code} {response.content}")
|
||||
return None
|
||||
return (response.json() or {}).get("id")
|
||||
media_id = (response.json() or {}).get("id")
|
||||
return media_id
|
||||
|
||||
@staticmethod
|
||||
def get_single(token: str, media_id: str) -> dict | None:
|
||||
|
@ -233,15 +239,17 @@ class ThreadsAccount(SocialAccount):
|
|||
self.save(update_fields=["account_data", "handle", "last_refresh"])
|
||||
return True
|
||||
|
||||
def post(self, content: str, **kwargs):
|
||||
def post(self, content: str, reply_to_id=None, **kwargs):
|
||||
text = (
|
||||
content.replace(settings.STAR_SOLID + " ", "🌕")
|
||||
.replace(settings.STAR_HALF + " ", "🌗")
|
||||
.replace(settings.STAR_EMPTY + " ", "🌑")
|
||||
)
|
||||
media_id = Threads.post_single(self.access_token, self.uid, text)
|
||||
if media_id:
|
||||
d = Threads.get_single(self.access_token, media_id)
|
||||
if d:
|
||||
return {"id": media_id, "url": d["permalink"]}
|
||||
media_id = Threads.post_single(self.access_token, self.uid, text, reply_to_id)
|
||||
if not media_id:
|
||||
raise RequestAborted()
|
||||
return {"id": media_id}
|
||||
# if media_id:
|
||||
# d = Threads.get_single(self.access_token, media_id)
|
||||
# if d:
|
||||
# return {"id": media_id, "url": d["permalink"]}
|
||||
|
|
|
@ -51,13 +51,9 @@ def threads_oauth(request: HttpRequest):
|
|||
|
||||
@require_http_methods(["GET"])
|
||||
def threads_uninstall(request: HttpRequest):
|
||||
print(request.GET)
|
||||
print(request.POST)
|
||||
return redirect(reverse("users:data"))
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def threads_delete(request: HttpRequest):
|
||||
print(request.GET)
|
||||
print(request.POST)
|
||||
return redirect(reverse("users:data"))
|
||||
|
|
Loading…
Add table
Reference in a new issue