Scripts used to grab or read stats in RoK
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

rok-stat-grabber.py 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import argparse
  2. import re
  3. import io
  4. import os
  5. import sys
  6. import time
  7. from datetime import datetime
  8. from ppadb.client import Client as AdbClient
  9. from PIL import Image
  10. import pytesseract
  11. # ----
  12. # Program
  13. # ----
  14. PROGRAM_NAME = "RoK Scraper"
  15. PROGRAM_VERSION = "0.3"
  16. PROGRAM_DESCRIPTION = "This program controls devices via ADB to take screenshots in Rise of Kingdoms."
  17. # TODO:
  18. # - Skip your own ranking
  19. # - Correct for device rotation
  20. # - Better logic for tap_profile()
  21. # - Draw box around skipped profiles
  22. # - Support for the last ~995-1000
  23. # - Coordinates to percentages
  24. # ----
  25. # Arguments
  26. # ----
  27. parser = argparse.ArgumentParser(description = PROGRAM_DESCRIPTION)
  28. parser.add_argument("--power", help = "get full power rankings", default = False, action = "store_true")
  29. parser.add_argument("--top", help = "get stats on the top 300", default = False, action = "store_true")
  30. parser.add_argument("-p", "--project", help = "project name", required = True)
  31. parser.add_argument("-s", "--serial", help = "device serial", required = True)
  32. parser.add_argument("-v", "--verbose", help = "be verbose", default = False, action = "store_true")
  33. parser.add_argument("-c", "--count", help = "how many to scrape", default = 0, type = int)
  34. parser.add_argument("-n", "--start", help = "what number to start scraping at", default = 1, type = int)
  35. parser.add_argument("-k", "--skipped", help = "how many have been skipped", type = int)
  36. parser.add_argument("--debug", help = "debug", default = False, action = "store_true")
  37. arguments = parser.parse_args()
  38. # ----
  39. # Coordinates
  40. # ----
  41. ROTATE = 90 # TODO: fix this; shouldn't be needed
  42. TAP_LOCATIONS = {
  43. "1": (460, 460),
  44. "2": (460, 624),
  45. "3": (460, 780),
  46. "4": (460, 940),
  47. "Middle": (460, 980),
  48. "Middle + 1": (460, 1136),
  49. "Middle + 2": (460, 1300)
  50. }
  51. # ----
  52. # Functions
  53. # ----
  54. # TODO: pass device instead of image
  55. def read_string_from_image(image, x, y, x2, y2):
  56. return pytesseract.image_to_string(image.crop((x, y, x2, y2)), config="--psm 6").strip().replace('\n', ' ').replace('\r', '').replace('\t', ' ')
  57. def get_clipboard(device):
  58. raw = device.shell('am broadcast -n "ch.pete.adbclipboard/.ReadReceiver"')
  59. dataMatcher = re.compile("^.*\n.*data=\"(.*)\"$", re.DOTALL)
  60. dataMatch = dataMatcher.match(raw)
  61. return dataMatch.group(1)
  62. def take_screenshot(device, path):
  63. screencap = device.screencap()
  64. image = Image.open(io.BytesIO(screencap)).rotate(ROTATE, expand=1)
  65. image.save(path + ".png", "PNG")
  66. return
  67. # TODO: this can be done better
  68. def get_datetime_string():
  69. d = datetime.now()
  70. 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)
  71. def read_single_power_ranking(device, folder):
  72. datetime = get_datetime_string()
  73. # Screenshot profile
  74. # ID, Username, Power, Kill Points
  75. time.sleep(1)
  76. take_screenshot(device, folder + datetime + "_profile")
  77. # Copy and save username
  78. device.shell("input tap 1055 455")
  79. time.sleep(1)
  80. username = get_clipboard(device)
  81. with open(projectFolder + "usernames.txt", 'a+', newline='', encoding='utf-8') as output_file:
  82. output_file.write(datetime + "\t")
  83. output_file.write(username)
  84. output_file.write("\n")
  85. if arguments.verbose: print(username, end = "", flush = True)
  86. # Click "More Info"
  87. # Screenshot "More Info"
  88. # Power, Kill Points, Highest Power, Victory, Defeat, Dead, Scout Times, Resources Gathered, Resource Assistance, Alliance Help Times
  89. device.shell("input tap 620 1070")
  90. time.sleep(2)
  91. take_screenshot(device, folder + datetime + "_more")
  92. # Click "?"
  93. # Screenshot Kills
  94. # Kill Points, T1-5 Kills, T1-5 Points
  95. device.shell("input tap 1725 250")
  96. time.sleep(1)
  97. take_screenshot(device, folder + datetime + "_kills")
  98. # Exit
  99. if arguments.verbose: print("") # TODO: say "done" or something here if successful
  100. device.shell("input tap 2230 85")
  101. time.sleep(1)
  102. device.shell("input tap 2185 160")
  103. return
  104. # Tap to enter a profile, skipping ones that cannot be clicked
  105. def tap_profile(i, folder): # TODO: fix this absolute mess of a function
  106. if i == 1:
  107. if is_profile(TAP_LOCATIONS["1"], folder, i): return 1
  108. elif is_profile(TAP_LOCATIONS["2"], folder, i + 1): return 2
  109. elif is_profile(TAP_LOCATIONS["3"], folder, i + 2): return 3
  110. elif is_profile(TAP_LOCATIONS["4"], folder, i + 3): return 4
  111. else: sys.exit("Profile not found.")
  112. elif i == 2:
  113. if is_profile(TAP_LOCATIONS["2"], folder, i): return 1
  114. elif is_profile(TAP_LOCATIONS["3"], folder, i + 1): return 2
  115. elif is_profile(TAP_LOCATIONS["4"], folder, i + 2): return 3
  116. else: sys.exit("Profile not found.")
  117. elif i == 3:
  118. if is_profile(TAP_LOCATIONS["3"], folder, i): return 1
  119. elif is_profile(TAP_LOCATIONS["4"], folder, i + 1): return 2
  120. else: sys.exit("Profile not found.")
  121. elif i == 4:
  122. if is_profile(TAP_LOCATIONS["4"], folder, i): return 1
  123. else: sys.exit("Profile not found.")
  124. else:
  125. if is_profile(TAP_LOCATIONS["Middle"], folder, i): return 1
  126. elif is_profile(TAP_LOCATIONS["Middle + 1"], folder, i + 1): return 2
  127. elif is_profile(TAP_LOCATIONS["Middle + 2"], folder, i + 2): return 3
  128. else: sys.exit("Profile not found.")
  129. # Tap and test if you are in a profile
  130. def is_profile(coordinates, folder, i):
  131. device.shell("input tap " + str(coordinates[0]) + " " + str(coordinates[1]))
  132. time.sleep(1)
  133. if read_string_from_image(Image.open(io.BytesIO(device.screencap())).rotate(ROTATE, expand=1), 1652, 1167, 1745, 1209) == "Mail":
  134. return True
  135. else:
  136. device.shell("input tap " + str(coordinates[0]) + " " + str(coordinates[1]))
  137. time.sleep(2)
  138. if read_string_from_image(Image.open(io.BytesIO(device.screencap())).rotate(ROTATE, expand=1), 1652, 1167, 1745, 1209) == "Mail":
  139. return True
  140. if arguments.verbose: print("skipped rank " + str(i) + ". ", flush = True)
  141. take_screenshot(device, folder + "skipped/" + get_datetime_string() + "_skipped_" + str(i))
  142. return False
  143. # Read from the power rankings list
  144. def read_from_power_rankings(count, start, device, folder):
  145. if arguments.verbose: print("Reading", count, "power rankings.")
  146. time.sleep(1)
  147. i = start
  148. while i < (count + 1):
  149. if arguments.verbose: print(str(i) + "/" + str(count) + ": ", end = "", flush = True)
  150. # Scrape
  151. i = i + tap_profile(i, folder)
  152. read_single_power_ranking(device, folder)
  153. # Rest
  154. time.sleep(1)
  155. if i%10 == 0: time.sleep(5)
  156. if i%25 == 0: time.sleep(10)
  157. return
  158. def read_top_300_stats(start, skipped, device, folder):
  159. if arguments.verbose: print("Reading the top 300 power rankings.")
  160. if skipped > 0: print("Adding", skipped, "already skipped rankings.")
  161. time.sleep(1)
  162. count = 300 + 5 + skipped
  163. i = start
  164. while i < (count + 1):
  165. if arguments.verbose: print(str(i) + "/" + str(count) + ": ", end = "", flush = True)
  166. # Scrape
  167. tap_modifier = tap_profile(i, folder)
  168. i = i + tap_modifier
  169. count = count + tap_modifier - 1
  170. grab_profile_simple(device, folder)
  171. # Rest
  172. time.sleep(1)
  173. if i%10 == 0: time.sleep(5)
  174. if i%25 == 0: time.sleep(10)
  175. return
  176. def grab_profile_simple(device, folder):
  177. datetime = get_datetime_string()
  178. # Screenshot profile
  179. # ID, Username, Power, Kill Points
  180. time.sleep(2)
  181. take_screenshot(device, folder + datetime + "_profile")
  182. # Copy and save username
  183. device.shell("input tap 1055 455")
  184. time.sleep(1)
  185. username = get_clipboard(device)
  186. with open(projectFolder + "usernames.txt", 'a+', newline='', encoding='utf-8') as output_file:
  187. output_file.write(datetime + "\t")
  188. output_file.write(username)
  189. output_file.write("\n")
  190. if arguments.verbose: print(username, end = "", flush = True)
  191. # Exit
  192. if arguments.verbose: print("") # TODO: say "done" or something here if successful
  193. device.shell("input tap 2185 160")
  194. return
  195. # ----
  196. # Main
  197. # ----
  198. if __name__ == '__main__':
  199. # Connect to ADB device
  200. client = AdbClient(host="127.0.0.1", port=5037)
  201. device = client.device(arguments.serial)
  202. print("Connected to", device.serial)
  203. # Create project folder
  204. projectFolder = "output/" + arguments.project + "/"
  205. if not os.path.exists(projectFolder):
  206. os.makedirs(projectFolder + "skipped/")
  207. # Debug
  208. if arguments.debug: take_screenshot(device, projectFolder + get_datetime_string() + "_debug")
  209. # Scrape
  210. if arguments.power: read_from_power_rankings(arguments.count, arguments.start, device, projectFolder)
  211. if arguments.top: read_top_300_stats(arguments.start, arguments.skipped, device, projectFolder)