bluesky graph
This commit is contained in:
parent
e29b921d34
commit
196ac2a616
6 changed files with 235 additions and 173 deletions
|
@ -4,12 +4,14 @@ from functools import cached_property
|
||||||
|
|
||||||
from atproto import Client, SessionEvent, client_utils
|
from atproto import Client, SessionEvent, client_utils
|
||||||
from atproto_client import models
|
from atproto_client import models
|
||||||
|
from atproto_client.exceptions import AtProtocolError
|
||||||
from atproto_identity.did.resolver import DidResolver
|
from atproto_identity.did.resolver import DidResolver
|
||||||
from atproto_identity.handle.resolver import HandleResolver
|
from atproto_identity.handle.resolver import HandleResolver
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from catalog.common import jsondata
|
from catalog.common import jsondata
|
||||||
|
from takahe.utils import Takahe
|
||||||
|
|
||||||
from .common import SocialAccount
|
from .common import SocialAccount
|
||||||
|
|
||||||
|
@ -172,6 +174,52 @@ class BlueskyAccount(SocialAccount):
|
||||||
)
|
)
|
||||||
return True
|
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(
|
def post(
|
||||||
self,
|
self,
|
||||||
content,
|
content,
|
||||||
|
|
|
@ -44,13 +44,13 @@ class SocialAccount(TypedModel):
|
||||||
last_refresh = models.DateTimeField(default=None, null=True)
|
last_refresh = models.DateTimeField(default=None, null=True)
|
||||||
last_reachable = models.DateTimeField(default=None, null=True)
|
last_reachable = models.DateTimeField(default=None, null=True)
|
||||||
|
|
||||||
sync_profile = jsondata.BooleanField(
|
# sync_profile = jsondata.BooleanField(
|
||||||
json_field_name="preference_data", default=True
|
# json_field_name="preference_data", default=True
|
||||||
)
|
# )
|
||||||
sync_graph = jsondata.BooleanField(json_field_name="preference_data", default=True)
|
# sync_graph = jsondata.BooleanField(json_field_name="preference_data", default=True)
|
||||||
sync_timeline = jsondata.BooleanField(
|
# sync_timeline = jsondata.BooleanField(
|
||||||
json_field_name="preference_data", default=True
|
# json_field_name="preference_data", default=True
|
||||||
)
|
# )
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
indexes = [
|
indexes = [
|
||||||
|
@ -124,3 +124,6 @@ class SocialAccount(TypedModel):
|
||||||
self.refresh_graph()
|
self.refresh_graph()
|
||||||
logger.debug(f"{self} refreshed")
|
logger.debug(f"{self} refreshed")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def sync_graph(self) -> int:
|
||||||
|
return 0
|
||||||
|
|
|
@ -778,6 +778,46 @@ class MastodonAccount(SocialAccount):
|
||||||
)
|
)
|
||||||
return True
|
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):
|
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)
|
||||||
|
|
||||||
|
|
|
@ -254,40 +254,6 @@ class User(AbstractUser):
|
||||||
self.identity.save()
|
self.identity.save()
|
||||||
self.social_accounts.all().delete()
|
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):
|
def sync_identity(self):
|
||||||
"""sync display name, bio, and avatar from available sources"""
|
"""sync display name, bio, and avatar from available sources"""
|
||||||
identity = self.identity.takahe_identity
|
identity = self.identity.takahe_identity
|
||||||
|
@ -356,17 +322,18 @@ class User(AbstractUser):
|
||||||
if skip_graph:
|
if skip_graph:
|
||||||
return
|
return
|
||||||
if not self.preference.mastodon_skip_relationship:
|
if not self.preference.mastodon_skip_relationship:
|
||||||
self.sync_relationship()
|
c = 0
|
||||||
return
|
for account in self.social_accounts.all():
|
||||||
|
c += account.sync_graph()
|
||||||
|
if c:
|
||||||
|
logger.debug(f"{self} graph updated with {c} new relationship.")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sync_accounts_task(user_id):
|
def sync_accounts_task(user_id):
|
||||||
user = User.objects.get(pk=user_id)
|
user = User.objects.get(pk=user_id)
|
||||||
logger.info(f"{user} accounts sync start")
|
logger.info(f"{user} accounts sync start")
|
||||||
if user.sync_accounts():
|
user.sync_accounts()
|
||||||
logger.info(f"{user} accounts sync done")
|
logger.info(f"{user} accounts sync end")
|
||||||
else:
|
|
||||||
logger.warning(f"{user} accounts sync failed")
|
|
||||||
|
|
||||||
def sync_accounts_later(self):
|
def sync_accounts_later(self):
|
||||||
django_rq.get_queue("mastodon").enqueue(User.sync_accounts_task, self.pk)
|
django_rq.get_queue("mastodon").enqueue(User.sync_accounts_task, self.pk)
|
||||||
|
|
|
@ -129,143 +129,144 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</details>
|
</details>
|
||||||
</article>
|
</article>
|
||||||
|
{% if enable_threads %}
|
||||||
|
<article>
|
||||||
|
<details>
|
||||||
|
<summary>{% trans "Threads.net" %}</summary>
|
||||||
|
<form action="{% url 'mastodon:threads_reconnect' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<fieldset>
|
||||||
|
{% if request.user.threads %}
|
||||||
|
<label>
|
||||||
|
<i class="fa-brands fa-threads"></i> {% trans "Verified threads.net account" %}
|
||||||
|
<input type="input"
|
||||||
|
aria-invalid="false"
|
||||||
|
value="{{ request.user.threads.handle }}"
|
||||||
|
readonly>
|
||||||
|
<small>
|
||||||
|
{% if request.user.threads.last_refresh %}
|
||||||
|
{% trans "Last updated" %} {{ request.user.threads.last_refresh }}
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
<input type="submit"
|
||||||
|
value="{% if request.user.threads %} {% trans 'Link with a different threads.net account' %} {% else %} {% trans "Link with a threads.net account" %} {% endif %} " />
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% if request.user.threads %}
|
||||||
|
<form action="{% url 'mastodon:threads_disconnect' %}"
|
||||||
|
method="post"
|
||||||
|
onsubmit="return confirm('{% trans "Once disconnected, you will no longer be able login with this identity. Are you sure to continue?" %}')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit"
|
||||||
|
value="{% trans 'Disconnect with Threads' %}"
|
||||||
|
class="secondary" />
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</details>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% if enable_bluesky %}
|
||||||
|
<article>
|
||||||
|
<details>
|
||||||
|
<summary>{% trans "Bluesky (ATProto)" %}</summary>
|
||||||
|
<form action="{% url 'mastodon:bluesky_reconnect' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<fieldset>
|
||||||
|
{% if request.user.bluesky %}
|
||||||
|
<label>
|
||||||
|
<i class="fa-brands fa-bluesky"></i> {% trans "Verified ATProto identity" %}
|
||||||
|
<input type="input"
|
||||||
|
aria-invalid="false"
|
||||||
|
value="@{{ request.user.bluesky.handle }} {{ request.user.bluesky.uid }}"
|
||||||
|
readonly>
|
||||||
|
<small>
|
||||||
|
{% if request.user.bluesky.last_refresh %}
|
||||||
|
{% trans "Last updated" %} {{ request.user.bluesky.last_refresh }}
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
{% endif %}
|
||||||
|
<input required
|
||||||
|
name="username"
|
||||||
|
autofocus
|
||||||
|
placeholder="{% trans 'Bluesky Login ID' %}"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false" />
|
||||||
|
<input required
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="{% trans 'Bluesky app password' %}"
|
||||||
|
autocorrect="off"
|
||||||
|
autocapitalize="off"
|
||||||
|
autocomplete="off"
|
||||||
|
spellcheck="false" />
|
||||||
|
<input type="submit"
|
||||||
|
value="{% if request.user.bluesky %} {% trans 'Link with a different ATProto identity' %} {% else %} {% trans "Link with an ATProto identity" %} {% endif %} " />
|
||||||
|
<small>{% blocktrans %}App password can be created on <a href="https://bsky.app/settings/app-passwords" target="_blank">bsky.app</a>.{% endblocktrans %}</small>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
{% if request.user.bluesky %}
|
||||||
|
<form action="{% url 'mastodon:bluesky_disconnect' %}"
|
||||||
|
method="post"
|
||||||
|
onsubmit="return confirm('{% trans "Once disconnected, you will no longer be able login with this identity. Are you sure to continue?" %}')">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="submit"
|
||||||
|
value="{% trans 'Disconnect with ATProto identity' %}"
|
||||||
|
class="secondary" />
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</details>
|
||||||
|
</article>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if request.user.social_accounts.all %}
|
||||||
<article>
|
<article>
|
||||||
<details>
|
<details>
|
||||||
<summary>{% trans "Threads.net" %}</summary>
|
<summary>{% trans 'Sync and import social account' %}</summary>
|
||||||
<form action="{% url 'mastodon:threads_reconnect' %}" method="post">
|
<form action="{% url 'users:sync_mastodon_preference' %}"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{% if request.user.threads %}
|
<label>
|
||||||
<label>
|
<input type="checkbox"
|
||||||
<i class="fa-brands fa-threads"></i> {% trans "Verified threads.net account" %}
|
name="mastodon_sync_userinfo"
|
||||||
<input type="input"
|
{% if not request.user.preference.mastodon_skip_userinfo %}checked{% endif %}>
|
||||||
aria-invalid="false"
|
{% trans 'Sync display name, bio and avatar' %}
|
||||||
value="{{ request.user.threads.handle }}"
|
</label>
|
||||||
readonly>
|
|
||||||
<small>
|
|
||||||
{% if request.user.threads.last_refresh %}
|
|
||||||
{% trans "Last updated" %} {{ request.user.threads.last_refresh }}
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
<input type="submit"
|
|
||||||
value="{% if request.user.threads %} {% trans 'Link with a different threads.net account' %} {% else %} {% trans "Link with a threads.net account" %} {% endif %} " />
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
|
||||||
{% if request.user.threads %}
|
|
||||||
<form action="{% url 'mastodon:threads_disconnect' %}"
|
|
||||||
method="post"
|
|
||||||
onsubmit="return confirm('{% trans "Once disconnected, you will no longer be able login with this identity. Are you sure to continue?" %}')">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="submit"
|
|
||||||
value="{% trans 'Disconnect with Threads' %}"
|
|
||||||
class="secondary" />
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
</details>
|
|
||||||
</article>
|
|
||||||
<article>
|
|
||||||
<details>
|
|
||||||
<summary>{% trans "Bluesky (ATProto)" %}</summary>
|
|
||||||
<form action="{% url 'mastodon:bluesky_reconnect' %}" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{% if request.user.bluesky %}
|
<label>
|
||||||
<label>
|
<input type="checkbox"
|
||||||
<i class="fa-brands fa-bluesky"></i> {% trans "Verified ATProto identity" %}
|
name="mastodon_sync_relationship"
|
||||||
<input type="input"
|
{% if not request.user.preference.mastodon_skip_relationship %}checked{% endif %}>
|
||||||
aria-invalid="false"
|
{% trans 'Sync follow, mute and block' %}
|
||||||
value="@{{ request.user.bluesky.handle }} {{ request.user.bluesky.uid }}"
|
</label>
|
||||||
readonly>
|
|
||||||
<small>
|
|
||||||
{% if request.user.bluesky.last_refresh %}
|
|
||||||
{% trans "Last updated" %} {{ request.user.bluesky.last_refresh }}
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</label>
|
|
||||||
{% endif %}
|
|
||||||
<input required
|
|
||||||
type="email"
|
|
||||||
name="username"
|
|
||||||
autofocus
|
|
||||||
placeholder="{% trans 'Bluesky Login ID' %}"
|
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="off"
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false" />
|
|
||||||
<input required
|
|
||||||
type="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="{% trans 'Bluesky app password' %}"
|
|
||||||
autocorrect="off"
|
|
||||||
autocapitalize="off"
|
|
||||||
autocomplete="off"
|
|
||||||
spellcheck="false" />
|
|
||||||
<input type="submit"
|
|
||||||
value="{% if request.user.bluesky %} {% trans 'Link with a different ATProto identity' %} {% else %} {% trans "Link with an ATProto identity" %} {% endif %} " />
|
|
||||||
<small>{% blocktrans %}App password can be created on <a href="https://bsky.app/settings/app-passwords" target="_blank">bsky.app</a>.{% endblocktrans %}</small>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<input type="submit" value="{% trans 'Save sync settings' %}" />
|
||||||
|
<small>
|
||||||
|
{% trans "New follow, mute and blocks in the associated identity may be automatically imported; removal has to be done manually." %}
|
||||||
|
</small>
|
||||||
|
</form>
|
||||||
|
<form action="{% url 'users:sync_mastodon' %}"
|
||||||
|
method="post"
|
||||||
|
enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<small>{% trans "Click button below to start sync now." %}</small>
|
||||||
|
<input type="submit" value="{% trans 'Sync now' %}" />
|
||||||
|
<small>
|
||||||
|
{% if request.user.mastodon.last_refresh %}
|
||||||
|
{% trans "Last updated" %} {{ request.user.mastodon.last_refresh }}
|
||||||
|
{% endif %}
|
||||||
|
</small>
|
||||||
</form>
|
</form>
|
||||||
{% if request.user.bluesky %}
|
|
||||||
<form action="{% url 'mastodon:bluesky_disconnect' %}"
|
|
||||||
method="post"
|
|
||||||
onsubmit="return confirm('{% trans "Once disconnected, you will no longer be able login with this identity. Are you sure to continue?" %}')">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="submit"
|
|
||||||
value="{% trans 'Disconnect with ATProto identity' %}"
|
|
||||||
class="secondary" />
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
</details>
|
</details>
|
||||||
</article>
|
</article>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<article>
|
|
||||||
<details>
|
|
||||||
<summary>{% trans 'Sync and import social account' %}</summary>
|
|
||||||
<form action="{% url 'users:sync_mastodon_preference' %}"
|
|
||||||
method="post"
|
|
||||||
enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
<fieldset>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox"
|
|
||||||
name="mastodon_sync_userinfo"
|
|
||||||
{% if not request.user.preference.mastodon_skip_userinfo %}checked{% endif %}>
|
|
||||||
{% trans 'Sync display name, bio and avatar' %}
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<fieldset>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox"
|
|
||||||
name="mastodon_sync_relationship"
|
|
||||||
{% if not request.user.preference.mastodon_skip_relationship %}checked{% endif %}>
|
|
||||||
{% trans 'Sync follow, mute and block' %}
|
|
||||||
</label>
|
|
||||||
</fieldset>
|
|
||||||
<input type="submit"
|
|
||||||
value="{% trans 'Save sync settings' %}"
|
|
||||||
{% if not request.user.mastodon_username %}disabled{% endif %} />
|
|
||||||
<small>
|
|
||||||
{% trans "New follow, mute and blocks in the associated identity may be automatically imported; removal has to be done manually." %}
|
|
||||||
</small>
|
|
||||||
</form>
|
|
||||||
<form action="{% url 'users:sync_mastodon' %}"
|
|
||||||
method="post"
|
|
||||||
enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
<small>{% trans "Click button below to start sync now." %}</small>
|
|
||||||
<input type="submit"
|
|
||||||
value="{% trans 'Sync now' %}"
|
|
||||||
{% if not request.user.mastodon_username %}disabled{% endif %} />
|
|
||||||
<small>
|
|
||||||
{% if request.user.mastodon.last_refresh %}
|
|
||||||
{% trans "Last updated" %} {{ request.user.mastodon.last_refresh }}
|
|
||||||
{% endif %}
|
|
||||||
</small>
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
</article>
|
|
||||||
<article>
|
<article>
|
||||||
<details>
|
<details>
|
||||||
<summary>{% trans 'Users you are following' %}</summary>
|
<summary>{% trans 'Users you are following' %}</summary>
|
||||||
|
|
|
@ -37,6 +37,9 @@ def account_info(request):
|
||||||
"users/account.html",
|
"users/account.html",
|
||||||
{
|
{
|
||||||
"allow_any_site": settings.MASTODON_ALLOW_ANY_SITE,
|
"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,
|
"profile_form": profile_form,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue