def SendLikeActivity(self, req, context): response = general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK) # Get article from DB article, err = self._get_article(req.article_id) if err is not None: self._logger.error("Error getting article: " + err) response.result_type = general_pb2.ResultType.ERROR response.error = err return response author = self._get_author(article) if author is None: self._logger.error("Error getting article author from DB") response.result_type = general_pb2.ResultType.ERROR response.error = "Error getting article author from DB" return response activity = build_like_activity( self._activ_util.build_actor(req.liker_handle, self._hostname), self._activ_util.build_article_ap_id(author, article)) inbox = self._activ_util.build_inbox_url(author.handle, author.host) resp, err = self._activ_util.send_activity(activity, inbox) if err is not None: response.result_type = general_pb2.ResultType.ERROR response.error = err return response
def ReceiveCreate(self, req, context): self._logger.debug("Recieved a new create notification.") resp = general_pb2.GeneralResponse() # get actor ids author_id, follower_id = self._get_actor_ids(req.attributedTo, req.recipient) if author_id is None: resp.result_type = general_pb2.ResultType.ERROR_400 return resp # check if local user follows follower_flag = self._check_follow(author_id, follower_id) if follower_flag is False: resp.result_type = general_pb2.ResultType.ERROR return resp # add to article db added_flag = self._add_to_posts_db(author_id, req) if added_flag is False: resp.result_type = general_pb2.ResultType.ERROR return resp self._logger.debug("Recieve create Article success") resp.result_type = general_pb2.ResultType.OK return resp
def ReceiveLikeUndoActivity(self, req, ctx): self._logger.debug("Got undo for like object") user = self.get_user(req.liking_user_ap_id) if user is None: return self.gen_error("Couldn't get user: "******"Error getting article: %s", err) return self.gen_error("Could not get article: " + req.liked_object_ap_id) if not self.remove_like_from_db(user.global_id, article.global_id): return self.gen_error("Error removing like from DB") # TODO(CianLR): If this is the author's local server then federate # the unlike if self._users_util.user_is_local(article.author_id): # Build the activity. a = self._activ_util.build_undo(like_util.build_like_activity( req.liking_user_ap_id, req.liked_object_ap_id)) # Forward it to the followers self._activ_util.forward_activity_to_followers( article.author_id, a) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK, )
def SendUnfollowActivity(self, req, context): resp = general_pb2.GeneralResponse() follower_actor = self._activ_util.build_actor( req.follower.handle, req.follower.host) followed_actor = self._activ_util.build_actor( req.followed.handle, req.followed.host) # Build a follow activity, then wrap it in an Undo activity follow_activity = self._build_activity(follower_actor, followed_actor, sendable=False) undo_activity = self._build_undo(follower_actor, followed_actor, follow_activity) inbox_url = self._activ_util.build_inbox_url( req.followed.handle, req.followed.host) self._logger.debug('Sending unfollow activity to foreign server') self._logger.debug(str(undo_activity)) sender_id = self._get_local_user_id(req.follower.handle) _, err = self._activ_util.send_activity(undo_activity, inbox_url, sender_id=sender_id) if err is None: resp.result_type = general_pb2.ResultType.OK else: resp.result_type = general_pb2.ResultType.ERROR resp.error = err return resp
def ReceiveUnfollow(self, request, context): resp = general_pb2.GeneralResponse() local_user, foreign_user = self._util.validate_and_get_users( resp, request) if foreign_user is None or local_user is None: self._logger.info('Error receiving unfollow: %s', resp.error) return resp self._logger.info('User ID %d is unfollowing User ID %d', foreign_user.global_id, local_user.global_id) follow_resp = self._util.delete_follow_in_db(foreign_user.global_id, local_user.global_id) if follow_resp.result_type == general_pb2.ResultType.ERROR: self._logger.error('Error deleting follow: %s', follow_resp.error) resp.result_type = general_pb2.ResultType.ERROR resp.error = 'Could not delete requested follow from database' return resp if self._recommender_stub is not None: req = recommend_follows_pb2.UpdateFollowRecommendationsRequest( follower=foreign_user.global_id, followed=local_user.global_id, following=False) self._recommender_stub.UpdateFollowRecommendations(req) resp.result_type = general_pb2.ResultType.OK return resp
def parse_actor_error(self, actor_name, actor_id): self._logger.error("Received error while parsing %s author id: %s", actor_name, actor_id) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR_400, error="Could not parse {} author id".format(actor_name), )
def WriteLog(self, request, context): py_level = self._log_levels[request.severity] log_message = self._format_log(request.timestamp, request.source, py_level, request.message) self._logger.log(py_level, log_message) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK)
def SendCreate(self, req, context): self._logger.debug("Recieved a new create action.") author = self._users_util.get_user_from_db(global_id=req.author_id) # Insert ActivityPub ID into database. # build author entry from scratch to add host into call ue = database_pb2.UsersEntry(handle=author.handle, host=self._host_name) pe = database_pb2.PostsEntry(global_id=req.global_id) ap_id = self._activ_util.build_article_ap_id(ue, pe) article_url = self._activ_util.build_local_article_url(ue, pe) err = self._add_ap_id(req.global_id, ap_id) if err is not None: self._logger.error("Continuing through error: %s", err) # list of follow objects follow_list = self._users_util.get_follower_list(author.global_id) # remove local users foreign_follows = self._users_util.remove_local_users(follow_list) # go through follow send create activity # TODO (sailslick) make async/ parallel in the future for follower in foreign_follows: self._post_create_req(follower, req, ap_id, author, article_url) resp = general_pb2.GeneralResponse() resp.result_type = general_pb2.ResultType.OK return resp
def ReceiveDeleteActivity(self, req, ctx): self._logger.info("Received delete for article '%s'", req.ap_id) article = get_article(self._logger, self._db, ap_id=req.ap_id) if article is None: # Don't have the article, our work here is done. # This can happen for natural reasons, dublicate deletes, # deletes of articles that were created before a follower # followed the creator, etc. self._logger.info("Don't have article %s, exiting", req.ap_id) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK ) author = self._users_util.get_user_from_db(global_id=article.author_id) if author is None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not retrieve author", ) # Grab the people who shared the article before we delete everything. sharer_ids = get_sharers_of_article( self._logger, self._db, article.global_id) # Delete the local copy. if not delete_article(self._logger, self._db, ap_id=req.ap_id): return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not delete article", ) # Forward the delete to the announcers. delete_obj = self._activ_util.build_delete( author, article, self._hostname) for user_id in sharer_ids: err = self._activ_util.forward_activity_to_followers( user_id, delete_obj) if err is not None: # Warn but do not quit on error sending to announcer followers. self._logger.warning( "Sending activity to %d followers failed", user_id) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK )
def ReceiveUpdateActivity(self, req, ctx): self._logger.info("Received edit for article '%s'", req.title) html_body = md_to_html(self._md, req.body) resp = self._db.Posts( dbpb.PostsRequest( request_type=dbpb.RequestType.UPDATE, match=dbpb.PostsEntry(ap_id=req.ap_id), entry=dbpb.PostsEntry( title=req.title, body=html_body, md_body=req.body, summary=req.summary, ), )) if resp.result_type != general_pb2.ResultType.OK: self._logger.error("Could not update article: %s", resp.error) return general_pb2.GeneralResponse( result_type=upb.UpdateRespones.ERROR, error="Error updating article in DB", ) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK)
def ReceiveLikeActivity(self, req, context): self._logger.debug("Got like for %s from %s", req.liked_object, req.liker_id) # Get article. article, err = self._activ_util.get_article_by_ap_id(req.liked_object) if err is not None: self._logger.error("Error getting article: %s", err) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error=err, ) # Get user. user_id = self._get_liking_user(req.liker_id) if user_id is None: self._logger.error("Could not get liking user") return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not get liking user", ) # Add like to local database. err = self._add_like_to_db(user_id, article.global_id) if err is not None: self._logger.error("Could not add like to DB: %s", err) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not add like to DB: " + err) if self._user_util.user_is_local(article.author_id): # This is the author's local server, must distribute like to # all the servers who follow this user. self._activ_util.forward_activity_to_followers( article.author_id, build_like_activity(req.liker_id, req.liked_object)) # If post_recommender is on, send like to post_recommender if self._post_recommendation_stub is not None: self._add_like_to_user_model(user_id, article.global_id) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK)
def ReceiveUnfollowActivity(self, req, context): self._logger.debug('Received unfollow activity.') resp = general_pb2.GeneralResponse() follow = self._s2s_req_to_follows_req(req) self._logger.info('{}@{} requested to unfollow {}.'.format( follow.follower_handle, follow.follower_host, follow.followed)) follows_resp = self._follows_stub.ReceiveUnfollow(follow) resp.result_type = follows_resp.result_type resp.error = follows_resp.error return resp
def RssFollowRequest(self, req, context): resp = general_pb2.GeneralResponse() self._logger.info("Got follow rss request.") # check if url has right endings valid_url = self._validate_url(req.feed_url) if not valid_url: self._logger.error("Invalid rss/atom url: %s", req.feed_url) resp.result_type = general_pb2.ResultType.ERROR resp.error = "Invalid rss/atom url" return resp # check if rss user already exists (handle: domain, local so no host) rss_handle = self._convert_rss_url_to_handle(req.feed_url) rss_entry = self._users_util.get_user_from_db(handle=rss_handle, host_is_null=True) if rss_entry is None: # send to rss service to be created rss_user_id, rss_error = self._create_rss_user(req.feed_url) if rss_error: resp.result_type = general_pb2.ResultType.ERROR resp.error = rss_error return resp else: rss_user_id = rss_entry.global_id # Get local user id follower_entry = self._users_util.get_user_from_db(handle=req.follower, host_is_null=True) if follower_entry is None: error = "Could not find local user {}".format(req.follower) self._logger.error(error) resp.result_type = general_pb2.ResultType.ERROR resp.error = error return resp self._logger.info("User ID %d has requested to follow RSS user ID %d", follower_entry.global_id, rss_user_id) # Add follower follow_resp = self._util.create_follow_in_db(follower_entry.global_id, rss_user_id) if follow_resp.result_type == general_pb2.ResultType.ERROR: self._logger.error("Error creating follow: %s", follow_resp.error) resp.result_type = general_pb2.ResultType.ERROR resp.error = "Could not add requested follow to database" return resp resp.result_type = general_pb2.ResultType.OK return resp
def SendApproval(self, req, context): resp = general_pb2.GeneralResponse() url = self._activ_util.build_inbox_url(req.follow.follower.handle, req.follow.follower.host) activity = self._build_activity(req) sender_id = self._get_local_user_id(req.follow.followed.handle) _, err = self._activ_util.send_activity(activity, url, sender_id=sender_id) if err is not None: resp.error = err resp.result_type = general_pb2.ResultType.ERROR return resp
def SendLikeUndoActivity(self, req, ctx): self._logger.info( "Got request to undo like for article {} by user {}".format( req.article_id, req.liker_handle)) try: article = self._get_article(req.article_id) author = self._users_util.get_user_from_db( global_id=article.author_id) if author is None: raise SendUndoException("Error getting author") if not author.host: author.host = self._hostname undo_obj = self._build_like_undo_object(req.liker_handle, author, article) inbox = self._activ_util.build_inbox_url(author.handle, author.host) _, err = self._activ_util.send_activity(undo_obj, inbox) if err: raise SendUndoException(err) except SendUndoException as e: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error=str(e)) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK)
def SendDeleteActivity(self, req, ctx): self._logger.info("Got request to delete article %d from %d", req.article_id, req.user_id) user = self._users_util.get_user_from_db(global_id=req.user_id) if user is None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not retrieve user", ) article = get_article(self._logger, self._db, global_id=req.article_id) if article is None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not retrieve article", ) if article.author_id != req.user_id: self._logger.error("User requesting article deletion isn't author") return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR_401, error="User is not the author of this article", ) sharer_ids = get_sharers_of_article(self._logger, self._db, article.global_id) if not delete_article( self._logger, self._db, global_id=article.global_id): return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not delete article locally", ) delete_obj = self._activ_util.build_delete(user, article, self._hostname) self._logger.info("Activity: %s", str(delete_obj)) err = self._activ_util.forward_activity_to_followers( req.user_id, delete_obj) if err is not None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error=err, ) # Send deletes of the article to the followers of people who # announced the article. There may be some duplicate Deletes # sent but this is acceptable. # This roughly the same pattern Mastodon follows: # https://github.com/tootsuite/mastodon/issues/5761#issuecomment-345875480 for user_id in sharer_ids: err = self._activ_util.forward_activity_to_followers( user_id, delete_obj) if err is not None: # Warn but do not quit on error sending to announcer followers. self._logger.warning( "Sending activity to followers of user %d failed", user_id) self._logger.info("Article %d successfully deleted", req.article_id) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK)
def SendFollowActivity(self, req, context): resp = general_pb2.GeneralResponse() follower_actor = self._activ_util.build_actor( req.follower.handle, req.follower.host) followed_actor = self._activ_util.build_actor( req.followed.handle, req.followed.host) activity = self._build_activity(follower_actor, followed_actor) inbox_url = self._activ_util.build_inbox_url( req.followed.handle, req.followed.host) self._logger.debug('Sending follow activity to foreign server') self._logger.debug(str(activity)) err = self._send(activity, inbox_url, req.follower.handle) if err is None: resp.result_type = general_pb2.ResultType.OK else: resp.result_type = general_pb2.ResultType.ERROR resp.error = err return resp
def add_share_update_count(self, announcer, article, announce_datetime): self._logger.debug("Adding share") req = db_pb.ShareEntry(user_id=announcer.global_id, article_id=article.global_id, announce_datetime=announce_datetime) # check if share already exists resp = self._db.FindShare(req) if resp.result_type == general_pb2.ResultType.OK and resp.exists: # Share exists. return success return None resp = self._db.AddShare(req) if resp.result_type != general_pb2.ResultType.OK: self._logger.error("Received error while adding share %s", resp.error) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not add share to db {}".format(resp.error), ) return None
def AddLike(self, req, context): self._logger.debug("Adding like by %d to article %d", req.user_id, req.article_id) response = general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK) try: self._db.execute( 'INSERT INTO likes (user_id, article_id) ' 'VALUES (?, ?)', req.user_id, req.article_id, commit=False) self._db.execute( 'UPDATE posts SET likes_count = likes_count + 1 ' 'WHERE global_id=?', req.article_id) except sqlite3.Error as e: self._db.discard_cursor() self._logger.error("AddLike error: %s", str(e)) response.result_type = general_pb2.ResultType.ERROR response.error = str(e) return response
def RemoveLike(self, req, context): self._logger.debug("Removing like by %d to article %d", req.user_id, req.article_id) response = general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK) try: self._db.execute( 'DELETE FROM likes WHERE user_id=? AND article_id=?', req.user_id, req.article_id, commit=False) self._db.execute( 'UPDATE posts SET likes_count = likes_count - 1 ' 'WHERE global_id=?', req.article_id, commit=True) except sqlite3.Error as e: self._db.discard_cursor() self._logger.error("RemoveLike error %s", str(e)) response.result_type = general_pb2.ResultType.ERROR response.error = str(e) return response
def ReceiveFollowRequest(self, request, context): resp = general_pb2.GeneralResponse() local_user, foreign_user = self._util.validate_and_get_users(resp, request) if foreign_user is None or local_user is None: return resp self._logger.info('User ID %d has requested to follow User ID %d', foreign_user.global_id, local_user.global_id) if not local_user.private.value: self._logger.info('Accepting follow request') self._util.attempt_to_accept( local_user, foreign_user, self._host_name, True) state = database_pb2.Follow.ACTIVE if local_user.private.value: self._logger.info('Follow private user: waiting for approval') state = database_pb2.Follow.PENDING follow_resp = self._util.create_follow_in_db(foreign_user.global_id, local_user.global_id, state=state) if follow_resp.result_type == general_pb2.ResultType.ERROR: self._logger.error('Error creating follow: %s', follow_resp.error) resp.result_type = general_pb2.ResultType.ERROR resp.error = 'Could not add requested follow to database' return resp if self._recommender_stub is not None: req = recommend_follows_pb2.UpdateFollowRecommendationsRequest( follower=foreign_user.global_id, followed=local_user.global_id, following=True) self._recommender_stub.UpdateFollowRecommendations(req) resp.result_type = general_pb2.ResultType.OK return resp
def ReceiveApproval(self, req, context): self._logger.info("Received an approval request") resp = general_pb2.GeneralResponse() follower, followed = self._get_users(resp, req) if follower is None or followed is None: return resp db_resp = None if req.accept: db_resp = self._set_approved(follower, followed) else: db_resp = self._set_rejected(follower, followed) if db_resp.result_type == general_pb2.ResultType.ERROR: err = "Could not add follow to database: " + db_resp.error self._logger.error(err) resp.error = err resp.result_type = general_pb2.ResultType.ERROR return resp resp.result_type = general_pb2.ResultType.OK return resp
def AcceptFollow(self, request, context): resp = general_pb2.GeneralResponse() if not request.handle or not request.follower.handle: self._logger.error('Error accepting follow: bad arguments') resp.result_type = general_pb2.ResultType.ERROR resp.error = 'Could not accept follow.' return resp follower, followed = self._get_users(resp, request) if follower is None or followed is None: return resp err = self._get_follow(resp, follower.global_id, followed.global_id) if err is not None: return resp if request.follower.host: # Acceptor handles everything from here self._util.attempt_to_accept(followed, follower, self._host_name, request.is_accepted) self._modify_follow(resp, follower.global_id, followed.global_id, request.is_accepted) return resp
def SendUpdateActivity(self, req, ctx): self._logger.info("Got request to update article %d from %d", req.article_id, req.user_id) user = self._users_util.get_user_from_db(global_id=req.user_id) if user is None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Error retrieving user", ) article = get_article(self._logger, self._db, global_id=req.article_id) if article is None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Error retrieving article", ) if article.author_id != user.global_id: self._logger.warning( "User %d requested to edit article belonging to user %d", req.user_id, article.author_id) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR_401 ) # Update article locally if not self._update_locally(article, req): return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Error updating article", ) # Send out update activity update_obj = self._build_update(user, article, req) self._logger.info("Activity: %s", str(update_obj)) err = self._activ_util.forward_activity_to_followers( req.user_id, update_obj) if err is not None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error=err, ) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK )
def SendFollowRequest(self, request, context): resp = general_pb2.GeneralResponse() self._logger.info('Sending follow request.') from_handle, from_instance = self._users_util.parse_username( request.follower) to_handle, to_instance = \ self._users_util.parse_username(request.followed) self._logger.info('%s@%s has requested to follow %s@%s.', from_handle, from_instance, to_handle, to_instance) if to_instance is None and to_handle is None: resp.result_type = general_pb2.ResultType.ERROR resp.error = 'Could not parse followed username' return resp # Get user IDs for follow. follower_entry = self._users_util.get_or_create_user_from_db( handle=from_handle, host=from_instance, host_is_null=(from_instance is None)) if follower_entry is None: error = 'Could not find or create user {}@{}'.format(from_handle, from_instance) self._logger.error(error) resp.result_type = general_pb2.ResultType.ERROR resp.error = error return resp is_local = to_instance is None created_user = False followed_entry = self._users_util.get_user_from_db( handle=to_handle, host=to_instance, host_is_null=is_local) if not is_local: created_user = True fu_host, _, fu_bio = self._users_util.get_actor_details( to_handle, to_instance) if fu_host is None: resp.result_type = general_pb2.ResultType.ERROR resp.error = "Invalid foreign user to follow" return resp followed_entry = self._users_util.get_or_create_user_from_db( handle=to_handle, host=to_instance, bio=fu_bio) if followed_entry is None: error = 'Could not find or create user {}@{}'.format(to_handle, to_instance) self._logger.error(error) resp.result_type = general_pb2.ResultType.ERROR resp.error = error return resp self._logger.info('User ID %d has requested to follow User ID %d', follower_entry.global_id, followed_entry.global_id) err = self._add_follow(resp, follower_entry.global_id, followed_entry.global_id, followed_entry.private.value, not is_local) if err is not None: return resp if not is_local: err = self._send_s2s(from_handle, to_handle, to_instance) if err is not None: self._logger.error("Error from s2sFollow: %s", err) self._roll_back_follow(follower_entry.global_id, followed_entry.global_id, created_user) resp.result_type = general_pb2.ResultType.ERROR resp.error = err return resp if self._recommender_stub is not None: req = recommend_follows_pb2.UpdateFollowRecommendationsRequest( follower=follower_entry.global_id, followed=followed_entry.global_id, following=True) self._recommender_stub.UpdateFollowRecommendations(req) resp.result_type = general_pb2.ResultType.OK return resp
def SendAnnounceActivity(self, req, context): self._logger.debug("Sending announce for article_id %s from %s at %s", req.article_id, req.announcer_id, req.announce_time.seconds) response = general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK) announcer = self._users_util.get_user_from_db( global_id=req.announcer_id) if announcer is None: response.result_type = general_pb2.ResultType.ERROR_400 response.error = "Announcer does not exist" return response # Get post author & reblogged article article, err = self._get_shared_article(req.article_id) if err is not None: response.result_type = general_pb2.ResultType.ERROR_400 response.error = "Shared Article does not exist" return response author = self._users_util.get_user_from_db(global_id=article.author_id) if author is None: response.result_type = general_pb2.ResultType.ERROR_400 response.error = "Author of shared post does not exist" return response if author.global_id == announcer.global_id: response.result_type = general_pb2.ResultType.ERROR_400 response.error = "Author cannot share their own post" return response host = author.host article_url = None # check if author is local, if they are build local article url if not author.host: host = self._hostname article_url = self._activ_util.build_local_article_url( author, article) author_actor = self._activ_util.build_actor(author.handle, host) # Create Announce activity article_ap_id = self._activ_util.build_article_ap_id(author, article) timestamp = self._activ_util.timestamp_to_rfc( article.creation_datetime) article_obj = self._activ_util.build_article( article_ap_id, article.title, timestamp, author_actor, article.md_body, article.summary, article_url=article_url, ) actor = self._activ_util.build_actor(announcer.handle, self._hostname) creation_datetime = self._activ_util.timestamp_to_rfc( req.announce_time) announce_activity = self._announce_util.build_announce_activity( actor, article_obj, creation_datetime) # Create a list of foreign followers follow_list = self._users_util.get_follower_list(req.announcer_id) foreign_follows = self._users_util.remove_local_users(follow_list) # Add article author if not in list # This is so they can increment their share count & supply to followers if author not in foreign_follows: foreign_follows.append(author) # Add announcer so local instance can add to share db if announcer not in foreign_follows: foreign_follows.append(announcer) # Send activity to all followers response = self._announce_util.send_announce_activity( foreign_follows, announce_activity, response) return response
def gen_error(self, err): return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error=err, )
def SendUnfollow(self, request, context): resp = general_pb2.GeneralResponse() self._logger.info('Setting unfollow.') from_handle, from_instance = self._users_util.parse_username( request.follower) to_handle, to_instance = \ self._users_util.parse_username(request.followed) self._logger.info('%s@%s has requested to unfollow %s@%s.', from_handle, from_instance, to_handle, to_instance) if to_instance is None and to_handle is None: resp.result_type = general_pb2.ResultType.ERROR resp.error = 'Could not parse unfollowed username' return resp # Get user IDs for unfollow. follower_entry = self._users_util.get_or_create_user_from_db( handle=from_handle, host=from_instance, host_is_null=(from_instance is None)) if follower_entry is None: error = 'Could not find or create user {}@{}'.format(from_handle, from_instance) self._logger.error(error) resp.result_type = general_pb2.ResultType.ERROR resp.error = error return resp followed_entry = self._users_util.get_or_create_user_from_db( handle=to_handle, host=to_instance, host_is_null=(to_instance is None)) if followed_entry is None: error = 'Could not find or create user {}@{}'.format(to_handle, to_instance) self._logger.error(error) resp.result_type = general_pb2.ResultType.ERROR resp.error = error return resp self._logger.info('User ID %d has requested to unfollow User ID %d', follower_entry.global_id, followed_entry.global_id) is_foreign = to_instance is not None err = self._remove_follow(resp, follower_entry.global_id, followed_entry.global_id) if err is not None: # If there was an error during unfollowing, return it. return resp if is_foreign: # Local user won't have a from_instance, set it. from_instance = self._host_name s2s_follower = s2s_follow_pb2.FollowActivityUser(handle=from_handle, host=from_instance) s2s_followed = s2s_follow_pb2.FollowActivityUser(handle=to_handle, host=to_instance) s2s_req = s2s_follow_pb2.FollowDetails(follower=s2s_follower, followed=s2s_followed) self._s2s_stub.SendUnfollowActivity(s2s_req) if self._recommender_stub is not None: req = recommend_follows_pb2.UpdateFollowRecommendationsRequest( follower=follower_entry.global_id, followed=followed_entry.global_id, following=False) self._recommender_stub.UpdateFollowRecommendations(req) resp.result_type = general_pb2.ResultType.OK return resp
def ReceiveAnnounceActivity(self, req, context): self._logger.debug("Received announce for %s of %s from %s at %s", req.target_id, req.announced_object, req.announcer_id, req.announce_time.seconds) response = general_pb2.GeneralResponse( result_type=general_pb2.ResultType.OK) # Parse announcer, author and target ids author_tuple = self._users_util.parse_actor_details(req.author_ap_id) if author_tuple[0] is None: return parse_actor_error("author", req.author_ap_id) announcer_tuple = self._users_util.parse_actor_details( req.announcer_id) if announcer_tuple[0] is None: return parse_actor_error("announcer", req.announcer_id) author = self.get_user_by_ap_id(author_tuple) # TODO(sailslick) check if author is actual author/post exists irl # https://github.com/CPSSD/rabble/issues/409 if author is None: self._logger.debug( "Author does not exist on this server, creating") # Author is foreign and doesn't exist. # Check if announcer exists announcer = self.get_user_by_ap_id(announcer_tuple) if announcer is None: # If announcer & author doesn't exist, announce is error self._logger.error( "Received announce without knowing author or announcer") return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR_400, error= "Received announce without knowing author or announcer", ) # Follower of announcer # Create author. author = self._users_util.get_or_create_user_from_db( handle=author_tuple[1], host=author_tuple[0], bio=author_tuple[2]) # Create post article = self.create_post(author, req) if article is None: self._logger.debug( "Issue creating new article from unknown author") return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not create local version of article id: {}". format(req.announced_object), ) else: # Check if announcer exists announcer = self.get_user_by_ap_id(announcer_tuple) if announcer is None: # If announcer doesn't exist, target is follower of author # Add announcer announcer = self._users_util.get_or_create_user_from_db( handle=announcer_tuple[1], host=announcer_tuple[0], bio=announcer_tuple[2]) # Check if article exists article, err = self._activ_util.get_article_by_ap_id( req.announced_object) if err is not None and (author.host_is_null or not author.host): # Author is local but the post doesn't exist. # This is a bad request. self._logger.error( "Received announce with local author for post that doesn't exist" ) return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR_400, error= "Announce with local author for post that doesn't exist", ) elif err is not None: message = "Could not find new article ap_id: {} even though author exists".format( req.announced_object) self._logger.debug(message) # TODO(sailslick) check when author is foreign, maybe we didn't get article? # But this requires checking the ap_id of article and matching to author # Also check if author actually wrote this # https://github.com/CPSSD/rabble/issues/409 # Create post article = self.create_post(author, req) if article is None: return general_pb2.GeneralResponse( result_type=general_pb2.ResultType.ERROR, error="Could not create local version of article id: {}" .format(req.announced_object), ) elif author.host_is_null or not author.host: self._logger.debug("Author and article local") # author and article local, check if author is target. # if not target, send success as author will receive share if req.target_id != req.author_ap_id: self._logger.debug( "Target is local but not author, returning success") return response # if target, add to shares db, update share count, send to followers err_resp = self.add_share_update_count(announcer, article, req.announce_time) if err_resp is not None: return err_resp article_url = self._activ_util.build_local_article_url( author, article) timestamp = self._activ_util.timestamp_to_rfc( article.creation_datetime) article_obj = self._activ_util.build_article( req.announced_object, article.title, timestamp, req.author_ap_id, article.md_body, article.summary, article_url=article_url, ) return self.send_to_followers(author, req.announce_time, req.announcer_id, article_obj, response) # At this point, author, announcer and article all exist. err_resp = self.add_share_update_count(announcer, article, req.announce_time) if err_resp is not None: return err_resp return response