bluesky post links and embeds
This commit is contained in:
parent
4e97cb7083
commit
b727397cd1
16 changed files with 152 additions and 124 deletions
|
@ -498,6 +498,10 @@ class Item(PolymorphicModel):
|
|||
def display_title(self) -> str:
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def display_description(self):
|
||||
return self.brief[:155]
|
||||
|
||||
@classmethod
|
||||
def get_by_url(cls, url_or_b62: str, resolve_merge=False) -> "Self | None":
|
||||
b62 = url_or_b62.strip().split("/")[-1]
|
||||
|
|
|
@ -12,12 +12,7 @@ from users.models import APIdentity
|
|||
|
||||
from .common import Content
|
||||
from .rating import Rating
|
||||
from .renderers import (
|
||||
render_post_with_macro,
|
||||
render_rating,
|
||||
render_spoiler_text,
|
||||
render_text,
|
||||
)
|
||||
from .renderers import render_post_with_macro, render_spoiler_text, render_text
|
||||
from .shelf import ShelfManager, ShelfType
|
||||
|
||||
|
||||
|
@ -125,15 +120,14 @@ class Comment(Content):
|
|||
|
||||
def to_crosspost_params(self):
|
||||
spoiler_text, txt = render_spoiler_text(self.text, self.item)
|
||||
content = (
|
||||
self.get_crosspost_template().format(item=self.item.display_title)
|
||||
+ f"\n{txt}\n{settings.SITE_INFO['site_url']}{self.item_url}"
|
||||
+ self.get_crosspost_postfix()
|
||||
)
|
||||
txt = "\n" + txt if txt else ""
|
||||
action = self.get_crosspost_template().format(item="##obj##")
|
||||
content = f"{action}\n##obj_link_if_plain##{txt}{self.get_crosspost_postfix()}"
|
||||
params = {
|
||||
"content": content,
|
||||
"spoiler_text": spoiler_text,
|
||||
"sensitive": bool(spoiler_text),
|
||||
"obj": self.item,
|
||||
}
|
||||
return params
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ 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
|
||||
|
@ -123,11 +122,11 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
|
||||
@property
|
||||
def url(self):
|
||||
return f"/{self.url_path}/{self.uuid}" if self.url_path else None
|
||||
return f"/{self.url_path}/{self.uuid}"
|
||||
|
||||
@property
|
||||
def absolute_url(self):
|
||||
return (settings.SITE_INFO["site_url"] + self.url) if self.url_path else None
|
||||
return settings.SITE_INFO["site_url"] + self.url
|
||||
|
||||
@property
|
||||
def api_url(self):
|
||||
|
@ -219,10 +218,18 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
|
||||
@abstractmethod
|
||||
def to_post_params(self) -> dict[str, Any]:
|
||||
"""
|
||||
returns a dict of parameter to create a post
|
||||
"""
|
||||
return {}
|
||||
|
||||
@abstractmethod
|
||||
def to_crosspost_params(self) -> dict[str, Any]:
|
||||
"""
|
||||
returns a dict of parameter to create a post for each platform
|
||||
"content" - required, may contain ##obj## / ##obj_link_if_plain## / ##rating##
|
||||
...
|
||||
"""
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
|
@ -343,6 +350,7 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
except Exception as e:
|
||||
logger.warning(f"Delete {bluesky} post {post_id} error {e}")
|
||||
r = bluesky.post(**params)
|
||||
if r:
|
||||
self.metadata.update({"bluesky_" + k: v for k, v in r.items()})
|
||||
return True
|
||||
|
||||
|
@ -359,6 +367,7 @@ class Piece(PolymorphicModel, UserOwnedObjectMixin):
|
|||
logger.warning(f"{self} post to {threads} failed")
|
||||
messages.error(threads.user, _("A recent post was not posted to Threads."))
|
||||
return False
|
||||
if r:
|
||||
self.metadata.update({"threads_" + k: v for k, v in r.items()})
|
||||
return True
|
||||
|
||||
|
@ -495,6 +504,14 @@ class Content(Piece):
|
|||
def __str__(self):
|
||||
return f"{self.__class__.__name__}:{self.uuid}@{self.item}"
|
||||
|
||||
@property
|
||||
def display_title(self) -> str:
|
||||
raise NotImplementedError("subclass should override this")
|
||||
|
||||
@property
|
||||
def display_description(self) -> str:
|
||||
raise NotImplementedError("subclass should override this")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
|
|
@ -4,12 +4,10 @@ from typing import Any
|
|||
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from loguru import logger
|
||||
|
||||
from catalog.models import Item
|
||||
from mastodon.models import get_spoiler_text
|
||||
from takahe.utils import Takahe
|
||||
from users.models import APIdentity, User
|
||||
from users.models import APIdentity
|
||||
|
||||
from .comment import Comment
|
||||
from .note import Note
|
||||
|
|
|
@ -73,7 +73,7 @@ def render_rating(score: int | None, star_mode=0) -> str:
|
|||
solid_stars = score // 2
|
||||
half_star = int(bool(score % 2))
|
||||
empty_stars = 5 - solid_stars if not half_star else 5 - solid_stars - 1
|
||||
if star_mode == 1:
|
||||
if star_mode == 0:
|
||||
emoji_code = "🌕" * solid_stars + "🌗" * half_star + "🌑" * empty_stars
|
||||
else:
|
||||
emoji_code = (
|
||||
|
@ -87,12 +87,10 @@ def render_rating(score: int | None, star_mode=0) -> str:
|
|||
|
||||
|
||||
def render_spoiler_text(text, item):
|
||||
if not text:
|
||||
return None, ""
|
||||
if text.find(">!") != -1:
|
||||
if text and text.find(">!") != -1:
|
||||
spoiler_text = _(
|
||||
"regarding {item_title}, may contain spoiler or triggering content"
|
||||
).format(item_title=item.display_title)
|
||||
return spoiler_text, text.replace(">!", "").replace("!<", "")
|
||||
else:
|
||||
return None, text
|
||||
return None, text or ""
|
||||
|
|
|
@ -27,6 +27,14 @@ class Review(Content):
|
|||
title = models.CharField(max_length=500, blank=False, null=False)
|
||||
body = MarkdownxField()
|
||||
|
||||
@property
|
||||
def display_title(self):
|
||||
return self.title
|
||||
|
||||
@property
|
||||
def display_description(self):
|
||||
return self.plain_content[:155]
|
||||
|
||||
@property
|
||||
def html_content(self):
|
||||
return render_md(self.body)
|
||||
|
@ -89,10 +97,10 @@ class Review(Content):
|
|||
def to_crosspost_params(self):
|
||||
content = (
|
||||
self.get_crosspost_template().format(item=self.item.display_title)
|
||||
+ f"\n{self.title}\n{self.absolute_url} "
|
||||
+ " ##rating##\n##obj##\n##obj_link_if_plain##"
|
||||
+ self.get_crosspost_postfix()
|
||||
)
|
||||
params = {"content": content}
|
||||
params = {"content": content, "obj": self, "rating": self.rating_grade}
|
||||
return params
|
||||
|
||||
def to_post_params(self):
|
||||
|
@ -103,9 +111,7 @@ class Review(Content):
|
|||
)
|
||||
+ f'<br><a href="{self.absolute_url}">{self.title}</a>'
|
||||
)
|
||||
content = (
|
||||
f"{render_rating(self.rating_grade, 1)}\n{self.get_crosspost_postfix()}"
|
||||
)
|
||||
content = f"{render_rating(self.rating_grade)}\n{self.get_crosspost_postfix()}"
|
||||
return {
|
||||
"prepend_content": prepend_content,
|
||||
"content": content,
|
||||
|
|
|
@ -14,7 +14,7 @@ from users.models import APIdentity
|
|||
|
||||
from .common import q_item_in_category
|
||||
from .itemlist import List, ListMember
|
||||
from .renderers import render_post_with_macro, render_rating
|
||||
from .renderers import render_post_with_macro, render_rating, render_spoiler_text
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .comment import Comment
|
||||
|
@ -368,16 +368,20 @@ class ShelfMember(ListMember):
|
|||
return _(ShelfManager.get_action_template(self.shelf_type, self.item.category))
|
||||
|
||||
def to_crosspost_params(self):
|
||||
action = self.get_crosspost_template().format(item=self.item.display_title)
|
||||
action = self.get_crosspost_template().format(item="##obj##")
|
||||
if self.sibling_comment:
|
||||
spoiler, txt = Takahe.get_spoiler_text(self.sibling_comment.text, self.item)
|
||||
spoiler, txt = render_spoiler_text(self.sibling_comment.text, self.item)
|
||||
else:
|
||||
spoiler, txt = None, ""
|
||||
stars = (
|
||||
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}
|
||||
rating = self.sibling_rating.grade if self.sibling_rating else ""
|
||||
txt = "\n" + txt if txt else ""
|
||||
content = f"{action} ##rating## \n##obj_link_if_plain##{txt}{self.get_crosspost_postfix()}"
|
||||
params = {
|
||||
"content": content,
|
||||
"spoiler_text": spoiler,
|
||||
"obj": self.item,
|
||||
"rating": rating,
|
||||
}
|
||||
return params
|
||||
|
||||
def to_post_params(self):
|
||||
|
@ -386,14 +390,14 @@ class ShelfMember(ListMember):
|
|||
item=f'<a href="{item_link}">{self.item.display_title}</a>'
|
||||
)
|
||||
if self.sibling_comment:
|
||||
spoiler, txt = Takahe.get_spoiler_text(self.sibling_comment.text, self.item)
|
||||
spoiler, txt = render_spoiler_text(self.sibling_comment.text, self.item)
|
||||
else:
|
||||
spoiler, txt = None, ""
|
||||
postfix = self.get_crosspost_postfix()
|
||||
# add @user.mastodon.handle so that user can see it on Mastodon ?
|
||||
# if self.visibility and self.owner.user.mastodon:
|
||||
# postfix += f" @{self.owner.user.mastodon.handle}"
|
||||
content = f"{render_rating(self.sibling_rating.grade, 1) if self.sibling_rating else ''} \n{txt}\n{postfix}"
|
||||
content = f"{render_rating(self.sibling_rating.grade) if self.sibling_rating else ''} \n{txt}\n{postfix}"
|
||||
return {
|
||||
"prepend_content": action,
|
||||
"content": content,
|
||||
|
|
|
@ -6,7 +6,6 @@ from .mastodon import (
|
|||
MastodonAccount,
|
||||
MastodonApplication,
|
||||
detect_server_info,
|
||||
get_spoiler_text,
|
||||
verify_client,
|
||||
)
|
||||
from .threads import Threads, ThreadsAccount
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import re
|
||||
import typing
|
||||
from functools import cached_property
|
||||
from operator import pos
|
||||
|
||||
from atproto import Client, SessionEvent, client_utils
|
||||
from atproto_client import models
|
||||
from atproto_identity.did.resolver import DidResolver
|
||||
from atproto_identity.handle.resolver import HandleResolver
|
||||
from django.db.models import base
|
||||
from django.utils import timezone
|
||||
from loguru import logger
|
||||
|
||||
|
@ -14,11 +13,15 @@ from catalog.common import jsondata
|
|||
|
||||
from .common import SocialAccount
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from catalog.common.models import Item
|
||||
from journal.models.common import Content
|
||||
|
||||
|
||||
class Bluesky:
|
||||
_DOMAIN = "-"
|
||||
_RE_HANDLE = re.compile(
|
||||
r"/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/"
|
||||
r"^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$"
|
||||
)
|
||||
# for BlueskyAccount
|
||||
# uid is did and the only unique identifier
|
||||
|
@ -103,7 +106,7 @@ class BlueskyAccount(SocialAccount):
|
|||
|
||||
@property
|
||||
def url(self):
|
||||
return f"{self.base_url}/profile/{self.handle}"
|
||||
return f"https://{self.handle}"
|
||||
|
||||
def refresh(self, save=True, did_refresh=True):
|
||||
if did_refresh:
|
||||
|
@ -159,7 +162,16 @@ class BlueskyAccount(SocialAccount):
|
|||
]
|
||||
)
|
||||
|
||||
def post(self, content, reply_to_id=None, **kwargs):
|
||||
def post(
|
||||
self,
|
||||
content,
|
||||
reply_to_id=None,
|
||||
obj: "Item | Content | None" = None,
|
||||
rating=None,
|
||||
**kwargs,
|
||||
):
|
||||
from journal.models.renderers import render_rating
|
||||
|
||||
reply_to = None
|
||||
if reply_to_id:
|
||||
posts = self._client.get_posts([reply_to_id]).posts
|
||||
|
@ -168,10 +180,30 @@ class BlueskyAccount(SocialAccount):
|
|||
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, reply_to=reply_to)
|
||||
text = (
|
||||
content.replace("##rating##", render_rating(rating))
|
||||
.replace("##obj_link_if_plain##", "")
|
||||
.split("##obj##")
|
||||
)
|
||||
richtext = client_utils.TextBuilder()
|
||||
first = True
|
||||
for t in text:
|
||||
if not first and obj:
|
||||
richtext.link(obj.display_title, obj.absolute_url)
|
||||
else:
|
||||
first = False
|
||||
richtext.text(t)
|
||||
if obj:
|
||||
embed = models.AppBskyEmbedExternal.Main(
|
||||
external=models.AppBskyEmbedExternal.External(
|
||||
title=obj.display_title,
|
||||
description=obj.display_description,
|
||||
uri=obj.absolute_url,
|
||||
)
|
||||
)
|
||||
else:
|
||||
embed = None
|
||||
post = self._client.send_post(richtext, reply_to=reply_to, embed=embed)
|
||||
# return AT uri as id since it's used as so.
|
||||
return {"cid": post.cid, "id": post.uri}
|
||||
|
||||
|
|
|
@ -27,7 +27,8 @@ from takahe.utils import Takahe
|
|||
from .common import SocialAccount
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from journal.models.common import VisibilityType
|
||||
from catalog.common.models import Item
|
||||
from journal.models.common import Content, VisibilityType
|
||||
|
||||
|
||||
class TootVisibilityEnum(StrEnum):
|
||||
|
@ -177,8 +178,6 @@ def post_toot2(
|
|||
"status": content,
|
||||
"visibility": visibility,
|
||||
}
|
||||
# update_id = get_status_id_by_url(update_toot_url)
|
||||
# reply_to_id = get_status_id_by_url(reply_to_toot_url)
|
||||
if reply_to_id:
|
||||
payload["in_reply_to_id"] = reply_to_id
|
||||
if spoiler_text:
|
||||
|
@ -263,26 +262,6 @@ def random_string_generator(n):
|
|||
return "".join(random.choice(s) for i in range(n))
|
||||
|
||||
|
||||
def rating_to_emoji(score, star_mode=0):
|
||||
"""convert score to mastodon star emoji code"""
|
||||
if score is None or score == "" or score == 0:
|
||||
return ""
|
||||
solid_stars = score // 2
|
||||
half_star = int(bool(score % 2))
|
||||
empty_stars = 5 - solid_stars if not half_star else 5 - solid_stars - 1
|
||||
if star_mode == 1:
|
||||
emoji_code = "🌕" * solid_stars + "🌗" * half_star + "🌑" * empty_stars
|
||||
else:
|
||||
emoji_code = (
|
||||
settings.STAR_SOLID * solid_stars
|
||||
+ settings.STAR_HALF * half_star
|
||||
+ settings.STAR_EMPTY * empty_stars
|
||||
)
|
||||
emoji_code = emoji_code.replace("::", ": :")
|
||||
emoji_code = " " + emoji_code + " "
|
||||
return emoji_code
|
||||
|
||||
|
||||
def verify_account(site, token):
|
||||
url = "https://" + get_api_domain(site) + API_VERIFY_ACCOUNT
|
||||
try:
|
||||
|
@ -426,25 +405,6 @@ def obtain_token(site, code, request):
|
|||
return data.get("access_token"), data.get("refresh_token", "")
|
||||
|
||||
|
||||
def get_status_id_by_url(url):
|
||||
if not url:
|
||||
return None
|
||||
r = re.match(
|
||||
r".+/(\w+)$", url
|
||||
) # might be re.match(r'.+/([^/]+)$', u) if Pleroma supports edit
|
||||
return r[1] if r else None
|
||||
|
||||
|
||||
def get_spoiler_text(text, item):
|
||||
if text.find(">!") != -1:
|
||||
spoiler_text = _(
|
||||
"regarding {item_title}, may contain spoiler or triggering content"
|
||||
).format(item_title=item.display_title)
|
||||
return spoiler_text, text.replace(">!", "").replace("!<", "")
|
||||
else:
|
||||
return None, text
|
||||
|
||||
|
||||
def get_toot_visibility(visibility, user) -> TootVisibilityEnum:
|
||||
if visibility == 2:
|
||||
return TootVisibilityEnum.DIRECT
|
||||
|
@ -662,16 +622,18 @@ class MastodonAccount(SocialAccount):
|
|||
return app
|
||||
|
||||
@functools.cached_property
|
||||
def api_domain(self) -> str:
|
||||
def _api_domain(self) -> str:
|
||||
app = self.application
|
||||
return app.api_domain if app else self.domain
|
||||
|
||||
def rating_to_emoji(self, rating_grade: int) -> str:
|
||||
app = self.application
|
||||
return rating_to_emoji(rating_grade, app.star_mode if app else 0)
|
||||
def rating_to_emoji(self, rating_grade: int | None) -> str:
|
||||
from journal.models.renderers import render_rating
|
||||
|
||||
app = self.application # TODO fix star mode data flip in app
|
||||
return render_rating(rating_grade, (0 if app.star_mode else 1) if app else 0)
|
||||
|
||||
def _get(self, url: str):
|
||||
url = url if url.startswith("https://") else f"https://{self.api_domain}{url}"
|
||||
url = url if url.startswith("https://") else f"https://{self._api_domain}{url}"
|
||||
headers = {
|
||||
"User-Agent": settings.NEODB_USER_AGENT,
|
||||
"Authorization": f"Bearer {self.access_token}",
|
||||
|
@ -679,7 +641,7 @@ class MastodonAccount(SocialAccount):
|
|||
return get(url, headers=headers)
|
||||
|
||||
def _post(self, url: str, data, files=None):
|
||||
url = url if url.startswith("https://") else f"https://{self.api_domain}{url}"
|
||||
url = url if url.startswith("https://") else f"https://{self._api_domain}{url}"
|
||||
return post(
|
||||
url,
|
||||
data=data,
|
||||
|
@ -692,7 +654,7 @@ class MastodonAccount(SocialAccount):
|
|||
)
|
||||
|
||||
def _delete(self, url: str, data, files=None):
|
||||
url = url if url.startswith("https://") else f"https://{self.api_domain}{url}"
|
||||
url = url if url.startswith("https://") else f"https://{self._api_domain}{url}"
|
||||
return delete(
|
||||
url,
|
||||
headers={
|
||||
|
@ -702,7 +664,7 @@ class MastodonAccount(SocialAccount):
|
|||
)
|
||||
|
||||
def _put(self, url: str, data, files=None):
|
||||
url = url if url.startswith("https://") else f"https://{self.api_domain}{url}"
|
||||
url = url if url.startswith("https://") else f"https://{self._api_domain}{url}"
|
||||
return put(
|
||||
url,
|
||||
data=data,
|
||||
|
@ -814,19 +776,19 @@ class MastodonAccount(SocialAccount):
|
|||
)
|
||||
|
||||
def boost(self, post_url: str):
|
||||
boost_toot(self.api_domain, self.access_token, post_url)
|
||||
boost_toot(self._api_domain, self.access_token, post_url)
|
||||
|
||||
def boost_later(self, post_url: str):
|
||||
django_rq.get_queue("fetch").enqueue(
|
||||
boost_toot, self.api_domain, self.access_token, post_url
|
||||
boost_toot, self._api_domain, self.access_token, post_url
|
||||
)
|
||||
|
||||
def delete_post(self, post_id: str):
|
||||
delete_toot(self.api_domain, self.access_token, post_id)
|
||||
delete_toot(self._api_domain, self.access_token, post_id)
|
||||
|
||||
def delete_post_later(self, post_id: str):
|
||||
django_rq.get_queue("fetch").enqueue(
|
||||
delete_toot, self.api_domain, self.access_token, post_id
|
||||
delete_toot, self._api_domain, self.access_token, post_id
|
||||
)
|
||||
|
||||
def post(
|
||||
|
@ -838,12 +800,21 @@ class MastodonAccount(SocialAccount):
|
|||
sensitive: bool = False,
|
||||
spoiler_text: str | None = None,
|
||||
attachments: list = [],
|
||||
obj: "Item | Content | None" = None,
|
||||
rating: int | None = None,
|
||||
) -> dict:
|
||||
from journal.models.renderers import render_rating
|
||||
|
||||
v = get_toot_visibility(visibility, self.user)
|
||||
text = (
|
||||
content.replace("##rating##", self.rating_to_emoji(rating))
|
||||
.replace("##obj_link_if_plain##", obj.absolute_url + "\n" if obj else "")
|
||||
.replace("##obj##", obj.display_title if obj else "")
|
||||
)
|
||||
response = post_toot2(
|
||||
self.api_domain,
|
||||
self._api_domain,
|
||||
self.access_token,
|
||||
content,
|
||||
text,
|
||||
v,
|
||||
update_id,
|
||||
reply_to_id,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import functools
|
||||
import typing
|
||||
from datetime import timedelta
|
||||
from urllib.parse import quote
|
||||
|
||||
|
@ -14,6 +15,10 @@ from catalog.common import jsondata
|
|||
|
||||
from .common import SocialAccount
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from catalog.common.models import Item
|
||||
from journal.models.common import Content, VisibilityType
|
||||
|
||||
get = functools.partial(
|
||||
requests.get,
|
||||
timeout=settings.THREADS_TIMEOUT,
|
||||
|
@ -239,11 +244,21 @@ class ThreadsAccount(SocialAccount):
|
|||
self.save(update_fields=["account_data", "handle", "last_refresh"])
|
||||
return True
|
||||
|
||||
def post(self, content: str, reply_to_id=None, **kwargs):
|
||||
def post(
|
||||
self,
|
||||
content: str,
|
||||
visibility: "VisibilityType",
|
||||
reply_to_id=None,
|
||||
obj: "Item | Content | None" = None,
|
||||
rating=None,
|
||||
**kwargs,
|
||||
):
|
||||
from journal.models.renderers import render_rating
|
||||
|
||||
text = (
|
||||
content.replace(settings.STAR_SOLID + " ", "🌕")
|
||||
.replace(settings.STAR_HALF + " ", "🌗")
|
||||
.replace(settings.STAR_EMPTY + " ", "🌑")
|
||||
content.replace("##rating##", render_rating(rating))
|
||||
.replace("##obj_link_if_plain##", obj.absolute_url + "\n" if obj else "")
|
||||
.replace("##obj##", obj.display_title if obj else "")
|
||||
)
|
||||
media_id = Threads.post_single(self.access_token, self.uid, text, reply_to_id)
|
||||
if not media_id:
|
||||
|
|
|
@ -56,7 +56,7 @@ dependencies = [
|
|||
"validators",
|
||||
"deepmerge>=1.1.1",
|
||||
"django-typed-models @ git+https://github.com/alphatownsman/django-typed-models.git",
|
||||
"atproto>=0.0.48",
|
||||
"atproto>=0.0.49",
|
||||
"pyright>=1.1.370",
|
||||
]
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ asgiref==3.8.1
|
|||
# via django
|
||||
# via django-cors-headers
|
||||
# via django-stubs
|
||||
atproto==0.0.48
|
||||
atproto==0.0.49
|
||||
attrs==23.2.0
|
||||
# via aiohttp
|
||||
babel==2.15.0
|
||||
|
|
|
@ -19,7 +19,7 @@ anyio==4.4.0
|
|||
asgiref==3.8.1
|
||||
# via django
|
||||
# via django-cors-headers
|
||||
atproto==0.0.48
|
||||
atproto==0.0.49
|
||||
attrs==23.2.0
|
||||
# via aiohttp
|
||||
beautifulsoup4==4.12.3
|
||||
|
|
|
@ -543,16 +543,6 @@ class Takahe:
|
|||
# TimelineEvent.objects.filter(subject_post__in=[post.pk]).delete()
|
||||
PostInteraction.objects.filter(post__in=post_pks).update(state="undone")
|
||||
|
||||
@staticmethod
|
||||
def get_spoiler_text(text, item):
|
||||
if text and text.find(">!") != -1:
|
||||
spoiler_text = _(
|
||||
"regarding {item_title}, may contain spoiler or triggering content"
|
||||
).format(item_title=item.display_title)
|
||||
return spoiler_text, text.replace(">!", "").replace("!<", "")
|
||||
else:
|
||||
return None, text or ""
|
||||
|
||||
@staticmethod
|
||||
def visibility_n2t(visibility: int, post_public_mode: int) -> Visibilities:
|
||||
if visibility == 1:
|
||||
|
|
|
@ -108,14 +108,14 @@
|
|||
required=""
|
||||
id="mastodon_repost_mode_0"
|
||||
{% if request.user.preference.mastodon_repost_mode == 0 %}checked{% endif %}>
|
||||
<label for="mastodon_repost_mode_0">{% trans "boost" %}</label>
|
||||
<label for="mastodon_repost_mode_0">{% trans "Boost if possible" %}</label>
|
||||
<input type="radio"
|
||||
name="mastodon_repost_mode"
|
||||
value="1"
|
||||
required=""
|
||||
id="mastodon_repost_mode_1"
|
||||
{% if request.user.preference.mastodon_repost_mode == 1 %}checked{% endif %}>
|
||||
<label for="mastodon_repost_mode_1">{% trans "create a new post" %}</label>
|
||||
<label for="mastodon_repost_mode_1">{% trans "Create a new post" %}</label>
|
||||
<em data-tooltip="{% trans "this method is less optimal, may generate duplicated posts and miss reactions." %}"><i class="fa fa-question-circle"></i></em>
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
|
|
Loading…
Add table
Reference in a new issue