WIP co-op APIs. Added /ei/user_data_info endpoint
authorJoann Mõndresku <joann@cernodile.com>
Sun, 19 May 2024 17:17:20 +0000 (20:17 +0300)
committerJoann Mõndresku <joann@cernodile.com>
Sun, 19 May 2024 17:17:20 +0000 (20:17 +0300)
app.py
contracts.py
db_store.py

diff --git a/app.py b/app.py
index 1f03c5e8572436f5f230ed9a8f1c9706e779c171..47f127fb0bb6559e448ad7e5a7fa5b1ff25533e6 100644 (file)
--- 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/<path:subpath>', methods=['POST'])
 def ei_unidentified_routes(subpath):
        print("UNIMPLEMENTED REQ: /ei/" + subpath)
index 7cef4e0878cce26d95e83ca42d366651dd39488d..e345b4c8925778e9ce31bfb86fe967765addf5c9 100644 (file)
@@ -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())
index 20ac43b94828a251ba5a739b7d811c1bc8db5d7c..bdb9c62f2db13c14cc796b9caa041cb3893bd341 100644 (file)
@@ -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