├── .gitattributes ├── .gitignore ├── README.md ├── accounts.txt ├── bootstrap.py ├── config.json ├── fastmap.gif ├── fastmap ├── __init__.py ├── apiwrap.py ├── db.py ├── pbar.py └── utils.py ├── pgoapi ├── magiclib │ └── put_lib_here └── put_pgoapi_here ├── requirements.txt └── vacuum.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python,eclipse,git 2 | 3 | ### config ### 4 | config.json 5 | 6 | ### Python ### 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv/ 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | 99 | ### Eclipse ### 100 | 101 | .metadata 102 | bin/ 103 | tmp/ 104 | *.tmp 105 | *.bak 106 | *.swp 107 | *~.nib 108 | local.properties 109 | .settings/ 110 | .loadpath 111 | .recommenders 112 | 113 | # Eclipse Core 114 | .project 115 | 116 | # External tool builders 117 | .externalToolBuilders/ 118 | 119 | # Locally stored "Eclipse launch configurations" 120 | *.launch 121 | 122 | # PyDev specific (Python IDE for Eclipse) 123 | *.pydevproject 124 | 125 | # CDT-specific (C/C++ Development Tooling) 126 | .cproject 127 | 128 | # JDT-specific (Eclipse Java Development Tools) 129 | .classpath 130 | 131 | # Java annotation processor (APT) 132 | .factorypath 133 | 134 | # PDT-specific (PHP Development Tools) 135 | .buildpath 136 | 137 | # sbteclipse plugin 138 | .target 139 | 140 | # Tern plugin 141 | .tern-project 142 | 143 | # TeXlipse plugin 144 | .texlipse 145 | 146 | # STS (Spring Tool Suite) 147 | .springBeans 148 | 149 | # Code Recommenders 150 | .recommenders/ 151 | 152 | 153 | ### Git ### 154 | *.orig 155 | /magiclib/ 156 | /pgoapi/ 157 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fastmap 2 | ======= 3 | 4 | The *fast* PoGo map. 5 | 6 | ![Alt Text](https://github.com/Tr4sHCr4fT/fastmap/blob/master/fastmap.gif) 7 | 8 | ##Featurette & ToDo: 9 | 10 | - [x] *fast* bootstrap data generator 11 | - [x] standardized database structure 12 | - [x] forts, gyms, park spawn points 13 | - [x] threading / multi accounts 14 | - [ ] scanning for urban spawnpoints 15 | - [ ] spawn times classification 16 | - [ ] GUI / (live) map display 17 | 18 | ### bootstrap.py Usage: 19 | 20 | #### Options 21 | 22 | bootstrap.py [-h] [-a AUTH_SERVICE] [-u USERNAME] [-p PASSWORD] 23 | [-l LOCATION] [-r RADIUS] [-w WIDTH] [-f DBFILE] 24 | [-m --minions THREADS] [--level LEVEL] [-t DELAY] [-d] 25 | 26 | #### Examples 27 | 28 | bootstrap.py -l "Area 51" -w 9000 29 | ... scans a 9,000 x 9,000 m square 30 | 31 | bootstrap.py -l "37.235, -115.811" -r 9000 32 | ... scans a circle with 9km radius 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /accounts.txt: -------------------------------------------------------------------------------- 1 | username:password 2 | 3 | user2:pass2 4 | 5 | ... 6 | -------------------------------------------------------------------------------- /bootstrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | VERSION = '2.1' 3 | 4 | """ 5 | based on: pgoapi - Pokemon Go API 6 | Copyright (c) 2016 tjado 7 | 8 | Author: TC 9 | Version: 1.5 10 | """ 11 | 12 | import os, sys, json 13 | import argparse, logging 14 | import sqlite3 15 | 16 | from time import sleep 17 | from pkgutil import find_loader 18 | from s2sphere.sphere import CellId, LatLng 19 | from sqlite3 import IntegrityError, ProgrammingError, DataError 20 | from sqlite3 import OperationalError, InterfaceError, DatabaseError 21 | 22 | from pgoapi.exceptions import NotLoggedInException 23 | from fastmap.db import check_db, fill_db 24 | from fastmap.apiwrap import api_init, get_response, AccountBannedException 25 | from fastmap.utils import get_accounts, cover_circle, cover_square, get_cell_ids, sub_cells_normalized, set_bit 26 | 27 | log = logging.getLogger(__name__) 28 | 29 | 30 | def init_config(): 31 | parser = argparse.ArgumentParser() 32 | logging.basicConfig(level=logging.INFO,\ 33 | format='[%(levelname)5s] %(asctime)s %(message)s', datefmt="%d.%m.%y %H:%M:%S") 34 | load = {} 35 | config_file = "config.json" 36 | if os.path.isfile(config_file): 37 | with open(config_file) as data: 38 | load.update(json.load(data)) 39 | 40 | parser.add_argument("-a", "--auth_service", help="Auth Service ('ptc' or 'google')", default="ptc") 41 | parser.add_argument("-u", "--username", help="Username") 42 | parser.add_argument("-p", "--password", help="Password") 43 | parser.add_argument("-l", "--location", help="Location") 44 | parser.add_argument("-r", "--radius", help="area circle radius", type=int) 45 | parser.add_argument("-w", "--width", help="area square width", type=int) 46 | parser.add_argument("--dbfile", help="DB filename", default='db.sqlite') 47 | parser.add_argument("--accfile", help="ptc account list", default='accounts.txt') 48 | parser.add_argument("--level", help="cell level used for tiling", default=12, type=int) 49 | parser.add_argument("--maxq", help="maximum queue per worker", default=500, type=int) 50 | parser.add_argument("--pbar", help="tqdm progressbar", action='store_true', default=1) 51 | parser.add_argument("-t", "--delay", help="rpc request interval", default=10, type=int) 52 | parser.add_argument("-m", "--minions", help="thread / worker count", default=10, type=int) 53 | parser.add_argument("-v", "--verbose", help="Verbose Mode", action='store_true', default=0) 54 | parser.add_argument("-d", "--debug", help="Debug Mode", action='store_true', default=0) 55 | config = parser.parse_args() 56 | 57 | for key in config.__dict__: 58 | if key in load and config.__dict__[key] == None: 59 | config.__dict__[key] = load[key] 60 | 61 | if config.auth_service not in ['ptc', 'google']: 62 | log.error("Invalid Auth service specified! ('ptc' or 'google')") 63 | return None 64 | 65 | if config.debug: 66 | logging.getLogger("requests").setLevel(logging.DEBUG) 67 | logging.getLogger("pgoapi").setLevel(logging.DEBUG) 68 | logging.getLogger("rpc_api").setLevel(logging.DEBUG) 69 | else: 70 | logging.getLogger("requests").setLevel(logging.WARNING) 71 | logging.getLogger("pgoapi").setLevel(logging.WARNING) 72 | logging.getLogger("rpc_api").setLevel(logging.WARNING) 73 | 74 | if not check_db(config.dbfile): return 75 | 76 | if config.location: 77 | from fastmap.utils import get_pos_by_name 78 | lat, lng, alt = get_pos_by_name(config.location); del alt 79 | if config.radius: 80 | cells = cover_circle(lat, lng, config.radius, config.level) 81 | elif config.width: 82 | cells = cover_square(lat, lng, config.width, config.level) 83 | else: log.error('Area size not given!'); return 84 | log.info('Added %d items to scan queue.' % fill_db(config.dbfile, cells)) 85 | del cells, lat, lng 86 | 87 | if config.minions < 1: config.minions = 1 88 | 89 | if os.path.isfile('DEBUG'): logging.getLogger(__name__).setLevel(logging.DEBUG) 90 | 91 | return config 92 | 93 | def main(): 94 | 95 | config = init_config() 96 | if not config: 97 | log.error('Configuration Error!'); return 98 | 99 | if config.pbar: 100 | bar = find_loader('tqdm') 101 | if bar is not None: from fastmap.pbar import TqdmLogHandler 102 | else: log.warning("'pip install tqdm' to see a fancy progress bar!"); config.pbar = False 103 | 104 | bar = dummybar() 105 | minibar = dummybar() 106 | minions = config.minions 107 | db = sqlite3.connect(config.dbfile) 108 | log.info('DB loaded.') 109 | totalwork = db.cursor().execute("SELECT COUNT(*) FROM _queue WHERE scan_status=0").fetchone()[0] 110 | 111 | # some sanity checks 112 | if totalwork == 0: log.info('Nothing to scan!'); return 113 | 114 | if not os.path.isfile(config.accfile): 115 | minions = 1; accounts = [config] 116 | else: 117 | accounts = get_accounts(config.accfile) 118 | if len(accounts) < config.minions: minions = len(accounts) 119 | 120 | if totalwork < minions: minions = totalwork 121 | 122 | workpart = ( totalwork / minions ) 123 | if workpart > config.maxq : workpart = config.maxq 124 | 125 | # all OK? 126 | done, donetotal = 0, 0 127 | try: 128 | # initialize APIs 129 | workers = [] 130 | for m in xrange(minions): 131 | log.info('Initializing worker %2d of %2d' % (m+1,minions)) 132 | api = api_init(accounts[m]); sleep(3) 133 | if api is not None: 134 | workers.append(api) 135 | log.info("Logged into '%s'" % accounts[m].username) 136 | else: log.error("Login failed for '%s'" % accounts[m].username) 137 | log.info('Workers:%3d' % len(workers)) 138 | # end worker init loop 139 | 140 | # ETA 141 | n = (totalwork / len(workers)) 142 | log.info('Total %5d cells, %3d Workers, %5d cells each.' % (totalwork, minions, n)) 143 | ttot = (n * config.delay + 1); m, s = divmod(ttot, 60); h, m = divmod(m, 60) 144 | log.info('ETA %d:%02d:%02d' % (h, m, s)); del h,m,s, ttot, minions 145 | 146 | # last chance to abort 147 | log.info('Start in 3...');sleep(1) 148 | log.info('Start in 2..');sleep(1) 149 | log.info('Start in 1.');sleep(1) 150 | log.info("Let's go!") 151 | 152 | # init bar 153 | if config.pbar: import tqdm; log.addHandler(TqdmLogHandler()); bar = tqdm.tqdm(total=totalwork, desc=' total', unit='scan') 154 | 155 | # open DB 156 | with sqlite3.connect(config.dbfile) as db: 157 | totalstats = [0, 0, 0, 0] 158 | 159 | ## main loop 160 | while donetotal < totalwork and len(workers) > 0: 161 | ## 162 | done = 0 163 | # kill some zombies 164 | for i in xrange(len(workers)): 165 | if workers[i] is None: 166 | workers[i].pop 167 | 168 | # fetch DB 169 | cells = [x[0] for x in db.cursor().execute("SELECT cell_id FROM '_queue' WHERE scan_status=0 " 170 | "ORDER BY cell_id LIMIT %d" % ((len(workers)))).fetchall()] 171 | 172 | # RPC loop 173 | responses = [] 174 | delay = float( float(config.delay) / float(len(cells)) ) 175 | if config.pbar: minibar = tqdm.tqdm(total=len(cells), desc='worker', unit='scan') 176 | 177 | for i in xrange(len(cells)): 178 | 179 | sleep(delay) 180 | minibar.update() 181 | 182 | cell = CellId.from_token(cells[i]) 183 | lat = CellId.to_lat_lng(cell).lat().degrees 184 | lng = CellId.to_lat_lng(cell).lng().degrees 185 | cell_ids = get_cell_ids(sub_cells_normalized(cell, level=15)) 186 | 187 | log.debug('W%2d doing request for %s (%f, %f)' % (i,cells[i],lat,lng)) 188 | 189 | try: 190 | response_dict = get_response(workers[i], cell_ids, lat, lng) 191 | except AccountBannedException: 192 | workers[i] = None 193 | log.error('Worker %d down: Banned' % accounts[i]) 194 | except NotLoggedInException: 195 | sleep(config.delay / 2) 196 | workers[i] = None 197 | workers[i] = api_init(accounts[i]) 198 | response_dict = get_response(workers[i], cell_ids, lat, lng) 199 | except: log.error(sys.exc_info()[0]); sleep(config.delay) 200 | finally: 201 | responses.append(response_dict) 202 | 203 | minibar.close() 204 | # end RP loop 205 | 206 | # parse loop 207 | querys = [] 208 | for i in xrange(len(responses)): 209 | 210 | stats = [0, 0, 0, 0] 211 | response_dict = responses[i] 212 | 213 | if response_dict is None or len(response_dict) == 0: 214 | querys.append("UPDATE _queue SET scan_status=3 WHERE cell_id='{}'".format(cells[i])); done += 1 215 | continue 216 | 217 | if 'map_cells' in response_dict['responses']['GET_MAP_OBJECTS']: 218 | for map_cell in response_dict['responses']['GET_MAP_OBJECTS']['map_cells']: 219 | cellid = CellId(map_cell['s2_cell_id']).to_token() 220 | stats[0] += 1 221 | content = 0 222 | 223 | if 'forts' in map_cell: 224 | for fort in map_cell['forts']: 225 | if 'gym_points' in fort: 226 | stats[1]+=1 227 | content = set_bit(content, 2) 228 | querys.append("INSERT OR IGNORE INTO forts (fort_id, cell_id, pos_lat, pos_lng, fort_enabled, fort_type, last_scan) " 229 | "VALUES ('{}','{}',{},{},{},{},{})".format(fort['id'],cellid,fort['latitude'],fort['longitude'], \ 230 | int(fort['enabled']),0,int(map_cell['current_timestamp_ms']/1000))) 231 | else: 232 | stats[2]+=1 233 | content = set_bit(content, 1) 234 | querys.append("INSERT OR IGNORE INTO forts (fort_id, cell_id, pos_lat, pos_lng, fort_enabled, fort_type, last_scan) " 235 | "VALUES ('{}','{}',{},{},{},{},{})".format(fort['id'],cellid,fort['latitude'],fort['longitude'], \ 236 | int(fort['enabled']),1,int(map_cell['current_timestamp_ms']/1000))) 237 | 238 | if 'spawn_points' in map_cell: 239 | content = set_bit(content, 0) 240 | for spawn in map_cell['spawn_points']: 241 | stats[3]+=1; 242 | spwn_id = CellId.from_lat_lng(LatLng.from_degrees(spawn['latitude'],spawn['longitude'])).parent(20).to_token() 243 | querys.append("INSERT OR IGNORE INTO spawns (spawn_id, cell_id, pos_lat, pos_lng, last_scan) " 244 | "VALUES ('{}','{}',{},{},{})".format(spwn_id,cellid,spawn['latitude'],spawn['longitude'],int(map_cell['current_timestamp_ms']/1000))) 245 | if 'decimated_spawn_points' in map_cell: 246 | content = set_bit(content, 0) 247 | for spawn in map_cell['decimated_spawn_points']: 248 | stats[3]+=1; 249 | spwn_id = CellId.from_lat_lng(LatLng.from_degrees(spawn['latitude'],spawn['longitude'])).parent(20).to_token() 250 | querys.append("INSERT OR IGNORE INTO spawns (spawn_id, cell_id, pos_lat, pos_lng, last_scan) " 251 | "VALUES ('{}','{}',{},{},{})".format(spwn_id,cellid,spawn['latitude'],spawn['longitude'],int(map_cell['current_timestamp_ms']/1000))) 252 | 253 | querys.append("INSERT OR IGNORE INTO cells (cell_id, content, last_scan) " 254 | "VALUES ('{}', {}, {})".format(cellid,content,int(map_cell['current_timestamp_ms']/1000))) 255 | 256 | log.debug('%s: ' % cells[i] + '%d Cells, %d Gyms, %d Pokestops, %d Spawns.' % tuple(stats)) 257 | totalstats[0] += stats[0]; totalstats[1] += stats[1]; totalstats[2] += stats[2]; totalstats[3] += stats[3] 258 | if (stats[1]+stats[2]+stats[3]) > 0: querys.append("UPDATE _queue SET scan_status=1 WHERE cell_id='{}'".format(cells[i])) 259 | else: querys.append("UPDATE _queue SET scan_status=2 WHERE cell_id='{}'".format(cells[i])) 260 | log.debug('Marked %s as Done.' % cells[i]) 261 | done += 1 262 | 263 | else: querys.append("UPDATE _queue SET scan_status=3 WHERE cell_id='{}'".format(cells[i])); done += 1 264 | # end parse loop 265 | 266 | # save to DB #f = open('dump.sql','a') 267 | try: 268 | dbc = db.cursor() 269 | for query in querys: 270 | dbc.execute(query) #;f.write(query); f.write(';\n') 271 | 272 | except (IntegrityError, ProgrammingError, DataError): 273 | db.rollback(); log.error('SQL Syntax Error'); 274 | except (OperationalError, InterfaceError, DatabaseError): 275 | log.critical('Database corrupted or locked'); return 276 | except KeyboardInterrupt: db.rollback(); raise KeyboardInterrupt 277 | else: db.commit(); log.debug('Inserted %d queries' % len(querys)) 278 | 279 | # feedback 280 | bar.update(done); donetotal += done 281 | if not config.pbar: log.info('Queue: %5d done, %5d left' % (donetotal,totalwork-donetotal)) 282 | ## end main loop 283 | 284 | bar.close() 285 | log.info('Total: %d Cells, %d Gyms, %d Pokestops, %d Spawns.' % tuple(totalstats)) 286 | 287 | ## 288 | except KeyboardInterrupt: log.info('Aborted!') 289 | else: print("Dekimashita!") 290 | finally: db.close() 291 | 292 | 293 | class dummybar(object): 294 | def __init__(self): pass 295 | def close(self): pass 296 | def update(self, dummy=0): pass 297 | 298 | if __name__ == '__main__': 299 | main() -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth_service": "ptc", 3 | "username": "login", 4 | "password": "password" 5 | } 6 | -------------------------------------------------------------------------------- /fastmap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcmaps/fastmap/a401192830d3b135894fa7fb5b2f8942bba9ada2/fastmap.gif -------------------------------------------------------------------------------- /fastmap/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcmaps/fastmap/a401192830d3b135894fa7fb5b2f8942bba9ada2/fastmap/__init__.py -------------------------------------------------------------------------------- /fastmap/apiwrap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import time 4 | import os 5 | import sys 6 | import uuid # @UnusedImport 7 | import platform 8 | import logging 9 | 10 | from pgoapi import PGoApi 11 | from pgoapi.exceptions import AuthException 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | class AccountBannedException(AuthException): 17 | pass 18 | 19 | class PoGoAccount(): 20 | def __init__(self, auth, login, passw): 21 | self.auth_service = auth 22 | self.username = login 23 | self.password = passw 24 | 25 | def api_init(account): 26 | api = PGoApi() 27 | 28 | try: 29 | api.set_position(360,360,0) 30 | api.set_authentication(provider = account.auth_service,\ 31 | username = account.username, password = account.password) 32 | api.activate_signature(get_encryption_lib_path()); time.sleep(1); api.get_player() 33 | 34 | except AuthException: 35 | log.error('Login for %s:%s failed - wrong credentials?' % (account.username, account.password)) 36 | return None 37 | 38 | else: 39 | time.sleep(1); response = api.get_inventory() 40 | 41 | if response: 42 | if 'status_code' in response: 43 | if response['status_code'] == 1 or response['status_code'] == 2: return api 44 | 45 | elif response['status_code'] == 3: 46 | # try to accept ToS 47 | time.sleep(5); response = api.mark_tutorial_complete(tutorials_completed = 0,\ 48 | send_marketing_emails = False, send_push_notifications = False) 49 | 50 | if response['status_code'] == 1 or response['status_code'] == 2: 51 | print('Accepted TOS for %s' % account.username) 52 | return api 53 | 54 | elif response['status_code'] == 3: 55 | print('Account %s BANNED!' % account.username) 56 | raise AccountBannedException; return None 57 | 58 | return None 59 | 60 | def get_response(api, cell_ids, lat, lng, alt=0): 61 | 62 | timestamps = [0,] * len(cell_ids) 63 | response_dict = [] 64 | delay = 11 65 | 66 | while True: 67 | api.set_position(lat, lng, alt) 68 | response_dict = api.get_map_objects(latitude=lat, longitude=lng, since_timestamp_ms = timestamps, cell_id = cell_ids) 69 | if response_dict: 70 | if 'responses' in response_dict: 71 | if 'status' in response_dict['responses']['GET_MAP_OBJECTS']: 72 | if response_dict['responses']['GET_MAP_OBJECTS']['status'] == 1: 73 | return response_dict 74 | if response_dict['responses']['GET_MAP_OBJECTS']['status'] == 3: 75 | log.critical("Account banned!") 76 | raise AccountBannedException 77 | 78 | time.sleep(delay) 79 | delay += 5 80 | 81 | def check_reponse(response): 82 | 83 | if response: 84 | if 'responses' in response and 'status_code' in response: 85 | if response['status_code'] == 1 or response['status_code'] == 2: 86 | return response 87 | elif response['status_code'] == 3: 88 | raise AccountBannedException; return None 89 | else: return None 90 | 91 | return None 92 | 93 | def get_encryption_lib_path(): 94 | # win32 doesn't mean necessarily 32 bits 95 | if sys.platform == "win32" or sys.platform == "cygwin": 96 | if platform.architecture()[0] == '64bit': 97 | lib_name = "encrypt64bit.dll" 98 | else: 99 | lib_name = "encrypt32bit.dll" 100 | 101 | elif sys.platform == "darwin": 102 | lib_name = "libencrypt-osx-64.so" 103 | 104 | elif os.uname()[4].startswith("arm") and platform.architecture()[0] == '32bit': # @UndefinedVariable 105 | lib_name = "libencrypt-linux-arm-32.so" 106 | 107 | elif os.uname()[4].startswith("aarch64") and platform.architecture()[0] == '64bit': # @UndefinedVariable 108 | lib_name = "libencrypt-linux-arm-64.so" 109 | 110 | elif sys.platform.startswith('linux'): 111 | if "centos" in platform.platform(): 112 | if platform.architecture()[0] == '64bit': 113 | lib_name = "libencrypt-centos-x86-64.so" 114 | else: 115 | lib_name = "libencrypt-linux-x86-32.so" 116 | else: 117 | if platform.architecture()[0] == '64bit': 118 | lib_name = "libencrypt-linux-x86-64.so" 119 | else: 120 | lib_name = "libencrypt-linux-x86-32.so" 121 | 122 | elif sys.platform.startswith('freebsd'): 123 | lib_name = "libencrypt-freebsd-64.so" 124 | 125 | else: 126 | err = "Unexpected/unsupported platform '{}'".format(sys.platform) 127 | log.error(err) 128 | raise Exception(err) 129 | 130 | # check for lib in root dir or PATH 131 | if os.path.isfile(lib_name): 132 | return lib_name 133 | 134 | test_paths = ["../pgoapi/magiclib","../pgoapi/libencrypt","../magiclib","../libencrypt"] 135 | 136 | for test_path in test_paths: 137 | lib_path = os.path.join(os.path.dirname(__file__), test_path, lib_name) 138 | if os.path.isfile(lib_path): return lib_path 139 | 140 | err = "Could not find [{}] encryption library '{}'".format(sys.platform, lib_name) 141 | log.error(err) 142 | raise Exception(err) 143 | 144 | return None 145 | 146 | def limit_cells(cells, limit=100): 147 | return cells[:limit] -------------------------------------------------------------------------------- /fastmap/db.py: -------------------------------------------------------------------------------- 1 | FMDBVERSION = 2.2 2 | import sqlite3, os, logging 3 | log = logging.getLogger(__name__) 4 | 5 | def check_db(dbfile): 6 | 7 | if not os.path.isfile(dbfile): 8 | if not create_db(dbfile): return False 9 | 10 | with sqlite3.connect(dbfile) as db: 11 | try: version = db.cursor().execute("SELECT version FROM '_config'").fetchone()[0] 12 | except Exception as e: log.error(e); return False 13 | 14 | if version != FMDBVERSION: 15 | version = convert_db(dbfile, version) 16 | 17 | if version != FMDBVERSION: 18 | log.error("Database version mismatch! Expected {}, got {}...".format(FMDBVERSION,version)) 19 | return False 20 | 21 | return True 22 | 23 | def fill_db(dbfile, cells): 24 | with sqlite3.connect(dbfile) as db: 25 | counter=0 26 | for cell in cells: 27 | db.cursor().execute("INSERT OR IGNORE INTO _queue (cell_id,cell_level,scan_status) " 28 | "VALUES ('{}',{}, 0)".format(cell.to_token(),cell.level())) 29 | counter+=1 30 | db.commit() 31 | return counter 32 | 33 | def convert_db(dbfile, olddbv): 34 | with sqlite3.connect(dbfile) as db: 35 | try: 36 | newdbv = olddbv 37 | if newdbv == 2.1 or newdbv == '2.1' or newdbv == "'2.1'": 38 | log.info('Converting DB from 2.1 to 2.2...') 39 | 40 | db.cursor().execute("DROP TABLE _config") 41 | db.execute("CREATE TABLE _config (version DECIMAL DEFAULT 1.0)") 42 | db.cursor().execute("ALTER TABLE _queue ADD scan_status INT") 43 | db.cursor().execute("UPDATE _queue SET scan_status = 0 WHERE scan_status IS NULL") 44 | db.execute("INSERT INTO _config (version) VALUES (2.2)") 45 | newdbv = 2.2 46 | 47 | db.cursor().execute("VACUUM") 48 | except Exception as e: log.error(e); db.rollback() 49 | else: db.commit() 50 | finally: 51 | if newdbv == FMDBVERSION: 52 | log.info("DB converted to {}".format(FMDBVERSION)); return newdbv 53 | else: 54 | log.error('DB could not be converted!'); return olddbv 55 | 56 | def create_db(dbfile): 57 | try: 58 | db = sqlite3.connect(dbfile); dbc = db.cursor() 59 | dbc.execute("CREATE TABLE _config (version DECIMAL DEFAULT 1.0)") 60 | dbc.execute("CREATE TABLE _queue (cell_id VARCHAR PRIMARY KEY, cell_level INT, scan_status INT) WITHOUT ROWID") 61 | dbc.execute("CREATE TABLE cells (cell_id VARCHAR PRIMARY KEY, content INT, last_scan TIMESTAMP) WITHOUT ROWID") 62 | dbc.execute("CREATE TABLE forts (fort_id VARCHAR PRIMARY KEY, cell_id VARCHAR, \ 63 | pos_lat DOUBLE, pos_lng DOUBLE, fort_enabled BOOLEAN, fort_type INT, fort_description TEXT, \ 64 | fort_image BLOB, fort_sponsor TEXT, fort_last_modified TIMESTAMP, last_scan TIMESTAMP,\ 65 | FOREIGN KEY ( cell_id ) REFERENCES cells (cell_id) ) WITHOUT ROWID") 66 | dbc.execute("CREATE TABLE spawns (spawn_id VARCHAR PRIMARY KEY, cell_id VARCHAR, \ 67 | pos_lat DOUBLE, pos_lng DOUBLE, static_spawner INT DEFAULT (0), nest_spawner INT DEFAULT (0), \ 68 | spawn_time_base TIME, spawn_time_offset TIME, spawn_time_dur TIME, last_scan TIMESTAMP, \ 69 | FOREIGN KEY (cell_id) REFERENCES cells (cell_id) ) WITHOUT ROWID") 70 | dbc.execute("INSERT INTO _config (version) VALUES (%s)" % FMDBVERSION) 71 | log.info('DB created!') 72 | except Exception as e: log.error(e); db.rollback(); return False 73 | db.commit(); db.close() 74 | return True 75 | 76 | if __name__ == '__main__': 77 | print check_db('db.sqlite') -------------------------------------------------------------------------------- /fastmap/pbar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import logging 3 | import tqdm 4 | 5 | class TqdmLogHandler (logging.Handler): 6 | def __init__ (self, level = logging.NOTSET): 7 | super (self.__class__, self).__init__ (level) 8 | 9 | def emit (self, record): 10 | try: 11 | msg = self.format (record) 12 | tqdm.tqdm.write (msg) 13 | self.flush () 14 | except (KeyboardInterrupt, SystemExit): 15 | raise KeyboardInterrupt 16 | except: 17 | self.handleError(record) -------------------------------------------------------------------------------- /fastmap/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import re 4 | import math 5 | import logging 6 | 7 | from geopy.geocoders import GoogleV3 8 | from geographiclib.geodesic import Geodesic 9 | from s2sphere import CellId, Angle, LatLng, LatLngRect, Cap, RegionCoverer 10 | 11 | from fastmap.apiwrap import PoGoAccount 12 | 13 | log = logging.getLogger(__name__) 14 | 15 | 16 | 17 | def set_bit(value, bit): 18 | return value | (1< 1: dbfilename = sys.argv[1] 9 | 10 | with sqlite3.connect(dbfilename) as db: 11 | 12 | db.cursor().execute("VACUUM") 13 | 14 | print('Donezo!') --------------------------------------------------------------------------------