diff --git a/mastodon/api.py b/mastodon/api.py index d6cedfad..079713c3 100644 --- a/mastodon/api.py +++ b/mastodon/api.py @@ -198,6 +198,28 @@ def random_string_generator(n): return ''.join(random.choice(s) for i in range(n)) +def verify_account(site, token): + url = 'https://' + site + API_VERIFY_ACCOUNT + response = get(url, headers={'User-Agent': 'NeoDB/1.0', 'Authorization': f'Bearer {token}'}) + return response.status_code, response.json() if response.status_code == 200 else None + + +def get_related_acct_list(site, token, api): + url = 'https://' + site + api + results = [] + while url: + response = get(url, headers={'User-Agent': 'NeoDB/1.0', 'Authorization': f'Bearer {token}'}) + url = None + if response.status_code == 200: + results.extend(map(lambda u: u['acct'] if u['acct'].find('@') != -1 else u['acct'] + site, response.json())) + if 'Link' in response.headers: + for ls in response.headers['Link'].split(','): + li = ls.strip().split(';') + if li[1].strip() == 'rel="next"': + url = li[0].strip().replace('>', '').replace('<', '') + return results + + class TootVisibilityEnum: PUBLIC = 'public' PRIVATE = 'private' diff --git a/users/management/commands/backfill_mastodon.py b/users/management/commands/backfill_mastodon.py new file mode 100644 index 00000000..55f4dea6 --- /dev/null +++ b/users/management/commands/backfill_mastodon.py @@ -0,0 +1,21 @@ +from django.core.management.base import BaseCommand +from users.models import User +from django.contrib.sessions.models import Session + + +class Command(BaseCommand): + help = 'Backfill Mastodon data if missing' + + def handle(self, *args, **options): + for session in Session.objects.order_by('-expire_date'): + uid = session.get_decoded().get('_auth_user_id') + token = session.get_decoded().get('oauth_token') + if uid and token: + user = User.objects.get(pk=uid) + if user.mastodon_token: + print(f'skip {user}') + continue + user.mastodon_token = token + user.refresh_mastodon_data() + user.save() + print(f"Refreshed {user}") diff --git a/users/management/commands/refresh_mastodon.py b/users/management/commands/refresh_mastodon.py new file mode 100644 index 00000000..c1609a4b --- /dev/null +++ b/users/management/commands/refresh_mastodon.py @@ -0,0 +1,24 @@ +from django.core.management.base import BaseCommand +from users.models import User +from datetime import timedelta +from django.utils import timezone + + +class Command(BaseCommand): + help = 'Refresh Mastodon data for all users if not updated in last 24h' + + def handle(self, *args, **options): + count = 0 + for user in User.objects.filter(mastodon_last_refresh__lt=timezone.now() - timedelta(hours=24)): + if user.mastodon_token: + print(f"Refreshing {user}") + if user.refresh_mastodon_data(): + print(f"Refreshed {user}") + count += 1 + else: + print(f"Refresh failed for {user}") + user.save() + else: + print(f'Missing token for {user}') + + print(f'{count} users updated') diff --git a/users/models.py b/users/models.py index cebde9ad..18a12e8a 100644 --- a/users/models.py +++ b/users/models.py @@ -7,6 +7,7 @@ from django.core.serializers.json import DjangoJSONEncoder from django.utils.translation import ugettext_lazy as _ from common.utils import GenerateDateUUIDMediaFilePath from django.conf import settings +from mastodon.api import * def report_image_path(instance, filename): @@ -24,6 +25,14 @@ class User(AbstractUser): mastodon_id = models.CharField(max_length=100, blank=False) # mastodon domain name, eg donotban.com mastodon_site = models.CharField(max_length=100, blank=False) + mastodon_token = models.CharField(max_length=100, default='') + mastodon_locked = models.BooleanField(default=False) + mastodon_followers = models.JSONField(default=list) + mastodon_following = models.JSONField(default=list) + mastodon_mutes = models.JSONField(default=list) + mastodon_blocks = models.JSONField(default=list) + mastodon_account = models.JSONField(default=dict) + mastodon_last_refresh = models.DateTimeField(default=timezone.now) # store the latest read announcement id, # every time user read the announcement update this field read_announcement_index = models.PositiveIntegerField(default=0) @@ -42,6 +51,25 @@ class User(AbstractUser): def __str__(self): return self.username + '@' + self.mastodon_site + def refresh_mastodon_data(self): + """ Try refresh account data from mastodon server, return true if refreshed successfully, note it will not save to db """ + self.mastodon_last_refresh = timezone.now() + code, mastodon_account = verify_account(self.mastodon_site, self.mastodon_token) + updated = False + if mastodon_account: + self.mastodon_account = mastodon_account + self.mastodon_locked = mastodon_account['locked'] + # self.mastodon_token = token + # user.mastodon_id = mastodon_account['id'] + self.mastodon_followers = get_related_acct_list(self.mastodon_site, self.mastodon_token, f'/api/v1/accounts/{self.mastodon_id}/followers') + self.mastodon_following = get_related_acct_list(self.mastodon_site, self.mastodon_token, f'/api/v1/accounts/{self.mastodon_id}/following') + self.mastodon_mutes = get_related_acct_list(self.mastodon_site, self.mastodon_token, '/api/v1/mutes') + self.mastodon_blocks = get_related_acct_list(self.mastodon_site, self.mastodon_token, '/api/v1/blocks') + updated = True + elif code == 401: + self.mastodon_token = '' + return updated + class Preference(models.Model): user = models.OneToOneField(User, models.CASCADE, primary_key=True) diff --git a/users/views.py b/users/views.py index e2c1b2ba..e1a748c4 100644 --- a/users/views.py +++ b/users/views.py @@ -838,10 +838,21 @@ def manage_report(request): # Utils ######################################## +def refresh_mastodon_data_task(user, token): + user.mastodon_token = token + if user.refresh_mastodon_data(): + user.save() + print(f"{user} mastodon data refreshed") + else: + print(f"{user} mastodon data refresh failed") + + def auth_login(request, user, token): """ Decorates django ``login()``. Attach token to session.""" request.session['oauth_token'] = token auth.login(request, user) + # refresh_mastodon_data_task(user, token) + django_rq.get_queue('mastodon').enqueue(refresh_mastodon_data_task, user, token) def auth_logout(request):