From 254059623323197befc45bf8187dd920d29df29d Mon Sep 17 00:00:00 2001 From: =?utf8?q?Joann=20M=C3=B5ndresku?= Date: Sun, 19 May 2024 20:17:20 +0300 Subject: [PATCH] WIP co-op APIs. Added /ei/user_data_info endpoint --- app.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ contracts.py | 14 ++++++- db_store.py | 101 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 insertions(+), 2 deletions(-) diff --git a/app.py b/app.py index 1f03c5e..47f127f 100644 --- a/app.py +++ b/app.py @@ -22,6 +22,7 @@ upgrade_cache = {} app = Flask(__name__) contracts.load_contracts() db_store.create_backups_db() +db_store.create_contracts_db() def calculate_backup_checksum(SaveBackup): # WIP - Need to figure out what the 0 and 61 still are. @@ -83,6 +84,24 @@ def ei_save_backup(): db_store.add_backup(SaveBackup.user_id, base64.b64encode(zlib.compress(SaveBackup.SerializeToString()))) return "" +@app.route('/ei/user_data_info', methods=['POST']) +def ei_user_data_info(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + udiReq = EIProto.UserDataInfoRequest() + udiReq.ParseFromString(data) + udiRes = EIProto.UserDataInfoResponse() + Backups = db_store.get_backups(udiReq.device_id) + if len(Backups) == 0: + return base64.b64encode(udiRes.SerializeToString()) + LastBackup = EIProto.Backup() + LastBackup.ParseFromString(zlib.decompress(base64.b64decode(Backups[-1][3]))) + udiRes.backup_checksum = LastBackup.checksum + udiRes.backup_total_cash = LastBackup.game.lifetime_cash_earned + memberships = db_store.get_coop_memberships(udiReq.device_id) + for membership in memberships: + udiRes.coop_memberships.append(membership[1]) + return base64.b64encode(udiRes.SerializeToString()) + @app.route('/ei/daily_gift_info', methods=['POST']) def ei_daily_gift_info(): DateInfo = (datetime.datetime.now() - datetime.datetime(1970, 1, 1)) @@ -103,6 +122,103 @@ def ei_periodicals_request(): c.CopyFrom(contract) return base64.b64encode(PeriodicalResp.SerializeToString()) +@app.route('/ei/query_coop', methods=['POST']) +def ei_query_coop(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + QueryCoop = EIProto.QueryCoopRequest() + QueryCoop.ParseFromString(data) + QueryCoopResp = EIProto.QueryCoopResponse() + db_query = db_store.is_coop_identifier_used(QueryCoop.coop_identifier) + if db_query is not None: + QueryCoopResp.exists = True + else: + QueryCoopResp.exists = False + if QueryCoopResp.exists: + if QueryCoop.league != db_query: + QueryCoopResp.different_league = True + # TODO: Ask contract defs for max coop allowed. + #print(QueryCoopResp) + return base64.b64encode(QueryCoopResp.SerializeToString()) + +@app.route('/ei/create_coop', methods=['POST']) +def ei_create_coop(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + CreateCoop = EIProto.CreateCoopRequest() + CreateCoop.ParseFromString(data) + CreateResponse = EIProto.CreateCoopResponse() + # Double check if in use + db_query = db_store.is_coop_identifier_used(CreateCoop.coop_identifier) + if db_query is not None: + CreateResponse.success = False + CreateResponse.message = "That co-op already exists." + return base64.b64encode(CreateResponse.SerializeToString()) + # Can we identify the contract? + contract = contracts.get_contract_by_identifier(CreateCoop.contract_identifier) + if contract is None: + CreateResponse.success = False + CreateResponse.message = "Couldn't find your contract." + return base64.b64encode(CreateResponse.SerializeToString()) + # Calculate timestamp of the contract so we can later tell actual seconds left to new joins. + stamp = int(time.time() - contract.length_seconds + CreateCoop.seconds_remaining) + # Actually creating the co-op now. + res = db_store.create_coop_contract(CreateCoop.coop_identifier, CreateCoop.contract_identifier, CreateCoop.league, stamp, CreateCoop.user_id, CreateCoop.user_name) + if not res: + CreateResponse.success = False + CreateResponse.message = "Unknown error with your request." + else: + CreateResponse.success = True + CreateResponse.message = "Co-op created." + return base64.b64encode(CreateResponse.SerializeToString()) + +@app.route('/ei/coop_status', methods=['POST']) +def ei_coop_status(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + StatusReq = EIProto.ContractCoopStatusRequest() + StatusReq.ParseFromString(data) + StatusResp = EIProto.ContractCoopStatusResponse() + # Get some base info (TODO: error handling) + BaseInfo = db_store.get_contract_info(StatusReq.coop_identifier) + ContribInfo = db_store.get_coop_contributors(StatusReq.coop_identifier) + ContractInfo = contracts.get_contract_by_identifier(BaseInfo[2]) + TotalEggs = 0 + for x in ContribInfo: + contributor = StatusResp.contributors.add() + contributor.user_id = x[0] + contributor.user_name = x[2] + contributor.contribution_amount = x[4] + TotalEggs += x[4] + contributor.contribution_rate = x[5] + contributor.soul_power = x[6] + contributor.active = True + StatusResp.coop_identifier = StatusReq.coop_identifier + StatusResp.total_amount = TotalEggs + StatusResp.auto_generated = False + StatusResp.public = False + StatusResp.creator_id = BaseInfo[5] + StatusResp.seconds_remaining = (BaseInfo[4] + int(ContractInfo.length_seconds)) - int(time.time()) + return base64.b64encode(StatusResp.SerializeToString()) + +@app.route('/ei/update_coop_status', methods=['POST']) +def ei_update_coop_status(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + UpdateReq = EIProto.ContractCoopStatusUpdateRequest() + UpdateReq.ParseFromString(data) + db_store.update_coop_contribution(UpdateReq.coop_identifier, UpdateReq.user_id, UpdateReq.amount, UpdateReq.rate, UpdateReq.soul_power, UpdateReq.boost_tokens, UpdateReq.time_cheats_detected) + Resp = EIProto.ContractCoopStatusUpdateResponse() + Resp.finalized = True + return base64.b64encode(Resp.SerializeToString()) + +@app.route('/ei/auto_join_coop', methods=['POST']) +def ei_auto_join_coop(): + data = base64.b64decode(request.form["data"].replace(" ", "+")) + AutoJoinCoopRequest = EIProto.AutoJoinCoopRequest() + AutoJoinCoopRequest.ParseFromString(data) + print(AutoJoinCoopRequest) + Resp = EIProto.JoinCoopResponse() + Resp.success = False + Resp.message = "Unable to find any public co-ops to join." + return base64.b64encode(Resp.SerializeToString()) + @app.route('/ei/', methods=['POST']) def ei_unidentified_routes(subpath): print("UNIMPLEMENTED REQ: /ei/" + subpath) diff --git a/contracts.py b/contracts.py index 7cef4e0..e345b4c 100644 --- a/contracts.py +++ b/contracts.py @@ -6,7 +6,7 @@ import time import math # TODO: Co-op contracts, toggle this off when we have them -ALL_SOLO_CONTRACTS = True +ALL_SOLO_CONTRACTS = False # Keep a cache of all contracts contract_epoch = 1714867200 @@ -36,6 +36,12 @@ def get_active_contracts(): # DESIGN QUESTION: Do we even *want* regular contracts? Could just run two "leggacy" branches in parallel. return list +def get_contract_by_identifier(identifier): + for contract in global_contract_db["legacy"]: + if contract.identifier == identifier: + return contract + return None + def __convert_contract_to_proto(obj): # Map values from JSON object to Protobuf object. contract = EIProto.Contract() @@ -50,7 +56,7 @@ def __convert_contract_to_proto(obj): scaler = 1.0 / scale_factor for goalset in contract.goal_sets: for goal in goalset.goals: - goal.target_amount *= scale_factor + goal.target_amount *= scaler return contract def load_contracts(): @@ -77,4 +83,8 @@ def create_perma_contract(): contract.max_soul_eggs = 5000.0 return contract + +def start_new_coop(contract_id, name): + return + global_contract_db["permanent"].append(create_perma_contract()) diff --git a/db_store.py b/db_store.py index 20ac43b..bdb9c62 100644 --- a/db_store.py +++ b/db_store.py @@ -19,6 +19,107 @@ def create_backups_db(): con.close() return FreshInstall +def create_contracts_db(): + FreshInstall = True + con = get_connection("contracts") + cur = con.cursor() + try: + cur.execute("CREATE TABLE Contracts(ID INTEGER PRIMARY KEY AUTOINCREMENT, CoopName TEXT, ContractName TEXT, League SMALLINT, ContractStamp BIGINT, OwnerDevice TEXT)") + cur.execute("CREATE TABLE ContractMember(DeviceID TEXT, CoopName TEXT, DisplayName TEXT, LastVisit BIGINT, Contribution BIGINT, ContribRate BIGINT, SoulPower DOUBLE, BoostTokens INTEGER, TimeCheats INT, Banned BOOL, LeftCoop BOOL)") + cur.execute("CREATE TABLE ContractGift(DeviceID TEXT, RewardType TEXT, Quantity INTEGER)") + except: + FreshInstall = False + con.commit() + con.close() + return FreshInstall + +def get_contract_info(coop_identifier): + if not coop_identifier.isalnum(): + return None + con = get_connection("contracts") + cur = con.cursor() + res = cur.execute('SELECT * FROM Contracts WHERE CoopName="' + coop_identifier + '"') + retval = res.fetchone() + con.close() + return retval + +def is_coop_full(coop_identifier, max_members): + if not coop_identifier.isalnum(): + return True + con = get_connection("contracts") + cur = con.cursor() + res = cur.execute('SELECT COUNT(DeviceID) FROM ContractMember WHERE CoopName="' + coop_identifier + '"') + retval = res.fetchone() + print(retval) + con.close() + return False + +def is_coop_identifier_used(coop_identifier): + if not coop_identifier.isalnum(): + return True + con = get_connection("contracts") + cur = con.cursor() + res = cur.execute('SELECT League FROM Contracts WHERE CoopName="' + coop_identifier + '"') + retval = res.fetchone() + con.close() + return retval + +def create_coop_contract(coop_identifier, contract_id, league, stamp, device_id, display_name): + if is_coop_identifier_used(coop_identifier): + return False + if not device_id.isalnum(): + return False + con = get_connection("contracts") + cur = con.cursor() + stamp = int(time.time()) + cur.execute("INSERT INTO Contracts(CoopName, ContractName, League, ContractStamp, OwnerDevice) VALUES(?, ?, ?, ?, ?)", (coop_identifier, contract_id, league, stamp, device_id)) + cur.execute("INSERT INTO ContractMember(DeviceID, CoopName, DisplayName, LastVisit) VALUES(?, ?, ?, ?)", (device_id, coop_identifier, display_name, int(time.time()))) + con.commit() + con.close() + return True + +def get_coop_contributors(coop_identifier): + if not is_coop_identifier_used(coop_identifier): + return None + con = get_connection("contracts") + cur = con.cursor() + stamp = int(time.time()) + res = cur.execute('SELECT * FROM ContractMember WHERE CoopName="' + coop_identifier + '\"') + x = res.fetchall() + con.close() + if x is None: + return None + else: + return x + +def update_coop_contribution(coop_identifier, device_id, contribution, rate, soul_power, boost_tokens, cheats): + if not is_coop_identifier_used(coop_identifier): + return False + if not device_id.isalnum(): + return False + con = get_connection("contracts") + cur = con.cursor() + stamp = int(time.time()) + values = (int(time.time()), contribution, rate, soul_power, boost_tokens, cheats, device_id, coop_identifier) + cur.execute("UPDATE ContractMember SET LastVisit=?, Contribution=?, ContribRate=?, SoulPower=?, BoostTokens=?, TimeCheats=? WHERE DeviceID=? AND CoopName=?", values) + con.commit() + con.close() + return True + +def get_coop_memberships(device_id): + if not device_id.isalnum(): + return None + con = get_connection("contracts") + cur = con.cursor() + stamp = int(time.time()) + res = cur.execute('SELECT * FROM ContractMember WHERE DeviceID="' + device_id + '\"') + x = res.fetchall() + con.close() + if x is None: + return None + else: + return x + def get_backup(ref_id): if not isinstance(ref_id, int): return None -- 2.25.1