def save(self, box: Box, activity: ap.BaseActivity) -> None: """Save an Activity in database""" current_app.logger.info(f"asked to save an activity {activity!r}") # Save remote Actor ap_actor = activity.get_actor() domain = urlparse(ap_actor.id) current_app.logger.debug(f"actor.id=={ap_actor.__dict__}") current_app.logger.debug(f"actor domain {domain.netloc} and " f"name {ap_actor.preferredUsername}") actor = Actor.query.filter(Actor.domain == domain.netloc, Actor.name == ap_actor.preferredUsername).first() # FIXME TODO: check if it still works with unknown remote actor if not actor: current_app.logger.debug("cannot find actor") actor = Actor.query.filter(Actor.url == ap_actor.id).first() if not actor: current_app.logger.debug(f"actor {ap_actor.id} not found") actor, user = create_remote_actor(ap_actor) db.session.add(user) db.session.add(actor) current_app.logger.debug("created one in DB") else: current_app.logger.debug(f"got local one {actor.url}") else: current_app.logger.debug(f"got remote one {actor.url}") # Save Activity act = Activity() act.payload = activity.to_dict() act.url = activity.id act.type = activity.type act.box = box.value # Activity is local only if the url starts like BASE_URL base_url = current_app.config["BASE_URL"] act.local = activity.id.startswith(base_url) act.actor_id = actor.id db.session.add(act) db.session.commit()
def search(): """ Search. --- tags: - Global parameters: - name: q in: query type: string required: true description: search string responses: 200: description: fixme. """ # Get logged in user from bearer token, or None if not logged in if current_token: current_user = current_token.user else: current_user = None s = request.args.get("q", None) if not s: return jsonify({"error": "No search string provided"}), 400 # This is the old search endpoint and needs to be improved # Especially tracks and accounts needs to be returned in the right format, with the data helpers # Users should be searched from known Actors or fetched # URI should be searched from known activities or fetched # FTS, well, FTS needs to be implemented results = {"accounts": [], "sounds": [], "mode": None, "from": None} if current_user: results["from"] = current_user.name # Search for sounds # TODO: Implement FTS to get sounds search sounds = [] # Search for accounts accounts = [] is_user_at_account = RE_ACCOUNT.match(s) if s.startswith("https://"): # Try to match the URI from Activities in database results["mode"] = "uri" users = Actor.query.filter(Actor.meta_deleted.is_(False), Actor.url == s).all() elif is_user_at_account: # It matches [email protected], try to match it locally results["mode"] = "acct" user = is_user_at_account.group("user") instance = is_user_at_account.group("instance") users = Actor.query.filter(Actor.meta_deleted.is_(False), Actor.preferred_username == user, Actor.domain == instance).all() else: # It's a FTS search results["mode"] = "username" # Match actor username in database if current_user: users = (db.session.query(Actor, Follower).outerjoin( Follower, and_(Actor.id == Follower.target_id, Follower.actor_id == current_user.actor[0].id)).filter( or_(Actor.preferred_username.contains(s), Actor.name.contains(s))).filter( not_(Actor.id == current_user.actor[0].id)).all()) else: users = (db.session.query(Actor).filter( or_(Actor.preferred_username.contains(s), Actor.name.contains(s))).all()) # Handle the found users if len(users) > 0: for actor in users: relationship = False if current_user: relationship = to_json_relationship(current_user, actor.user) accounts.append(to_json_account(actor.user, relationship)) if len(accounts) <= 0: # Do a webfinger # TODO FIXME: We should do this only if https:// or user@account submitted # And rework it slightly differently since we needs to backend.fetch_iri() for https:// who # can match a Sound and not only an Actor current_app.logger.debug(f"webfinger for {s}") try: remote_actor_url = get_actor_url(s, debug=current_app.debug) # We need to get the remote Actor backend = ap.get_backend() iri = backend.fetch_iri(remote_actor_url) if iri: # We have fetched an unknown Actor # Save it in database and return it properly current_app.logger.debug( f"got remote actor URL {remote_actor_url}") act = ap.parse_activity(iri) fetched_actor, fetched_user = create_remote_actor(act) db.session.add(fetched_user) db.session.add(fetched_actor) db.session.commit() relationship = False if current_user: relationship = to_json_relationship( current_user, fetched_user) accounts.append(to_json_account(fetched_user, relationship)) results["mode"] = "webfinger" except (InvalidURLError, ValueError): current_app.logger.exception(f"Invalid AP URL: {s}") # Then test fetching as a "normal" Activity ? # Finally fill the results dict results["accounts"] = accounts # FIXME: handle exceptions if results["mode"] == "uri" and len(sounds) <= 0: backend = ap.get_backend() iri = backend.fetch_iri(s) if iri: # FIXME: Is INBOX the right choice here ? backend.save(Box.INBOX, iri) # Fetch again, but get it from database activity = Activity.query.filter(Activity.url == iri).first() if not activity: current_app.logger.exception("WTF Activity is not saved") else: from tasks import create_sound_for_remote_track, upload_workflow sound_id = create_sound_for_remote_track(activity) sound = Sound.query.filter(Sound.id == sound_id).one() upload_workflow.delay(sound.id) relationship = False if current_user: relationship = to_json_relationship(current_user, sound.user) acct = to_json_account(sound.user, relationship) sounds.append(to_json_track(sound, acct)) return jsonify({"who": s, "results": results})
def unfollow(): user = request.args.get("user") actor_me = current_user.actor[0] if user.startswith("https://"): actor = Actor.query.filter(Actor.url == user).first() local_user = actor.user else: local_user = User.query.filter(User.name == user).first() if local_user: # Process local unfollow actor_me.unfollow(local_user.actor[0]) flash(gettext("Unfollow successful"), "success") else: # Might be a remote unfollow # 1. Webfinger the user try: remote_actor_url = get_actor_url(user, debug=current_app.debug) except InvalidURLError: current_app.logger.exception(f"Invalid webfinger URL: {user}") remote_actor_url = None except requests.exceptions.HTTPError: current_app.logger.exception(f"Invali webfinger URL: {user}") remote_actor_url = None if not remote_actor_url: flash(gettext("User not found"), "error") return redirect(url_for("bp_users.profile", name=current_user.name)) # 2. Check if we have a local user actor_target = Actor.query.filter( Actor.url == remote_actor_url).first() if not actor_target: # 2.5 Fetch and save remote actor backend = ap.get_backend() iri = backend.fetch_iri(remote_actor_url) if not iri: flash(gettext("User not found"), "error") return redirect(url_for("bp_main.home")) act = ap.parse_activity(iri) actor_target, user_target = create_remote_actor(act) db.session.add(user_target) db.session.add(actor_target) # 2.5 Get the relation of the follow follow_relation = Follower.query.filter( Follower.actor_id == actor_me.id, Follower.target_id == actor_target.id).first() if not follow_relation: flash(gettext("You don't follow this user"), "error") return redirect(url_for("bp_users.profile", name=current_user.name)) # 3. Fetch the Activity of the Follow accept_activity = Activity.query.filter( Activity.url == follow_relation.activity_url).first() if not accept_activity: current_app.logger.error( f"cannot find accept activity {follow_relation.activity_url}") flash(gettext("Whoops, something went wrong")) return redirect(url_for("bp_users.profile", name=current_user.name)) # Then the Activity ID of the Accept will be the object id activity = ap.parse_activity(payload=accept_activity.payload) # Get the final activity (the Follow one) follow_activity = Activity.query.filter( Activity.url == activity.get_object_id()).first() if not follow_activity: current_app.logger.error( f"cannot find follow activity {activity.get_object_id()}") flash(gettext("Whoops, something went wrong")) return redirect(url_for("bp_users.profile", name=current_user.name)) ap_follow_activity = ap.parse_activity(payload=follow_activity.payload) # 4. Initiate a Follow request from actor_me to actor_target unfollow = ap_follow_activity.build_undo() post_to_outbox(unfollow) flash(gettext("Unfollow request have been transmitted"), "success") return redirect(url_for("bp_users.profile", name=current_user.name))
def follow(): user = request.args.get("user") actor_me = current_user.actor[0] if user.startswith("https://"): actor = Actor.query.filter(Actor.url == user).first() if actor: local_user = actor.user else: local_user = None else: local_user = User.query.filter(User.name == user).first() if local_user: # Process local follow actor_me.follow(None, local_user.actor[0]) flash(gettext("Follow successful"), "success") else: # Might be a remote follow # 1. Webfinger the user try: remote_actor_url = get_actor_url(user, debug=current_app.debug) except InvalidURLError: current_app.logger.exception(f"Invalid webfinger URL: {user}") remote_actor_url = None # except requests.exceptions.HTTPError: # current_app.logger.exception(f"Invali webfinger URL: {user}") # remote_actor_url = None if not remote_actor_url: flash(gettext("User not found"), "error") return redirect(url_for("bp_users.profile", name=current_user.name)) # 2. Check if we have a local user actor_target = Actor.query.filter( Actor.url == remote_actor_url).first() if not actor_target: # 2.5 Fetch and save remote actor backend = ap.get_backend() iri = backend.fetch_iri(remote_actor_url) if not iri: flash(gettext("User not found"), "error") return redirect(url_for("bp_main.home")) act = ap.parse_activity(iri) actor_target, user_target = create_remote_actor(act) db.session.add(user_target) db.session.add(actor_target) # 2.7 Check if we already have a relation rel = Follower.query.filter( Follower.actor_id == actor_me.id, Follower.target_id == actor_target.id).first() if not rel: # 3. Initiate a Follow request from actor_me to actor_target follow = ap.Follow(actor=actor_me.url, object=actor_target.url) post_to_outbox(follow) flash(gettext("Follow request have been transmitted"), "success") else: flash(gettext("You already follow this user", "info")) return redirect(url_for("bp_users.profile", name=current_user.name))