from flask import Blueprint, render_template, request, redirect, url_for, jsonify, make_response, flash, abort from app.util import auth, friends, membership, websiteFeatures, placeinfo from app.extensions import db, limiter, csrf, user_limiter from app.models.user import User from app.models.friend_request import FriendRequest from app.models.friend_relationship import FriendRelationship from datetime import datetime from app.models.asset import Asset from app.models.place import Place from app.models.placeserver_players import PlaceServerPlayer from app.models.follow_relationship import FollowRelationship from app.models.user_avatar_asset import UserAvatarAsset from app.models.userassets import UserAsset from app.models.universe import Universe from app.models.place_badge import PlaceBadge, UserBadge from app.enums.AssetType import AssetType from sqlalchemy import or_ from app.enums.MembershipType import MembershipType from slugify import slugify from app.routes.rate import GetAssetLikesAndDislikes, GetUserVoteStatus, GetAssetVotePercentage, GetAssetFavoriteCount, GetUserFavoriteStatus from app.services import economy from app.services.user_relationships import followings Profile = Blueprint("profile", __name__, template_folder="pages") @Profile.route("/users//profile", methods=["GET"]) @auth.authenticated_required def profile_page(userid): user : User = User.query.filter_by(id=userid).first() if user is None: abort(404) if user.accountstatus == 4 or user.accountstatus == 3: abort(404) Friends = friends.GetFriends(user.id) FriendCount = len(Friends) DescriptionLines = user.description.split("\n") FriendsData = [] for friend in Friends: friendObjData = {} friendObjData = { "id": friend.id, "username": friend.username, "isonline": True if (datetime.utcnow() - friend.lastonline).total_seconds() < 60 else False, "ingame": True if PlaceServerPlayer.query.filter_by(userid=friend.id).first() is not None else False } FriendsData.append(friendObjData) FriendsData.sort(key=lambda x: x["isonline"], reverse=True) FriendsData.sort(key=lambda x: x["ingame"], reverse=True) FriendsData = FriendsData[:8] AuthenticatedUser = auth.GetCurrentUser() if AuthenticatedUser.id != user.id: isFriends = friends.IsFriends(AuthenticatedUser.id, user.id) FriendRequestPending = FriendRequest.query.filter_by(requester_id=AuthenticatedUser.id, requestee_id=user.id).first() is not None FriendRequestToAuthenticatedUser = FriendRequest.query.filter_by(requester_id=user.id, requestee_id=AuthenticatedUser.id).first() is not None isViewerFollowing = FollowRelationship.query.filter_by(followerUserId=AuthenticatedUser.id, followeeUserId=user.id).first() is not None doesTargetUserFollowViewer = FollowRelationship.query.filter_by(followerUserId=user.id, followeeUserId=AuthenticatedUser.id).first() is not None else: isFriends = False FriendRequestPending = False FriendRequestToAuthenticatedUser = False isViewerFollowing = False doesTargetUserFollowViewer = False joindate = user.created.strftime("%d/%m/%Y") TotalVisits = 0 UniverseList : list[Universe] = Universe.query.filter_by( creator_id = user.id, creator_type = 0 ).all() for UniverseObj in UniverseList: TotalVisits += UniverseObj.visit_count FollowerCount = followings.get_follower_count( user ) FollowingCount = followings.get_following_count( user ) WearingAssets = UserAvatarAsset.query.filter_by(user_id=user.id).all() membershipValue : int = membership.GetUserMembership(user.id).value UserGames : list[Asset] = Asset.query.filter_by(creator_id = user.id, creator_type = 0, asset_type = AssetType.Place).all() UserGamesInfo : list[dict] = [] for UserGame in UserGames: PlaceObj : Place = Place.query.filter_by(placeid=UserGame.id).first() if PlaceObj is None: continue if PlaceObj.is_public == False: continue PlaceObjData = { "id": UserGame.id, "name": UserGame.name, "playingcount": placeinfo.GetPlayingCount( PlaceObj ), "slug": slugify(UserGame.name, lowercase=False) if UserGame.name is not None else "", "likePercentage": GetAssetVotePercentage(UserGame.id), "placeyear": PlaceObj.placeyear } UserGamesInfo.append(PlaceObjData) ProfileData = { "username": user.username, "id": user.id, "friendcount": FriendCount, "isonline": True if (datetime.utcnow() - user.lastonline).total_seconds() < 60 else False, "ingame": True if PlaceServerPlayer.query.filter_by(userid=user.id).first() is not None else False, "isfriends": isFriends, "friendrequestpending": FriendRequestPending, "friendrequesttoauthenticateduser": FriendRequestToAuthenticatedUser, "friendsdata": FriendsData, "joindate": joindate, "TotalVisits": TotalVisits, "FollowerCount": FollowerCount, "FollowingCount": FollowingCount, "wearing": WearingAssets, "isviewerfollowing": isViewerFollowing, "doestargetuserfollowviewer": doesTargetUserFollowViewer, "membershipValue": membershipValue, "UserGames": UserGamesInfo, "UserRAP": economy.CalculateUserRAP( user ) } return render_template("profiles/profile.html", profile=ProfileData, descriptionlines=DescriptionLines) @Profile.route("/users//addfriend", methods=["POST"]) @limiter.limit("5/minute") @auth.authenticated_required @user_limiter.limit("5/minute") def add_friend(userid): user = User.query.filter_by(id=userid).first() if user is None: abort(404) if user.accountstatus == 4 or user.accountstatus == 3: abort(404) if not websiteFeatures.GetWebsiteFeature("SendFriendRequest"): flash("Adding friends is temporarily disabled.", "error") return redirect(f"/users/{user.id}/profile") AuthenticatedUser = auth.GetCurrentUser() if userid == AuthenticatedUser.id: flash("You cannot add yourself as a friend.", "error") return redirect(f"/users/{user.id}/profile") if FriendRequest.query.filter_by(requester_id=AuthenticatedUser.id, requestee_id=user.id).first() is not None: flash("You have already sent a friend request to this user.", "error") return redirect(f"/users/{user.id}/profile") if friends.IsFriends(AuthenticatedUser.id, user.id): flash("You are already friends with this user.", "error") return redirect(f"/users/{user.id}/profile") friendRequest = FriendRequest(requester_id=AuthenticatedUser.id, requestee_id=user.id) db.session.add(friendRequest) db.session.commit() return redirect(f"/users/{user.id}/profile") @Profile.route("/users//unfriend", methods=["POST"]) @limiter.limit("5/minute") @auth.authenticated_required @user_limiter.limit("5/minute") def unfriend(userid): user = User.query.filter_by(id=userid).first() if user is None: abort(404) AuthenticatedUser = auth.GetCurrentUser() if not friends.IsFriends(AuthenticatedUser.id, user.id): flash("You are not friends with this user.", "error") return handle_redirect(user) Friendrelationship : FriendRelationship = friends.GetFriendRelationship(AuthenticatedUser.id, user.id) db.session.delete(Friendrelationship) db.session.commit() return handle_redirect(user) def handle_redirect(user): wantRedirect = request.args.get("redirect", type=int, default=1) if wantRedirect == 1: return redirect(f"/users/{user.id}/profile") else: return redirect(request.referrer) @Profile.route("/users//acceptfriend", methods=["POST"]) @limiter.limit("30/minute") @auth.authenticated_required @user_limiter.limit("30/minute") def accept_friend(userid): user = User.query.filter_by(id=userid).first() if user is None: abort(404) if user.accountstatus == 4 or user.accountstatus == 3: abort(404) if not websiteFeatures.GetWebsiteFeature("SendFriendRequest"): flash("Adding friends is temporarily disabled.", "error") return handle_redirect(user) AuthenticatedUser = auth.GetCurrentUser() if userid == AuthenticatedUser.id: flash("You cannot add yourself as a friend.", "error") return handle_redirect(user) friendRequest = FriendRequest.query.filter_by(requester_id=user.id, requestee_id=AuthenticatedUser.id).first() if friendRequest is None: flash("Friend request not found.", "error") return handle_redirect(user) FriendCount = len(friends.GetFriends(user.id)) if FriendCount >= 200: flash("This user has reached their friend limit.", "error") return handle_redirect(user) AuthenticatedUserFriendCount = len(friends.GetFriends(AuthenticatedUser.id)) if AuthenticatedUserFriendCount >= 200: flash("You have reached your friend limit.", "error") return handle_redirect(user) Friendrelationship = FriendRelationship(user_id=user.id, friend_id=AuthenticatedUser.id) db.session.add(Friendrelationship) db.session.delete(friendRequest) db.session.commit() return handle_redirect(user) @Profile.route("/users//declinefriend", methods=["POST"]) @limiter.limit("30/minute") @auth.authenticated_required @user_limiter.limit("30/minute") def decline_friend(userid): TargetUser = User.query.filter_by(id=userid).first() if TargetUser is None: abort(404) if not websiteFeatures.GetWebsiteFeature("SendFriendRequest"): flash("Declining friend requests is temporarily disabled.", "error") return handle_redirect(TargetUser) AuthenticatedUser = auth.GetCurrentUser() if userid == AuthenticatedUser.id: abort(404) friendRequest = FriendRequest.query.filter_by(requester_id=TargetUser.id, requestee_id=AuthenticatedUser.id).first() if friendRequest is None: return handle_redirect(TargetUser) db.session.delete(friendRequest) db.session.commit() return handle_redirect(TargetUser) @Profile.route("/users//unfollowuser", methods=["POST"]) @limiter.limit("5/minute") @auth.authenticated_required def unfollow_user(userid): UserObj : User = User.query.filter_by(id=userid).first() if UserObj is None: abort(404) AuthenticatedUser : User = auth.GetCurrentUser() try: followings.unfollow_user( current_follower = AuthenticatedUser, followed_user = UserObj ) except followings.FollowingExceptions.UserNotFollowing: flash("You are not following this user.", "error") return redirect(f"/users/{UserObj.id}/profile") except followings.FollowingExceptions.UserRateLimited: flash("You are being rate limited from unfollowing users, please slow down.", "error") return redirect(f"/users/{UserObj.id}/profile") return redirect(f"/users/{UserObj.id}/profile") @Profile.route("/users//followuser", methods=["POST"]) @limiter.limit("5/minute") @auth.authenticated_required def follow_user(userid): UserObj : User = User.query.filter_by( id = userid ).first() if UserObj is None: abort(404) if UserObj.accountstatus == 4 or UserObj.accountstatus == 3: abort(404) AuthenticatedUser : User = auth.GetCurrentUser() try: followings.follow_user( follower_user = AuthenticatedUser, followed_user = UserObj ) except followings.FollowingExceptions.AlreadyFollowing: flash("You are already following this user.", "error") return redirect(f"/users/{UserObj.id}/profile") except followings.FollowingExceptions.UserRateLimited: flash("You are being rate limited from following users, please slow down.", "error") return redirect(f"/users/{UserObj.id}/profile") except followings.FollowingExceptions.CannotFollowSelf: flash("You cannot follow yourself.", "error") return redirect(f"/users/{UserObj.id}/profile") except followings.FollowingExceptions.FollowingIsDisabled: flash("Following users is disabled.", "error") return redirect(f"/users/{UserObj.id}/profile") return redirect(f"/users/{UserObj.id}/profile") @Profile.route("/users//friends", methods=["GET"]) @auth.authenticated_required def FriendsPage( userid : int ): user = User.query.filter_by(id=userid).first() if user is None: abort(404) if user.accountstatus == 4 or user.accountstatus == 3: abort(404) PageNumber = request.args.get("page", 1, type=int) from app.models.friend_relationship import FriendRelationship FriendRelationships : list[FriendRelationship] = FriendRelationship.query.filter( or_( FriendRelationship.user_id == user.id, FriendRelationship.friend_id == user.id ) ).join( User, or_( FriendRelationship.user_id == User.id, FriendRelationship.friend_id == User.id ) ).filter( User.id != user.id ).order_by( User.username ).paginate( page=PageNumber, per_page=18, error_out=False ) FriendUsers : list[User] = [] for FriendRelationshipObj in FriendRelationships: if FriendRelationshipObj.user_id == user.id: TargetUserId : int = FriendRelationshipObj.friend_id else: TargetUserId : int = FriendRelationshipObj.user_id FriendUser : User = User.query.filter_by(id=TargetUserId).first() if FriendUser is not None: FriendUsers.append({ "id": FriendUser.id, "username": FriendUser.username, "isonline": True if (datetime.utcnow() - FriendUser.lastonline).total_seconds() < 60 else False, "ingame": True if PlaceServerPlayer.query.filter_by(userid=FriendUser.id).first() is not None else False }) FriendCount = FriendRelationship.query.filter((FriendRelationship.user_id == user.id) | (FriendRelationship.friend_id == user.id)).count() if FriendRelationships.pages < PageNumber: redirect(f"/users/{user.id}/friends") return render_template("profiles/friends.html", profile=user, FriendUsers=FriendUsers, PageNumber=PageNumber, isThereNextPage=FriendRelationships.has_next, isTherePreviousPage=FriendRelationships.has_prev, FriendCount=FriendCount, TotalPages=FriendRelationships.pages) @Profile.route("/users//following", methods=["GET"]) @auth.authenticated_required def FollowingPage( userid : int ): user = User.query.filter_by(id=userid).first() if user is None: abort(404) if user.accountstatus == 4 or user.accountstatus == 3: abort(404) PageNumber = request.args.get("page", 1, type=int) from app.models.follow_relationship import FollowRelationship FollowRelationships : list[FollowRelationship] = FollowRelationship.query.filter_by(followerUserId=user.id).join( User, FollowRelationship.followeeUserId == User.id ).order_by( User.username ).paginate( page=PageNumber, per_page=18, error_out=False ) FollowUsers : list[User] = [] for FollowRelationship in FollowRelationships: FollowUser : User = User.query.filter_by(id=FollowRelationship.followeeUserId).first() if FollowUser is not None: FollowUsers.append({ "id": FollowUser.id, "username": FollowUser.username, "isonline": True if (datetime.utcnow() - FollowUser.lastonline).total_seconds() < 60 else False, "ingame": True if PlaceServerPlayer.query.filter_by(userid=FollowUser.id).first() is not None else False }) FollowCount = FollowRelationship.query.filter_by(followerUserId=user.id).count() if FollowRelationships.pages < PageNumber: redirect(f"/users/{user.id}/following") return render_template("profiles/following.html", profile=user, FollowUsers=FollowUsers, PageNumber=PageNumber, isThereNextPage=FollowRelationships.has_next, isTherePreviousPage=FollowRelationships.has_prev, FollowCount=FollowCount, TotalPages=FollowRelationships.pages) @Profile.route("/users//followers", methods=["GET"]) @auth.authenticated_required def FollowersPage( userid : int ): user = User.query.filter_by(id=userid).first() if user is None: abort(404) if user.accountstatus == 4 or user.accountstatus == 3: abort(404) PageNumber = request.args.get("page", 1, type=int) from app.models.follow_relationship import FollowRelationship FollowRelationships : list[FollowRelationship] = FollowRelationship.query.filter_by(followeeUserId=user.id).join( User, FollowRelationship.followerUserId == User.id ).order_by( User.username ).paginate( page=PageNumber, per_page=18, error_out=False ) FollowUsers : list[User] = [] for FollowRelationship in FollowRelationships: FollowUser : User = User.query.filter_by(id=FollowRelationship.followerUserId).first() if FollowUser is not None: FollowUsers.append({ "id": FollowUser.id, "username": FollowUser.username, "isonline": True if (datetime.utcnow() - FollowUser.lastonline).total_seconds() < 60 else False, "ingame": True if PlaceServerPlayer.query.filter_by(userid=FollowUser.id).first() is not None else False }) FollowCount = FollowRelationship.query.filter_by(followeeUserId=user.id).count() if FollowRelationships.pages < PageNumber: redirect(f"/users/{user.id}/followers") return render_template("profiles/followers.html", profile=user, FollowUsers=FollowUsers, PageNumber=PageNumber, isThereNextPage=FollowRelationships.has_next, isTherePreviousPage=FollowRelationships.has_prev, FollowCount=FollowCount, TotalPages=FollowRelationships.pages) @Profile.route("/users//requests", methods=["GET"]) @auth.authenticated_required def FriendRequestsPage( userid : int ): AuthenticatedUser = auth.GetCurrentUser() if AuthenticatedUser.id != userid: abort(404) PageNumber = request.args.get("page", 1, type=int) FriendRequests : list[FriendRequest] = FriendRequest.query.filter_by(requestee_id=userid).order_by(FriendRequest.created_at.desc()).paginate(page=PageNumber, per_page=18, error_out=False) return render_template("profiles/requests.html", profile=AuthenticatedUser, FriendRequests=FriendRequests) @Profile.route("/users//requests/clear", methods=["POST"]) @auth.authenticated_required def ClearFriendRequests(userid : int): AuthenticatedUser = auth.GetCurrentUser() if AuthenticatedUser.id != userid: abort(404) FriendRequests : list[FriendRequest] = FriendRequest.query.filter_by(requestee_id=userid).all() for FriendRequestObj in FriendRequests: db.session.delete(FriendRequestObj) db.session.commit() return redirect(f"/users/{userid}/requests") @Profile.route("/users//inventory", methods=["GET"]) @auth.authenticated_required def UserInventoryPage( userid : int ): UserObj : User = User.query.filter_by( id = userid ).first() if UserObj is None: return abort(404) InventoryTypesDict = { 0: lambda queryObj: queryObj.filter(Asset.asset_type.in_(( AssetType.HairAccessory, AssetType.FaceAccessory, AssetType.NeckAccessory, AssetType.ShoulderAccessory, AssetType.FrontAccessory, AssetType.BackAccessory, AssetType.WaistAccessory, ))), 1: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Audio ), 2: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Package ), 3: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Face ), 4: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Head ), 5: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Hat ), 6: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.GamePass ), 7: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.TShirt ), 8: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Shirt ), 9: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Pants ), 10: lambda queryObj: queryObj.filter( Asset.asset_type == AssetType.Gear ) } InventoryTypeToFriendlyName = { 0: "Accessories", 1: "Audio", 2: "Packages", 3: "Faces", 4: "Heads", 5: "Hats", 6: "Passes", 7: "T-Shirts", 8: "Shirts", 9: "Pants", 10: "Gears", 11: "Badges" } CategoryType = request.args.get( key = "category", default = 0, type = int ) if CategoryType not in InventoryTypeToFriendlyName: return redirect(f"/users/{userid}/inventory") PageNumber = request.args.get( key = "page", default = 1, type = int ) if PageNumber < 1: PageNumber = 1 if CategoryType not in [11]: SearchQuery = UserAsset.query.filter_by( userid = userid ).outerjoin( Asset, Asset.id == UserAsset.assetid ) SearchQuery = InventoryTypesDict[CategoryType](SearchQuery).order_by( UserAsset.updated.desc() ).paginate( page = PageNumber, per_page = 24, error_out = False ) for UserAssetObj in SearchQuery.items: if UserAssetObj.asset.is_limited and not UserAssetObj.asset.is_for_sale: BestPriceResult : UserAsset = UserAsset.query.filter_by(assetid=UserAssetObj.assetid, is_for_sale=True).order_by(UserAsset.price.asc()).first() if BestPriceResult is not None: UserAssetObj.best_price = str(BestPriceResult.price) else: UserAssetObj.best_price = "--" elif CategoryType == 11: SearchQuery = UserBadge.query.filter_by( user_id = userid ).order_by( UserBadge.awarded_at.desc() ).paginate( page = PageNumber, per_page = 24, error_out = False ) return render_template( "profiles/inventory.html", profile = UserObj, SearchQuery = SearchQuery, CategoryName = InventoryTypeToFriendlyName[CategoryType], CategoryIndex = CategoryType )