123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import argparse
- import re
- import io
- import os
- import sys
- import time
- from datetime import datetime
- from ppadb.client import Client as AdbClient
- from PIL import Image
- import pytesseract
-
- # ----
- # Program
- # ----
-
- PROGRAM_NAME = "RoK Scraper"
- PROGRAM_VERSION = "0.3"
- PROGRAM_DESCRIPTION = "This program controls devices via ADB to take screenshots in Rise of Kingdoms."
-
- # TODO:
- # - Skip your own ranking
- # - Correct for device rotation
- # - Better logic for tap_profile()
- # - Draw box around skipped profiles
- # - Support for the last ~995-1000
- # - Coordinates to percentages
-
- # ----
- # Arguments
- # ----
-
- parser = argparse.ArgumentParser(description = PROGRAM_DESCRIPTION)
- parser.add_argument("--power", help = "get full power rankings", default = False, action = "store_true")
- parser.add_argument("--top", help = "get stats on the top 300", default = False, action = "store_true")
- parser.add_argument("-p", "--project", help = "project name", required = True)
- parser.add_argument("-s", "--serial", help = "device serial", required = True)
- parser.add_argument("-v", "--verbose", help = "be verbose", default = False, action = "store_true")
- parser.add_argument("-c", "--count", help = "how many to scrape", default = 0, type = int)
- parser.add_argument("-n", "--start", help = "what number to start scraping at", default = 1, type = int)
- parser.add_argument("-k", "--skipped", help = "how many have been skipped", type = int)
- parser.add_argument("--debug", help = "debug", default = False, action = "store_true")
- arguments = parser.parse_args()
-
- # ----
- # Coordinates
- # ----
-
- ROTATE = 90 # TODO: fix this; shouldn't be needed
-
- TAP_LOCATIONS = {
- "1": (460, 460),
- "2": (460, 624),
- "3": (460, 780),
- "4": (460, 940),
- "Middle": (460, 980),
- "Middle + 1": (460, 1136),
- "Middle + 2": (460, 1300)
- }
-
- # ----
- # Functions
- # ----
-
- # TODO: pass device instead of image
- def read_string_from_image(image, x, y, x2, y2):
- return pytesseract.image_to_string(image.crop((x, y, x2, y2)), config="--psm 6").strip().replace('\n', ' ').replace('\r', '').replace('\t', ' ')
-
- def get_clipboard(device):
- raw = device.shell('am broadcast -n "ch.pete.adbclipboard/.ReadReceiver"')
- dataMatcher = re.compile("^.*\n.*data=\"(.*)\"$", re.DOTALL)
- dataMatch = dataMatcher.match(raw)
- return dataMatch.group(1)
-
- def take_screenshot(device, path):
- screencap = device.screencap()
- image = Image.open(io.BytesIO(screencap)).rotate(ROTATE, expand=1)
- image.save(path + ".png", "PNG")
- return
-
- # TODO: this can be done better
- def get_datetime_string():
- d = datetime.now()
- return str(d.year).zfill(4) + str(d.month).zfill(2) + str(d.day).zfill(2) + str(d.hour).zfill(2) + str(d.minute).zfill(2) + str(d.second).zfill(2)
-
- def read_single_power_ranking(device, folder):
- datetime = get_datetime_string()
-
- # Screenshot profile
- # ID, Username, Power, Kill Points
- time.sleep(1)
- take_screenshot(device, folder + datetime + "_profile")
-
- # Copy and save username
- device.shell("input tap 1055 455")
- time.sleep(1)
- username = get_clipboard(device)
- with open(projectFolder + "usernames.txt", 'a+', newline='', encoding='utf-8') as output_file:
- output_file.write(datetime + "\t")
- output_file.write(username)
- output_file.write("\n")
- if arguments.verbose: print(username, end = "", flush = True)
-
- # Click "More Info"
- # Screenshot "More Info"
- # Power, Kill Points, Highest Power, Victory, Defeat, Dead, Scout Times, Resources Gathered, Resource Assistance, Alliance Help Times
- device.shell("input tap 620 1070")
- time.sleep(2)
- take_screenshot(device, folder + datetime + "_more")
-
- # Click "?"
- # Screenshot Kills
- # Kill Points, T1-5 Kills, T1-5 Points
- device.shell("input tap 1725 250")
- time.sleep(1)
- take_screenshot(device, folder + datetime + "_kills")
-
- # Exit
- if arguments.verbose: print("") # TODO: say "done" or something here if successful
- device.shell("input tap 2230 85")
- time.sleep(1)
- device.shell("input tap 2185 160")
- return
-
- # Tap to enter a profile, skipping ones that cannot be clicked
- def tap_profile(i, folder): # TODO: fix this absolute mess of a function
- if i == 1:
- if is_profile(TAP_LOCATIONS["1"], folder, i): return 1
- elif is_profile(TAP_LOCATIONS["2"], folder, i + 1): return 2
- elif is_profile(TAP_LOCATIONS["3"], folder, i + 2): return 3
- elif is_profile(TAP_LOCATIONS["4"], folder, i + 3): return 4
- else: sys.exit("Profile not found.")
- elif i == 2:
- if is_profile(TAP_LOCATIONS["2"], folder, i): return 1
- elif is_profile(TAP_LOCATIONS["3"], folder, i + 1): return 2
- elif is_profile(TAP_LOCATIONS["4"], folder, i + 2): return 3
- else: sys.exit("Profile not found.")
- elif i == 3:
- if is_profile(TAP_LOCATIONS["3"], folder, i): return 1
- elif is_profile(TAP_LOCATIONS["4"], folder, i + 1): return 2
- else: sys.exit("Profile not found.")
- elif i == 4:
- if is_profile(TAP_LOCATIONS["4"], folder, i): return 1
- else: sys.exit("Profile not found.")
- else:
- if is_profile(TAP_LOCATIONS["Middle"], folder, i): return 1
- elif is_profile(TAP_LOCATIONS["Middle + 1"], folder, i + 1): return 2
- elif is_profile(TAP_LOCATIONS["Middle + 2"], folder, i + 2): return 3
- else: sys.exit("Profile not found.")
-
- # Tap and test if you are in a profile
- def is_profile(coordinates, folder, i):
- device.shell("input tap " + str(coordinates[0]) + " " + str(coordinates[1]))
- time.sleep(1)
- if read_string_from_image(Image.open(io.BytesIO(device.screencap())).rotate(ROTATE, expand=1), 1652, 1167, 1745, 1209) == "Mail":
- return True
- else:
- device.shell("input tap " + str(coordinates[0]) + " " + str(coordinates[1]))
- time.sleep(2)
- if read_string_from_image(Image.open(io.BytesIO(device.screencap())).rotate(ROTATE, expand=1), 1652, 1167, 1745, 1209) == "Mail":
- return True
- if arguments.verbose: print("skipped rank " + str(i) + ". ", flush = True)
- take_screenshot(device, folder + "skipped/" + get_datetime_string() + "_skipped_" + str(i))
- return False
-
- # Read from the power rankings list
- def read_from_power_rankings(count, start, device, folder):
- if arguments.verbose: print("Reading", count, "power rankings.")
- time.sleep(1)
-
- i = start
- while i < (count + 1):
- if arguments.verbose: print(str(i) + "/" + str(count) + ": ", end = "", flush = True)
-
- # Scrape
- i = i + tap_profile(i, folder)
- read_single_power_ranking(device, folder)
-
- # Rest
- time.sleep(1)
- if i%10 == 0: time.sleep(5)
- if i%25 == 0: time.sleep(10)
- return
-
- def read_top_300_stats(start, skipped, device, folder):
- if arguments.verbose: print("Reading the top 300 power rankings.")
- if skipped > 0: print("Adding", skipped, "already skipped rankings.")
- time.sleep(1)
-
- count = 300 + 5 + skipped
- i = start
- while i < (count + 1):
- if arguments.verbose: print(str(i) + "/" + str(count) + ": ", end = "", flush = True)
-
- # Scrape
- tap_modifier = tap_profile(i, folder)
- i = i + tap_modifier
- count = count + tap_modifier - 1
- grab_profile_simple(device, folder)
-
- # Rest
- time.sleep(1)
- if i%10 == 0: time.sleep(5)
- if i%25 == 0: time.sleep(10)
- return
-
- def grab_profile_simple(device, folder):
- datetime = get_datetime_string()
-
- # Screenshot profile
- # ID, Username, Power, Kill Points
- time.sleep(2)
- take_screenshot(device, folder + datetime + "_profile")
-
- # Copy and save username
- device.shell("input tap 1055 455")
- time.sleep(1)
- username = get_clipboard(device)
- with open(projectFolder + "usernames.txt", 'a+', newline='', encoding='utf-8') as output_file:
- output_file.write(datetime + "\t")
- output_file.write(username)
- output_file.write("\n")
- if arguments.verbose: print(username, end = "", flush = True)
-
- # Exit
- if arguments.verbose: print("") # TODO: say "done" or something here if successful
- device.shell("input tap 2185 160")
- return
-
- # ----
- # Main
- # ----
-
- if __name__ == '__main__':
- # Connect to ADB device
- client = AdbClient(host="127.0.0.1", port=5037)
- device = client.device(arguments.serial)
- print("Connected to", device.serial)
-
- # Create project folder
- projectFolder = "output/" + arguments.project + "/"
- if not os.path.exists(projectFolder):
- os.makedirs(projectFolder + "skipped/")
-
- # Debug
- if arguments.debug: take_screenshot(device, projectFolder + get_datetime_string() + "_debug")
-
- # Scrape
- if arguments.power: read_from_power_rankings(arguments.count, arguments.start, device, projectFolder)
- if arguments.top: read_top_300_stats(arguments.start, arguments.skipped, device, projectFolder)
|