312 lines
14 KiB
Python
312 lines
14 KiB
Python
# friends.roblox.com
|
|
|
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort, after_this_request
|
|
from app.util import auth, websiteFeatures, friends
|
|
from app.models.user import User
|
|
from app.models.friend_relationship import FriendRelationship
|
|
from app.models.friend_request import FriendRequest
|
|
from app.models.follow_relationship import FollowRelationship
|
|
from app.extensions import limiter, db, redis_controller, get_remote_address, csrf, user_limiter
|
|
from flask_wtf.csrf import CSRFError, generate_csrf
|
|
from sqlalchemy import or_
|
|
from datetime import datetime, timedelta
|
|
|
|
from app.services.user_relationships import followings
|
|
|
|
FriendsAPIRoute = Blueprint('friendsapi', __name__, url_prefix="/")
|
|
csrf.exempt(FriendsAPIRoute)
|
|
@FriendsAPIRoute.errorhandler(CSRFError)
|
|
def handle_csrf_error(e):
|
|
ErrorResponse = make_response(jsonify({
|
|
"errors": [
|
|
{
|
|
"code": 0,
|
|
"message": "Token Validation Failed"
|
|
}
|
|
]
|
|
}))
|
|
|
|
ErrorResponse.status_code = 403
|
|
ErrorResponse.headers["x-csrf-token"] = generate_csrf()
|
|
return ErrorResponse
|
|
|
|
@FriendsAPIRoute.errorhandler(429)
|
|
def handle_ratelimit_reached(e):
|
|
return jsonify({
|
|
"errors": [
|
|
{
|
|
"code": 9,
|
|
"message": "The flood limit has been exceeded."
|
|
}
|
|
]
|
|
}), 429
|
|
|
|
@FriendsAPIRoute.before_request
|
|
def before_request():
|
|
if "Roblox/" not in request.user_agent.string:
|
|
csrf.protect()
|
|
|
|
@FriendsAPIRoute.route('/v1/my/friends/count', methods=['GET'])
|
|
@limiter.limit("60/minute")
|
|
@auth.authenticated_required_api
|
|
def GetFriendCount():
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
return jsonify( { "count": friends.GetFriendCount( AuthenticatedUser.id ) } ), 200
|
|
|
|
@FriendsAPIRoute.route('/v1/users/<int:userId>/friends/count', methods=['GET'])
|
|
@limiter.limit("60/minute")
|
|
def GetFriendCountByUserId( userId : int ):
|
|
UserObj : User = User.query.filter_by( id = userId ).first()
|
|
if UserObj is None:
|
|
return jsonify( { "errors": [ { "code": 1, "message": "User not found" } ] } ), 404
|
|
return jsonify( { "count": friends.GetFriendCount( userId ) } ), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/user/friend-requests/count", methods=["GET"])
|
|
@limiter.limit("60/minute")
|
|
@auth.authenticated_required_api
|
|
def GetMyFriendRequestsCount():
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
FriendRequestCount : int = max(FriendRequest.query.filter_by( requestee_id = AuthenticatedUser.id ).count(), 500)
|
|
return jsonify( { "count": FriendRequestCount } ), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userid>/friends", methods=["GET"])
|
|
@limiter.limit("60/minute")
|
|
def get_user_friends( userid : int ):
|
|
UserObj : User = User.query.filter_by(id=userid).first()
|
|
if UserObj is None:
|
|
return jsonify({"success": False, "message": "Invalid request"}),400
|
|
|
|
FriendRelationshipList : list[FriendRelationship] = FriendRelationship.query.filter(or_(FriendRelationship.user_id == userid, FriendRelationship.friend_id == userid)).all()
|
|
FriendList = []
|
|
for FriendRelationshipObj in FriendRelationshipList:
|
|
FriendObj : User = User.query.filter_by(id=FriendRelationshipObj.user_id if FriendRelationshipObj.user_id != userid else FriendRelationshipObj.friend_id).first()
|
|
if FriendObj is None:
|
|
continue
|
|
FriendList.append({
|
|
"isOnline": FriendObj.lastonline > datetime.utcnow() - timedelta(minutes=1),
|
|
"isDeleted": False,
|
|
"friendFrequentScore": 0,
|
|
"friendFrequentRank": 1,
|
|
"hasVerifiedBadge": False,
|
|
"description": FriendObj.description,
|
|
"created": FriendObj.created.isoformat(),
|
|
"isBanned": False,
|
|
"externalAppDisplayName": None,
|
|
"id": FriendObj.id,
|
|
"name": FriendObj.username,
|
|
"displayName": FriendObj.username
|
|
})
|
|
|
|
return jsonify({
|
|
"data": FriendList
|
|
})
|
|
|
|
@FriendsAPIRoute.route("/v1/user/friend-requests/decline-all", methods=["POST"])
|
|
@limiter.limit("60/minute")
|
|
@auth.authenticated_required_api
|
|
def decline_all_friend_requests():
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
FriendRequestList : list[FriendRequest] = FriendRequest.query.filter_by( requestee_id = AuthenticatedUser.id ).all()
|
|
for FriendRequestObj in FriendRequestList:
|
|
db.session.delete( FriendRequestObj )
|
|
db.session.commit()
|
|
return jsonify({}), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/unfriend", methods=["POST"])
|
|
@limiter.limit("60/minute")
|
|
@auth.authenticated_required_api
|
|
@user_limiter.limit("60/minute")
|
|
def unfriend_user( userId : int ):
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({ "errors": [{ "code": 1, "message": "The target user is invalid or does not exist"}] }), 400
|
|
FriendRelationshipObj : FriendRelationship = friends.GetFriendRelationship( AuthenticatedUser.id, UserObj.id )
|
|
if FriendRelationshipObj is not None:
|
|
db.session.delete( FriendRelationshipObj )
|
|
db.session.commit()
|
|
|
|
return jsonify({}), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/request-friendship", methods=["POST"])
|
|
@limiter.limit("15/minute")
|
|
@auth.authenticated_required_api
|
|
@user_limiter.limit("15/minute")
|
|
def request_friendship( userId : int ):
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({ "errors": [{ "code": 1, "message": "The target user is invalid or does not exist"}] }), 400
|
|
if userId == AuthenticatedUser.id:
|
|
return jsonify({ "errors": [{ "code": 7, "message": "The user cannot be friends with itself."}] }), 400
|
|
if friends.GetFriendCount( AuthenticatedUser.id ) >= 200:
|
|
return jsonify({ "errors": [{ "code": 31, "message": "User with max friends sent friend request."}] }), 400
|
|
if friends.IsFriends( AuthenticatedUser.id, UserObj.id ):
|
|
return jsonify({ "errors": [{ "code": 5, "message": "The target user is already a friend."}] }), 400
|
|
|
|
OtherFriendRequestObj : FriendRequest = FriendRequest.query.filter_by( requestee_id = AuthenticatedUser.id, requester_id = UserObj.id ).first()
|
|
if OtherFriendRequestObj is not None:
|
|
if friends.GetFriendCount( UserObj.id ) >= 200:
|
|
return jsonify({ "errors": [{ "code": 12, "message": "The target users friends limit has been exceeded."}] }), 400
|
|
|
|
db.session.delete( OtherFriendRequestObj )
|
|
NewFriendRelationshipObj : FriendRelationship = FriendRelationship( user_id = AuthenticatedUser.id, friend_id = UserObj.id )
|
|
db.session.add( NewFriendRelationshipObj )
|
|
db.session.commit()
|
|
|
|
return jsonify({ "success": True }), 200
|
|
|
|
FriendRequestObj : FriendRequest = FriendRequest.query.filter_by( requestee_id = UserObj.id, requester_id = AuthenticatedUser.id ).first()
|
|
if FriendRequestObj is None:
|
|
FriendRequestObj = FriendRequest( requestee_id = UserObj.id, requester_id = AuthenticatedUser.id )
|
|
db.session.add( FriendRequestObj )
|
|
db.session.commit()
|
|
|
|
return jsonify({ "success": True }), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/accept-friend-request", methods=["POST"])
|
|
@limiter.limit("30/minute")
|
|
@auth.authenticated_required_api
|
|
@user_limiter.limit("30/minute")
|
|
def accept_friend_request( userId : int ):
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({ "errors": [{ "code": 1, "message": "The target user is invalid or does not exist"}] }), 400
|
|
|
|
FriendRequestObj : FriendRequest = FriendRequest.query.filter_by( requestee_id = AuthenticatedUser.id, requester_id = UserObj.id ).first()
|
|
if FriendRequestObj is None:
|
|
return jsonify({ "errors": [{ "code": 10, "message": "The friend request does not exist."}] }), 400
|
|
|
|
if friends.GetFriendCount( AuthenticatedUser.id ) >= 200:
|
|
return jsonify({ "errors": [{ "code": 11, "message": "The current users friends limit has been exceeded."}] }), 400
|
|
if friends.GetFriendCount( UserObj.id ) >= 200:
|
|
return jsonify({ "errors": [{ "code": 12, "message": "The target users friends limit has been exceeded."}] }), 400
|
|
|
|
db.session.delete( FriendRequestObj )
|
|
NewFriendRelationshipObj : FriendRelationship = FriendRelationship( user_id = AuthenticatedUser.id, friend_id = UserObj.id )
|
|
db.session.add( NewFriendRelationshipObj )
|
|
db.session.commit()
|
|
|
|
return jsonify({}), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/decline-friend-request", methods=["POST"])
|
|
@limiter.limit("30/minute")
|
|
@auth.authenticated_required_api
|
|
@user_limiter.limit("30/minute")
|
|
def decline_friend_request( userId : int ):
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({ "errors": [{ "code": 1, "message": "The target user is invalid or does not exist"}] }), 400
|
|
|
|
FriendRequestObj : FriendRequest = FriendRequest.query.filter_by( requestee_id = AuthenticatedUser.id, requester_id = UserObj.id ).first()
|
|
if FriendRequestObj is None:
|
|
return jsonify({ "errors": [{ "code": 10, "message": "The friend request does not exist."}] }), 400
|
|
|
|
db.session.delete( FriendRequestObj )
|
|
db.session.commit()
|
|
|
|
return jsonify({}), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/followers/count", methods=["GET"])
|
|
@limiter.limit("60/minute")
|
|
def get_user_followers_count( userId : int ):
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({"success": False, "message": "Invalid request"}),400
|
|
|
|
return jsonify( { "count": followings.get_follower_count( requested_user = UserObj ) } ), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/followings/count", methods=["GET"])
|
|
@limiter.limit("60/minute")
|
|
def get_user_followings_count( userId : int ):
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({"success": False, "message": "Invalid request"}),400
|
|
|
|
return jsonify( { "count": followings.get_following_count( requested_user = UserObj ) } ), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/user/following-exists", methods=["POST"])
|
|
@limiter.limit("60/minute")
|
|
@auth.authenticated_required_api
|
|
def following_exists():
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
if not request.is_json:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "An invalid userId was passed in."}] }), 400
|
|
if "targetUserIds" not in request.json:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "An invalid userId was passed in."}] }), 400
|
|
|
|
TargetUserIds : list[int] = request.json["targetUserIds"]
|
|
ResponseList = []
|
|
|
|
if len(TargetUserIds) > 100:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "An invalid userId was passed in."}] }), 400
|
|
|
|
for TargetUserId in TargetUserIds:
|
|
if TargetUserId < 1:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "An invalid userId was passed in."}] }), 400
|
|
TargetUser : User = User.query.filter_by( id = TargetUserId ).first()
|
|
if TargetUser is None:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "An invalid userId was passed in."}] }), 400
|
|
|
|
ResponseList.append({
|
|
"isFollowing": followings.is_following( follower_user = AuthenticatedUser, followed_user = TargetUser ),
|
|
"isFollowed": followings.is_following( follower_user = TargetUser, followed_user = AuthenticatedUser ),
|
|
"userId": TargetUserId
|
|
})
|
|
|
|
return jsonify({
|
|
"followings": ResponseList
|
|
}), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/unfollow", methods=["POST"])
|
|
@limiter.limit("60/minute")
|
|
@auth.authenticated_required_api
|
|
@user_limiter.limit("60/minute")
|
|
def unfollow_user( userId : int ):
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({ "errors": [{ "code": 1, "message": "The target user is invalid or does not exist"}] }), 400
|
|
|
|
try:
|
|
followings.unfollow_user(
|
|
current_follower = AuthenticatedUser,
|
|
followed_user = UserObj
|
|
)
|
|
except followings.FollowingExceptions.UserNotFollowing:
|
|
return jsonify({}), 200
|
|
except followings.FollowingExceptions.UserRateLimited:
|
|
return jsonify({ "errors": [{ "code": 9, "message": "The flood limit has been exceeded."}] }), 429
|
|
except followings.FollowingExceptions.FollowingIsDisabled:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "Following is disabled."}] }), 500
|
|
|
|
return jsonify({}), 200
|
|
|
|
@FriendsAPIRoute.route("/v1/users/<int:userId>/follow", methods=["POST"])
|
|
@limiter.limit("10/minute")
|
|
@auth.authenticated_required_api
|
|
@user_limiter.limit("10/minute")
|
|
def follow_user( userId : int ):
|
|
AuthenticatedUser : User = auth.GetCurrentUser()
|
|
UserObj : User = User.query.filter_by(id=userId).first()
|
|
if UserObj is None:
|
|
return jsonify({ "errors": [{ "code": 1, "message": "The target user is invalid or does not exist"}] }), 400
|
|
if userId == AuthenticatedUser.id:
|
|
return jsonify({ "errors": [{ "code": 8, "message": "The user cannot follow itself."}] }), 400
|
|
|
|
try:
|
|
followings.follow_user(
|
|
follow_user = AuthenticatedUser,
|
|
followed_user = UserObj
|
|
)
|
|
except followings.FollowingExceptions.AlreadyFollowing:
|
|
return jsonify({ "success": True }), 200
|
|
except followings.FollowingExceptions.UserRateLimited:
|
|
return jsonify({ "errors": [{ "code": 9, "message": "The flood limit has been exceeded."}] }), 429
|
|
except followings.FollowingExceptions.FollowingIsDisabled:
|
|
return jsonify({ "errors": [{ "code": 0, "message": "Following is disabled."}] }), 500
|
|
|
|
return jsonify({ "success": True }), 200
|