476 lines
20 KiB
Python
476 lines
20 KiB
Python
# Commands for flask shell
|
|
from app.extensions import db, redis_controller
|
|
from app.models.user import User
|
|
from app.util import auth
|
|
from sqlalchemy import func
|
|
import logging
|
|
def lookup_user_id():
|
|
"""Lookup user by Id"""
|
|
try:
|
|
UserId : int = int(input("User Lookup by UserId: "))
|
|
if UserId < 0:
|
|
raise Exception("UserId must be a positive integer")
|
|
except Exception as e:
|
|
logging.error(f"Unable to parse user input, please enter a valid integer, error: {e}")
|
|
|
|
UserObj : User = User.query.filter_by(id=UserId).first()
|
|
if UserObj is None:
|
|
logging.error(f"Unable to find user with UserId: {UserId}")
|
|
else:
|
|
logging.info(f"""
|
|
Username : {UserObj.username}
|
|
UserId : {UserObj.id}
|
|
CreatedOn : {UserObj.created}
|
|
LastPing : {UserObj.lastonline}
|
|
AccountStatus : {UserObj.accountstatus}
|
|
2FA Enabled : {UserObj.TOTPEnabled}
|
|
|
|
-- Description --
|
|
{UserObj.description}
|
|
-- End Description --
|
|
|
|
""")
|
|
|
|
def lookup_user_name():
|
|
"""Lookup user by Username"""
|
|
try:
|
|
Username : str = str(input("User Lookup by Username: "))
|
|
if len(Username) < 1:
|
|
raise Exception("Username must be a valid string")
|
|
except Exception as e:
|
|
logging.error(f"Unable to parse user input, please enter a valid string, error: {e}")
|
|
|
|
UserObj : User = User.query.filter(func.lower(User.username) == func.lower(Username)).first()
|
|
if UserObj is None:
|
|
logging.error(f"Unable to find user with Username: {Username}")
|
|
else:
|
|
logging.info(f"""
|
|
Username : {UserObj.username}
|
|
UserId : {UserObj.id}
|
|
CreatedOn : {UserObj.created}
|
|
LastPing : {UserObj.lastonline}
|
|
AccountStatus : {UserObj.accountstatus}
|
|
2FA Enabled : {UserObj.TOTPEnabled}
|
|
|
|
-- Description --
|
|
{UserObj.description}
|
|
-- End Description --
|
|
|
|
""")
|
|
|
|
def refund_unused_invite_keys():
|
|
from app.models.invite_key import InviteKey
|
|
from app.services.economy import IncrementTargetBalance
|
|
from app.util.transactions import CreateTransaction
|
|
from app.enums.TransactionType import TransactionType
|
|
from app.pages.messages.messages import CreateSystemMessage
|
|
|
|
allDistinctInviteKeyCreators : list[User] = User.query.join(InviteKey, User.id == InviteKey.created_by).filter(InviteKey.used_by == None).distinct(User.id).all()
|
|
|
|
def process_user_invite_keys( userObj : User ):
|
|
AmountOwed : int = 0
|
|
InviteKeysDeleted : int = 0
|
|
|
|
AllInviteKeys : list[InviteKey] = InviteKey.query.filter_by(created_by=userObj.id, used_by=None).all()
|
|
for InviteKeyObj in AllInviteKeys:
|
|
AmountOwed += 20
|
|
InviteKeysDeleted += 1
|
|
db.session.delete(InviteKeyObj)
|
|
db.session.commit()
|
|
|
|
if AmountOwed > 0:
|
|
IncrementTargetBalance(userObj, AmountOwed, 0)
|
|
CreateTransaction(
|
|
Reciever = userObj,
|
|
Sender = None,
|
|
CurrencyAmount = AmountOwed,
|
|
CurrencyType = 0,
|
|
TransactionType = TransactionType.BuildersClubStipend,
|
|
AssetId = None,
|
|
CustomText = f"Refunded {InviteKeysDeleted} unused invite keys",
|
|
)
|
|
CreateSystemMessage(
|
|
subject = "Invite Key Refund",
|
|
message = f"""Hello {userObj.username},
|
|
This is an automated message to inform you that as invite keys are no longer used on SYNTAX, we have refunded you R$ {AmountOwed} for {InviteKeysDeleted} unused invite keys. Please contact us on our Discord Server if you have any questions.
|
|
|
|
Sincerely,
|
|
The SYNTAX Team""",
|
|
userid = userObj.id
|
|
)
|
|
|
|
logging.info(f"Refunded {InviteKeysDeleted} unused invite keys for user {userObj.username}")
|
|
|
|
logging.info(f"Found {len(allDistinctInviteKeyCreators)} users with unused invite keys")
|
|
for UserObj in allDistinctInviteKeyCreators:
|
|
process_user_invite_keys(UserObj)
|
|
|
|
def refund_limiteds():
|
|
from app.models.userassets import UserAsset
|
|
from app.models.asset import Asset
|
|
from app.services.economy import IncrementTargetBalance, GetAssetRap
|
|
from app.util.transactions import CreateTransaction
|
|
from app.enums.TransactionType import TransactionType
|
|
from app.enums.MembershipType import MembershipType
|
|
from app.pages.messages.messages import CreateSystemMessage
|
|
from app.models.user import User
|
|
from app.util.membership import GetUserMembership
|
|
import math
|
|
|
|
limitedRAPValueLookup : dict[int, int] = {}
|
|
refundCapAmount : dict [MembershipType, int] = {
|
|
MembershipType.NonBuildersClub : 200,
|
|
MembershipType.BuildersClub : 500,
|
|
MembershipType.TurboBuildersClub : 750,
|
|
MembershipType.OutrageousBuildersClub : 1200
|
|
}
|
|
|
|
def _get_limited_rap_value( assetId : int ):
|
|
if assetId in limitedRAPValueLookup:
|
|
return limitedRAPValueLookup[assetId]
|
|
else:
|
|
rapValue : int = GetAssetRap(assetId)
|
|
limitedRAPValueLookup[assetId] = rapValue
|
|
return rapValue
|
|
|
|
def refund_user_limiteds( userObj : User ):
|
|
WipedAssets : list = []
|
|
AllLimitedUserAssets : list [UserAsset] = UserAsset.query.filter_by(userid=userObj.id).join(Asset, UserAsset.assetid == Asset.id).filter(Asset.is_limited == True).all()
|
|
AmountOwed : int = 0
|
|
|
|
for UserAssetObj in AllLimitedUserAssets:
|
|
AssetObj : Asset = UserAssetObj.asset
|
|
|
|
LimitedRAPValue : int = _get_limited_rap_value(UserAssetObj.assetid)
|
|
AssetOriginalPrice : int = AssetObj.price_robux if AssetObj.price_robux > 0 else AssetObj.price_tix
|
|
|
|
if ( not AssetObj.price_robux > 0 ) and AssetOriginalPrice > 0:
|
|
AssetOriginalPrice = math.floor(AssetOriginalPrice / 10)
|
|
|
|
RefundedAmount : int = max(AssetOriginalPrice if AssetOriginalPrice > LimitedRAPValue else LimitedRAPValue, 50)
|
|
AmountOwed += RefundedAmount
|
|
|
|
WipedAssets.append(f" - {AssetObj.name} [UAID: {UserAssetObj.id} / Serial: {UserAssetObj.serial}] - R$ {RefundedAmount}")
|
|
|
|
db.session.delete(UserAssetObj)
|
|
db.session.commit()
|
|
|
|
ActualAmountOwed = min(AmountOwed, refundCapAmount[GetUserMembership(userObj)])
|
|
|
|
if ActualAmountOwed > 0:
|
|
ItemListText : str = "\n".join(WipedAssets)
|
|
|
|
IncrementTargetBalance(userObj, ActualAmountOwed, 0)
|
|
CreateTransaction(
|
|
Reciever = userObj,
|
|
Sender = None,
|
|
CurrencyAmount = ActualAmountOwed,
|
|
CurrencyType = 0,
|
|
TransactionType = TransactionType.BuildersClubStipend,
|
|
AssetId = None,
|
|
CustomText = f"Limited item refund",
|
|
)
|
|
|
|
CreateSystemMessage(
|
|
subject = "Limited Item Refund",
|
|
message = f"""Hello {userObj.username},
|
|
|
|
This is an automated message to inform you that all limited items are being refunded as we are resetting the economy. We have refunded you R$ {ActualAmountOwed} for the following items:
|
|
{ItemListText}
|
|
|
|
Item Refund Cap: R$ {refundCapAmount[GetUserMembership(userObj)]}
|
|
Total Value before Cap: R$ {AmountOwed}
|
|
|
|
Refunded Amount: R$ {ActualAmountOwed}
|
|
|
|
Please contact us on our Discord Server if you have any questions.
|
|
|
|
Sincerely,
|
|
The SYNTAX Team""",
|
|
userid = userObj.id
|
|
)
|
|
|
|
logging.info(f"Refunded {len(AllLimitedUserAssets)} limited items for user {userObj.username}")
|
|
|
|
AllUsersWithLimiteds : list[User] = User.query.join(UserAsset, User.id == UserAsset.userid).join(Asset, UserAsset.assetid == Asset.id).filter(Asset.is_limited == True).distinct(User.id).all()
|
|
logging.info(f"Found {len(AllUsersWithLimiteds)} users with limiteds")
|
|
|
|
for UserObj in AllUsersWithLimiteds:
|
|
refund_user_limiteds(UserObj)
|
|
|
|
def delete_limited_assets():
|
|
from app.models.asset import Asset
|
|
from app.models.asset_version import AssetVersion
|
|
from app.models.asset_thumbnail import AssetThumbnail
|
|
|
|
# we have to delete these two table first as they have a relationship with Asset
|
|
AllLimitedAssetVersions : list[AssetVersion] = AssetVersion.query.join(Asset, AssetVersion.asset_id == Asset.id).filter(Asset.is_limited == True).all()
|
|
AllLimitedAssetThumbnails : list[AssetThumbnail] = AssetThumbnail.query.join(Asset, AssetThumbnail.asset_id == Asset.id).filter(Asset.is_limited == True).all()
|
|
|
|
for AssetVersionObj in AllLimitedAssetVersions:
|
|
db.session.delete(AssetVersionObj)
|
|
for AssetThumbnailObj in AllLimitedAssetThumbnails:
|
|
db.session.delete(AssetThumbnailObj)
|
|
db.session.commit()
|
|
|
|
AllLimitedAssets : list[Asset] = Asset.query.filter_by(is_limited=True).all()
|
|
for AssetObj in AllLimitedAssets:
|
|
db.session.delete(AssetObj)
|
|
|
|
db.session.commit()
|
|
|
|
def clear_user_avatar_assets():
|
|
from app.models.user_avatar_asset import UserAvatarAsset
|
|
from app.models.asset import Asset
|
|
from app.routes.thumbnailer import TakeUserThumbnail
|
|
|
|
NeedReRender : list[int] = []
|
|
|
|
AllUserAvatarAssets : list[UserAvatarAsset] = UserAvatarAsset.query.outerjoin(Asset, UserAvatarAsset.asset_id == Asset.id).filter(Asset.id == None).all()
|
|
for UserAvatarAssetObj in AllUserAvatarAssets:
|
|
if UserAvatarAssetObj.user_id not in NeedReRender:
|
|
NeedReRender.append(UserAvatarAssetObj.user_id)
|
|
db.session.delete(UserAvatarAssetObj)
|
|
|
|
db.session.commit()
|
|
|
|
for UserId in NeedReRender:
|
|
logging.info(f"Re-rendering avatar for user {UserId}")
|
|
TakeUserThumbnail(UserId)
|
|
|
|
def clear_bad_transactions():
|
|
from app.models.user_transactions import UserTransaction
|
|
from app.models.asset import Asset
|
|
|
|
AllBadTransactions : list[UserTransaction] = UserTransaction.query.outerjoin(Asset, UserTransaction.assetId == Asset.id).filter(Asset.id == None).all()
|
|
print(f"Found {len(AllBadTransactions)} bad transactions")
|
|
|
|
while True:
|
|
BatchTransactions : list[UserTransaction] = AllBadTransactions[:1000]
|
|
if len(BatchTransactions) == 0:
|
|
break
|
|
for TransactionObj in BatchTransactions:
|
|
db.session.delete(TransactionObj)
|
|
db.session.commit()
|
|
AllBadTransactions = AllBadTransactions[1000:]
|
|
logging.info(f"Deleted 1000 bad transactions, {len(AllBadTransactions)} remaining")
|
|
|
|
def delete_asset( asset_id : int ):
|
|
from app.models.asset import Asset
|
|
from app.models.asset_version import AssetVersion
|
|
from app.models.asset_thumbnail import AssetThumbnail
|
|
|
|
AllAssetVersions : list[AssetVersion] = AssetVersion.query.filter_by(asset_id=asset_id).all()
|
|
AllAssetThumbnails : list[AssetThumbnail] = AssetThumbnail.query.filter_by(asset_id=asset_id).all()
|
|
|
|
for AssetVersionObj in AllAssetVersions:
|
|
print(f"Deleting asset version {AssetVersionObj.id}")
|
|
db.session.delete(AssetVersionObj)
|
|
for AssetThumbnailObj in AllAssetThumbnails:
|
|
print(f"Deleting asset thumbnail {AssetThumbnailObj.id}")
|
|
db.session.delete(AssetThumbnailObj)
|
|
|
|
db.session.commit()
|
|
|
|
AssetObj : Asset = Asset.query.filter_by(id=asset_id).first()
|
|
db.session.delete(AssetObj)
|
|
|
|
db.session.commit()
|
|
|
|
def create_admin_user():
|
|
"""
|
|
! FIRST TIME SETUP ONLY !
|
|
Creates a user with all admin permissions and a random password
|
|
|
|
Note: will raise an exception if User ID 1 already exists
|
|
"""
|
|
import datetime
|
|
import random
|
|
import string
|
|
from app.models.user import User
|
|
from app.models.admin_permissions import AdminPermissions
|
|
from app.models.usereconomy import UserEconomy
|
|
from app.models.user_avatar import UserAvatar
|
|
from app.util.auth import SetPassword
|
|
from app.pages.admin.permissionsdefinition import PermissionsDefinition
|
|
|
|
if User.query.filter_by(id=1).first() is not None:
|
|
raise Exception("User ID 1 already exists")
|
|
|
|
NewUser : User = User(
|
|
username = "Admin",
|
|
password = "",
|
|
created = datetime.datetime.utcnow(),
|
|
lastonline = datetime.datetime.utcnow()
|
|
)
|
|
|
|
db.session.add(NewUser)
|
|
db.session.commit()
|
|
|
|
NewPassword : str = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(24))
|
|
|
|
SetPassword(
|
|
UserObj = NewUser,
|
|
password = NewPassword
|
|
)
|
|
|
|
UserEconomyObj : UserEconomy = UserEconomy(
|
|
userid = NewUser.id,
|
|
robux = 0,
|
|
tix = 0,
|
|
)
|
|
|
|
db.session.add(UserEconomyObj)
|
|
|
|
UserAvatarObj : UserAvatar = UserAvatar(
|
|
user_id = NewUser.id,
|
|
)
|
|
|
|
db.session.add(UserAvatarObj)
|
|
|
|
for permission_name in PermissionsDefinition:
|
|
permissionObj : AdminPermissions = AdminPermissions(
|
|
userid = NewUser.id,
|
|
permission = permission_name
|
|
)
|
|
db.session.add(permissionObj)
|
|
|
|
db.session.commit()
|
|
|
|
print(f"""
|
|
Successfully created Admin User
|
|
Username: Admin
|
|
Password: {NewPassword}""")
|
|
|
|
def convert_places_to_universes():
|
|
from app.models.asset import Asset
|
|
from app.models.place import Place
|
|
from app.models.universe import Universe
|
|
from app.models.place_datastore import PlaceDatastore
|
|
from app.models.place_ordered_datastore import PlaceOrderedDatastore
|
|
from app.models.legacy_data_persistence import LegacyDataPersistence
|
|
from app.models.place_badge import PlaceBadge
|
|
from app.models.gamepass_link import GamepassLink
|
|
from app.models.place_developer_product import DeveloperProduct
|
|
|
|
AllPlaces : list[Place] = Place.query.filter_by( parent_universe_id = 0 ).order_by(Place.placeid).all()
|
|
logging.info(f"convert_places_to_universes > Found {len(AllPlaces)} places to convert")
|
|
for PlaceObj in AllPlaces:
|
|
PlaceObj : Place
|
|
try:
|
|
PlaceAssetObj : Asset = Asset.query.filter_by( id = PlaceObj.placeid ).first()
|
|
UniverseObj : Universe = Universe(
|
|
root_place_id = PlaceObj.placeid,
|
|
creator_id = PlaceAssetObj.creator_id,
|
|
creator_type = PlaceAssetObj.creator_type,
|
|
place_rig_choice = PlaceObj.rig_choice,
|
|
place_year = PlaceObj.placeyear,
|
|
is_featured = PlaceObj.featured,
|
|
minimum_account_age = PlaceObj.min_account_age,
|
|
bc_required = PlaceObj.bc_required,
|
|
allow_direct_join = False,
|
|
is_public = PlaceObj.is_public,
|
|
updated_at = PlaceAssetObj.updated_at,
|
|
created_at = PlaceAssetObj.created_at
|
|
)
|
|
db.session.add(UniverseObj)
|
|
db.session.commit()
|
|
|
|
logging.info(f"convert_places_to_universes > Created universe {UniverseObj.id} from place {PlaceObj.placeid}")
|
|
PlaceObj.parent_universe_id = UniverseObj.id
|
|
db.session.commit()
|
|
|
|
logging.info(f"convert_places_to_universes > Converting place {PlaceObj.placeid}'s items to universe {UniverseObj.id}")
|
|
|
|
db.session.query(PlaceDatastore).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
|
|
db.session.query(PlaceOrderedDatastore).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
|
|
db.session.query(LegacyDataPersistence).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
|
|
db.session.query(PlaceBadge).filter_by( associated_place_id = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
|
|
db.session.query(GamepassLink).filter_by( place_id = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
|
|
db.session.query(DeveloperProduct).filter_by( placeid = PlaceObj.placeid ).update({"universe_id": UniverseObj.id})
|
|
|
|
db.session.commit()
|
|
|
|
logging.info(f"convert_places_to_universes > Successfully converted place {PlaceObj.placeid}'s items to universe {UniverseObj.id}")
|
|
except Exception as e:
|
|
logging.error(f"convert_places_to_universes > Failed to migrate place {PlaceObj.placeid}, error: {e}")
|
|
|
|
def recalculate_universe_visits():
|
|
from app.models.place import Place
|
|
from app.models.universe import Universe
|
|
|
|
AllUniverses : list[Universe] = Universe.query.all()
|
|
logging.info(f"recalculate_universe_visits > Found {len(AllUniverses)} universes to recalculate")
|
|
|
|
for UniverseObj in AllUniverses:
|
|
try:
|
|
UniverseObj : Universe
|
|
UniverseObj.visit_count = Place.query.filter_by( parent_universe_id = UniverseObj.id ).with_entities(func.sum(Place.visitcount)).scalar()
|
|
db.session.commit()
|
|
logging.info(f"recalculate_universe_visits > Successfully recalculated universe {UniverseObj.id}, new visit count: {UniverseObj.visit_count}")
|
|
|
|
except Exception as e:
|
|
logging.error(f"recalculate_universe_visits > Failed to recalculate universe {UniverseObj.id}, error: {e}")
|
|
|
|
def reverse_item_transfer():
|
|
from app.models.limited_item_transfers import LimitedItemTransfer
|
|
from app.models.asset import Asset
|
|
from app.models.user import User
|
|
from app.services.economy import IncrementTargetBalance, DecrementTargetBalance, GetAssetRap
|
|
from app.models.asset_rap import AssetRap
|
|
from app.models.userassets import UserAsset
|
|
import math
|
|
from datetime import datetime
|
|
from sqlalchemy import and_
|
|
from app.pages.messages.messages import CreateSystemMessage
|
|
|
|
def reverse_transfer( ItemTransferRecordObj : LimitedItemTransfer ):
|
|
logging.info(f"reverse_item_transfer > Reversing transfer {ItemTransferRecordObj.id}")
|
|
RecievingUserObj : User = User.query.filter_by( id = ItemTransferRecordObj.new_owner_id ).first()
|
|
SendingUserObj : User = User.query.filter_by( id = ItemTransferRecordObj.original_owner_id ).first()
|
|
UserAssetObj : UserAsset = UserAsset.query.filter_by( id = ItemTransferRecordObj.user_asset_id ).first()
|
|
|
|
if RecievingUserObj is None or SendingUserObj is None or UserAssetObj is None:
|
|
logging.error(f"reverse_item_transfer > Unable to find user or asset for transfer {ItemTransferRecordObj.id}")
|
|
return
|
|
|
|
AssetObj : Asset = Asset.query.filter_by( id = ItemTransferRecordObj.asset_id ).first()
|
|
AssetRapObj : AssetRap = AssetRap.query.filter_by( assetid = AssetObj.id ).first()
|
|
CurrentRapValue : int = GetAssetRap( AssetObj )
|
|
ReversedRapValue : int = math.floor( ( ( CurrentRapValue * 10 ) - ItemTransferRecordObj.purchased_price ) / 9 )
|
|
|
|
RobuxGivenToOriginalOwner : int = math.floor( ItemTransferRecordObj.purchased_price * 0.7 )
|
|
|
|
UserAssetObj.userid = ItemTransferRecordObj.original_owner_id
|
|
UserAssetObj.updated = datetime.utcnow()
|
|
UserAssetObj.is_for_sale = False
|
|
AssetRapObj.rap = ReversedRapValue
|
|
db.session.commit()
|
|
|
|
IncrementTargetBalance( Target = RecievingUserObj, Amount = ItemTransferRecordObj.purchased_price, CurrencyType = 0 )
|
|
DecrementTargetBalance( Target = SendingUserObj, Amount = RobuxGivenToOriginalOwner, CurrencyType = 0 )
|
|
|
|
CreateSystemMessage( subject = "Item Transfer Reversed", message = f"""Hello {RecievingUserObj.username},
|
|
This is an automated message to inform you that a recent item transfer has been reversed. You have been refunded R$ {ItemTransferRecordObj.purchased_price} for the item {AssetObj.name} which has been taken from your inventory.
|
|
Please contact us on our Discord Server if you have any questions.
|
|
|
|
Sincerely,
|
|
The SYNTAX Team""", userid = RecievingUserObj.id )
|
|
|
|
CreateSystemMessage( subject = "Item Transfer Reversed", message = f"""Hello {SendingUserObj.username},
|
|
This is an automated message to inform you that a recent item transfer has been reversed. R$ {RobuxGivenToOriginalOwner} has been taken from your account and your item ( {AssetObj.name} ) has been returned to your inventory.
|
|
Please contact us on our Discord Server if you have any questions.
|
|
|
|
Sincerely,
|
|
The SYNTAX Team""", userid = SendingUserObj.id )
|
|
|
|
logging.info(f"reverse_item_transfer > Successfully reversed transfer {ItemTransferRecordObj.id} - R$ {ItemTransferRecordObj.purchased_price} given to {RecievingUserObj.username}, R$ {RobuxGivenToOriginalOwner} taken from {SendingUserObj.username}")
|
|
logging.info(f"reverse_item_transfer > New RAP value for asset {AssetObj.id}: {CurrentRapValue} -> {ReversedRapValue}")
|
|
|
|
FradulentTransfers : list[LimitedItemTransfer] = LimitedItemTransfer.query.filter(and_( LimitedItemTransfer.id > 453, LimitedItemTransfer.id < 472 )).order_by(LimitedItemTransfer.id.desc()).all()
|
|
logging.info(f"reverse_item_transfer > Found {len(FradulentTransfers)} fradulent transfers to reverse")
|
|
|
|
for TransferObj in FradulentTransfers:
|
|
reverse_transfer( TransferObj )
|
|
|