Advertisement
Guest User

Untitled

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