Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,30 @@ Features:
* DB storage (sqlite or mysql) of all found pokemon
* Incredibly fast, efficient searching algorithm (compared to everything else available)

[![Deploy](https://raw.githubusercontent.com/sych74/PokemonGo-Map-in-Cloud/master/images/deploy-to-jelastic.png)](https://jelastic.com/install-application/?manifest=https://raw.githubusercontent.com/sych74/PokemonGo-Map-in-Cloud/master/manifest.jps) [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://github.com/AHAAAAAAA/PokemonGo-Map/wiki/Heroku-Deployment) [![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/AHAAAAAAA/PokemonGo-Map)
[![Deploy](https://raw.githubusercontent.com/sych74/PokemonGo-Map-in-Cloud/master/images/deploy-to-jelastic.png)](https://jelastic.com/install-application/?manifest=https://raw.githubusercontent.com/sych74/PokemonGo-Map-in-Cloud/master/manifest.jps) [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://github.com/scottstamp/PokemonGo-Map/wiki/Heroku-Deployment) [![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/scottstamp/PokemonGo-Map)


#[Twitter] (https://twitter.com/PokemapDev), [Website] (https://jz6.github.io/PoGoMap/)#

![Map](https://raw.githubusercontent.com/AHAAAAAAA/PokemonGo-Map/master/static/cover.png)
![Map](https://raw.githubusercontent.com/scottstamp/PokemonGo-Map/master/static/cover.png)


## How to setup

For instructions on how to setup and run the tool, please refer to the project [wiki](https://github.com/AHAAAAAAA/PokemonGo-Map/wiki), or the [video guide](https://www.youtube.com/watch?v=RJKAulPCkRI).
For instructions on how to setup and run the tool, please refer to the project [wiki](https://github.com/scottstamp/PokemonGo-Map/wiki), or the [video guide](https://www.youtube.com/watch?v=RJKAulPCkRI).


## Windows install
At a minimum, download and install the following:
git (https://git-scm.com/)
VC for Python 2.7 (https://www.microsoft.com/en-us/download/details.aspx?id=44266)
node.js (https://nodejs.org/en/download/)

After those are installed, open a commend prompt from the PoGoMap directory and do:
npm install

Also, need to fix search.py to point to encrypt.dll (but even that still doesn't work...)

## Android Version

There is an [Android port](https://github.com/omkarmoghe/Pokemap) in the works. All Android related prs and issues please refer to this [repo](https://github.com/omkarmoghe/Pokemap).
Expand All @@ -43,6 +54,6 @@ Using this software is against the ToS of the game. You can get banned, use this

## Contributions

Please submit all pull requests to [develop](https://github.com/AHAAAAAAA/PokemonGo-Map/tree/develop) branch.
Please submit all pull requests to [develop](https://github.com/scottstamp/PokemonGo-Map/tree/develop) branch.

Building off [tejado's python pgoapi](https://github.com/tejado/pgoapi), [Mila432](https://github.com/Mila432/Pokemon_Go_API)'s API, [leegao's additions](https://github.com/leegao/pokemongo-api-demo/tree/simulation) and [Flask-GoogleMaps](https://github.com/rochacbruno/Flask-GoogleMaps). Current version relies primarily on the pgoapi and Google Maps JS API.
7 changes: 5 additions & 2 deletions pogom/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import logging
import time
import math
import platform
import threading

from threading import Thread, Lock
Expand Down Expand Up @@ -267,8 +268,10 @@ def check_login(args, account, api, position):
i += 1
log.error('Failed to login to Pokemon Go with account %s. Trying again in %g seconds', account['username'], args.login_delay)
time.sleep(args.login_delay)

api.activate_signature("/root/PoGoMap/pogom/libencrypt.so")
if platform.system() == 'Windows':
api.activate_signature(".\pogom\encrypt.dll")
else:
api.activate_signature(".\pogom\libencrypt.so")
log.debug('Login for account %s successful', account['username'])

def map_request(api, position):
Expand Down
290 changes: 290 additions & 0 deletions search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

'''
Search Architecture:
- Have a list of accounts
- Create an "overseer" thread
- Search Overseer:
- Tracks incoming new location values
- Tracks "paused state"
- During pause or new location will clears current search queue
- Starts search_worker threads
- Search Worker Threads each:
- Have a unique API login
- Listens to the same Queue for areas to scan
- Can re-login as needed
- Shares a global lock for map parsing
'''

import logging
import time
import math
import platform
import threading

from threading import Thread, Lock
from queue import Queue, Empty

from pgoapi import PGoApi
from pgoapi.utilities import f2i
from pgoapi import utilities as util
from pgoapi.exceptions import AuthException

from . import config
from .models import parse_map

log = logging.getLogger(__name__)

TIMESTAMP = '\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000'

def get_new_coords(init_loc, distance, bearing):
""" Given an initial lat/lng, a distance(in kms), and a bearing (degrees),
this will calculate the resulting lat/lng coordinates.
"""
R = 6378.1 #km radius of the earth
bearing = math.radians(bearing)

init_coords = [math.radians(init_loc[0]), math.radians(init_loc[1])] # convert lat/lng to radians

new_lat = math.asin( math.sin(init_coords[0])*math.cos(distance/R) +
math.cos(init_coords[0])*math.sin(distance/R)*math.cos(bearing))

new_lon = init_coords[1] + math.atan2(math.sin(bearing)*math.sin(distance/R)*math.cos(init_coords[0]),
math.cos(distance/R)-math.sin(init_coords[0])*math.sin(new_lat))

return [math.degrees(new_lat), math.degrees(new_lon)]

def generate_location_steps(initial_loc, step_count):
#Bearing (degrees)
NORTH = 0
EAST = 90
SOUTH = 180
WEST = 270

pulse_radius = 0.07 # km - radius of players heartbeat is 70m
xdist = math.sqrt(3)*pulse_radius # dist between column centers
ydist = 3*(pulse_radius/2) # dist between row centers

yield (initial_loc[0], initial_loc[1], 0) #insert initial location

ring = 1
loc = initial_loc
while ring < step_count:
#Set loc to start at top left
loc = get_new_coords(loc, ydist, NORTH)
loc = get_new_coords(loc, xdist/2, WEST)
for direction in range(6):
for i in range(ring):
if direction == 0: # RIGHT
loc = get_new_coords(loc, xdist, EAST)
if direction == 1: # DOWN + RIGHT
loc = get_new_coords(loc, ydist, SOUTH)
loc = get_new_coords(loc, xdist/2, EAST)
if direction == 2: # DOWN + LEFT
loc = get_new_coords(loc, ydist, SOUTH)
loc = get_new_coords(loc, xdist/2, WEST)
if direction == 3: # LEFT
loc = get_new_coords(loc, xdist, WEST)
if direction == 4: # UP + LEFT
loc = get_new_coords(loc, ydist, NORTH)
loc = get_new_coords(loc, xdist/2, WEST)
if direction == 5: # UP + RIGHT
loc = get_new_coords(loc, ydist, NORTH)
loc = get_new_coords(loc, xdist/2, EAST)
yield (loc[0], loc[1], 0)
ring += 1


#
# A fake search loop which does....nothing!
#
def fake_search_loop():
while True:
log.info('Fake search loop running')
time.sleep(10)


# The main search loop that keeps an eye on the over all process
def search_overseer_thread(args, new_location_queue, pause_bit):

log.info('Search overseer starting')

search_items_queue = Queue()
parse_lock = Lock()

# Create a search_worker_thread per account
log.info('Starting search worker threads')
for i, account in enumerate(args.accounts):
log.debug('Starting search worker thread %d for user %s', i, account['username'])
t = Thread(target=search_worker_thread,
name='search_worker_{}'.format(i),
args=(args, account, search_items_queue, parse_lock))
t.daemon = True
t.start()

# A place to track the current location
current_location = False;

# The real work starts here but will halt on pause_bit.set()
while True:

# paused; clear queue if needed, otherwise sleep and loop
if pause_bit.is_set():
if not search_items_queue.empty():
try:
while True:
search_items_queue.get_nowait()
except Empty:
pass
time.sleep(1)
continue

# If a new location has been passed to us, get the most recent one
if not new_location_queue.empty():
log.info('New location caught, moving search grid')
try:
while True:
current_location = new_location_queue.get_nowait()
except Empty:
pass

# We (may) need to clear the search_items_queue
if not search_items_queue.empty():
try:
while True:
search_items_queue.get_nowait()
except Empty:
pass

# If there are no search_items_queue either the loop has finished (or been
# cleared above) -- either way, time to fill it back up
if search_items_queue.empty():
log.debug('Search queue empty, restarting loop')
for step, step_location in enumerate(generate_location_steps(current_location, args.step_limit), 1):
log.debug('Queueing step %d @ %f/%f/%f', step, step_location[0], step_location[1], step_location[2])
search_args = (step, step_location)
search_items_queue.put(search_args)
# else:
# log.info('Search queue processing, %d items left', search_items_queue.qsize())

# Now we just give a little pause here
time.sleep(1)


def search_worker_thread(args, account, search_items_queue, parse_lock):

log.debug('Search worker thread starting')

# The forever loop for the thread
while True:
try:
log.debug('Entering search loop')

# Create the API instance this will use
api = PGoApi()

# The forever loop for the searches
while True:

# Grab the next thing to search (when available)
step, step_location = search_items_queue.get()

log.info('Search step %d beginning (queue size is %d)', step, search_items_queue.qsize())

# Let the api know where we intend to be for this loop
api.set_position(*step_location)

# The loop to try very hard to scan this step
failed_total = 0
while True:

# After so many attempts, let's get out of here
if failed_total >= args.scan_retries:
# I am choosing to NOT place this item back in the queue
# otherwise we could get a "bad scan" area and be stuck
# on this overall loop forever. Better to lose one cell
# than have the scanner, essentially, halt.
log.error('Search step %d went over max scan_retires; abandoning', step)
break

# Increase sleep delay between each failed scan
# By default scan_dela=5, scan_retries=5 so
# We'd see timeouts of 5, 10, 15, 20, 25
sleep_time = args.scan_delay * (1+failed_total)

# Ok, let's get started -- check our login status
check_login(args, account, api, step_location)

# Make the actual request (finally!)
response_dict = map_request(api, step_location)

# G'damnit, nothing back. Mark it up, sleep, carry on
if not response_dict:
log.error('Search step %d area download failed, retyring request in %g seconds', step, sleep_time)
failed_total += 1
time.sleep(sleep_time)
continue

# Got the response, lock for parsing and do so (or fail, whatever)
with parse_lock:
try:
parsed = parse_map(response_dict, step_location)
log.debug('Search step %s completed', step)
search_items_queue.task_done()
break # All done, get out of the request-retry loop
except KeyError:
log.error('Search step %s map parsing failed, retyring request in %g seconds', step, sleep_time)
failed_total += 1
time.sleep(sleep_time)

time.sleep(args.scan_delay)

# catch any process exceptions, log them, and continue the thread
except Exception as e:
log.exception('Exception in search_worker: %s', e)


def check_login(args, account, api, position):

# Logged in? Enough time left? Cool!
if api._auth_provider and api._auth_provider._ticket_expire:
remaining_time = api._auth_provider._ticket_expire/1000 - time.time()
if remaining_time > 60:
log.debug('Credentials remain valid for another %f seconds', remaining_time)
return

# Try to login (a few times, but don't get stuck here)
i = 0
api.set_position(position[0], position[1], position[2])
while i < args.login_retries:
try:
api.set_authentication(provider = account['auth_service'], username = account['username'], password = account['password'])
break
except AuthException:
if i >= args.login_retries:
raise TooManyLoginAttempts('Exceeded login attempts')
else:
i += 1
log.error('Failed to login to Pokemon Go with account %s. Trying again in %g seconds', account['username'], args.login_delay)
time.sleep(args.login_delay)
if platform.system() == 'Windows':
api.activate_signature(".\pogom\encrypt.dll")
else:
api.activate_signature(".\pogom\libencrypt.so")
log.debug('Login for account %s successful', account['username'])

def map_request(api, position):
try:
cell_ids = util.get_cell_ids(position[0], position[1])
timestamps = [0,] * len(cell_ids)
return api.get_map_objects(latitude=f2i(position[0]),
longitude=f2i(position[1]),
since_timestamp_ms=timestamps,
cell_id=cell_ids)
except Exception as e:
log.warning('Exception while downloading map: %s', e)
return False

class TooManyLoginAttempts(Exception):
pass