diff --git a/app/activitypub/util.py b/app/activitypub/util.py index 1e8c2d65..ff12fe4d 100644 --- a/app/activitypub/util.py +++ b/app/activitypub/util.py @@ -2,6 +2,7 @@ from __future__ import annotations import json import os +from datetime import timedelta from random import randint from typing import Union, Tuple from flask import current_app, request, g @@ -205,6 +206,8 @@ def find_actor_or_create(actor: str) -> Union[User, Community, None]: user = Community.query.filter_by(ap_profile_id=actor).first() if user is not None: + if not user.is_local() and user.ap_fetched_at < utcnow() - timedelta(days=7): + refresh_user_profile(user.id) return user else: # User does not exist in the DB, it's going to need to be created from it's remote home instance if actor.startswith('https://'): @@ -247,11 +250,53 @@ def extract_domain_and_actor(url_string: str): return server_domain, actor +def refresh_user_profile(user_id): + if current_app.debug: + refresh_user_profile_task(user_id) + else: + refresh_user_profile_task.apply_async(args=(user_id), countdown=randint(1, 10)) + + +@celery.task +def refresh_user_profile_task(user_id): + user = User.query.get(user_id) + if user: + actor_data = get_request(user.ap_profile_id, headers={'Accept': 'application/activity+json'}) + if actor_data.status_code == 200: + activity_json = actor_data.json() + actor_data.close() + user.user_name = activity_json['preferredUsername'] + user.about_html = parse_summary(activity_json) + user.ap_fetched_at = utcnow() + user.public_key=activity_json['publicKey']['publicKeyPem'] + + avatar_changed = cover_changed = False + if 'icon' in activity_json: + if activity_json['icon']['url'] != user.avatar.source_url: + user.avatar.delete_from_disk() + avatar = File(source_url=activity_json['icon']['url']) + user.avatar = avatar + db.session.add(avatar) + avatar_changed = True + if 'image' in activity_json: + if activity_json['image']['url'] != user.cover.source_url: + user.cover.delete_from_disk() + cover = File(source_url=activity_json['image']['url']) + user.cover = cover + db.session.add(cover) + cover_changed = True + db.session.commit() + if user.avatar_id and avatar_changed: + make_image_sizes(user.avatar_id, 40, 250, 'users') + if user.cover_id and cover_changed: + make_image_sizes(user.cover_id, 700, 1600, 'users') + + def actor_json_to_model(activity_json, address, server): if activity_json['type'] == 'Person': user = User(user_name=activity_json['preferredUsername'], email=f"{address}@{server}", - about=parse_summary(activity_json), + about_html=parse_summary(activity_json), created=activity_json['published'] if 'published' in activity_json else utcnow(), ap_id=f"{address}@{server}", ap_public_url=activity_json['id'], diff --git a/app/main/routes.py b/app/main/routes.py index 8b555128..a5fca88d 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -1,7 +1,7 @@ from sqlalchemy.sql.operators import or_ from app import db, cache -from app.activitypub.util import default_context, make_image_sizes_async +from app.activitypub.util import default_context, make_image_sizes_async, refresh_user_profile from app.constants import SUBSCRIPTION_PENDING, SUBSCRIPTION_MEMBER, POST_TYPE_IMAGE, POST_TYPE_LINK, SUBSCRIPTION_OWNER from app.main import bp from flask import g, session, flash, request, current_app, url_for, redirect, make_response, jsonify @@ -99,9 +99,7 @@ def robots(): @bp.route('/test') def test(): - make_image_sizes_async(159, 60, 250, 'communities') - make_image_sizes_async(140, 60, 250, 'communities') - make_image_sizes_async(141, 700, 1600, 'communities') + refresh_user_profile(12) return 'done' diff --git a/app/models.py b/app/models.py index 74ba3f66..2b589307 100644 --- a/app/models.py +++ b/app/models.py @@ -278,7 +278,7 @@ class User(UserMixin, db.Model): search_vector = db.Column(TSVectorType('user_name', 'bio', 'keywords')) activity = db.relationship('ActivityLog', backref='account', lazy='dynamic', cascade="all, delete-orphan") posts = db.relationship('Post', lazy='dynamic', cascade="all, delete-orphan") - post_replies = db.relationship('PostReply', backref='author', lazy='dynamic', cascade="all, delete-orphan") + post_replies = db.relationship('PostReply', lazy='dynamic', cascade="all, delete-orphan") roles = db.relationship('Role', secondary=user_role, lazy='dynamic', cascade="all, delete") @@ -586,6 +586,8 @@ class PostReply(db.Model): search_vector = db.Column(TSVectorType('body')) + author = db.relationship('User', lazy='joined', foreign_keys=[user_id], single_parent=True) + def is_local(self): return self.ap_id is None or self.ap_id.startswith('https://' + current_app.config['SERVER_NAME'])