- 20.06.2022
- 23 868
- 218
- 36
- Награды
- 10
- Пол
- Муж.
Репутация:
- Автор темы
- Администратор
- Модератор
- Команда форума
- #1
В этой статье я расскажу о том, как я изучал возможность автоматизации стремительно набирающей популярность игры Hamster Kombat. Не все испробованные тактики оказались удачными, но, возможно, ты найдешь мои методы полезными в иных ситуациях.
Рассказывать подробно о самой игре не буду, о ней слышали, наверное, даже те, у кого нет мобильного телефона. Начнем сразу с интересного.
WARNING
Редакция не рекомендует рассматривать Hamster Kombat как потенциальный источник заработка. Мы будем подразумевать, что имеем дело с обычной игрой, и используем ее как популярный пример, на котором можно продемонстрировать техники автоматизации.ЗАДАЧА
Игра запускается через бота в Telegram, смысл заключается в наращивании пассивного дохода с биржи. Увеличивать доход можно с помощью покупки карточек, каждая карточка дает определенную прибавку к доходу. Деньги для покупки карточек добываются двумя путями: из того самого пассивного дохода и нажиманием большой кнопки в центре экрана.В процессе игры у меня быстро появилось желание автоматизировать две вещи: нажатие на кнопку и выбор оптимальных карточек для покупки. У каждой карточки две основные характеристики: размер прибавки к доходу и цена. Одни карточки дают небольшую прибавку и дорого стоят, другие, наоборот, очень выгодны. Для простоты анализа я ввел понятие удельного дохода, ну или его еще можно назвать «цена прибавки». Допустим, если карточка стоит миллион монет и дает прибавку к доходу 4000 монет, значит, цена прибавки будет 1 000 000 / 4000 = 250 монет. Чем меньше цена прибавки, тем целесообразнее покупка монеты.
Характеристики карточки меняются после каждой покупки, поэтому на глаз все проанализировать и выбрать наиболее выгодную карточку по цене прибавки практически нереально. Для начала я составил табличку, добавил в нее все карточки и отсортировал их по удельной доходности (цена/добавка). Все бы хорошо, но это не отменяет необходимости после каждой покупки вручную обновлять изменившиеся характеристики карточки. Что ж, попробуем автоматизировать.
С ходу в голову пришел очевидный способ взаимодействия с игрой — анализ изображения на экране и эмуляция нажатий на экран с использованием Android Debug Bridge (ADB). Конечно, в идеале бы все сделать на запросах, вообще без телефона, но для этого надо анализировать трафик, а с учетом того, что игра запускается только на телефоне, внутри Telegram, анализ трафика мне показался весьма трудоемкой задачей, поэтому я решил начать с более очевидной реализации.
АВТОМАТИЗАЦИЯ ЧЕРЕЗ ADB
Тапаем на экран
Надеюсь, что устанавливать ADB и взаимодействовать через него с телефоном все умеют, поэтому не буду лить воду про это, перейдем сразу к делу. Попытки запустить игру в Windows закончились неудачей, поскольку игра каким‑то образом определяет, что запущена не на телефоне, и выводит сообщение об этом. Что ж, телефон — значит, телефон. Вооружаемся телефоном с Android, расчехляем ADB. Вопрос с тапаньем по кнопке легко решается одной командой:
Код:
e:\Android\sdk\platform-tools\adb.exe shell input tap 540 1800
540 и 1800 — это координаты точки на экране, в которую мы хотим «ткнуть пальцем». Чтобы не возвращаться к этому второй раз, сразу хочу сказать про так называемую морзянку. В игре каждый день есть возможность получить дополнительный миллион монет, введя с помощью азбуки Морзе определенный код. Точка кодируется коротким нажатием, тире — длинным. Делать короткое нажатие мы уже умеем, длинное же делается вот такой командой:
Код:
e:\Android\sdk\platform-tools\adb.exe shell input swipe 500 1500 500 1500 555
Первые четыре числа — координаты начала и конца движения, а 555 — время движения. В нашем случае начало и конец совпадают.
Чтобы минимизировать ручную работу, можно написать на Python скрипт, который будет принимать на вход слово, перекодировать его в последовательность точек и тире и через нажатия вводить в игру. Приведу основные функции, которые понадобятся.
Функция, которая эмулирует ввод точки:
Код:
def tochka():
cmd = "adb.exe shell input tap 500 1500"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, creationflags=0x08000000)
process.wait()
Функция, эмулирующая ввод тире:
Код:
def tire():
cmd = "adb.exe shell input swipe 500 1500 500 1500 500"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, creationflags=0x08000000)
process.wait()
Функция, которая разбирает последовательность точек и тире и отправляет ее в телефон (пробел означает паузу в три секунды, необходимую для отделения букв друг от друга):
Код:
def mz2tap(morze):
for simvol in morze:
match simvol:
case ".":
tochka()
case "-":
tire()
case " ":
time.sleep(3)
Перевода текста в азбуку Морзе касаться не буду, не ради этого мы тут собрались. Пример использования:
Код:
mz2tap(".--")
mz2tap(".")
mz2tap("-...")
mz2tap("...--")
С первой задачей по автоматизации тапов разобрались, теперь самое время заняться анализом карточек, чтобы выбирать лучшую.
Распознаём карточки
С карточками пришлось повозиться. К задаче я решил подойти в лоб — пролистывать карточки и делать скриншоты, затем обрезать лишнее и распознавать то, что осталось.Для начала я наделал кучу скриншотов с помощью скрипта на Python. Этот код в цикле делает скриншот и скачивает его в каталог cards_dir на компьютере:
Код:
while True:
cmd = "adb.exe shell screencap -p /sdcard/screencap.png"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, creationflags=0x08000000)
process.wait()
cmd = f"adb.exe pull /sdcard/screencap.png {os.path.join(cards_dir, datetime.datetime.now().strftime("%Y-%m-%d_%H_%M_%S_%f"))}.png"
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, creationflags=0x08000000)
process.wait()
print(cmd)
Во время работы скрипта я вручную открывал каждую карточку на телефоне. После завершения следует удалить одинаковые скриншоты:
Код:
hash_list = []
list_of_files = os.walk(cards_dir)
dubl_count = 0
for root, folders, files in list_of_files:
for file in files:
file_path = os.path.join(root, file)
Hash_file = hashlib.md5(open(file_path, 'rb').read()).hexdigest()
if Hash_file not in hash_list:
hash_list.append(Hash_file)
else:
dubl_count += 1
os.remove(file_path)
print(file_path)
if dubl_count > 0:
print(f"Удалено {dubl_count} дубликатов")
else:
print("Нет дубликатов")
Для распознавания будем использовать готовый модуль pytesseract. Очень удобная штука, распознать текст на скриншоте можно одной строкой:
Код:
string = pytesseract.image_to_string(image, lang='eng')
Однако пробный запуск показал, что в распознанный текст попадает много мусора. Явно нужно обрезать картинку так, чтобы на ней был только текст. Приводить весь скрипт для обрезки не буду, чтобы не загромождать статью рутиной, скажу лишь, что использовал модуль PIL, из которого мне в основном понадобились функции Image.crop и Image.getpixel.
Я поэкспериментировал на паре скриншотов, чтобы определять границы нужной области, и разработал алгоритм, отсекающий большие области, цвет которых не совпадает с цветом фона и текста/.
Потом проверил на всех скриншотах, чтобы убедиться, что все работает правильно.
После удаления лишнего 99% скриншотов нормально распознались, нестандартные добил вручную, результат свел в табличку. Конечно, выбирать карточки стало гораздо удобнее, но тут пришло осознание того, что не сильно‑то это помогает. После каждой покупки все равно надо подключать телефон к компьютеру, запускать скрипт, проверять результат распознавания. Получается, что быстрее после покупки поправлять таблицу вручную. Увы, в таком виде эксперимент был признан неудачным.
АВТОМАТИЗАЦИЯ ЧЕРЕЗ ЗАПРОСЫ
Анализируем трафик
Деваться некуда, попробуем познакомиться с игрой более детально. Первым делом необходимо понять, что мешает хомяку запускаться на компьютере. Давай откроем веб‑версию телеграма в браузере, зайдем в бота и попробуем снова запустить игру. Хомяк просится к нам на телефон.Открываем консоль разработчика, в которой видно, что игра запускается в отдельном iframe.
Взглянем подробнее на параметр src у тега iframe и увидим там длиннющую строку параметров. Если присмотреться, то можно заметить параметр tgWebAppPlatform, содержащий значение web. Попробуем заменить его android и открыть полученный URL в новом окне.
Ура, игра нормально запускается в браузере. Теперь ничто не мешает поэкспериментировать с запросами. Принцип тут такой: открываем вкладку Network, очищаем ее от старых запросов, выполняем какое‑то действие в игре и смотрим, какие запросы при этом отправляются на сервер. Например, попробуем купить какую‑нибудь карточку.
Несложно заметить, что при этом отправляется POST-запрос на следующий адрес:
Код:
https://api.hamsterkombatgame.io/clicker/buy-upgrade
Заглянув на вкладку Payload, можно увидеть, что в запросе передаются два параметра:
Код:
{upgradeId: "hamster_youtube_gold_button", timestamp: 1722086163455}
Параметр upgradeId очень напоминает название карточки, которую мы купили, ну а параметр timestamp, судя по его названию и содержимому, очень похож на время в юниксовом формате. Любопытства ради заглянем в ответ на запрос (вкладка Response), а там просто счастье — куча разных параметров игры, среди которых есть полные характеристики всех карточек:
Код:
{
"clickerUser": {
"id": "ХХХХХХХХХХХХ",
"totalCoins": 5389463.6559000015,
"balanceCoins": 4603871.6559000015,
"level": 6,
"availableTaps": 3500,
"lastSyncUpdate": 1722086163,
"exchangeId": "mexc",
...
"upgradesForBuy": [
{
"id": "ceo",
"name": "CEO",
"price": 725363,
"profitPerHour": 2789,
"condition": null,
"section": "PR&Team",
"level": 16,
"currentProfitPerHour": 2513,
"profitPerHourDelta": 276,
"isAvailable": true,
"isExpired": false
},
{
"id": "marketing",
"name": "Marketing",
"price": 8557,
"profitPerHour": 838,
"condition": null,
"section": "PR&Team",
"level": 9,
"currentProfitPerHour": 718,
"profitPerHourDelta": 120,
"isAvailable": true,
"isExpired": false
},
...
}
...
}
Получается, что, во‑первых, мы можем покупать карточки с помощью запроса, а во‑вторых, после каждой покупки будем получать в ответ обновленные характеристики всех карточек. Чудесно. И стоило мучиться с распознаванием скриншотов...
Эксперименты с самой большой круглой кнопкой показали, что на сервер отправляется не каждое нажатие. Игра определяет серию последовательных кликов, а затем отправляет сразу количество нажатий и количество оставшихся монет с помощью POST-запроса на следующий URL:
Код:
https://api.hamsterkombatgame.io/clicker/tap
И передает эти параметры:
Код:
data = {
"count": 500, # Число нажатий
"availableTaps": 0,
"timestamp": timestamp()
}
Следует только быть внимательным при расчете параметра counts, чтобы при умножении его на количество монет за один тап и вычитании полученного значения из общего количества доступных монет получалось значение availableTaps.
Пишем скрипт
Теперь, когда мы разобрались с необходимыми для взаимодействия с сервером запросами и их параметрами, настало время немножко покодить. Пишем скрипт на Python:
Python:
import requests
from time import time
def tap():
url = "https://api.hamsterkombatgame.io/clicker/tap"
data = {
"count": 500,
"availableTaps": 0,
"timestamp": int(time())
}
response = requests.post(url, json=data)
if response.status_code == 200:
try:
result = response.json()
return result
except ValueError as e:
print(f"{e}")
return None
print(f"Error: {response.status_code, response.text}")
return None
print(tap())
После запуска получаем в ответ ошибку 422. Причин возникновения такой ошибки может быть весьма много. Сервер может ее вернуть, если в целом запрос верный, но ему что‑то не нравится. Внимательно смотрим на скрипт и понимаем: в нем нигде не сказано, кто мы. Мы указали, что мы тапнули 500 раз, но как игра поймет, какой пользователь это сделал?
Если повнимательнее изучить оригинальный запрос на tap через DevTools браузера, то можно заметить, что в заголовках запроса есть поле Authorization, содержащее вот такое значение:
Код:
Bearer 32432432432432432446t...cfC1y523432432432419
Попробуем изменить скрипт, вместо строки с запросом response = requests.post(...) напишем следующий код:
Код:
key = '32432432432432432446t...cfC1y523432432432419'
headers: dict = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 14; Nokia 3310; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36',
'Content-Type': 'application/json',
"Authorization": f"Bearer {key}",
}
response = requests.post(url, json=data, headers=headers)
Теперь в ответ приходит JSON c обновленной конфигурацией, а в интерфейсе игры отображаются изменения, как будто мы действительно тапнули указанное количество раз. Осталось добавить цикл, который будет, например, периодически тапать один раз, получать в ответ обновленный конфиг, в котором написано, сколько раз еще есть смысл тапать, и во второй раз посылать уже скорректированный запрос.
Полный код я здесь приводить не буду — можешь написать свою реализацию в качестве домашнего задания. Вообще говоря, смысл тапать пропадает довольно быстро, потому что доход от тапов становится несоизмеримо малым по сравнению с пассивным доходом от покупки карточек. У меня, например, тапы начисляются со скоростью три монеты в секунду, а пассивный доход — 1500 монет в секунду, так что смысл тапать мизерный.
Попробуем лучше добыть список карточек с ценами и привести его в удобный вид. Как оказалось при анализе запросов, помимо запроса на покупку, есть еще запрос на просто обновление характеристик, он‑то нам и нужен:
Код:
https://api.hamsterkombatgame.io/clicker/upgrades-for-buy
Никаких параметров ему передавать не надо, кроме токена в заголовке, а в ответ он присылает JSON с кучей параметров. Наша задача — достать оттуда параметры карточек, вычислить цену прибавки для каждой карточки и отсортировать их от самых дешевых к самым дорогим. У меня получилось так:
Код:
def get_cards():
key = '32432432432432432446t...cfC1y523432432432419'
headers: dict = {
'User-Agent': 'Mozilla/5.0 (Linux; Android 14; Nokia 3310; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36',
'Content-Type': 'application/json',
"Authorization": f"Bearer {key}",
}
url = "https://api.hamsterkombatgame.io/clicker/upgrades-for-buy"
response = requests.post(url, headers=headers)
if response.status_code == 200:
try:
data = response.json()
cards = []
for task in data["upgradesForBuy"]:
if task["isAvailable"] == True and task["isExpired"] == False and task["profitPerHourDelta"] > 0:
section = task["section"]
name = task["name"]
price = task["price"]
profitPerHour = task["profitPerHourDelta"]
udel_cena = price / profitPerHour
cards.append(
[section, name, price, int(udel_cena)])
sorted_cards = sorted(cards, key=lambda x: x[3])
for card in sorted_cards:
print(f"{card[3]}:\t{card[2]}:\t{card[1]}:\t{card[0]}")
except ValueError as e:
print(f"{e}")
В результате получаем список карточек, отсортированный по уменьшению «выгодности»:
Код:
7: 551: HamsterBook: PR&Team
7: 606: X: PR&Team
7: 2000: Risk management team: PR&Team
7: 7757: Special Hamster Conference: Specials
7: 2327: Hamster YouTube Channel: Specials
7: 11000: Web3 academy launch: Specials
8: 2205: IT team: PR&Team
...
200: 1000000: TON + Hamster Kombat = Success: Specials
1000: 5000000: Call for BTC to rise: Specials
1000: 1000000: HamsterWatch for soulmate: Specials
2628: 725363: CEO: PR&Team
4000: 12000000: Business jet: Specials
Что и требовалось доказать, в общем‑то. На а дальше уже можно сделать полностью автоматическую тапалку, которая будет периодически отправлять на сервер инфу о нажатиях кнопки, запрашивать баланс и при необходимости покупать наиболее выгодные карточки.
ВЫВОДЫ
Итак, мы научились:- использовать ADB для эмуляции нажатий;
- распознавать текст со скриншотов, чтобы управлять программой;
- анализировать и подделывать сетевые запросы.
Дополнительно отмечу, что при написании своего скрипта для автоматизации обязательно нужно выделить время на изучение возможных лимитов на использование API, таких как количество запросов в секунду, количество аккаунтов на одном IP и так далее, иначе можно нарваться на блокировку аккаунта.
Что касается оптимальных стратегий в Hamster Kombat, можно подумать, что выгоднее: покупать любые доступные карточки, лишь бы увеличивать доходность, или подождать какое‑то время и подкопить на наиболее выгодные карточки, или планировать покупку нескольких карточек в определенной последовательности в зависимости от их характеристик.
Последние темы в этом разделе:
- Обход captchaV2
- Как стать специалистом по кибербезопасности «с нуля»
- Тренировочная мишень. Сравниваем ратники с NjRat
- Ddos и Dos атаки. В чём их различие и чем опасны!
- Слив трафика из Ютуб Шортс + Выводим Shorts ролики и каналы в топ
- Пособие по финансовой грамотности от А до Я
- Поднимаем Веб-скрапер на основе ИИ
- Как сделать свой Discord бот на основе Chat GPT
- Получаем BYBIT MasterCard карту для граждан РФ и Беларуси - Оплата зарубежных сервисов
- Бесплатно пользуемся нейросетью без цензуры