PageRenderTime 70ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/contrib/fake-pgo-api.py

https://gitlab.com/HerrTapper/PokemonGoMap
Python | 189 lines | 178 code | 1 blank | 10 comment | 0 complexity | fb8cc8369049d0e596b4129e5da9f2dd MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. '''
  4. Fake PokemonGo API
  5. This is a simplistic flask app to emulate what a pokemon go api returns.
  6. It *does not* speak protobuff, and uses a hacky internal re-routing. That said,
  7. it does the trick well enough in my testing.
  8. When first "logged into" it will create a static map of evenly distributed
  9. gyms and poke stops. As each "scan" is run, it will gerenate a set of pokemon
  10. for that scan area. "New" pokemon will be found every 10 minutes.
  11. You can run this as is, and then just add `-m http://127.0.0.1:9090` to your
  12. runserver.py call to start using it.
  13. '''
  14. import logging
  15. import configargparse
  16. import math
  17. from flask import Flask, jsonify
  18. from random import randint, getrandbits, seed, random
  19. from uuid import uuid4
  20. from time import time
  21. from s2sphere import CellId, LatLng
  22. import geopy
  23. from geopy.distance import VincentyDistance
  24. from geopy.distance import vincenty
  25. logging.basicConfig(format=(
  26. '%(asctime)s [%(threadName)16s][%(module)14s][%(levelname)8s] ' +
  27. '%(message)s'))
  28. log = logging.getLogger()
  29. # Configish
  30. parser = configargparse.ArgParser()
  31. parser.add_argument('-H', '--host', help='Server Host', default='127.0.0.1')
  32. parser.add_argument('-p', '--port', help='Server Port', default=9090, type=int)
  33. parser.add_argument('-d', '--debug', help='Debug Mode', action='store_true')
  34. parser.set_defaults(DEBUG=False)
  35. args = parser.parse_args()
  36. if args.debug:
  37. log.setLevel(logging.DEBUG)
  38. else:
  39. log.setLevel(logging.INFO)
  40. # A holder of gyms/pokestops
  41. forts = []
  42. def getRandomPoint(location=None, maxMeters=70):
  43. origin = geopy.Point(location[0], location[1])
  44. b = randint(0, 360)
  45. d = math.sqrt(random()) * (float(maxMeters) / 1000)
  46. destination = VincentyDistance(kilometers=d).destination(origin, b)
  47. return (destination.latitude, destination.longitude)
  48. # TODO: This method is suuuuper inefficient.
  49. # Namely, it loops over EVERY POSSIBLE fort and does a distance calc
  50. # Not a time-cheap operation to do at all. It would be way more efficient
  51. # if we had all the forts setup with s2 locations and could more quickly
  52. # query for 'within XYZ cells'. Or something like that :/
  53. def getForts(location):
  54. global forts
  55. lforts = []
  56. for i in forts:
  57. f = (i['latitude'], i['longitude'])
  58. d = vincenty(location, f).meters
  59. if d < 900:
  60. lforts.append(i)
  61. return lforts
  62. def makeWildPokemon(location):
  63. # Cause the randomness to only shift every N minutes (thus new pokes
  64. # every N minutes)
  65. offset = int(time() % 3600) / 10
  66. seedid = str(location[0]) + str(location[1]) + str(offset)
  67. seed(seedid)
  68. # Now, collect the pokes for this can point
  69. pokes = []
  70. for i in range(randint(0, 2)):
  71. coords = getRandomPoint(location)
  72. ll = LatLng.from_degrees(coords[0], coords[1])
  73. cellId = CellId.from_lat_lng(ll).parent(20).to_token()
  74. pokes.append({
  75. 'encounter_id': 'pkm' + seedid + str(i),
  76. 'last_modified_timestamp_ms': int((time() - 10) * 1000),
  77. 'latitude': coords[0],
  78. 'longitude': coords[1],
  79. 'pokemon_data': {'pokemon_id': randint(1, 140)},
  80. 'spawn_point_id': cellId,
  81. 'time_till_hidden_ms': randint(60, 600) * 1000
  82. })
  83. return pokes
  84. # Fancy app time
  85. app = Flask(__name__)
  86. @app.route('/')
  87. def api_root():
  88. return 'This here be a Fake PokemonGo API Endpoint Server'
  89. @app.route('/login/<lat>/<lng>/<r>')
  90. def api_login(lat, lng, r):
  91. global forts
  92. if len(forts):
  93. # already generated
  94. return jsonify(forts)
  95. # coerce types
  96. r = int(r) # radius in meters
  97. lat = float(lat)
  98. lng = float(lng)
  99. forts = []
  100. area = 3.14 * (r * r)
  101. # One gym every N sq.m
  102. gymCount = int(math.ceil(area / 25000))
  103. # One pks every N sq.m
  104. pksCount = int(math.ceil(area / 15000))
  105. # Gyms
  106. for i in range(gymCount):
  107. coords = getRandomPoint(location=(lat, lng), maxMeters=r)
  108. forts.append({
  109. 'enabled': True,
  110. 'guard_pokemon_id': randint(1, 140),
  111. 'gym_points': randint(1, 30000),
  112. 'id': 'gym-{}'.format(i),
  113. 'is_in_battle': not getrandbits(1),
  114. 'last_modified_timestamp_ms': int((time() - 10) * 1000),
  115. 'latitude': coords[0],
  116. 'longitude': coords[1],
  117. 'owned_by_team': randint(0, 3)
  118. })
  119. # Pokestops
  120. for i in range(pksCount):
  121. coords = getRandomPoint(location=(lat, lng), maxMeters=r)
  122. forts.append({
  123. 'enabled': True,
  124. 'id': 'pks-{}'.format(i),
  125. 'last_modified_timestamp_ms': int((time() - 10) * 1000),
  126. 'latitude': coords[0],
  127. 'longitude': coords[1],
  128. 'type': 1
  129. })
  130. log.info('Login for location %f,%f generated %d gyms, %d pokestop', lat,
  131. lng, gymCount, pksCount)
  132. return jsonify(forts)
  133. @app.route('/scan/<lat>/<lng>')
  134. def api_scan(lat, lng):
  135. location = (float(lat), float(lng))
  136. cells = []
  137. # for i in range(randint(60,70)):
  138. for i in range(3):
  139. cells.append({
  140. 'current_timestamp_ms': int(time() * 1000),
  141. 'forts': getForts(location),
  142. 's2_cell_id': uuid4(), # wrong, but also unused so it
  143. # doesn't matter
  144. 'wild_pokemons': makeWildPokemon(location),
  145. 'catchable_pokemons': [], # unused
  146. 'nearby_pokemons': [] # unused
  147. })
  148. return jsonify({'responses': {'GET_MAP_OBJECTS': {'map_cells': cells}}})
  149. if __name__ == '__main__':
  150. app.run(threaded=True, debug=args.debug, host=args.host, port=args.port)