diff --git a/mastodon/models/bluesky.py b/mastodon/models/bluesky.py index 90c6fd28..8d7b4302 100644 --- a/mastodon/models/bluesky.py +++ b/mastodon/models/bluesky.py @@ -4,12 +4,14 @@ from functools import cached_property from atproto import Client, SessionEvent, client_utils from atproto_client import models +from atproto_client.exceptions import AtProtocolError from atproto_identity.did.resolver import DidResolver from atproto_identity.handle.resolver import HandleResolver from django.utils import timezone from loguru import logger from catalog.common import jsondata +from takahe.utils import Takahe from .common import SocialAccount @@ -172,6 +174,52 @@ class BlueskyAccount(SocialAccount): ) return True + def refresh_graph(self, save=True) -> bool: + try: + r = self._client.get_followers(self.uid) + self.followers = [p.did for p in r.followers] + r = self._client.get_follows(self.uid) + self.following = [p.did for p in r.follows] + r = self._client.app.bsky.graph.get_mutes( + models.AppBskyGraphGetMutes.Params(cursor=None, limit=None) + ) + self.mutes = [p.did for p in r.mutes] + except AtProtocolError as e: + logger.warning(f"{self} refresh_graph error: {e}") + return False + if save: + self.save( + update_fields=[ + "followers", + "following", + "mutes", + ] + ) + return True + + def sync_graph(self): + c = 0 + + def get_identity_ids(accts: list): + return set( + BlueskyAccount.objects.filter( + domain=Bluesky._DOMAIN, uid__in=accts + ).values_list("user__identity", flat=True) + ) + + me = self.user.identity.pk + for target_identity in get_identity_ids(self.following): + if not Takahe.get_is_following(me, target_identity): + Takahe.follow(me, target_identity, True) + c += 1 + + for target_identity in get_identity_ids(self.mutes): + if not Takahe.get_is_muting(me, target_identity): + Takahe.mute(me, target_identity) + c += 1 + + return c + def post( self, content, diff --git a/mastodon/models/common.py b/mastodon/models/common.py index f87600a8..a946b684 100644 --- a/mastodon/models/common.py +++ b/mastodon/models/common.py @@ -44,13 +44,13 @@ class SocialAccount(TypedModel): last_refresh = models.DateTimeField(default=None, null=True) last_reachable = models.DateTimeField(default=None, null=True) - sync_profile = jsondata.BooleanField( - json_field_name="preference_data", default=True - ) - sync_graph = jsondata.BooleanField(json_field_name="preference_data", default=True) - sync_timeline = jsondata.BooleanField( - json_field_name="preference_data", default=True - ) + # sync_profile = jsondata.BooleanField( + # json_field_name="preference_data", default=True + # ) + # sync_graph = jsondata.BooleanField(json_field_name="preference_data", default=True) + # sync_timeline = jsondata.BooleanField( + # json_field_name="preference_data", default=True + # ) class Meta: indexes = [ @@ -124,3 +124,6 @@ class SocialAccount(TypedModel): self.refresh_graph() logger.debug(f"{self} refreshed") return True + + def sync_graph(self) -> int: + return 0 diff --git a/mastodon/models/mastodon.py b/mastodon/models/mastodon.py index d465e380..191f50ba 100644 --- a/mastodon/models/mastodon.py +++ b/mastodon/models/mastodon.py @@ -778,6 +778,46 @@ class MastodonAccount(SocialAccount): ) return True + def sync_graph(self): + c = 0 + + def get_identity_ids(accts: list): + return set( + MastodonAccount.objects.filter(handle__in=accts).values_list( + "user__identity", flat=True + ) + ) + + def get_identity_ids_in_domains(domains: list): + return set( + MastodonAccount.objects.filter(domain__in=domains).values_list( + "user__identity", flat=True + ) + ) + + me = self.user.identity.pk + for target_identity in get_identity_ids(self.following): + if not Takahe.get_is_following(me, target_identity): + Takahe.follow(me, target_identity, True) + c += 1 + + for target_identity in get_identity_ids(self.blocks): + if not Takahe.get_is_blocking(me, target_identity): + Takahe.block(me, target_identity) + c += 1 + + for target_identity in get_identity_ids_in_domains(self.domain_blocks): + if not Takahe.get_is_blocking(me, target_identity): + Takahe.block(me, target_identity) + c += 1 + + for target_identity in get_identity_ids(self.mutes): + if not Takahe.get_is_muting(me, target_identity): + Takahe.mute(me, target_identity) + c += 1 + + return c + def boost(self, post_url: str): boost_toot(self._api_domain, self.access_token, post_url) diff --git a/users/models/user.py b/users/models/user.py index 101f69a7..81b75378 100644 --- a/users/models/user.py +++ b/users/models/user.py @@ -254,40 +254,6 @@ class User(AbstractUser): self.identity.save() self.social_accounts.all().delete() - def sync_relationship(self): - def get_identity_ids(accts: list): - return set( - MastodonAccount.objects.filter(handle__in=accts).values_list( - "user__identity", flat=True - ) - ) - - def get_identity_ids_in_domains(domains: list): - return set( - MastodonAccount.objects.filter(domain__in=domains).values_list( - "user__identity", flat=True - ) - ) - - me = self.identity.pk - if not self.mastodon: - return - for target_identity in get_identity_ids(self.mastodon.following): - if not Takahe.get_is_following(me, target_identity): - Takahe.follow(me, target_identity, True) - - for target_identity in get_identity_ids(self.mastodon.blocks): - if not Takahe.get_is_blocking(me, target_identity): - Takahe.block(me, target_identity) - - for target_identity in get_identity_ids_in_domains(self.mastodon.domain_blocks): - if not Takahe.get_is_blocking(me, target_identity): - Takahe.block(me, target_identity) - - for target_identity in get_identity_ids(self.mastodon.mutes): - if not Takahe.get_is_muting(me, target_identity): - Takahe.mute(me, target_identity) - def sync_identity(self): """sync display name, bio, and avatar from available sources""" identity = self.identity.takahe_identity @@ -356,17 +322,18 @@ class User(AbstractUser): if skip_graph: return if not self.preference.mastodon_skip_relationship: - self.sync_relationship() - return + c = 0 + for account in self.social_accounts.all(): + c += account.sync_graph() + if c: + logger.debug(f"{self} graph updated with {c} new relationship.") @staticmethod def sync_accounts_task(user_id): user = User.objects.get(pk=user_id) logger.info(f"{user} accounts sync start") - if user.sync_accounts(): - logger.info(f"{user} accounts sync done") - else: - logger.warning(f"{user} accounts sync failed") + user.sync_accounts() + logger.info(f"{user} accounts sync end") def sync_accounts_later(self): django_rq.get_queue("mastodon").enqueue(User.sync_accounts_task, self.pk) diff --git a/users/templates/users/account.html b/users/templates/users/account.html index 7f43bfca..e747de6f 100644 --- a/users/templates/users/account.html +++ b/users/templates/users/account.html @@ -129,143 +129,144 @@ {% endif %} + {% if enable_threads %} +
+
+ {% trans "Threads.net" %} +
+ {% csrf_token %} +
+ {% if request.user.threads %} + + {% endif %} + +
+
+ {% if request.user.threads %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+ {% endif %} + {% if enable_bluesky %} +
+
+ {% trans "Bluesky (ATProto)" %} +
+ {% csrf_token %} +
+ {% if request.user.bluesky %} + + {% endif %} + + + + {% blocktrans %}App password can be created on bsky.app.{% endblocktrans %} +
+
+ {% if request.user.bluesky %} +
+ {% csrf_token %} + +
+ {% endif %} +
+
+ {% endif %} + {% endif %} + {% if request.user.social_accounts.all %}
- {% trans "Threads.net" %} -
+ {% trans 'Sync and import social account' %} + {% csrf_token %}
- {% if request.user.threads %} - - {% endif %} - +
-
- {% if request.user.threads %} -
- {% csrf_token %} - -
- {% endif %} -
-
-
-
- {% trans "Bluesky (ATProto)" %} -
- {% csrf_token %}
- {% if request.user.bluesky %} - - {% endif %} - - - - {% blocktrans %}App password can be created on bsky.app.{% endblocktrans %} +
+ + + {% trans "New follow, mute and blocks in the associated identity may be automatically imported; removal has to be done manually." %} + +
+
+ {% csrf_token %} + {% trans "Click button below to start sync now." %} + + + {% if request.user.mastodon.last_refresh %} + {% trans "Last updated" %} {{ request.user.mastodon.last_refresh }} + {% endif %} +
- {% if request.user.bluesky %} -
- {% csrf_token %} - -
- {% endif %}
{% endif %} -
-
- {% trans 'Sync and import social account' %} -
- {% csrf_token %} -
- -
-
- -
- - - {% trans "New follow, mute and blocks in the associated identity may be automatically imported; removal has to be done manually." %} - -
-
- {% csrf_token %} - {% trans "Click button below to start sync now." %} - - - {% if request.user.mastodon.last_refresh %} - {% trans "Last updated" %} {{ request.user.mastodon.last_refresh }} - {% endif %} - -
-
-
{% trans 'Users you are following' %} diff --git a/users/views/profile.py b/users/views/profile.py index 7a5a9bcc..cf02efeb 100644 --- a/users/views/profile.py +++ b/users/views/profile.py @@ -37,6 +37,9 @@ def account_info(request): "users/account.html", { "allow_any_site": settings.MASTODON_ALLOW_ANY_SITE, + "enable_email": settings.ENABLE_LOGIN_EMAIL, + "enable_threads": settings.ENABLE_LOGIN_THREADS, + "enable_bluesky": settings.ENABLE_LOGIN_BLUESKY, "profile_form": profile_form, }, )