From 3e4aee1a65a2f89d4cc4520d351f03b8b6c56911 Mon Sep 17 00:00:00 2001 From: Lufabbiu <82877942+Lufabbiu@users.noreply.github.com> Date: Sat, 4 Jun 2022 22:20:20 +0200 Subject: [PATCH 1/6] Add files via upload --- metamon_play_fix.py | 438 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 metamon_play_fix.py diff --git a/metamon_play_fix.py b/metamon_play_fix.py new file mode 100644 index 0000000..3db82f0 --- /dev/null +++ b/metamon_play_fix.py @@ -0,0 +1,438 @@ +import argparse +from tqdm import trange +import requests +import os +import sys +import csv +import pandas as pd +from time import sleep +from datetime import datetime + + +# URLs to make api calls +BASE_URL = "https://metamon-api.radiocaca.com/usm-api" +TOKEN_URL = f"{BASE_URL}/login" +LIST_MONSTER_URL = f"{BASE_URL}/getWalletPropertyBySymbol" +CHANGE_FIGHTER_URL = f"{BASE_URL}/isFightMonster" +START_FIGHT_URL = f"{BASE_URL}/startBattle" +LIST_BATTLER_URL = f"{BASE_URL}/getBattelObjects" +WALLET_PROPERTY_LIST = f"{BASE_URL}/getWalletPropertyList" +RESET_METAMON_URL = f"{BASE_URL}/resetMonster" +LVL_UP_URL = f"{BASE_URL}/updateMonster" +MINT_EGG_URL = f"{BASE_URL}/composeMonsterEgg" +CHECK_BAG_URL = f"{BASE_URL}/checkBag" +yeslist = ["yes", "y", "yeah" , "oke" ,"si" , "s"] + +while True: + try: + restart = 'yes' + while restart in yeslist: + def datetime_now(): + return datetime.now().strftime("%m/%d/%Y %H:%M:%S") + + + def post_formdata(payload, url="", headers=None): + """Method to send request to game""" + files = [] + if headers is None: + headers = {} + + for _ in range(5): + try: + # Add delay to avoid error from too many requests per second + sleep(0.66) + response = requests.request("POST", + url, + headers=headers, + data=payload, + files=files) + return response.json() + except: + continue + return {} + + + def get_battler_score(monster): + """ Get opponent's power score""" + return monster["sca"] + + + def picker_battler(monsters_list): + """ Picking opponent """ + battlers = list(filter(lambda m: m["rarity"] == "N", monsters_list)) + + if len(battlers) == 0: + battlers = list(filter(lambda m: m["rarity"] == "R", monsters_list)) + + battler = battlers[0] + score_min = get_battler_score(battler) + for i in range(1, len(battlers)): + score = get_battler_score(battlers[i]) + if score < score_min: + battler = battlers[i] + score_min = score + return battler + + def pick_battle_level(level=1): + return 1 + + class MetamonPlayer: + try: + #avoid collapse errors + def __init__(self, + address, + sign, + msg="LogIn", + auto_reset_monster=True, + auto_lvl_up=False, + output_stats=False): + self.no_enough_money = False + self.output_stats = output_stats + self.total_bp_num = 0 + self.total_success = 0 + self.total_fail = 0 + self.mtm_stats_df = [] + self.token = None + self.address = address + self.sign = sign + self.msg = msg + self.auto_reset_monster = auto_reset_monster + self.auto_lvl_up = auto_lvl_up + + def init_token(self): + """Obtain token for game session to perform battles and other actions""" + payload = {"address": self.address, "sign": self.sign, "msg": self.msg, + "network": "1", "clientType": "MetaMask"} + response = post_formdata(payload, TOKEN_URL) + if response.get("code") != "SUCCESS": + sys.stderr.write("Login failed, token is not initialized. Terminating\n") + sys.exit(-1) + self.token = response.get("data").get("accessToken") + + def change_fighter(self, monster_id): + """Switch to next metamon if you have few""" + payload = { + "metamonId": monster_id, + "address": self.address, + } + post_formdata(payload, CHANGE_FIGHTER_URL) + + def list_battlers(self, monster_id, front=1): + """Obtain list of opponents""" + payload = { + "address": self.address, + "metamonId": monster_id, + "front": front, + } + headers = { + "accessToken": self.token, + } + response = post_formdata(payload, LIST_BATTLER_URL, headers) + return response.get("data", {}).get("objects") + + + def start_fight(self, + my_monster, + target_monster_id, + loop_count=1): + """ Main method to initiate battles (as many as monster has energy for)""" + success = 0 + fail = 0 + total_bp_fragment_num = 0 + mtm_stats = [] + my_monster_id = my_monster.get("id") + my_monster_token_id = my_monster.get("tokenId") + my_level = my_monster.get("level") + my_exp = my_monster.get("exp") + my_power = my_monster.get("sca") + battle_level = pick_battle_level(my_level) + tbar = trange(loop_count) + + for _ in tbar: + payload = { + "monsterA": my_monster_id, + "monsterB": target_monster_id, + "address": self.address, + "battleLevel": battle_level, + } + headers = { + "accessToken": self.token, + } + + + response = post_formdata(payload, START_FIGHT_URL, headers) + code = response.get("code") + if code == "BATTLE_NOPAY": + self.no_enough_money = True + break + data = response.get("data", {}) + fight_result = data.get("challengeResult", False) + bp_fragment_num = data.get("bpFragmentNum", 10) + #calculate the new experience point + if fight_result == True : + new_exp = 5 + else: + new_exp = 3 + my_exp = my_exp + new_exp + tbar.set_description(f"Fighting id {my_monster_token_id} -exp: {my_exp}") + #try to reset metamon level 60 with 395 exp + if my_exp >= 394: + res = post_formdata({"nftId": my_monster_id, "address": self.address}, + RESET_METAMON_URL, + headers) + code = res.get("code") + if code == "SUCCESS": + my_exp = 0 + tbar.set_description("RESET successful! Continue fighting...") + + if self.auto_lvl_up: + # Try to lvl up + res = post_formdata({"nftId": my_monster_id, "address": self.address}, + LVL_UP_URL, + headers) + code = res.get("code") + if code == "SUCCESS": + tbar.set_description(f"LVL UP successful! Continue fighting with {my_monster_token_id}...") + my_level += 1 + # Update league level if new level is 21 or 41 + battle_level = pick_battle_level(my_level) + + self.total_bp_num += bp_fragment_num + total_bp_fragment_num += bp_fragment_num + if fight_result: + success += 1 + self.total_success += 1 + else: + fail += 1 + self.total_fail += 1 + + mtm_stats.append({ + "My metamon id": my_monster_token_id, + "League lvl": battle_level, + "Total battles": loop_count, + "My metamon power": my_power, + "My metamon level": my_level, + "Victories": success, + "Defeats": fail, + "Total egg shards": total_bp_fragment_num, + "Timestamp": datetime_now() + }) + + mtm_stats_df = pd.DataFrame(mtm_stats) + print(mtm_stats_df) + self.mtm_stats_df.append(mtm_stats_df) + + def get_wallet_properties(self): + """ Obtain list of metamons on the wallet""" + + data = [] + payload = {"address": self.address} + headers = { + "accesstoken": self.token, + } + response = post_formdata(payload, WALLET_PROPERTY_LIST, headers) + mtms = response.get("data", {}).get("metamonList", []) + if len(mtms) > 0: + data.extend(mtms) + data = sorted(data, key=lambda metamon: metamon['id']) + else: + if 'code' in response and response['code'] == 'FAIL': + print(response['message']) + return data + + def list_monsters(self): + """ Obtain list of metamons on the wallet (deprecated)""" + payload = {"address": self.address, "page": 1, "pageSize": 60, "payType": -6} + headers = {"accessToken": self.token} + response = post_formdata(payload, LIST_MONSTER_URL, headers) + monsters = response.get("data", {}).get("data", {}) + return monsters + + def battle(self, w_name=None): + """ Main method to run all battles for the day""" + if w_name is None: + w_name = self.address + + summary_file_name = f"{w_name}_summary.tsv" + mtm_stats_file_name = f"{w_name}_stats.tsv" + self.init_token() + + wallet_monsters = self.get_wallet_properties() + print(f"Monsters total: {len(wallet_monsters)}") + + available_monsters = [ + monster for monster in wallet_monsters if (monster.get("level") < 60 or monster.get("allowReset") == True) and monster.get("tear") > 0 + ] + level60_monsters = [ + monster for monster in wallet_monsters if monster.get("level") >= 60 + ] + stats_l = [] + print(f"Available Monsters : {len(available_monsters)}") + print(f"Level 60 Monsters : {len(level60_monsters)}") + + for monster in available_monsters: + monster_id = monster.get("id") + tear = monster.get("tear") + level = monster.get("level") + exp = monster.get("exp") + + if int(exp) >= 600 or (int(level) >= 60 and int(exp) >= 395): + print(f"Monster {monster_id} cannot fight due to " + f"exp overflow. please reset manually") + continue + battlers = self.list_battlers(monster_id) + battler = picker_battler(battlers) + target_monster_id = battler.get("id") + + self.change_fighter(monster_id) + + self.start_fight(monster, + target_monster_id, + loop_count=tear) + if self.no_enough_money: + print("Not enough u-RACA") + break + total_count = self.total_success + self.total_fail + success_percent = .0 + if total_count > 0: + success_percent = (self.total_success / total_count) * 100 + + if total_count <= 0: + print("No battles to record") + return + + stats_l.append({ + "Victories": self.total_success, + "Defeats": self.total_fail, + "Win Rate": f"{success_percent:.2f}%", + "Total Egg Shards": self.total_bp_num, + "Datetime": datetime_now() + }) + + stats_df = pd.DataFrame(stats_l) + print(stats_df) + if os.path.exists(summary_file_name) and self.output_stats: + back_fn = f"{summary_file_name}.bak" + os.rename(summary_file_name, back_fn) + tmp_df = pd.read_csv(back_fn, sep="\t", dtype="str") + stats_upd_df = pd.concat([stats_df, tmp_df]) + stats_df = stats_upd_df + os.remove(back_fn) + + if self.output_stats: + stats_df.to_csv(summary_file_name, index=False, sep="\t") + + mtm_stats_df = pd.concat(self.mtm_stats_df) + if os.path.exists(mtm_stats_file_name) and self.output_stats: + back_fn = f"{mtm_stats_file_name}.bak" + os.rename(mtm_stats_file_name, back_fn) + tmp_df = pd.read_csv(back_fn, sep="\t", dtype="str") + upd_df = pd.concat([mtm_stats_df, tmp_df]) + mtm_stats_df = upd_df + os.remove(back_fn) + if self.output_stats: + mtm_stats_df.to_csv(mtm_stats_file_name, sep="\t", index=False) + + def mint_eggs(self): + self.init_token() + + headers = { + "accessToken": self.token, + } + payload = {"address": self.address} + + # Check current egg fragments + check_bag_res = post_formdata(payload, CHECK_BAG_URL, headers) + items = check_bag_res.get("data", {}).get("item") + total_egg_fragments = 0 + + for item in items: + if item.get("bpType") == 1: + total_egg_fragments = item.get("bpNum") + break + + total_egg = int(int(total_egg_fragments) / 1000) + + if total_egg < 1: + print("You don't have enough egg fragments to mint") + return + + # Mint egg + res = post_formdata(payload, MINT_EGG_URL, headers) + code = res.get("code") + if code != "SUCCESS": + print("Mint eggs failed!") + return + + print(f"Minted Eggs Total: {total_egg}") + + except Exception: + pass + + + if __name__ == "__main__": + try: + #avoid collapse errors + parser = argparse.ArgumentParser() + + parser.add_argument("-i", "--input-tsv", help="Path to tsv file with wallets' " + "access records (name, address, sign, login message) " + "name is used for filename with table of results. " + "Results for each wallet are saved in separate files", + default="wallets.tsv", type=str) + parser.add_argument("-nl", "--no-lvlup", help="Disable automatic lvl up " + "(if not enough potions/diamonds it will be disabled anyway) " + "by default lvl up will be attempted after each battle", + action="store_true", default=False) + parser.add_argument("-nb", "--skip-battles", help="No battles, use when need to only mint eggs from shards", + action="store_true", default=False) + parser.add_argument("-e", "--mint-eggs", help="Automatically mint eggs after all battles done for a day", + action="store_true", default=False) + parser.add_argument("-s", "--save-results", help="To enable saving results on disk use this option. " + "Two files _summary.tsv and _stats.tsv will " + "be saved in current dir.", + action="store_true", default=False) + + args = parser.parse_args() + + if not os.path.exists(args.input_tsv): + print(f"Input file {args.input_tsv} does not exist") + sys.exit(-1) + + # determine delimiter char from given input file + with open(args.input_tsv) as csvfile: + dialect = csv.Sniffer().sniff(csvfile.readline(), "\t ;,") + delim = dialect.delimiter + + wallets = pd.read_csv(args.input_tsv, sep=delim) + + auto_reset = True + for i, r in wallets.iterrows(): + mtm = MetamonPlayer(address=r.address, + sign=r.sign, + msg=r.msg, + auto_reset_monster=auto_reset, + output_stats=args.save_results) + + auto_lvlup = not args.no_lvlup + for i, r in wallets.iterrows(): + mtm = MetamonPlayer(address=r.address, + sign=r.sign, + msg=r.msg, + auto_lvl_up=auto_lvlup, + output_stats=args.save_results) + + if not args.skip_battles: + mtm.battle(w_name=r["name"]) + if args.mint_eggs: + mtm.mint_eggs() + restart = "no" + except Exception: + pass + restart = input("do you want to restart bot? ") + #manually restart bot after collapse errors + exit() + + except Exception: + print(" Go to HELL ") + exit() \ No newline at end of file From 1359a87959d19ff65465cfbf3b4cb45869ab923a Mon Sep 17 00:00:00 2001 From: Lufabbiu <82877942+Lufabbiu@users.noreply.github.com> Date: Sun, 5 Jun 2022 11:53:29 +0200 Subject: [PATCH 2/6] Update metamon_play_fix.py --- metamon_play_fix.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/metamon_play_fix.py b/metamon_play_fix.py index 3db82f0..ab7c7a2 100644 --- a/metamon_play_fix.py +++ b/metamon_play_fix.py @@ -429,10 +429,11 @@ def mint_eggs(self): restart = "no" except Exception: pass - restart = input("do you want to restart bot? ") - #manually restart bot after collapse errors + restart = "y" + #restart = input("do you want to restart bot? ") + #if you want to manually restart bot after collapse errors exit() except Exception: print(" Go to HELL ") - exit() \ No newline at end of file + exit() From 17d59853f2a7859cb86dc27c94afd148774df7f5 Mon Sep 17 00:00:00 2001 From: Lufabbiu <82877942+Lufabbiu@users.noreply.github.com> Date: Mon, 6 Jun 2022 10:00:07 +0200 Subject: [PATCH 3/6] Update metamon_play_fix.py --- metamon_play_fix.py | 1 + 1 file changed, 1 insertion(+) diff --git a/metamon_play_fix.py b/metamon_play_fix.py index ab7c7a2..9d7c1d7 100644 --- a/metamon_play_fix.py +++ b/metamon_play_fix.py @@ -192,6 +192,7 @@ def start_fight(self, headers) code = res.get("code") if code == "SUCCESS": + my_exp = 0 tbar.set_description(f"LVL UP successful! Continue fighting with {my_monster_token_id}...") my_level += 1 # Update league level if new level is 21 or 41 From 5905a3a1d539ce4452c5b2be8ef610661f7f2fb0 Mon Sep 17 00:00:00 2001 From: Lufabbiu <82877942+Lufabbiu@users.noreply.github.com> Date: Mon, 6 Jun 2022 10:11:54 +0200 Subject: [PATCH 4/6] Update metamon_play_fix.py --- metamon_play_fix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metamon_play_fix.py b/metamon_play_fix.py index 9d7c1d7..c9c595f 100644 --- a/metamon_play_fix.py +++ b/metamon_play_fix.py @@ -175,8 +175,8 @@ def start_fight(self, new_exp = 3 my_exp = my_exp + new_exp tbar.set_description(f"Fighting id {my_monster_token_id} -exp: {my_exp}") - #try to reset metamon level 60 with 395 exp - if my_exp >= 394: + #try to reset metamon level 60 with 395 exp - better to set 390 as limit to start reset, otherwise if reach 395 exp,you should reset him manually + if my_exp >= 390: res = post_formdata({"nftId": my_monster_id, "address": self.address}, RESET_METAMON_URL, headers) From 8acb422269d57af6245a65481379ecc189880741 Mon Sep 17 00:00:00 2001 From: Lufabbiu <82877942+Lufabbiu@users.noreply.github.com> Date: Tue, 7 Jun 2022 20:30:23 +0200 Subject: [PATCH 5/6] Added the features to auto restore Healthy when value is <= 90 % --- metamon_play_fix.py | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/metamon_play_fix.py b/metamon_play_fix.py index c9c595f..f1b89f1 100644 --- a/metamon_play_fix.py +++ b/metamon_play_fix.py @@ -18,6 +18,7 @@ LIST_BATTLER_URL = f"{BASE_URL}/getBattelObjects" WALLET_PROPERTY_LIST = f"{BASE_URL}/getWalletPropertyList" RESET_METAMON_URL = f"{BASE_URL}/resetMonster" +RESTORE_HEALTHY_URL = f"{BASE_URL}/addHealthy" LVL_UP_URL = f"{BASE_URL}/updateMonster" MINT_EGG_URL = f"{BASE_URL}/composeMonsterEgg" CHECK_BAG_URL = f"{BASE_URL}/checkBag" @@ -84,6 +85,7 @@ def __init__(self, sign, msg="LogIn", auto_reset_monster=True, + auto_healthy_monster=True, auto_lvl_up=False, output_stats=False): self.no_enough_money = False @@ -97,6 +99,7 @@ def __init__(self, self.sign = sign self.msg = msg self.auto_reset_monster = auto_reset_monster + self.auto_healthy_monster = auto_healthy_monster self.auto_lvl_up = auto_lvl_up def init_token(self): @@ -145,6 +148,7 @@ def start_fight(self, my_level = my_monster.get("level") my_exp = my_monster.get("exp") my_power = my_monster.get("sca") + my_health = my_monster.get("healthy") battle_level = pick_battle_level(my_level) tbar = trange(loop_count) @@ -158,8 +162,7 @@ def start_fight(self, headers = { "accessToken": self.token, } - - + response = post_formdata(payload, START_FIGHT_URL, headers) code = response.get("code") if code == "BATTLE_NOPAY": @@ -174,7 +177,7 @@ def start_fight(self, else: new_exp = 3 my_exp = my_exp + new_exp - tbar.set_description(f"Fighting id {my_monster_token_id} -exp: {my_exp}") + tbar.set_description(f"Fighting id {my_monster_token_id} -exp:{my_exp} Healthy {my_health}") #try to reset metamon level 60 with 395 exp - better to set 390 as limit to start reset, otherwise if reach 395 exp,you should reset him manually if my_exp >= 390: res = post_formdata({"nftId": my_monster_id, "address": self.address}, @@ -184,7 +187,16 @@ def start_fight(self, if code == "SUCCESS": my_exp = 0 tbar.set_description("RESET successful! Continue fighting...") - + + if my_health <= 90: + res = post_formdata({"nftId": my_monster_id, "address": self.address}, + RESTORE_HEALTHY_URL, + headers) + code = res.get("code") + if code == "SUCCESS": + my_health = 100 + tbar.set_description("RESTORED HEALTHY successful! Continue fighting...") + if self.auto_lvl_up: # Try to lvl up res = post_formdata({"nftId": my_monster_id, "address": self.address}, @@ -286,13 +298,13 @@ def battle(self, w_name=None): target_monster_id = battler.get("id") self.change_fighter(monster_id) - self.start_fight(monster, target_monster_id, loop_count=tear) if self.no_enough_money: print("Not enough u-RACA") break + total_count = self.total_success + self.total_fail success_percent = .0 if total_count > 0: @@ -367,8 +379,8 @@ def mint_eggs(self): print(f"Minted Eggs Total: {total_egg}") - except Exception: - pass + except Exception : + pass if __name__ == "__main__": @@ -407,6 +419,13 @@ def mint_eggs(self): wallets = pd.read_csv(args.input_tsv, sep=delim) + auto_healthy = True + for i, r in wallets.iterrows(): + mtm = MetamonPlayer(address=r.address, + sign=r.sign, + msg=r.msg, + auto_healthy_monster=auto_healthy, + output_stats=args.save_results) auto_reset = True for i, r in wallets.iterrows(): mtm = MetamonPlayer(address=r.address, @@ -430,9 +449,9 @@ def mint_eggs(self): restart = "no" except Exception: pass - restart = "y" + restart = "y" #restart = input("do you want to restart bot? ") - #if you want to manually restart bot after collapse errors + #manually restart bot after collapse errors exit() except Exception: From 08cefaa832ea3ac63fd1ef1ec00330d36121d14b Mon Sep 17 00:00:00 2001 From: Lufabbiu <82877942+Lufabbiu@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:09:45 +0200 Subject: [PATCH 6/6] Update metamon_play_fix.py --- metamon_play_fix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metamon_play_fix.py b/metamon_play_fix.py index f1b89f1..49d712e 100644 --- a/metamon_play_fix.py +++ b/metamon_play_fix.py @@ -178,8 +178,8 @@ def start_fight(self, new_exp = 3 my_exp = my_exp + new_exp tbar.set_description(f"Fighting id {my_monster_token_id} -exp:{my_exp} Healthy {my_health}") - #try to reset metamon level 60 with 395 exp - better to set 390 as limit to start reset, otherwise if reach 395 exp,you should reset him manually - if my_exp >= 390: + #try to reset metamon level 60 with 395 exp - works perfect + if my_exp >= 395: res = post_formdata({"nftId": my_monster_id, "address": self.address}, RESET_METAMON_URL, headers)