Advertisement
Guest User

Untitled

a guest
Aug 7th, 2016
87
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.43 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3.  
  4. '''
  5. Search Architecture:
  6. - Have a list of accounts
  7. - Create an "overseer" thread
  8. - Search Overseer:
  9. - Tracks incoming new location values
  10. - Tracks "paused state"
  11. - During pause or new location will clears current search queue
  12. - Starts search_worker threads
  13. - Search Worker Threads each:
  14. - Have a unique API login
  15. - Listens to the same Queue for areas to scan
  16. - Can re-login as needed
  17. - Shares a global lock for map parsing
  18. '''
  19.  
  20. import logging
  21. import time
  22. import math
  23. import threading
  24.  
  25. from threading import Thread, Lock
  26. from queue import Queue, Empty
  27.  
  28. from pgoapi import PGoApi
  29. from pgoapi.utilities import f2i
  30. from pgoapi import utilities as util
  31. from pgoapi.exceptions import AuthException
  32.  
  33. from . import config
  34. from .models import parse_map
  35.  
  36. log = logging.getLogger(__name__)
  37.  
  38. TIMESTAMP = '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
  39.  
  40. def get_new_coords(init_loc, distance, bearing):
  41. """ Given an initial lat/lng, a distance(in kms), and a bearing (degrees),
  42. this will calculate the resulting lat/lng coordinates.
  43. """
  44. R = 6378.1 #km radius of the earth
  45. bearing = math.radians(bearing)
  46.  
  47. init_coords = [math.radians(init_loc[0]), math.radians(init_loc[1])] # convert lat/lng to radians
  48.  
  49. new_lat = math.asin( math.sin(init_coords[0])*math.cos(distance/R) +
  50. math.cos(init_coords[0])*math.sin(distance/R)*math.cos(bearing))
  51.  
  52. new_lon = init_coords[1] + math.atan2(math.sin(bearing)*math.sin(distance/R)*math.cos(init_coords[0]),
  53. math.cos(distance/R)-math.sin(init_coords[0])*math.sin(new_lat))
  54.  
  55. return [math.degrees(new_lat), math.degrees(new_lon)]
  56.  
  57. def generate_location_steps(initial_loc, step_count):
  58. #Bearing (degrees)
  59. NORTH = 0
  60. EAST = 90
  61. SOUTH = 180
  62. WEST = 270
  63.  
  64. pulse_radius = 0.07 # km - radius of players heartbeat is 70m
  65. xdist = math.sqrt(3)*pulse_radius # dist between column centers
  66. ydist = 3*(pulse_radius/2) # dist between row centers
  67.  
  68. yield (initial_loc[0], initial_loc[1], 0) #insert initial location
  69.  
  70. ring = 1
  71. loc = initial_loc
  72. while ring < step_count:
  73. #Set loc to start at top left
  74. loc = get_new_coords(loc, ydist, NORTH)
  75. loc = get_new_coords(loc, xdist/2, WEST)
  76. for direction in range(6):
  77. for i in range(ring):
  78. if direction == 0: # RIGHT
  79. loc = get_new_coords(loc, xdist, EAST)
  80. if direction == 1: # DOWN + RIGHT
  81. loc = get_new_coords(loc, ydist, SOUTH)
  82. loc = get_new_coords(loc, xdist/2, EAST)
  83. if direction == 2: # DOWN + LEFT
  84. loc = get_new_coords(loc, ydist, SOUTH)
  85. loc = get_new_coords(loc, xdist/2, WEST)
  86. if direction == 3: # LEFT
  87. loc = get_new_coords(loc, xdist, WEST)
  88. if direction == 4: # UP + LEFT
  89. loc = get_new_coords(loc, ydist, NORTH)
  90. loc = get_new_coords(loc, xdist/2, WEST)
  91. if direction == 5: # UP + RIGHT
  92. loc = get_new_coords(loc, ydist, NORTH)
  93. loc = get_new_coords(loc, xdist/2, EAST)
  94. yield (loc[0], loc[1], 0)
  95. ring += 1
  96.  
  97.  
  98. #
  99. # A fake search loop which does....nothing!
  100. #
  101. def fake_search_loop():
  102. while True:
  103. log.info('Fake search loop running')
  104. time.sleep(10)
  105.  
  106.  
  107. # The main search loop that keeps an eye on the over all process
  108. def search_overseer_thread(args, new_location_queue, pause_bit):
  109.  
  110. log.info('Search overseer starting')
  111.  
  112. search_items_queue = Queue()
  113. parse_lock = Lock()
  114.  
  115. # Create a search_worker_thread per account
  116. log.info('Starting search worker threads')
  117. for i, account in enumerate(args.accounts):
  118. log.debug('Starting search worker thread %d for user %s', i, account['username'])
  119. t = Thread(target=search_worker_thread,
  120. name='search_worker_{}'.format(i),
  121. args=(args, account, search_items_queue, parse_lock))
  122. t.daemon = True
  123. t.start()
  124.  
  125. # A place to track the current location
  126. current_location = False;
  127.  
  128. # The real work starts here but will halt on pause_bit.set()
  129. while True:
  130.  
  131. # paused; clear queue if needed, otherwise sleep and loop
  132. if pause_bit.is_set():
  133. if not search_items_queue.empty():
  134. try:
  135. while True:
  136. search_items_queue.get_nowait()
  137. except Empty:
  138. pass
  139. time.sleep(1)
  140. continue
  141.  
  142. # If a new location has been passed to us, get the most recent one
  143. if not new_location_queue.empty():
  144. log.info('New location caught, moving search grid')
  145. try:
  146. while True:
  147. current_location = new_location_queue.get_nowait()
  148. except Empty:
  149. pass
  150.  
  151. # We (may) need to clear the search_items_queue
  152. if not search_items_queue.empty():
  153. try:
  154. while True:
  155. search_items_queue.get_nowait()
  156. except Empty:
  157. pass
  158.  
  159. # If there are no search_items_queue either the loop has finished (or been
  160. # cleared above) -- either way, time to fill it back up
  161. if search_items_queue.empty():
  162. log.debug('Search queue empty, restarting loop')
  163. for step, step_location in enumerate(generate_location_steps(current_location, args.step_limit), 1):
  164. log.debug('Queueing step %d @ %f/%f/%f', step, step_location[0], step_location[1], step_location[2])
  165. search_args = (step, step_location)
  166. search_items_queue.put(search_args)
  167. # else:
  168. # log.info('Search queue processing, %d items left', search_items_queue.qsize())
  169.  
  170. # Now we just give a little pause here
  171. time.sleep(1)
  172.  
  173.  
  174. def search_worker_thread(args, account, search_items_queue, parse_lock):
  175.  
  176. log.debug('Search worker thread starting')
  177.  
  178. # The forever loop for the thread
  179. while True:
  180. try:
  181. log.debug('Entering search loop')
  182.  
  183. # Create the API instance this will use
  184. api = PGoApi()
  185.  
  186. # The forever loop for the searches
  187. while True:
  188.  
  189. # Grab the next thing to search (when available)
  190. step, step_location = search_items_queue.get()
  191.  
  192. log.info('Search step %d beginning (queue size is %d)', step, search_items_queue.qsize())
  193.  
  194. # Let the api know where we intend to be for this loop
  195. api.set_position(*step_location)
  196.  
  197. # The loop to try very hard to scan this step
  198. failed_total = 0
  199. while True:
  200.  
  201. # After so many attempts, let's get out of here
  202. if failed_total >= args.scan_retries:
  203. # I am choosing to NOT place this item back in the queue
  204. # otherwise we could get a "bad scan" area and be stuck
  205. # on this overall loop forever. Better to lose one cell
  206. # than have the scanner, essentially, halt.
  207. log.error('Search step %d went over max scan_retires; abandoning', step)
  208. break
  209.  
  210. # Increase sleep delay between each failed scan
  211. # By default scan_dela=5, scan_retries=5 so
  212. # We'd see timeouts of 5, 10, 15, 20, 25
  213. sleep_time = args.scan_delay * (1+failed_total)
  214.  
  215. # Ok, let's get started -- check our login status
  216. check_login(args, account, api, step_location)
  217.  
  218. # Make the actual request (finally!)
  219. response_dict = map_request(api, step_location)
  220.  
  221. # G'damnit, nothing back. Mark it up, sleep, carry on
  222. if not response_dict:
  223. log.error('Search step %d area download failed, retyring request in %g seconds', step, sleep_time)
  224. failed_total += 1
  225. time.sleep(sleep_time)
  226. continue
  227.  
  228. # Got the response, lock for parsing and do so (or fail, whatever)
  229. with parse_lock:
  230. try:
  231. parsed = parse_map(response_dict, step_location)
  232. log.debug('Search step %s completed', step)
  233. search_items_queue.task_done()
  234. break # All done, get out of the request-retry loop
  235. except KeyError:
  236. log.error('Search step %s map parsing failed, retyring request in %g seconds', step, sleep_time)
  237. failed_total += 1
  238. time.sleep(sleep_time)
  239.  
  240. time.sleep(args.scan_delay)
  241.  
  242. # catch any process exceptions, log them, and continue the thread
  243. except Exception as e:
  244. log.exception('Exception in search_worker: %s', e)
  245.  
  246.  
  247. def check_login(args, account, api, position):
  248.  
  249. # Logged in? Enough time left? Cool!
  250. if api._auth_provider and api._auth_provider._ticket_expire:
  251. remaining_time = api._auth_provider._ticket_expire/1000 - time.time()
  252. if remaining_time > 60:
  253. log.debug('Credentials remain valid for another %f seconds', remaining_time)
  254. return
  255.  
  256. # Try to login (a few times, but don't get stuck here)
  257. i = 0
  258. # while not api.login(account['auth_service'], account['username'], account['password'], position[0], position[1], position[2], False):
  259. # if i >= args.login_retries:
  260. # raise TooManyLoginAttempts('Exceeded login attempts')
  261. # else:
  262. # i += 1
  263. # log.error('Failed to login to Pokemon Go. Trying again in %g seconds', args.login_delay)
  264. # time.sleep(args.login_delay)
  265.  
  266. while i < args.login_retries:
  267. try:
  268. api.set_authentication(provider = account['auth_service'], username = account['username'], password = account['password'])
  269. break
  270. except AuthException:
  271. if i >= args.login_retries:
  272. raise TooManyLoginAttempts('Exceeded login attempts')
  273. else:
  274. i += 1
  275. log.error('Failed to login to Pokemon Go with account %s. Trying again in %g seconds', account['username'], args.login_delay)
  276. time.sleep(args.login_delay)
  277.  
  278. api.activate_signature("encrypt.dll")
  279. log.debug('Login for account %s successful', account['username'])
  280.  
  281. def map_request(api, position):
  282. try:
  283. cell_ids = util.get_cell_ids(position[0], position[1])
  284. timestamps = [0,] * len(cell_ids)
  285. return api.get_map_objects(latitude=f2i(position[0]),
  286. longitude=f2i(position[1]),
  287. since_timestamp_ms=timestamps,
  288. cell_id=cell_ids)
  289. except Exception as e:
  290. log.warning('Exception while downloading map: %s', e)
  291. return False
  292.  
  293. class TooManyLoginAttempts(Exception):
  294. pass
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement