syntaxwebsite/app/shell_commands.py

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 )