├── .env.example ├── .gitignore ├── .gitmodules ├── .jshintrc ├── LICENSE.md ├── README.md ├── data └── .gitkeep ├── inc ├── blacklist.js ├── db.js └── utils.js ├── index.js ├── models ├── Gym.js ├── GymMember.js ├── GymPokemon.js ├── Pokemon.js ├── Pokestop.js └── Raid.js ├── package-lock.json ├── package.json └── routes ├── captcha.js ├── index.js └── raw_data.js /.env.example: -------------------------------------------------------------------------------- 1 | ################ 2 | # Application. # 3 | ################ 4 | 5 | # Specify enabled logging, separated via comma. Use * for wildcard. 6 | # Supported options: 7 | # devkat - All devkat logs. 8 | # devkat:db - Database & query logs. 9 | # devkat:db:pokemon - Pokémon DB logs. 10 | # devkat:db:pokemon:sql - Raw SQL query logs. 11 | # devkat:master - Process manager & general status. 12 | # devkat:routes - Webserver routing logs. 13 | # devkat:routes:raw_data - Webserver logs for /raw_data. 14 | # 15 | # Examples: 16 | # DEBUG=* - Enable all logging. 17 | # DEBUG=devkat:master - Only devkat status/process manager logging. 18 | # DEBUG=devkat:*,-devkat:routes* - Exclude webserver routes from logging. 19 | DEBUG=devkat*,-devkat:db:*:sql 20 | 21 | # Limits per query to /raw_data. 22 | POKEMON_LIMIT_PER_QUERY=50000 23 | POKESTOP_LIMIT_PER_QUERY=50000 24 | GYM_LIMIT_PER_QUERY=50000 25 | 26 | 27 | ############## 28 | # Webserver. # 29 | ############## 30 | 31 | # Webserver host IP to bind to. 127.0.0.1 for local (recommended with nginx 32 | # reverse proxy), and 0.0.0.0 binds to all interfaces. 33 | WEB_HOST=127.0.0.1 34 | 35 | # Webserver port. 36 | WEB_PORT=1337 37 | 38 | # Enable gzip compression. 39 | ENABLE_GZIP=true 40 | 41 | # Set up IP(s)/domain(s) to allow CORS for, via comma-separated list. 42 | CORS_WHITELIST=http://127.0.0.1,https://localhost 43 | 44 | # HTTPS key file paths. 45 | ENABLE_HTTPS=false 46 | HTTPS_KEY_PATH=privkey.pem 47 | HTTPS_CERT_PATH=cert.pem 48 | 49 | # Enable/disable webserver request throttling. 50 | ENABLE_THROTTLE=true 51 | # Rate limit: requests per second. 52 | THROTTLE_RATE=10 53 | # Allow user to temporarily go over the rate limit, up to the burst limit. 54 | THROTTLE_BURST=20 55 | 56 | 57 | ############# 58 | # Database. # 59 | ############# 60 | 61 | # MySQL only. 62 | DB_HOST=localhost 63 | DB_PORT=3306 64 | DB_USER=root 65 | DB_PASS= 66 | DB_DATABASE=database 67 | 68 | # Amount of max. DB connections. If your system allows for high concurrency, 69 | # make sure you have enough connections in the pool. 70 | DB_POOL_MAX_SIZE=10 71 | 72 | 73 | ######################################################################## 74 | # Warning: only customize below options if you know what you're doing. # 75 | ######################################################################## 76 | 77 | # Overwrite environment for end users, in case they forget. 78 | # Don't touch this! 79 | NODE_ENV=production 80 | 81 | 82 | ############## 83 | # Webserver. # 84 | ############## 85 | 86 | # Use built-in process management with cluster. 87 | ENABLE_CLUSTER=true 88 | 89 | # Number of web serving workers. Leave this commented to use your CPU count, 90 | # which is the optimal amount in nearly all cases. 91 | #WEB_WORKERS=1 92 | 93 | # Automatically restart workers if they've crashed. 94 | AUTORESTART_WORKERS=true 95 | 96 | 97 | ################### 98 | # Webserver load. # 99 | ################### 100 | 101 | # Enable/disable the load limiter. 102 | ENABLE_LOAD_LIMITER=true 103 | 104 | # Enable/disable logging when load limiter kicks in on a worker. 105 | ENABLE_LOAD_LIMITER_LOGGING=false 106 | 107 | # Represents the maximum amount of time in milliseconds that the event queue 108 | # is behind, before we consider the process "too busy". 109 | MAX_LAG_MS=150 110 | 111 | # The check interval for measuring event loop lag, in milliseconds. 112 | LAG_INTERVAL_MS=500 113 | 114 | 115 | ############ 116 | # Routing. # 117 | ############ 118 | 119 | # Remap the webserver's routing. 120 | ROUTE_RAW_DATA=/raw_data -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "data/pokedex"] 2 | path = data/pokedex 3 | url = https://github.com/sebastienvercammen/Pokemon-Go-Pokedex.git 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | Live visualization of all the pokemon in your area... and more! 633 | Copyright (C) 2017 RocketMap Team 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Donate](https://img.shields.io/badge/Support-Patreon-orange.svg)](https://www.patreon.com/devkat) 2 | ![Python 2.7](https://img.shields.io/badge/python-2.7-blue.svg) 3 | ![license](https://img.shields.io/github/license/sebastienvercammen/devkat-rm-node-webserver.svg) 4 | 5 | # The Sublimely Magnificent Node.js RM Webserver Mark III 6 | 7 | An asynchronous Node.js webserver using restify for the API, node-mysql for MySQL integration and that supports gzip compression, load limiting with toobusy-js, rate limiting with restify's throttling and multiprocessing (+ process management) with cluster. 8 | 9 | Since this webserver is meant to be set up as a managed microcomponent - not as a replacement webserver for static components - its functionality is strictly limited to serving dynamic data requests only. **Using a more mature webserver to serve static components and as a reverse proxy is highly recommended (nginx on Linux, apache2 on Windows)**, an example nginx configuration is provided below. 10 | 11 | If you want to use a more advanced process manager, we recommend [disabling cluster in your configuration](#disabling-process-management-with-cluster) to disable process management. 12 | 13 | ## Getting Started 14 | 15 | These instructions will help you deploy the project on a live system. 16 | 17 | **Important:** The default configuration example file `.env.example` overwrites the `NODE_ENV` environment variable to `production` for security purposes. 18 | 19 | ### Prerequisites 20 | 21 | - [Node.js v6.11.4 or higher](https://nodejs.org/en/) 22 | - npm v5.5.0 or higher 23 | 24 | ``` 25 | Windows: To update npm and Node.js, manually download the LTS version from their website. 26 | 27 | To update Node.js and npm: 28 | apt-get remove node nodejs 29 | curl -L https://git.io/n-install | bash 30 | n lts 31 | 32 | To update only npm: 33 | npm install -g npm 34 | ``` 35 | 36 | ### Installing 37 | 38 | Start by reading the license in LICENSE.md. 39 | 40 | Make sure Node.js and npm are properly installed: 41 | 42 | ``` 43 | node -v 44 | npm -v 45 | ``` 46 | 47 | Clone the project and its submodules: 48 | 49 | ``` 50 | git clone --recursive https://github.com/sebastienvercammen/devkat-rm-node-webserver.git 51 | ``` 52 | 53 | Make sure you are in the project directory with your terminal, and install the dependencies: 54 | 55 | ``` 56 | npm install 57 | ``` 58 | 59 | Copy the example configuration file `.env.example` to `.env`: 60 | 61 | ``` 62 | Linux: 63 | cp .env.example .env 64 | 65 | Windows: 66 | copy .env.example .env 67 | ``` 68 | 69 | And presto, you're ready to configure. 70 | 71 | After configuring, start the server with: 72 | 73 | ``` 74 | node index.js 75 | ``` 76 | 77 | ### Configuration 78 | 79 | #### Settings you must review 80 | 81 | ``` 82 | # Webserver host IP to bind to. 0.0.0.0 binds to all interfaces. 83 | WEB_HOST=0.0.0.0 84 | 85 | # Webserver port. 86 | WEB_PORT=8080 87 | 88 | # Set up domain(s) to allow CORS for, via comma-separated list. 89 | CORS_WHITELIST=* 90 | 91 | # And all database settings. 92 | DB_HOST=localhost 93 | DB_PORT=3306 94 | DB_USER=root 95 | DB_PASS= 96 | DB_DATABASE=database 97 | ``` 98 | 99 | #### Enabling gzip compression 100 | 101 | ``` 102 | ENABLE_GZIP=true 103 | ``` 104 | 105 | #### Enabling HTTPS 106 | 107 | ``` 108 | # HTTPS key file paths. 109 | ENABLE_HTTPS=true 110 | HTTPS_KEY_PATH=privkey.pem 111 | HTTPS_CERT_PATH=cert.pem 112 | ``` 113 | 114 | #### Enabling load limiter 115 | 116 | ``` 117 | # Enable/disable the load limiter. 118 | ENABLE_LOAD_LIMITER=true 119 | 120 | # Enable/disable logging when load limiter kicks in on a worker. 121 | ENABLE_LOAD_LIMITER_LOGGING=true 122 | 123 | # Represents the maximum amount of time in milliseconds that the event queue 124 | # is behind, before we consider the process "too busy". 125 | MAX_LAG_MS=75 126 | 127 | # The check interval for measuring event loop lag, in milliseconds. 128 | LAG_INTERVAL_MS=500 129 | ``` 130 | 131 | #### Enabling request throttling 132 | 133 | **Note:** When enabling request throttling, make sure you're not using a reverse proxy, since all requests will come from the same IP. 134 | 135 | ``` 136 | # Enable/disable webserver request throttling. 137 | ENABLE_THROTTLE=true 138 | # Rate limit: requests per second. 139 | THROTTLE_RATE=5 140 | # Allow user to temporarily go over the rate limit, up to the burst limit. 141 | THROTTLE_BURST=10 142 | ``` 143 | 144 | #### Allowing CORS for all domains 145 | 146 | **Warning:** Enabling CORS for all domains is not recommended. You will only make it easier for scrapers to get your data. 147 | 148 | ``` 149 | # Set up domain(s) to allow CORS for, via comma-separated list. 150 | CORS_WHITELIST=* 151 | ``` 152 | 153 | #### Disabling process management with cluster 154 | 155 | **Note:** Disabling process management with cluster will automatically make the webserver ignore all configuration items related to multiprocessing/cluster. 156 | 157 | ``` 158 | ENABLE_CLUSTER=false 159 | ``` 160 | 161 | ## Using nginx as a reverse proxy to /raw_data, with fallback to Flask for unsupported features 162 | 163 | If you're using nginx to serve your RocketMap website, make sure your nginx configuration looks like the example below to serve /raw_data with the new webserver, and all other paths with RocketMap's Flask/werkzeug. 164 | 165 | This example assumes your RM webserver is running on port 5000 and the devkat webserver on port 1337. Adjust accordingly. 166 | 167 | If a feature is not yet implemented in this webserver, the example configuration falls back to the Flask webserver. The focus of this webserver is to have the best performance for /raw_data requests, although other features are planned to be implemented (at a lower priority). 168 | 169 | Based on [RocketMap's nginx example](http://rocketmap.readthedocs.io/en/develop/advanced-install/nginx.html). 170 | 171 | ``` 172 | upstream flask { 173 | server 127.0.0.1:5000; 174 | } 175 | upstream devkat { 176 | server 127.0.0.1:1337; 177 | } 178 | 179 | server { 180 | listen 80; 181 | 182 | location /raw_data { 183 | # /stats 184 | if ($arg_seen = "true") { 185 | proxy_pass http://flask; 186 | } 187 | 188 | # /status 189 | if ($arg_status = "true") { 190 | proxy_pass http://flask; 191 | } 192 | 193 | # Appearances & appearance details. 194 | if ($arg_appearances = "true") { 195 | proxy_pass http://flask; 196 | } 197 | 198 | if ($arg_appearancesDetails = "true") { 199 | proxy_pass http://flask; 200 | } 201 | 202 | # Spawnpoints. 203 | if ($arg_spawnpoints = "true") { 204 | proxy_pass http://flask; 205 | } 206 | 207 | # Scanned locations. 208 | if ($arg_scanned = "true") { 209 | proxy_pass http://flask; 210 | } 211 | 212 | proxy_pass http://devkat; 213 | } 214 | 215 | location / { 216 | proxy_pass http://flask; 217 | } 218 | } 219 | ``` 220 | 221 | ## License 222 | 223 | This project is licensed under AGPLv3 - see the [LICENSE.md](LICENSE.md) file for details. 224 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebastienvercammen/devkat-rm-node-webserver/f78e23e033735d8cd3b077ca100f9c5329811e06/data/.gitkeep -------------------------------------------------------------------------------- /inc/blacklist.js: -------------------------------------------------------------------------------- 1 | /* 2 | Blacklist fingerprinting methods. They receive the restify request object as 3 | argument and return true when a blacklisted fingerprint matches. 4 | */ 5 | 6 | const blacklist = {}; 7 | 8 | // No referrer = request w/o being on a website. 9 | blacklist.no_referrer = (req) => { 10 | return req.header('Referer', false) === false; 11 | }; 12 | 13 | // iPokeGo. 14 | blacklist.iPokeGo = (req) => { 15 | const agent = req.header('User-Agent', ''); 16 | return agent.toLowerCase().indexOf('ipokego') > -1; 17 | }; 18 | 19 | module.exports = blacklist; 20 | -------------------------------------------------------------------------------- /inc/db.js: -------------------------------------------------------------------------------- 1 | // Parse config. 2 | require('dotenv').config(); 3 | 4 | const debug = require('debug')('devkat:db'); 5 | const mysql = require('mysql'); 6 | const utils = require('./utils.js'); 7 | 8 | 9 | /* Settings. */ 10 | 11 | const DB_HOST = process.env.DB_HOST || 'localhost'; 12 | const DB_PORT = process.env.DB_PORT || 3306; 13 | const DB_USER = process.env.DB_USER || 'root'; 14 | const DB_PASS = process.env.DB_PASS || ''; 15 | const DB_DATABASE = process.env.DB_DATABASE || 'database'; 16 | 17 | const DB_POOL_MAX_SIZE = process.env.DB_POOL_MAX_SIZE || 10; 18 | 19 | 20 | /* DB. */ 21 | 22 | const pool = mysql.createPool({ 23 | 'host': DB_HOST, 24 | 'port': DB_PORT, 25 | 'user': DB_USER, 26 | 'password': DB_PASS, 27 | 'database': DB_DATABASE, 28 | 'connectionLimit': DB_POOL_MAX_SIZE, 29 | 'insecureAuth': true, 30 | 'dateStrings': true 31 | }); 32 | 33 | 34 | /** 35 | * Connection to the local MySQL database. 36 | * 37 | * @param {function} callback Gets called with the error and DB pool as parameters. 38 | * Error parameter is null if the connection succeeded. 39 | */ 40 | function connect(callback) { 41 | debug('[%s] Connecting to db on %s:%s...', process.pid, DB_HOST, DB_PORT); 42 | 43 | pool.getConnection(function (err, connection) { 44 | // Something happened. 45 | if (err) { 46 | debug('[%s] Error connecting to db on %s:%s.', process.pid, DB_HOST, DB_PORT, err); 47 | return callback(err, null); 48 | } 49 | 50 | // Connected! 51 | debug('[%s] Connected to db on %s:%s...', process.pid, DB_HOST, DB_PORT); 52 | connection.release(); 53 | return callback(null, pool); 54 | }); 55 | } 56 | 57 | module.exports = { 58 | 'pool': pool, 59 | 'connect': connect 60 | }; -------------------------------------------------------------------------------- /inc/utils.js: -------------------------------------------------------------------------------- 1 | var utils = { 2 | // Generic error log & exit. 3 | handle_error: (err) => { 4 | console.error(err); 5 | process.exit(1); 6 | }, 7 | 8 | // Fix SIGINT on Windows systems. 9 | fixWinSIGINT: () => { 10 | if (process.platform === 'win32') { 11 | require('readline') 12 | .createInterface({ 13 | input: process.stdin, 14 | output: process.stdout 15 | }) 16 | .on('SIGINT', function () { 17 | process.emit('SIGINT'); 18 | }); 19 | } 20 | }, 21 | 22 | // Readability methods. 23 | isUndefined: (val) => { 24 | return (typeof val === 'undefined'); 25 | }, 26 | 27 | // TODO: Figure out better name than "isEmpty". 28 | isEmpty: (val) => { 29 | return (utils.isUndefined(val) || val === null || val === ''); 30 | }, 31 | 32 | // Check if a string is numeric (e.g. for GET params). 33 | isNumeric: (n) => { 34 | return !isNaN(parseFloat(n)) && isFinite(n); 35 | } 36 | }; 37 | 38 | 39 | /* 40 | * Pokémon data related methods. 41 | */ 42 | utils.pokemon = { 43 | // Check if Pokémon ID is in data. 44 | hasPokemonData: (pokedex, id) => { 45 | return pokedex.hasOwnProperty(id); 46 | }, 47 | 48 | // Get a Pokémon's data. 49 | getPokemonData: (pokedex, id) => { 50 | // Are we sure we have this Pokémon? 51 | if (!utils.pokemon.hasPokemonData(pokedex, id)) { 52 | return false; 53 | } 54 | 55 | return pokedex[id]; 56 | }, 57 | 58 | // Get a Pokémon's name. 59 | getPokemonName: (pokedex, id) => { 60 | // Are we sure we have this Pokémon? 61 | if (!utils.pokemon.hasPokemonData(pokedex, id)) { 62 | return null; 63 | } 64 | 65 | return pokedex[id].name; 66 | }, 67 | 68 | // Get a Pokémon's types. 69 | getPokemonTypes: (pokedex, id) => { 70 | // Are we sure we have this Pokémon? 71 | if (!utils.pokemon.hasPokemonData(pokedex, id)) { 72 | return null; 73 | } 74 | 75 | return pokedex[id].types; 76 | } 77 | }; 78 | 79 | module.exports = utils; 80 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Async Node.js webserver w/ restify and node-mysql for RocketMap. 3 | Supports gzip compression, load limiting w/ toobusy-js and multiprocessing 4 | with cluster. 5 | 6 | Developed by Sébastien Vercammen, for devkat. 7 | It's all thanks to our Patrons: https://www.patreon.com/devkat 8 | 9 | Thank you for supporting us to do what we do best. 10 | */ 11 | 12 | // Parse config. 13 | require('dotenv').config(); 14 | 15 | const fs = require('fs'); 16 | const debug = require('debug')('devkat:master'); 17 | const cluster = require('cluster'); 18 | const utils = require('./inc/utils.js'); 19 | const db = require('./inc/db.js'); 20 | var shuttingDown = false; // Are we shutting down? 21 | var online_workers = {}; // Status per worker PID. 22 | 23 | 24 | /* Readability references. */ 25 | 26 | const fixWinSIGINT = utils.fixWinSIGINT; 27 | 28 | 29 | /* Settings. */ 30 | 31 | const SERVER_NAME = 'devkat RM Webserver'; 32 | const SERVER_VERSION = '2.2.1'; 33 | const HTTPS = process.env.ENABLE_HTTPS === 'true' || false; 34 | const HTTPS_KEY_PATH = process.env.HTTPS_KEY_PATH || 'privkey.pem'; 35 | const HTTPS_CERT_PATH = process.env.HTTPS_CERT_PATH || 'cert.pem'; 36 | const GZIP = process.env.ENABLE_GZIP === 'true' || false; 37 | const WEB_HOST = process.env.WEB_HOST || '0.0.0.0'; 38 | const WEB_PORT = parseInt(process.env.WEB_PORT) || 3000; 39 | const WEB_WORKERS = parseInt(process.env.WEB_WORKERS) || require('os').cpus().length; 40 | const ENABLE_LOAD_LIMITER = process.env.ENABLE_LOAD_LIMITER !== 'false' || false; 41 | const ENABLE_LOAD_LIMITER_LOGGING = process.env.ENABLE_LOAD_LIMITER_LOGGING !== 'false' || false; 42 | const MAX_LAG_MS = parseInt(process.env.MAX_LAG_MS) || 75; 43 | const LAG_INTERVAL_MS = parseInt(process.env.LAG_INTERVAL_MS) || 500; 44 | const ENABLE_CLUSTER = process.env.ENABLE_CLUSTER !== 'false' || false; 45 | const AUTORESTART_WORKERS = process.env.AUTORESTART_WORKERS !== 'false' || false; 46 | const ENABLE_THROTTLE = process.env.ENABLE_THROTTLE !== 'false' || false; 47 | const THROTTLE_RATE = parseInt(process.env.THROTTLE_RATE) || 5; 48 | const THROTTLE_BURST = parseInt(process.env.THROTTLE_BURST) || 10; 49 | 50 | 51 | // If we're on Windows, fix the SIGINT event. 52 | //fixWinSIGINT(); 53 | 54 | // If we're the cluster master, manage our processes. 55 | if (ENABLE_CLUSTER && cluster.isMaster) { 56 | debug('Master cluster setting up %s workers...', WEB_WORKERS); 57 | 58 | for (let i = 0; i < WEB_WORKERS; i++) { 59 | cluster.fork(); 60 | } 61 | 62 | // Worker is online, but not yet ready to handle requests. 63 | cluster.on('online', function (worker) { 64 | debug('Worker %s (PID %s) is starting...', worker.id, worker.process.pid); 65 | }); 66 | 67 | // Worker is ded :( 68 | cluster.on('exit', function (worker, code, signal) { 69 | let id = worker.id; 70 | let pid = worker.process.pid; 71 | 72 | // Don't continue if we're shutting down. 73 | if (shuttingDown) { 74 | debug('Worker %s (PID %s) has exited.', worker.id, worker.process.pid); 75 | return; 76 | } 77 | 78 | // If the worker wasn't online yet, something happened during startup. 79 | if (!online_workers.hasOwnProperty(worker.process.pid)) { 80 | debug('Worker %s (PID %s) encountered an error on startup. Exiting.', id, pid); 81 | shuttingDown = true; 82 | 83 | // Graceful shutdown instead of process.exit(). 84 | process.emit('SIGINT'); 85 | return; 86 | } 87 | 88 | debug('Worker %s died with code %s, and signal %s.', pid, code, signal); 89 | if (AUTORESTART_WORKERS) debug('Starting a new worker.'); 90 | 91 | // Start new worker if autorestart is enabled. 92 | if (AUTORESTART_WORKERS) 93 | cluster.fork(); 94 | }); 95 | 96 | // Worker disconnected, either on graceful shutdown or kill. 97 | cluster.on('disconnect', (worker) => { 98 | debug('Worker %s (PID %s) has disconnected.', worker.id, worker.process.pid); 99 | }); 100 | 101 | // Receive messages from workers. 102 | cluster.on('message', (worker, msg, handle) => { 103 | var status = msg.status; 104 | var id = msg.id; 105 | var pid = msg.pid; 106 | 107 | // Worker had a successful startup. 108 | if (status === 'ONLINE') { 109 | online_workers[pid] = status; 110 | } 111 | }); 112 | 113 | 114 | /* Graceful shutdown. */ 115 | 116 | process.on('SIGINT', function graceful() { 117 | shuttingDown = true; 118 | debug('Gracefully closing server...'); 119 | 120 | // Kill all workers, but let them kill themselves because otherwise they 121 | // might not be ready listening, and you end up with EPIPE errors. 122 | for (var id in cluster.workers) { 123 | let worker = cluster.workers[id]; 124 | 125 | // Disconnected workers will exit themselves. 126 | if (worker.isConnected()) { 127 | worker.send('shutdown'); 128 | worker.disconnect(); 129 | } 130 | } 131 | 132 | // Make sure all workers have exited. 133 | function allWorkersDied(workers) { 134 | for (var id in workers) { 135 | let worker = workers[id]; 136 | if (!worker.isDead()) return false; 137 | } 138 | 139 | return true; 140 | } 141 | 142 | // Run code when all workers are dead w/o starving CPU. 143 | function waitUntilAllWorkersDied(workers, interval_ms, callback) { 144 | if (!allWorkersDied(workers)) { 145 | setTimeout(() => { 146 | waitUntilAllWorkersDied(workers, interval_ms, callback); 147 | }, interval_ms); 148 | } else { 149 | // All dead! Yay! 150 | callback(); 151 | } 152 | } 153 | 154 | waitUntilAllWorkersDied(cluster.workers, 500, () => { 155 | process.exit(0); 156 | }); 157 | }); 158 | } else { 159 | // We're a worker, prepare to handle requests. 160 | const toobusy = require('toobusy-js'); 161 | const restify = require('restify'); 162 | const errors = require('restify-errors'); 163 | const restifyPlugins = restify.plugins; 164 | 165 | // Webserver settings & optional HTTPS. 166 | const HTTP_OPTIONS = { 167 | name: SERVER_NAME, 168 | version: SERVER_VERSION 169 | }; 170 | 171 | if (HTTPS) { 172 | HTTP_OPTIONS.key = fs.readFileSync(HTTPS_KEY_PATH); 173 | HTTP_OPTIONS.certificate = fs.readFileSync(HTTPS_CERT_PATH); 174 | } 175 | 176 | // Create our server. 177 | const server = restify.createServer(HTTP_OPTIONS); 178 | 179 | // Middleware. 180 | server.use(restifyPlugins.jsonBodyParser({ mapParams: true })); 181 | server.use(restifyPlugins.acceptParser(server.acceptable)); 182 | server.use(restifyPlugins.queryParser({ mapParams: true })); 183 | server.use(restifyPlugins.fullResponse()); 184 | 185 | if (GZIP) { 186 | server.use(restifyPlugins.gzipResponse()); 187 | } 188 | 189 | if (ENABLE_THROTTLE) { 190 | debug('Throttle enabled: %d requests per second, %d burst.', THROTTLE_RATE, THROTTLE_BURST); 191 | server.use(restifyPlugins.throttle({ 192 | rate: THROTTLE_RATE, 193 | burst: THROTTLE_BURST, 194 | ip: true 195 | })); 196 | } 197 | 198 | // Middleware which blocks requests when we're too busy. 199 | if (ENABLE_LOAD_LIMITER) { 200 | toobusy.maxLag(MAX_LAG_MS); 201 | toobusy.interval(LAG_INTERVAL_MS); 202 | 203 | debug('Enabled load limiter: ' + MAX_LAG_MS + 'ms limit, ' + LAG_INTERVAL_MS + ' ms check.'); 204 | 205 | server.use(function (req, res, next) { 206 | if (toobusy()) { 207 | return next(new errors.ServiceUnavailableError()); 208 | } else { 209 | return next(); 210 | } 211 | }); 212 | 213 | toobusy.onLag((currentLag) => { 214 | currentLag = Math.round(currentLag); 215 | 216 | if (ENABLE_LOAD_LIMITER_LOGGING) { 217 | debug('[%s] Event loop lag detected! Latency: %sms.', process.pid, currentLag); 218 | } 219 | }); 220 | } 221 | 222 | 223 | /* App. */ 224 | 225 | // Workers can share any TCP connection. 226 | server.listen(WEB_PORT, WEB_HOST, () => { 227 | // Connect to DB. 228 | db.connect(() => { 229 | // Attach routes. 230 | require('./routes')(server); 231 | 232 | // BEEP BOOP, R O B O T I S S E N T I E N T. 233 | if (ENABLE_CLUSTER) { 234 | debug('Worker %s (PID %s) is listening on %s.', cluster.worker.id, process.pid, server.url); 235 | 236 | // We're online. Let's tell our sensei (it's a "master" joke 👀). 237 | process.send({ 238 | 'status': 'ONLINE', 239 | 'id': cluster.worker.id, 240 | 'pid': process.pid 241 | }); 242 | } else { 243 | debug('Server (PID %s) is listening on %s.', process.pid, server.url); 244 | } 245 | }); 246 | }); 247 | 248 | 249 | /* Graceful worker shutdown. */ 250 | function shutdownWorker() { 251 | if (ENABLE_LOAD_LIMITER) { 252 | // Calling .shutdown allows your process to exit normally. 253 | toobusy.shutdown(); 254 | } 255 | 256 | server.close(); 257 | 258 | if (ENABLE_CLUSTER) { 259 | debug('Gracefully closed worker %s (PID %s).', cluster.worker.id, process.pid); 260 | } else { 261 | debug('Gracefully closed server (PID %s).', process.pid); 262 | } 263 | 264 | process.exit(0); 265 | } 266 | 267 | process.on('message', function workerMsg(msg) { 268 | if (msg === 'shutdown') { 269 | shutdownWorker(); 270 | } 271 | }); 272 | 273 | // Handle graceful shutdown if we're not using cluster/process management. 274 | process.on('SIGINT', function gracefulWorker() { 275 | shutdownWorker(); 276 | }); 277 | } 278 | -------------------------------------------------------------------------------- /models/Gym.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Parse config. 4 | require('dotenv').config(); 5 | 6 | 7 | /* Includes. */ 8 | 9 | const db = require('../inc/db.js').pool; 10 | const utils = require('../inc/utils.js'); 11 | const pokedex = require('../data/pokedex/pokemon.json'); 12 | 13 | const Raid = require('./Raid'); 14 | const GymMember = require('./GymMember'); 15 | const GymPokemon = require('./GymPokemon'); 16 | 17 | 18 | /* Readability references. */ 19 | 20 | const isEmpty = utils.isEmpty; 21 | const getPokemonName = utils.pokemon.getPokemonName; 22 | const getPokemonTypes = utils.pokemon.getPokemonTypes; 23 | 24 | 25 | /* Settings. */ 26 | 27 | const GYM_LIMIT_PER_QUERY = parseInt(process.env.GYM_LIMIT_PER_QUERY) || 50000; 28 | 29 | 30 | /* Helpers. */ 31 | 32 | // Make sure SQL uses proper timezone. 33 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')"; 34 | 35 | function prepareQuery(options) { 36 | // Parse options. 37 | var swLat = options.swLat; 38 | var swLng = options.swLng; 39 | var neLat = options.neLat; 40 | var neLng = options.neLng; 41 | var oSwLat = options.oSwLat; 42 | var oSwLng = options.oSwLng; 43 | var oNeLat = options.oNeLat; 44 | var oNeLng = options.oNeLng; 45 | var timestamp = options.timestamp || false; 46 | 47 | // Query options. 48 | var query_where = []; 49 | 50 | // Optional viewport. 51 | if (!isEmpty(swLat) && !isEmpty(swLng) && !isEmpty(neLat) && !isEmpty(neLng)) { 52 | query_where.push( 53 | [ 54 | 'g.latitude >= ? AND g.latitude <= ?', 55 | [swLat, neLat] 56 | ] 57 | ); 58 | query_where.push( 59 | [ 60 | 'g.longitude >= ? AND g.longitude <= ?', 61 | [swLng, neLng] 62 | ] 63 | ); 64 | } 65 | 66 | if (timestamp) { 67 | // Change POSIX timestamp to UTC time. 68 | timestamp = new Date(timestamp).getTime(); 69 | 70 | query_where.push( 71 | [ 72 | 'g.last_scanned > ' + FROM_UNIXTIME, 73 | [Math.round(timestamp / 1000)] 74 | ] 75 | ); 76 | 77 | // Send Gyms in view but exclude those within old boundaries. 78 | // Don't use when timestamp is empty, it means we're intentionally trying to get 79 | // old gyms as well (new viewport or first request). 80 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) { 81 | query_where.push( 82 | [ 83 | 'g.latitude < ? AND g.latitude > ? AND g.longitude < ? AND g.longitude > ?', 84 | [oSwLat, oNeLat, oSwLng, oNeLng] 85 | ] 86 | ); 87 | } 88 | } 89 | 90 | // Prepare query. 91 | let query = ' WHERE '; 92 | let partials = []; 93 | let values = []; // Unnamed query params. 94 | 95 | // Add individual options. 96 | for (var i = 0; i < query_where.length; i++) { 97 | let w = query_where[i]; 98 | // w = [ 'query ?', [opt1] ] 99 | partials.push(w[0]); 100 | values = values.concat(w[1]); 101 | } 102 | query += partials.join(' AND '); 103 | 104 | // Set limit. 105 | query += ' LIMIT ' + GYM_LIMIT_PER_QUERY; 106 | 107 | return [ query, values ]; 108 | } 109 | 110 | function prepareGymPromise(query, params) { 111 | return new Promise((resolve, reject) => { 112 | db.query(query, params, (err, results, fields) => { 113 | if (err) { 114 | return reject(err); 115 | } else { 116 | // If there are no gyms, let's just go. 👀 117 | if (results.length == 0) { 118 | return resolve([]); 119 | } 120 | 121 | // Gym references. 122 | const gym_refs = {}; 123 | 124 | 125 | /* Add raids. */ 126 | 127 | // One query to rule them all. 128 | for (var i = 0; i < results.length; i++) { 129 | let gym = results[i]; 130 | 131 | // Avoid timezone issues. This is a UTC timestamp. 132 | gym.last_modified = gym.last_modified.replace(' ', 'T') + 'Z'; 133 | gym.last_scanned = gym.last_scanned.replace(' ', 'T') + 'Z'; 134 | 135 | // Convert datetime to UNIX timestamp. 136 | gym.last_modified = Date.parse(gym.last_modified) || 0; 137 | gym.last_scanned = Date.parse(gym.last_scanned) || 0; 138 | 139 | // Always send data, even for empty lists. RM expects it. 140 | gym.pokemon = []; 141 | 142 | gym_refs['' + gym.gym_id] = gym; 143 | } 144 | 145 | // Make it easier to use. 146 | const gym_ids = Object.keys(gym_refs); 147 | 148 | // Lesgo. 149 | Raid.from_gym_ids(gym_ids) 150 | .then((raids) => { 151 | // Attach raids to gyms. 152 | for (var i = 0; i < raids.length; i++) { 153 | const raid = raids[i]; 154 | 155 | // Avoid timezone issues. This is a UTC timestamp. 156 | raid.spawn = raid.spawn.replace(' ', 'T') + 'Z'; 157 | raid.start = raid.start.replace(' ', 'T') + 'Z'; 158 | raid.end = raid.end.replace(' ', 'T') + 'Z'; 159 | 160 | // Convert datetime to UNIX timestamp. 161 | raid.spawn = Date.parse(raid.spawn) || 0; 162 | raid.start = Date.parse(raid.start) || 0; 163 | raid.end = Date.parse(raid.end) || 0; 164 | 165 | // Assign Pokémon data. 166 | raid.pokemon_name = getPokemonName(pokedex, raid.pokemon_id) || ''; 167 | raid.pokemon_types = getPokemonTypes(pokedex, raid.pokemon_id) || []; 168 | 169 | gym_refs['' + raid.gym_id].raid = raid; 170 | } 171 | }) 172 | .then(() => GymMember.from_gym_ids(gym_ids)) 173 | .then((gymMembers) => { 174 | // Get gym Pokémon from gym members by 175 | // mapping pokemon_uid to gym member. 176 | const pokemon_uids = {}; 177 | 178 | if (gymMembers.length > 0) { 179 | for (var i = 0; i < gymMembers.length; i++) { 180 | const member = gymMembers[i]; 181 | pokemon_uids[member.pokemon_uid] = member; 182 | } 183 | 184 | return GymPokemon.from_pokemon_uids_map(pokemon_uids); 185 | } else { 186 | return { 187 | 'map': pokemon_uids, 188 | 'pokemon': [] 189 | } 190 | } 191 | }) 192 | .then((result) => { 193 | const map_obj = result.map; 194 | const gymPokes = result.pokemon; 195 | 196 | // Attach gym members to gyms. 197 | for (var i = 0; i < gymPokes.length; i++) { 198 | const poke = gymPokes[i]; 199 | const member = map_obj['' + poke.pokemon_uid] 200 | const gym_id = member.gym_id; 201 | const gym = gym_refs[gym_id]; 202 | 203 | // Avoid timezone issues. This is a UTC timestamp. 204 | poke.last_seen = poke.last_seen.replace(' ', 'T') + 'Z'; 205 | 206 | // Convert datetime to UNIX timestamp. 207 | poke.last_seen = Date.parse(poke.last_seen) || 0; 208 | 209 | // Assign member data to the Pokémon being sent. 210 | poke.cp_decayed = member.cp_decayed; 211 | poke.last_scanned = member.last_scanned; 212 | poke.deployment_time = member.deployment_time; 213 | 214 | // Assign Pokémon data. 215 | poke.pokemon_name = getPokemonName(pokedex, poke.pokemon_id) || ''; 216 | poke.pokemon_types = getPokemonTypes(pokedex, poke.pokemon_id) || []; 217 | 218 | gym.pokemon.push(poke); 219 | } 220 | 221 | const values = Object.keys(gym_refs).map((k) => gym_refs[k]); 222 | 223 | return resolve(values); 224 | }) 225 | .catch(utils.handle_error); 226 | } 227 | }); 228 | }); 229 | } 230 | 231 | 232 | /* Model. */ 233 | 234 | const tablename = 'gym'; 235 | const Gym = {}; 236 | 237 | // Get active Gyms by coords or timestamp. 238 | Gym.get_gyms = (swLat, swLng, neLat, neLng, timestamp, oSwLat, oSwLng, oNeLat, oNeLng) => { 239 | // Prepare query. 240 | const query_where = prepareQuery({ 241 | 'swLat': swLat, 242 | 'swLng': swLng, 243 | 'neLat': neLat, 244 | 'neLng': neLng, 245 | 'oSwLat': oSwLat, 246 | 'oSwLng': oSwLng, 247 | 'oNeLat': oNeLat, 248 | 'oNeLng': oNeLng, 249 | 'timestamp': timestamp 250 | }); 251 | 252 | const query = 'SELECT g.*, gd.name, gd.description, gd.url FROM ' + tablename + ' g INNER JOIN gymdetails gd ON g.gym_id = gd.gym_id' + query_where[0]; 253 | const params = query_where[1]; 254 | 255 | // Return promise. 256 | return prepareGymPromise(query, params); 257 | }; 258 | 259 | // Get single Gym + Pokémon in Gym by ID. 260 | Gym.get_gym = (id) => { 261 | // This is a simple one. 262 | const query = 'SELECT g.*, gd.name, gd.description, gd.url FROM ' + tablename + ' g INNER JOIN gymdetails gd ON g.gym_id = gd.gym_id WHERE gym_id = ? LIMIT 1'; 263 | const params = [ id ]; 264 | 265 | // Return promise. 266 | return prepareGymPromise(query, params); 267 | }; 268 | 269 | 270 | module.exports = Gym; 271 | -------------------------------------------------------------------------------- /models/GymMember.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Parse config. 4 | require('dotenv').config(); 5 | 6 | 7 | /* Includes. */ 8 | 9 | const db = require('../inc/db.js').pool; 10 | 11 | 12 | /* Helpers. */ 13 | 14 | // Make sure SQL uses proper timezone. 15 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')"; 16 | 17 | function prepareGymMemberPromise(query, params) { 18 | return new Promise((resolve, reject) => { 19 | db.query(query, params, (err, results, fields) => { 20 | if (err) { 21 | return reject(err); 22 | } else { 23 | return resolve(results); 24 | } 25 | }); 26 | }); 27 | } 28 | 29 | 30 | /* Model. */ 31 | 32 | const tablename = 'gymmember'; 33 | const GymMember = {}; 34 | 35 | // Get gym member by gym ID. 36 | GymMember.from_gym_id = (id) => { 37 | // This is a simple one. 38 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id = ?'; 39 | const params = [ id ]; 40 | 41 | // Return promise. 42 | return prepareGymMemberPromise(query, params); 43 | }; 44 | 45 | // Get gym members by gym IDs. 46 | GymMember.from_gym_ids = (ids) => { 47 | // This is another simple one. 48 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id IN (?)'; 49 | const params = [ ids ]; 50 | 51 | // Return promise. 52 | return prepareGymMemberPromise(query, params); 53 | }; 54 | 55 | 56 | module.exports = GymMember; -------------------------------------------------------------------------------- /models/GymPokemon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Parse config. 4 | require('dotenv').config(); 5 | 6 | 7 | /* Includes. */ 8 | 9 | const db = require('../inc/db.js').pool; 10 | 11 | 12 | /* Helpers. */ 13 | 14 | // Make sure SQL uses proper timezone. 15 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')"; 16 | 17 | function prepareGymPokemonPromise(query, params, map_object) { 18 | return new Promise((resolve, reject) => { 19 | db.query(query, params, (err, results, fields) => { 20 | if (err) { 21 | return reject(err); 22 | } else { 23 | return resolve({ 24 | 'map': map_object, 25 | 'pokemon': results 26 | }); 27 | } 28 | }); 29 | }); 30 | } 31 | 32 | 33 | /* Model. */ 34 | 35 | const tablename = 'gympokemon'; 36 | const GymPokemon = {}; 37 | 38 | // Get gym Pokémon by Pokémon uIDs. 39 | GymPokemon.from_pokemon_uids_map = (pokemon_uids_obj) => { 40 | // This is another simple one. 41 | const query = 'SELECT * FROM ' + tablename + ' WHERE pokemon_uid IN (?)'; 42 | const params = [ Object.keys(pokemon_uids_obj) ]; 43 | 44 | // Return promise. 45 | return prepareGymPokemonPromise(query, params, pokemon_uids_obj); 46 | }; 47 | 48 | 49 | module.exports = GymPokemon; -------------------------------------------------------------------------------- /models/Pokemon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Parse config. 4 | require('dotenv').config(); 5 | 6 | 7 | /* Includes. */ 8 | 9 | const db = require('../inc/db.js').pool; 10 | const utils = require('../inc/utils.js'); 11 | const debug = require('debug')('devkat:db:pokemon'); 12 | const debug_query = require('debug')('devkat:db:pokemon:sql'); 13 | const pokedex = require('../data/pokedex/pokemon.json'); 14 | 15 | 16 | /* Readability references. */ 17 | 18 | const isEmpty = utils.isEmpty; 19 | const getPokemonName = utils.pokemon.getPokemonName; 20 | const getPokemonTypes = utils.pokemon.getPokemonTypes; 21 | 22 | 23 | /* Settings. */ 24 | 25 | const POKEMON_LIMIT_PER_QUERY = parseInt(process.env.POKEMON_LIMIT_PER_QUERY) || 50000; 26 | 27 | 28 | /* Helpers. */ 29 | 30 | // Make sure SQL uses proper timezone. 31 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')"; 32 | 33 | function prepareQuery(options) { 34 | // Parse options. 35 | var whitelist = options.whitelist || []; 36 | var blacklist = options.blacklist || []; 37 | var swLat = options.swLat; 38 | var swLng = options.swLng; 39 | var neLat = options.neLat; 40 | var neLng = options.neLng; 41 | var oSwLat = options.oSwLat; 42 | var oSwLng = options.oSwLng; 43 | var oNeLat = options.oNeLat; 44 | var oNeLng = options.oNeLng; 45 | var timestamp = options.timestamp || false; 46 | 47 | // Query options. 48 | debug('Selecting Pokémon where disappear_time > %s.', Math.round(Date.now() / 1000)); 49 | var query_where = [ 50 | [ 51 | 'disappear_time > ' + FROM_UNIXTIME, 52 | [ Math.round(Date.now() / 1000) ] 53 | ] 54 | ]; 55 | 56 | // Optional viewport. 57 | if (!isEmpty(swLat) && !isEmpty(swLng) && !isEmpty(neLat) && !isEmpty(neLng)) { 58 | query_where.push( 59 | [ 60 | 'latitude >= ? AND latitude <= ?', 61 | [swLat, neLat] 62 | ] 63 | ); 64 | query_where.push( 65 | [ 66 | 'longitude >= ? AND longitude <= ?', 67 | [swLng, neLng] 68 | ] 69 | ); 70 | 71 | 72 | /* 73 | * TODO: If we have a viewport, use distance ordering? 74 | */ 75 | 76 | // Center of viewport. 77 | /*var viewport_width = neLng - swLng; 78 | var viewport_height = neLat - swLat; 79 | var middle_point_lat = neLat - (viewport_height / 2); 80 | var middle_point_lng = neLng - (viewport_width / 2); 81 | 82 | poke_options.attributes.include = [ 83 | [ 84 | // Calculate distance from middle point in viewport w/ MySQL. 85 | Sequelize.literal(` 86 | 3959 * 87 | acos(cos(radians(` + middle_point_lat + `)) * 88 | cos(radians(\`latitude\`)) * 89 | cos(radians(\`longitude\`) - 90 | radians(` + middle_point_lng + `)) + 91 | sin(radians(` + middle_point_lat + `)) * 92 | sin(radians(\`latitude\`))) 93 | `), 94 | 'distance' 95 | ] 96 | ]; 97 | 98 | poke_options.order.push(Sequelize.literal('`distance` ASC'));*/ 99 | } 100 | 101 | if (whitelist.length > 0) { 102 | query_where.push( 103 | [ 104 | 'pokemon_id IN (?)', 105 | [whitelist] 106 | ] 107 | ); 108 | } 109 | 110 | if (blacklist.length > 0) { 111 | query_where.push( 112 | [ 113 | 'pokemon_id NOT IN (?)', 114 | [blacklist] 115 | ] 116 | ); 117 | } 118 | 119 | // If timestamp is known, only load modified Pokemon. 120 | if (timestamp) { 121 | // Change POSIX timestamp to UTC time. 122 | timestamp = new Date(timestamp).getTime(); 123 | 124 | query_where.push( 125 | [ 126 | 'last_modified > ' + FROM_UNIXTIME, 127 | [Math.round(timestamp / 1000)] 128 | ] 129 | ); 130 | 131 | // Send Pokémon in view but exclude those within old boundaries. 132 | // Don't use when timestamp is empty, it means we're intentionally trying to get 133 | // old Pokémon as well (new viewport or first request). 134 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) { 135 | query_where.push( 136 | [ 137 | 'latitude < ? AND latitude > ? AND longitude < ? AND longitude > ?', 138 | [oSwLat, oNeLat, oSwLng, oNeLng] 139 | ] 140 | ); 141 | } 142 | } else { 143 | debug('Request without timestamp.'); 144 | } 145 | 146 | // Prepare query. 147 | let query = ' WHERE '; 148 | let partials = []; 149 | let values = []; // Unnamed query params. 150 | 151 | // Add individual options. 152 | for (var i = 0; i < query_where.length; i++) { 153 | let w = query_where[i]; 154 | // w = [ 'query ?', [opt1] ] 155 | partials.push(w[0]); 156 | values = values.concat(w[1]); 157 | } 158 | query += partials.join(' AND '); 159 | 160 | // Set limit. 161 | query += ' LIMIT ' + POKEMON_LIMIT_PER_QUERY; 162 | 163 | return [ query, values ]; 164 | } 165 | 166 | function preparePokemonPromise(query, params) { 167 | return new Promise((resolve, reject) => { 168 | db.query(query, params, (err, results, fields) => { 169 | if (err) { 170 | return reject(err); 171 | } else { 172 | // Manipulate response. 173 | for (var i = 0; i < results.length; i++) { 174 | let poke = results[i]; 175 | let pokemon_id = poke.pokemon_id; 176 | 177 | // Avoid timezone issues. This is a UTC timestamp. 178 | poke.disappear_time = poke.disappear_time.replace(' ', 'T') + 'Z'; 179 | poke.last_modified = poke.last_modified.replace(' ', 'T') + 'Z'; 180 | 181 | // Add name/types and transform times. Destructive. 182 | poke.disappear_time = Date.parse(poke.disappear_time) || 0; 183 | poke.last_modified = Date.parse(poke.last_modified) || 0; 184 | poke.pokemon_name = getPokemonName(pokedex, pokemon_id) || ''; 185 | poke.pokemon_types = getPokemonTypes(pokedex, pokemon_id) || []; 186 | } 187 | 188 | return resolve(results); 189 | } 190 | }); 191 | }); 192 | } 193 | 194 | 195 | /* Model. */ 196 | 197 | const tablename = 'pokemon'; 198 | const Pokemon = {}; 199 | 200 | Pokemon.get_active = (excluded, swLat, swLng, neLat, neLng, timestamp, oSwLat, oSwLng, oNeLat, oNeLng) => { 201 | // Prepare query. 202 | const query_where = prepareQuery({ 203 | 'blacklist': excluded, 204 | 'swLat': swLat, 205 | 'swLng': swLng, 206 | 'neLat': neLat, 207 | 'neLng': neLng, 208 | 'oSwLat': oSwLat, 209 | 'oSwLng': oSwLng, 210 | 'oNeLat': oNeLat, 211 | 'oNeLng': oNeLng, 212 | 'timestamp': timestamp 213 | }); 214 | 215 | const query = 'SELECT * FROM ' + tablename + query_where[0]; 216 | const params = query_where[1]; 217 | 218 | debug_query(query_where); 219 | 220 | // Return promise. 221 | return preparePokemonPromise(query, params); 222 | }; 223 | 224 | Pokemon.get_active_by_ids = (ids, excluded, swLat, swLng, neLat, neLng) => { 225 | // Query options. 226 | const query_where = prepareQuery({ 227 | 'whitelist': ids, 228 | 'blacklist': excluded, 229 | 'swLat': swLat, 230 | 'swLng': swLng, 231 | 'neLat': neLat, 232 | 'neLng': neLng 233 | }); 234 | 235 | const query = 'SELECT * FROM ' + tablename + query_where[0]; 236 | const params = query_where[1]; 237 | 238 | debug_query(query_where); 239 | 240 | // Return promise. 241 | return preparePokemonPromise(query, params); 242 | }; 243 | 244 | module.exports = Pokemon; 245 | -------------------------------------------------------------------------------- /models/Pokestop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Parse config. 4 | require('dotenv').config(); 5 | 6 | 7 | /* Includes. */ 8 | 9 | const db = require('../inc/db.js').pool; 10 | const utils = require('../inc/utils.js'); 11 | 12 | 13 | /* Readability references. */ 14 | 15 | var isEmpty = utils.isEmpty; 16 | 17 | 18 | /* Settings. */ 19 | 20 | const POKESTOP_LIMIT_PER_QUERY = parseInt(process.env.POKESTOP_LIMIT_PER_QUERY) || 50000; 21 | 22 | 23 | /* Helpers. */ 24 | 25 | // Make sure SQL uses proper timezone. 26 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')"; 27 | 28 | function prepareQueryOptions(options) { 29 | // Parse options. 30 | var swLat = options.swLat; 31 | var swLng = options.swLng; 32 | var neLat = options.neLat; 33 | var neLng = options.neLng; 34 | var oSwLat = options.oSwLat; 35 | var oSwLng = options.oSwLng; 36 | var oNeLat = options.oNeLat; 37 | var oNeLng = options.oNeLng; 38 | var timestamp = options.timestamp || false; 39 | var lured = options.lured || false; 40 | 41 | // Query options. 42 | var query_where = []; 43 | 44 | // Optional viewport. 45 | if (!isEmpty(swLat) && !isEmpty(swLng) && !isEmpty(neLat) && !isEmpty(neLng)) { 46 | query_where.push( 47 | [ 48 | 'latitude >= ? AND latitude <= ?', 49 | [swLat, neLat] 50 | ] 51 | ); 52 | query_where.push( 53 | [ 54 | 'longitude >= ? AND longitude <= ?', 55 | [swLng, neLng] 56 | ] 57 | ); 58 | } 59 | 60 | if (timestamp) { 61 | // Change POSIX timestamp to UTC time. 62 | timestamp = new Date(timestamp).getTime(); 63 | 64 | query_where.push( 65 | [ 66 | 'last_updated > ' + FROM_UNIXTIME, 67 | [Math.round(timestamp / 1000)] 68 | ] 69 | ); 70 | 71 | // Send Pokéstops in view but exclude those within old boundaries. 72 | // Don't use when timestamp is empty, it means we're intentionally trying to get 73 | // old Pokéstops as well (new viewport or first request). 74 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) { 75 | query_where.push( 76 | [ 77 | 'latitude < ? AND latitude > ? AND longitude < ? AND longitude > ?', 78 | [oSwLat, oNeLat, oSwLng, oNeLng] 79 | ] 80 | ); 81 | } 82 | } 83 | 84 | // Lured stops. 85 | if (lured) { 86 | query_where.push( 87 | [ 88 | 'active_fort_modifier IS NOT NULL', 89 | [] 90 | ] 91 | ); 92 | } 93 | 94 | // Prepare query. 95 | let query = ' WHERE '; 96 | let partials = []; 97 | let values = []; // Unnamed query params. 98 | 99 | // Add individual options. 100 | for (var i = 0; i < query_where.length; i++) { 101 | let w = query_where[i]; 102 | // w = [ 'query ?', [opt1] ] 103 | partials.push(w[0]); 104 | values = values.concat(w[1]); 105 | } 106 | query += partials.join(' AND '); 107 | 108 | // Set limit. 109 | query += ' LIMIT ' + POKESTOP_LIMIT_PER_QUERY; 110 | 111 | return [ query, values ]; 112 | } 113 | 114 | function preparePokestopPromise(query, params) { 115 | return new Promise((resolve, reject) => { 116 | db.query(query, params, (err, results, fields) => { 117 | if (err) { 118 | reject(err); 119 | } else { 120 | // If there are no pokéstops, let's just go. 👀 121 | if (results.length == 0) { 122 | return resolve(results); 123 | } 124 | 125 | // Manipulate pokéstops, destructive operations. 126 | for (var i = 0; i < results.length; i++) { 127 | let pokestop = results[i]; 128 | 129 | // Avoid timezone issues. This is a UTC timestamp. 130 | pokestop.last_modified = pokestop.last_modified.replace(' ', 'T') + 'Z'; 131 | pokestop.last_updated = pokestop.last_updated.replace(' ', 'T') + 'Z'; 132 | 133 | if (pokestop.lure_expiration) { 134 | pokestop.lure_expiration = pokestop.lure_expiration.replace(' ', 'T') + 'Z'; 135 | } 136 | 137 | // Convert datetime to UNIX timestamp. 138 | pokestop.last_modified = Date.parse(pokestop.last_modified) || 0; 139 | pokestop.last_updated = Date.parse(pokestop.last_updated) || 0; 140 | pokestop.lure_expiration = Date.parse(pokestop.lure_expiration) || 0; 141 | } 142 | 143 | return resolve(results); 144 | } 145 | }); 146 | }); 147 | } 148 | 149 | 150 | /* Model. */ 151 | 152 | const tablename = 'pokestop'; 153 | const Pokestop = {}; 154 | 155 | // Get active Pokéstops by coords or timestamp. 156 | Pokestop.get_stops = (swLat, swLng, neLat, neLng, lured, timestamp, oSwLat, oSwLng, oNeLat, oNeLng) => { 157 | // Prepare query. 158 | var query_where = prepareQueryOptions({ 159 | 'swLat': swLat, 160 | 'swLng': swLng, 161 | 'neLat': neLat, 162 | 'neLng': neLng, 163 | 'oSwLat': oSwLat, 164 | 'oSwLng': oSwLng, 165 | 'oNeLat': oNeLat, 166 | 'oNeLng': oNeLng, 167 | 'lured': lured, 168 | 'timestamp': timestamp 169 | }); 170 | 171 | const query = 'SELECT * FROM ' + tablename + query_where[0]; 172 | const params = query_where[1]; 173 | 174 | // Return promise. 175 | return preparePokestopPromise(query, params); 176 | }; 177 | 178 | module.exports = Pokestop; -------------------------------------------------------------------------------- /models/Raid.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Parse config. 4 | require('dotenv').config(); 5 | 6 | 7 | /* Includes. */ 8 | 9 | const db = require('../inc/db.js').pool; 10 | 11 | 12 | /* Helpers. */ 13 | 14 | // Make sure SQL uses proper timezone. 15 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')"; 16 | 17 | function prepareRaidPromise(query, params) { 18 | return new Promise((resolve, reject) => { 19 | db.query(query, params, (err, results, fields) => { 20 | if (err) { 21 | return reject(err); 22 | } else { 23 | return resolve(results); 24 | } 25 | }); 26 | }); 27 | } 28 | 29 | 30 | /* Model. */ 31 | 32 | const tablename = 'raid'; 33 | const Raid = {}; 34 | 35 | // Get raid by gym ID. 36 | Raid.from_gym_id = (id) => { 37 | // This is a simple one. 38 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id = ? LIMIT 1'; 39 | const params = [ id ]; 40 | 41 | // Return promise. 42 | return prepareRaidPromise(query, params); 43 | }; 44 | 45 | // Get raids by gym IDs. 46 | Raid.from_gym_ids = (ids) => { 47 | // This is another simple one. 48 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id IN (?)'; 49 | const params = [ ids ]; 50 | 51 | // Return promise. 52 | return prepareRaidPromise(query, params); 53 | }; 54 | 55 | 56 | module.exports = Raid; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rm-node-webserver", 3 | "version": "2.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "asn1": { 8 | "version": "0.2.3", 9 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 10 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 11 | }, 12 | "bcrypt-pbkdf": { 13 | "version": "1.0.1", 14 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 15 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 16 | "optional": true, 17 | "requires": { 18 | "tweetnacl": "0.14.5" 19 | } 20 | }, 21 | "bunyan": { 22 | "version": "1.8.12", 23 | "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", 24 | "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", 25 | "requires": { 26 | "dtrace-provider": "0.8.5", 27 | "moment": "2.19.1", 28 | "mv": "2.1.1", 29 | "safe-json-stringify": "1.0.4" 30 | }, 31 | "dependencies": { 32 | "moment": { 33 | "version": "2.19.1", 34 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz", 35 | "integrity": "sha1-VtoaLRy/AdOLfhr8McELz6GSkWc=", 36 | "optional": true 37 | } 38 | } 39 | }, 40 | "clone-regexp": { 41 | "version": "1.0.0", 42 | "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.0.tgz", 43 | "integrity": "sha1-6uCiQT9VwJQvgYwin+/OhF1/Oxw=", 44 | "requires": { 45 | "is-regexp": "1.0.0", 46 | "is-supported-regexp-flag": "1.0.0" 47 | } 48 | }, 49 | "cluster": { 50 | "version": "0.7.7", 51 | "resolved": "https://registry.npmjs.org/cluster/-/cluster-0.7.7.tgz", 52 | "integrity": "sha1-5JfiZ8yVa9CwUTrbSqOTNX0Ahe8=", 53 | "requires": { 54 | "log": "1.4.0", 55 | "mkdirp": "0.5.1" 56 | }, 57 | "dependencies": { 58 | "log": { 59 | "version": "1.4.0", 60 | "resolved": "https://registry.npmjs.org/log/-/log-1.4.0.tgz", 61 | "integrity": "sha1-S6HYkP3iSbAx3KA7w36q8yVlbxw=" 62 | }, 63 | "mkdirp": { 64 | "version": "0.5.1", 65 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 66 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 67 | "requires": { 68 | "minimist": "0.0.8" 69 | } 70 | } 71 | } 72 | }, 73 | "csv": { 74 | "version": "1.2.1", 75 | "resolved": "https://registry.npmjs.org/csv/-/csv-1.2.1.tgz", 76 | "integrity": "sha1-UjHt/BxxUlEuxFeBB2p6l/9SXAw=", 77 | "requires": { 78 | "csv-generate": "1.1.2", 79 | "csv-parse": "1.3.3", 80 | "csv-stringify": "1.1.2", 81 | "stream-transform": "0.2.2" 82 | } 83 | }, 84 | "csv-generate": { 85 | "version": "1.1.2", 86 | "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-1.1.2.tgz", 87 | "integrity": "sha1-7GsA7a7W5ZrZwgWC9MNk4osUYkA=" 88 | }, 89 | "csv-parse": { 90 | "version": "1.3.3", 91 | "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-1.3.3.tgz", 92 | "integrity": "sha1-0c/YdDwvhJoKuy/VRNtWaV0ZpJA=" 93 | }, 94 | "csv-stringify": { 95 | "version": "1.1.2", 96 | "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-1.1.2.tgz", 97 | "integrity": "sha1-d6QVJlgbzjOA8SsA18W7rHDIK1g=", 98 | "requires": { 99 | "lodash.get": "4.4.2" 100 | } 101 | }, 102 | "dashdash": { 103 | "version": "1.14.1", 104 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 105 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 106 | "requires": { 107 | "assert-plus": "1.0.0" 108 | }, 109 | "dependencies": { 110 | "assert-plus": { 111 | "version": "1.0.0", 112 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 113 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 114 | } 115 | } 116 | }, 117 | "debug": { 118 | "version": "3.1.0", 119 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 120 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 121 | "requires": { 122 | "ms": "2.0.0" 123 | }, 124 | "dependencies": { 125 | "ms": { 126 | "version": "2.0.0", 127 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 128 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 129 | } 130 | } 131 | }, 132 | "detect-node": { 133 | "version": "2.0.3", 134 | "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", 135 | "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc=" 136 | }, 137 | "dotenv": { 138 | "version": "4.0.0", 139 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz", 140 | "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=" 141 | }, 142 | "dtrace-provider": { 143 | "version": "0.8.5", 144 | "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.5.tgz", 145 | "integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=", 146 | "optional": true, 147 | "requires": { 148 | "nan": "2.7.0" 149 | }, 150 | "dependencies": { 151 | "nan": { 152 | "version": "2.7.0", 153 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", 154 | "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", 155 | "optional": true 156 | } 157 | } 158 | }, 159 | "ecc-jsbn": { 160 | "version": "0.1.1", 161 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 162 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 163 | "optional": true, 164 | "requires": { 165 | "jsbn": "0.1.1" 166 | } 167 | }, 168 | "escape-regexp-component": { 169 | "version": "1.0.2", 170 | "resolved": "https://registry.npmjs.org/escape-regexp-component/-/escape-regexp-component-1.0.2.tgz", 171 | "integrity": "sha1-nGO20LJf8qiMOtvRjFthrMO5+qI=" 172 | }, 173 | "ewma": { 174 | "version": "2.0.1", 175 | "resolved": "https://registry.npmjs.org/ewma/-/ewma-2.0.1.tgz", 176 | "integrity": "sha512-MYYK17A76cuuyvkR7MnqLW4iFYPEi5Isl2qb8rXiWpLiwFS9dxW/rncuNnjjgSENuVqZQkIuR4+DChVL4g1lnw==", 177 | "requires": { 178 | "assert-plus": "1.0.0" 179 | }, 180 | "dependencies": { 181 | "assert-plus": { 182 | "version": "1.0.0", 183 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 184 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 185 | } 186 | } 187 | }, 188 | "extsprintf": { 189 | "version": "1.0.2", 190 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", 191 | "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" 192 | }, 193 | "formidable": { 194 | "version": "1.1.1", 195 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", 196 | "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" 197 | }, 198 | "getpass": { 199 | "version": "0.1.7", 200 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 201 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 202 | "requires": { 203 | "assert-plus": "1.0.0" 204 | }, 205 | "dependencies": { 206 | "assert-plus": { 207 | "version": "1.0.0", 208 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 209 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 210 | } 211 | } 212 | }, 213 | "glob": { 214 | "version": "6.0.4", 215 | "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", 216 | "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", 217 | "optional": true, 218 | "requires": { 219 | "inflight": "1.0.6", 220 | "inherits": "2.0.3", 221 | "minimatch": "3.0.4", 222 | "once": "1.4.0", 223 | "path-is-absolute": "1.0.1" 224 | }, 225 | "dependencies": { 226 | "balanced-match": { 227 | "version": "1.0.0", 228 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 229 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 230 | "optional": true 231 | }, 232 | "brace-expansion": { 233 | "version": "1.1.8", 234 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", 235 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", 236 | "optional": true, 237 | "requires": { 238 | "balanced-match": "1.0.0", 239 | "concat-map": "0.0.1" 240 | } 241 | }, 242 | "concat-map": { 243 | "version": "0.0.1", 244 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 245 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 246 | "optional": true 247 | }, 248 | "inflight": { 249 | "version": "1.0.6", 250 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 251 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 252 | "optional": true, 253 | "requires": { 254 | "once": "1.4.0", 255 | "wrappy": "1.0.2" 256 | } 257 | }, 258 | "inherits": { 259 | "version": "2.0.3", 260 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 261 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 262 | "optional": true 263 | }, 264 | "minimatch": { 265 | "version": "3.0.4", 266 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 267 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 268 | "optional": true, 269 | "requires": { 270 | "brace-expansion": "1.1.8" 271 | } 272 | }, 273 | "once": { 274 | "version": "1.4.0", 275 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 276 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 277 | "requires": { 278 | "wrappy": "1.0.2" 279 | } 280 | }, 281 | "path-is-absolute": { 282 | "version": "1.0.1", 283 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 284 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 285 | "optional": true 286 | }, 287 | "wrappy": { 288 | "version": "1.0.2", 289 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 290 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 291 | } 292 | } 293 | }, 294 | "handle-thing": { 295 | "version": "1.2.5", 296 | "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", 297 | "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=" 298 | }, 299 | "has-flag": { 300 | "version": "2.0.0", 301 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 302 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" 303 | }, 304 | "hpack.js": { 305 | "version": "2.1.6", 306 | "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", 307 | "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", 308 | "requires": { 309 | "inherits": "2.0.3", 310 | "obuf": "1.1.1", 311 | "readable-stream": "2.3.3", 312 | "wbuf": "1.7.2" 313 | }, 314 | "dependencies": { 315 | "core-util-is": { 316 | "version": "1.0.2", 317 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 318 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 319 | }, 320 | "inherits": { 321 | "version": "2.0.3", 322 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 323 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 324 | }, 325 | "isarray": { 326 | "version": "1.0.0", 327 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 328 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 329 | }, 330 | "readable-stream": { 331 | "version": "2.3.3", 332 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 333 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 334 | "requires": { 335 | "core-util-is": "1.0.2", 336 | "inherits": "2.0.3", 337 | "isarray": "1.0.0", 338 | "process-nextick-args": "1.0.7", 339 | "safe-buffer": "5.1.1", 340 | "string_decoder": "1.0.3", 341 | "util-deprecate": "1.0.2" 342 | } 343 | }, 344 | "string_decoder": { 345 | "version": "1.0.3", 346 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 347 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 348 | "requires": { 349 | "safe-buffer": "5.1.1" 350 | } 351 | } 352 | } 353 | }, 354 | "http-deceiver": { 355 | "version": "1.2.7", 356 | "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", 357 | "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=" 358 | }, 359 | "is-regexp": { 360 | "version": "1.0.0", 361 | "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", 362 | "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" 363 | }, 364 | "is-supported-regexp-flag": { 365 | "version": "1.0.0", 366 | "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz", 367 | "integrity": "sha1-i1IMhfrnolM4LUsCZS4EVXbhO7g=" 368 | }, 369 | "jsbn": { 370 | "version": "0.1.1", 371 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 372 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 373 | "optional": true 374 | }, 375 | "json-schema": { 376 | "version": "0.2.3", 377 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 378 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 379 | }, 380 | "jsprim": { 381 | "version": "1.4.0", 382 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", 383 | "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", 384 | "requires": { 385 | "assert-plus": "1.0.0", 386 | "extsprintf": "1.0.2", 387 | "json-schema": "0.2.3", 388 | "verror": "1.3.6" 389 | }, 390 | "dependencies": { 391 | "assert-plus": { 392 | "version": "1.0.0", 393 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 394 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 395 | } 396 | } 397 | }, 398 | "lodash.get": { 399 | "version": "4.4.2", 400 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 401 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" 402 | }, 403 | "minimalistic-assert": { 404 | "version": "1.0.0", 405 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", 406 | "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M=" 407 | }, 408 | "minimist": { 409 | "version": "0.0.8", 410 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 411 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 412 | }, 413 | "mv": { 414 | "version": "2.1.1", 415 | "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", 416 | "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", 417 | "optional": true, 418 | "requires": { 419 | "mkdirp": "0.5.1", 420 | "ncp": "2.0.0", 421 | "rimraf": "2.4.5" 422 | }, 423 | "dependencies": { 424 | "mkdirp": { 425 | "version": "0.5.1", 426 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 427 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 428 | "optional": true, 429 | "requires": { 430 | "minimist": "0.0.8" 431 | } 432 | }, 433 | "rimraf": { 434 | "version": "2.4.5", 435 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", 436 | "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", 437 | "optional": true, 438 | "requires": { 439 | "glob": "6.0.4" 440 | } 441 | } 442 | } 443 | }, 444 | "mysql": { 445 | "version": "2.15.0", 446 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.15.0.tgz", 447 | "integrity": "sha512-C7tjzWtbN5nzkLIV+E8Crnl9bFyc7d3XJcBAvHKEVkjrYjogz3llo22q6s/hw+UcsE4/844pDob9ac+3dVjQSA==", 448 | "requires": { 449 | "bignumber.js": "4.0.4", 450 | "readable-stream": "2.3.3", 451 | "safe-buffer": "5.1.1", 452 | "sqlstring": "2.3.0" 453 | }, 454 | "dependencies": { 455 | "bignumber.js": { 456 | "version": "4.0.4", 457 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.4.tgz", 458 | "integrity": "sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg==" 459 | }, 460 | "core-util-is": { 461 | "version": "1.0.2", 462 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 463 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 464 | }, 465 | "inherits": { 466 | "version": "2.0.3", 467 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 468 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 469 | }, 470 | "isarray": { 471 | "version": "1.0.0", 472 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 473 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 474 | }, 475 | "readable-stream": { 476 | "version": "2.3.3", 477 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 478 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 479 | "requires": { 480 | "core-util-is": "1.0.2", 481 | "inherits": "2.0.3", 482 | "isarray": "1.0.0", 483 | "process-nextick-args": "1.0.7", 484 | "safe-buffer": "5.1.1", 485 | "string_decoder": "1.0.3", 486 | "util-deprecate": "1.0.2" 487 | } 488 | }, 489 | "sqlstring": { 490 | "version": "2.3.0", 491 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.0.tgz", 492 | "integrity": "sha1-UluKT9Jtb3GqYegipsr5dtMa0qg=" 493 | }, 494 | "string_decoder": { 495 | "version": "1.0.3", 496 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 497 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 498 | "requires": { 499 | "safe-buffer": "5.1.1" 500 | } 501 | } 502 | } 503 | }, 504 | "ncp": { 505 | "version": "2.0.0", 506 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", 507 | "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", 508 | "optional": true 509 | }, 510 | "obuf": { 511 | "version": "1.1.1", 512 | "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", 513 | "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=" 514 | }, 515 | "pidusage": { 516 | "version": "1.1.6", 517 | "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-1.1.6.tgz", 518 | "integrity": "sha512-ua0CWpD05cMMumfoeUcF1zcvpDkkWxedbkLfGQWwkTsHVkaBPUs7OY/0OmnVU/CrcEhRCwCx5VJ4nMKjnsgGIg==" 519 | }, 520 | "process-nextick-args": { 521 | "version": "1.0.7", 522 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 523 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" 524 | }, 525 | "restify": { 526 | "version": "6.2.3", 527 | "resolved": "https://registry.npmjs.org/restify/-/restify-6.2.3.tgz", 528 | "integrity": "sha1-bPtHNZeday0iv4f3vfyn6Tkytng=", 529 | "requires": { 530 | "assert-plus": "1.0.0", 531 | "bunyan": "1.8.12", 532 | "clone-regexp": "1.0.0", 533 | "csv": "1.2.1", 534 | "dtrace-provider": "0.8.5", 535 | "escape-regexp-component": "1.0.2", 536 | "ewma": "2.0.1", 537 | "formidable": "1.1.1", 538 | "http-signature": "1.2.0", 539 | "lodash": "4.17.4", 540 | "lru-cache": "4.1.1", 541 | "mime": "1.4.1", 542 | "negotiator": "0.6.1", 543 | "once": "1.4.0", 544 | "pidusage": "1.1.6", 545 | "qs": "6.5.1", 546 | "restify-errors": "5.0.0", 547 | "semver": "5.4.1", 548 | "spdy": "3.4.7", 549 | "uuid": "3.1.0", 550 | "vasync": "1.6.4", 551 | "verror": "1.10.0" 552 | }, 553 | "dependencies": { 554 | "assert-plus": { 555 | "version": "1.0.0", 556 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 557 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 558 | }, 559 | "core-util-is": { 560 | "version": "1.0.2", 561 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 562 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 563 | }, 564 | "extsprintf": { 565 | "version": "1.3.0", 566 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 567 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 568 | }, 569 | "http-signature": { 570 | "version": "1.2.0", 571 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 572 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 573 | "requires": { 574 | "assert-plus": "1.0.0", 575 | "jsprim": "1.4.0", 576 | "sshpk": "1.13.1" 577 | } 578 | }, 579 | "lodash": { 580 | "version": "4.17.4", 581 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 582 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 583 | }, 584 | "lru-cache": { 585 | "version": "4.1.1", 586 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", 587 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", 588 | "requires": { 589 | "pseudomap": "1.0.2", 590 | "yallist": "2.1.2" 591 | } 592 | }, 593 | "mime": { 594 | "version": "1.4.1", 595 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 596 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 597 | }, 598 | "negotiator": { 599 | "version": "0.6.1", 600 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 601 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 602 | }, 603 | "once": { 604 | "version": "1.4.0", 605 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 606 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 607 | "requires": { 608 | "wrappy": "1.0.2" 609 | } 610 | }, 611 | "pseudomap": { 612 | "version": "1.0.2", 613 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", 614 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" 615 | }, 616 | "qs": { 617 | "version": "6.5.1", 618 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 619 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 620 | }, 621 | "semver": { 622 | "version": "5.4.1", 623 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", 624 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" 625 | }, 626 | "uuid": { 627 | "version": "3.1.0", 628 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 629 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 630 | }, 631 | "verror": { 632 | "version": "1.10.0", 633 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 634 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 635 | "requires": { 636 | "assert-plus": "1.0.0", 637 | "core-util-is": "1.0.2", 638 | "extsprintf": "1.3.0" 639 | } 640 | }, 641 | "wrappy": { 642 | "version": "1.0.2", 643 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 644 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 645 | }, 646 | "yallist": { 647 | "version": "2.1.2", 648 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", 649 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" 650 | } 651 | } 652 | }, 653 | "restify-cors-middleware": { 654 | "version": "1.0.1", 655 | "resolved": "https://registry.npmjs.org/restify-cors-middleware/-/restify-cors-middleware-1.0.1.tgz", 656 | "integrity": "sha1-Y4jQ1S0obiJlATPRUaidSLIvoKs=", 657 | "requires": { 658 | "assert-plus": "1.0.0" 659 | }, 660 | "dependencies": { 661 | "assert-plus": { 662 | "version": "1.0.0", 663 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 664 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 665 | } 666 | } 667 | }, 668 | "restify-errors": { 669 | "version": "5.0.0", 670 | "resolved": "https://registry.npmjs.org/restify-errors/-/restify-errors-5.0.0.tgz", 671 | "integrity": "sha512-+vby9Kxf7qlzvbZSTIEGkIixkeHG+pVCl34dk6eKnL+ua4pCezpdLT/1/eabzPZb65ADrgoc04jeWrrF1E1pvQ==", 672 | "requires": { 673 | "assert-plus": "1.0.0", 674 | "lodash": "4.17.4", 675 | "safe-json-stringify": "1.0.4", 676 | "verror": "1.10.0" 677 | }, 678 | "dependencies": { 679 | "assert-plus": { 680 | "version": "1.0.0", 681 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 682 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 683 | }, 684 | "core-util-is": { 685 | "version": "1.0.2", 686 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 687 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 688 | }, 689 | "extsprintf": { 690 | "version": "1.3.0", 691 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 692 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 693 | }, 694 | "lodash": { 695 | "version": "4.17.4", 696 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", 697 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" 698 | }, 699 | "verror": { 700 | "version": "1.10.0", 701 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 702 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 703 | "requires": { 704 | "assert-plus": "1.0.0", 705 | "core-util-is": "1.0.2", 706 | "extsprintf": "1.3.0" 707 | } 708 | } 709 | } 710 | }, 711 | "safe-buffer": { 712 | "version": "5.1.1", 713 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 714 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 715 | }, 716 | "safe-json-stringify": { 717 | "version": "1.0.4", 718 | "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", 719 | "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=", 720 | "optional": true 721 | }, 722 | "select-hose": { 723 | "version": "2.0.0", 724 | "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", 725 | "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=" 726 | }, 727 | "spdy": { 728 | "version": "3.4.7", 729 | "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz", 730 | "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=", 731 | "requires": { 732 | "debug": "2.6.9", 733 | "handle-thing": "1.2.5", 734 | "http-deceiver": "1.2.7", 735 | "safe-buffer": "5.1.1", 736 | "select-hose": "2.0.0", 737 | "spdy-transport": "2.0.20" 738 | }, 739 | "dependencies": { 740 | "debug": { 741 | "version": "2.6.9", 742 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 743 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 744 | "requires": { 745 | "ms": "2.0.0" 746 | } 747 | }, 748 | "ms": { 749 | "version": "2.0.0", 750 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 751 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 752 | } 753 | } 754 | }, 755 | "spdy-transport": { 756 | "version": "2.0.20", 757 | "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz", 758 | "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=", 759 | "requires": { 760 | "debug": "2.6.9", 761 | "detect-node": "2.0.3", 762 | "hpack.js": "2.1.6", 763 | "obuf": "1.1.1", 764 | "readable-stream": "2.3.3", 765 | "safe-buffer": "5.1.1", 766 | "wbuf": "1.7.2" 767 | }, 768 | "dependencies": { 769 | "core-util-is": { 770 | "version": "1.0.2", 771 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 772 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 773 | }, 774 | "debug": { 775 | "version": "2.6.9", 776 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 777 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 778 | "requires": { 779 | "ms": "2.0.0" 780 | } 781 | }, 782 | "inherits": { 783 | "version": "2.0.3", 784 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 785 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 786 | }, 787 | "isarray": { 788 | "version": "1.0.0", 789 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 790 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 791 | }, 792 | "ms": { 793 | "version": "2.0.0", 794 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 795 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 796 | }, 797 | "readable-stream": { 798 | "version": "2.3.3", 799 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", 800 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", 801 | "requires": { 802 | "core-util-is": "1.0.2", 803 | "inherits": "2.0.3", 804 | "isarray": "1.0.0", 805 | "process-nextick-args": "1.0.7", 806 | "safe-buffer": "5.1.1", 807 | "string_decoder": "1.0.3", 808 | "util-deprecate": "1.0.2" 809 | } 810 | }, 811 | "string_decoder": { 812 | "version": "1.0.3", 813 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", 814 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", 815 | "requires": { 816 | "safe-buffer": "5.1.1" 817 | } 818 | } 819 | } 820 | }, 821 | "sshpk": { 822 | "version": "1.13.1", 823 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", 824 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", 825 | "requires": { 826 | "asn1": "0.2.3", 827 | "assert-plus": "1.0.0", 828 | "bcrypt-pbkdf": "1.0.1", 829 | "dashdash": "1.14.1", 830 | "ecc-jsbn": "0.1.1", 831 | "getpass": "0.1.7", 832 | "jsbn": "0.1.1", 833 | "tweetnacl": "0.14.5" 834 | }, 835 | "dependencies": { 836 | "assert-plus": { 837 | "version": "1.0.0", 838 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 839 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 840 | } 841 | } 842 | }, 843 | "stream-transform": { 844 | "version": "0.2.2", 845 | "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-0.2.2.tgz", 846 | "integrity": "sha1-dYZ0h/SVKPi/HYJJllh1PQLfeDg=" 847 | }, 848 | "supports-color": { 849 | "version": "4.5.0", 850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", 851 | "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", 852 | "requires": { 853 | "has-flag": "2.0.0" 854 | } 855 | }, 856 | "toobusy-js": { 857 | "version": "0.5.1", 858 | "resolved": "https://registry.npmjs.org/toobusy-js/-/toobusy-js-0.5.1.tgz", 859 | "integrity": "sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk=" 860 | }, 861 | "tweetnacl": { 862 | "version": "0.14.5", 863 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 864 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 865 | "optional": true 866 | }, 867 | "util-deprecate": { 868 | "version": "1.0.2", 869 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 870 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 871 | }, 872 | "vasync": { 873 | "version": "1.6.4", 874 | "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz", 875 | "integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=", 876 | "requires": { 877 | "verror": "1.6.0" 878 | }, 879 | "dependencies": { 880 | "extsprintf": { 881 | "version": "1.2.0", 882 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.2.0.tgz", 883 | "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk=" 884 | }, 885 | "verror": { 886 | "version": "1.6.0", 887 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz", 888 | "integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=", 889 | "requires": { 890 | "extsprintf": "1.2.0" 891 | } 892 | } 893 | } 894 | }, 895 | "verror": { 896 | "version": "1.3.6", 897 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", 898 | "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", 899 | "requires": { 900 | "extsprintf": "1.0.2" 901 | } 902 | }, 903 | "wbuf": { 904 | "version": "1.7.2", 905 | "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", 906 | "integrity": "sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=", 907 | "requires": { 908 | "minimalistic-assert": "1.0.0" 909 | } 910 | } 911 | } 912 | } 913 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rm-node-webserver", 3 | "version": "2.2.1", 4 | "description": "Event-driven RocketMap webserver in Node.js as a replacement for python's Flask/werkzeug development webserver.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/sebastienvercammen/devkat-rm-node-webserver.git" 12 | }, 13 | "keywords": [ 14 | "rocketmap", 15 | "devkat", 16 | "webserver", 17 | "node.js", 18 | "restify" 19 | ], 20 | "author": "devkat", 21 | "private": true, 22 | "license": "SEE LICENSE IN LICENSE.md", 23 | "dependencies": { 24 | "cluster": "^0.7.7", 25 | "debug": "^3.1.0", 26 | "dotenv": "^4.0.0", 27 | "mysql": "^2.13.0", 28 | "restify": "^6.0.1", 29 | "restify-cors-middleware": "^1.0.1", 30 | "restify-errors": "^5.0.0", 31 | "supports-color": "^4.4.0", 32 | "toobusy-js": "^0.5.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /routes/captcha.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | router.get('/bookmarklet', function (req, res) { 5 | res.send('Not implemented yet.'); 6 | }); 7 | 8 | router.get('/inject.js', function (req, res) { 9 | res.send('Not implemented yet.'); 10 | }); 11 | 12 | router.get('/submit_token', function (req, res) { 13 | res.send('Not implemented yet.'); 14 | }); 15 | 16 | module.exports = router; -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | // Attach routes. 2 | module.exports = (server) => { 3 | require('./raw_data')(server); 4 | }; 5 | -------------------------------------------------------------------------------- /routes/raw_data.js: -------------------------------------------------------------------------------- 1 | // Parse config. 2 | require('dotenv').config(); 3 | 4 | const debug = require('debug')('devkat:routes:raw_data'); 5 | const pokemon_debug = require('debug')('devkat:db:pokemon'); 6 | 7 | const errors = require('restify-errors'); 8 | const corsMiddleware = require('restify-cors-middleware'); 9 | 10 | const utils = require('../inc/utils.js'); 11 | const blacklist = require('../inc/blacklist.js'); 12 | 13 | const Pokemon = require('../models/Pokemon'); 14 | const Pokestop = require('../models/Pokestop'); 15 | const Gym = require('../models/Gym'); 16 | 17 | 18 | /* Readability. */ 19 | const isEmpty = utils.isEmpty; 20 | const isUndefined = utils.isUndefined; 21 | 22 | 23 | /* Settings. */ 24 | const ROUTE_RAW_DATA = process.env.ROUTE_RAW_DATA || '/raw_data'; 25 | const CORS_WHITELIST = process.env.CORS_WHITELIST || ''; 26 | 27 | 28 | /* CORS. */ 29 | const whitelist = CORS_WHITELIST.split(','); 30 | const cors = corsMiddleware({ 31 | origins: whitelist 32 | }); 33 | 34 | 35 | /* Helpers. */ 36 | 37 | // Query is a combination of partials. When all completed, return response. 38 | function partialCompleted(pokemon, pokestops, gyms, res, response) { 39 | if (pokemon && pokestops && gyms) { 40 | return res.json(response); 41 | } 42 | } 43 | 44 | function queryHasRequiredParams(query, requiredParams) { 45 | for (let i = 0; i < requiredParams.length; i++) { 46 | let item = requiredParams[i]; 47 | 48 | // Missing or empty parameter, bad request. 49 | if (!query.hasOwnProperty(item) || isEmpty(item)) { 50 | return false; 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | 57 | function parseGetParam(param, defaultVal) { 58 | // Undefined? 59 | if (isEmpty(param)) { 60 | return defaultVal; 61 | } 62 | 63 | // Ok, we have a value. 64 | var val = param; 65 | 66 | // Truthy/falsy strings? 67 | if (val === 'true') { 68 | return true; 69 | } else if (val === 'false') { 70 | return false; 71 | } 72 | 73 | // Make sure single values adhere to defaultVal type. 74 | if (defaultVal instanceof Array && typeof val === 'string') { 75 | val = [val]; 76 | } 77 | 78 | // No empty values should be left over. 79 | if (val instanceof Array) { 80 | for (let i = 0; i < val.length; i++) { 81 | let item = val[i]; 82 | 83 | // Remove empty item. 84 | if (item.length === 0) { 85 | val = val.splice(i, 1); 86 | } 87 | } 88 | } 89 | 90 | // Numbers should be converted back to numeric types. 91 | // Don't use parseInt() for numeric checking, use parseFloat() instead. 92 | if (utils.isNumeric(val)) { 93 | val = parseFloat(val); 94 | } 95 | 96 | // Rest is good to go. 97 | return val; 98 | } 99 | 100 | // Node.js. 101 | module.exports = (server) => { 102 | // CORS middleware. 103 | server.pre(cors.preflight); 104 | server.use(cors.actual); 105 | 106 | 107 | /* Route. */ 108 | 109 | server.get(ROUTE_RAW_DATA, (req, res, next) => { 110 | let data = req.params || {}; 111 | 112 | debug('Request to %s from %s.', ROUTE_RAW_DATA, req.connection.remoteAddress); 113 | 114 | 115 | /* Verify request. */ 116 | 117 | // Don't allow blacklisted fingerprints. 118 | const fingerprints = Object.keys(blacklist); 119 | 120 | for (var i = 0; i < fingerprints.length; i++) { 121 | const key = fingerprints[i]; 122 | const fingerprint = blacklist[key]; 123 | 124 | if (fingerprint(req)) { 125 | return next( 126 | new errors.ForbiddenError('Blacklisted fingerprint.') 127 | ); 128 | } 129 | } 130 | 131 | // Make sure we have all required parameters for a correct request. 132 | const required = [ 133 | 'swLat', 'swLng', 'neLat', 'neLng', 134 | /*'oSwLat', 'oSwLng', 'oNeLat', 'oNeLng'*/ 135 | ]; 136 | 137 | // Bad request. 138 | if (!queryHasRequiredParams(data, required)) { 139 | return next( 140 | new errors.ForbiddenError('Invalid parameters.') 141 | ); 142 | } 143 | 144 | 145 | /* Parse GET params. */ 146 | 147 | // Show/hide. 148 | const no_pokemon = parseGetParam(data.no_pokemon, false); 149 | const no_pokestops = parseGetParam(data.no_pokestops, false); 150 | const no_gyms = parseGetParam(data.no_gyms, false); 151 | 152 | const show_pokemon = parseGetParam(data.pokemon, true) && !no_pokemon; 153 | const show_pokestops = parseGetParam(data.pokestops, true) && !no_pokestops; 154 | const show_gyms = parseGetParam(data.gyms, true) && !no_gyms; 155 | 156 | // Previous switch settings. 157 | const last_gyms = parseGetParam(data.lastgyms, false); 158 | const last_pokestops = parseGetParam(data.lastpokestops, false); 159 | const last_pokemon = parseGetParam(data.lastpokemon, false); 160 | const last_slocs = parseGetParam(data.lastslocs, false); 161 | const last_spawns = parseGetParam(data.lastspawns, false); 162 | 163 | // Locations. 164 | const swLat = parseGetParam(data.swLat, undefined); 165 | const swLng = parseGetParam(data.swLng, undefined); 166 | const neLat = parseGetParam(data.neLat, undefined); 167 | const neLng = parseGetParam(data.neLng, undefined); 168 | const oSwLat = parseGetParam(data.oSwLat, undefined); 169 | const oSwLng = parseGetParam(data.oSwLng, undefined); 170 | const oNeLat = parseGetParam(data.oNeLat, undefined); 171 | const oNeLng = parseGetParam(data.oNeLng, undefined); 172 | 173 | // TODO: Check distance in locations. If it exceeds a certain distance, 174 | // refuse the query. 175 | 176 | // Other. 177 | const scanned = parseGetParam(data.scanned, false); 178 | const spawnpoints = parseGetParam(data.spawnpoints, false); 179 | var timestamp = parseGetParam(data.timestamp, undefined); 180 | 181 | // Convert to usable date object. 182 | if (!isEmpty(timestamp)) { 183 | debug('Request for timestamp %s.', timestamp); 184 | timestamp = new Date(timestamp); 185 | } 186 | 187 | // Query response is a combination of Pokémon + Pokéstops + Gyms, so 188 | // we have to wait until the necessary Promises have completed. 189 | var completed_pokemon = !show_pokemon; 190 | var completed_pokestops = !show_pokestops; 191 | var completed_gyms = !show_gyms; 192 | 193 | // General/optional. 194 | // TODO: Check if "lured_only" is proper var name. 195 | const lured_only = parseGetParam(data.luredonly, true); 196 | 197 | var new_area = false; // Did we zoom in/out? 198 | 199 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) { 200 | // We zoomed in, no new area uncovered. 201 | if (oSwLng < swLng && oSwLat < swLat && oNeLat > neLat && oNeLng > neLng) { 202 | new_area = false; 203 | } else if (!(oSwLat === swLat && oSwLng === swLng && oNeLat === neLat && oNeLng === neLng)) { 204 | new_area = true; // We moved. 205 | } 206 | } 207 | 208 | 209 | /* Prepare response. */ 210 | var response = {}; 211 | 212 | // UTC timestamp. 213 | response.timestamp = Date.now(); 214 | 215 | // Values for next request. 216 | response.lastgyms = show_gyms; 217 | response.lastpokestops = show_pokestops; 218 | response.lastpokemon = show_pokemon; 219 | response.lastslocs = scanned; 220 | response.lastspawns = spawnpoints; 221 | 222 | // Pass current coords as old coords. 223 | response.oSwLat = swLat; 224 | response.oSwLng = swLng; 225 | response.oNeLat = neLat; 226 | response.oNeLng = neLng; 227 | 228 | // Handle Pokémon. 229 | if (show_pokemon) { 230 | // Pokémon IDs, whitelist or blacklist. 231 | let ids = []; 232 | let excluded = []; 233 | 234 | if (!isEmpty(data.ids)) { 235 | ids = parseGetParam(data.ids.split(','), []); 236 | } 237 | if (!isEmpty(data.eids)) { 238 | excluded = parseGetParam(data.eids.split(','), []); 239 | } 240 | if (!isEmpty(data.reids)) { 241 | // TODO: Check this implementation of reids. In original, it's 242 | // separate to other query types. 243 | let reids = parseGetParam(data.reids.split(','), []); 244 | ids += reids; 245 | response.reids = reids; 246 | } 247 | 248 | // TODO: Change .then() below w/ custom "completed" flags into proper 249 | // Promise queue. 250 | 251 | // Completion handler. 252 | let foundMons = (pokes) => { 253 | response.pokemons = pokes; 254 | completed_pokemon = true; 255 | 256 | pokemon_debug('Found %s relevant Pokémon results.', pokes.length); 257 | 258 | return partialCompleted(completed_pokemon, completed_pokestops, completed_gyms, res, response); 259 | }; 260 | 261 | // TODO: Rewrite below workflow. We reimplemented the old Python code, 262 | // but it's kinda ugly. 263 | 264 | // Whitelist query? 265 | if (ids.length > 0) { 266 | // Run query async. 267 | Pokemon.get_active_by_ids(ids, excluded, swLat, swLng, neLat, neLng).then(foundMons).catch(utils.handle_error); 268 | } else if (!last_pokemon) { 269 | // First query from client? 270 | Pokemon.get_active(null, swLat, swLng, neLat, neLng).then(foundMons).catch(utils.handle_error); 271 | } else { 272 | // If map is already populated only request modified Pokémon 273 | // since last request time. 274 | Pokemon.get_active(excluded, swLat, swLng, neLat, neLng, timestamp).then((pokes) => { 275 | // If screen is moved add newly uncovered Pokémon to the 276 | // ones that were modified since last request time. 277 | if (new_area) { 278 | debug('Request for new viewport.'); 279 | 280 | Pokemon.get_active(excluded, swLat, swLng, neLat, neLng, null, oSwLat, oSwLng, oNeLat, oNeLng).then((new_pokes) => { 281 | // Add the new ones to the old result and pass to handler. 282 | return foundMons(pokes.concat(new_pokes)); 283 | }).catch(utils.handle_error); 284 | } else { 285 | // Unchanged viewport. 286 | return foundMons(pokes); 287 | } 288 | }).catch(utils.handle_error); 289 | } 290 | 291 | // TODO: On first visit, send in-memory data for viewport. 292 | } 293 | 294 | // Handle Pokéstops. 295 | if (show_pokestops) { 296 | // Completion handler. 297 | let foundPokestops = function (stops) { 298 | response.pokestops = stops; 299 | completed_pokestops = true; 300 | 301 | return partialCompleted(completed_pokemon, completed_pokestops, completed_gyms, res, response); 302 | }; 303 | 304 | // First query from client? 305 | if (!last_pokestops) { 306 | Pokestop.get_stops(swLat, swLng, neLat, neLng, lured_only).then(foundPokestops).catch(utils.handle_error); 307 | } else { 308 | // If map is already populated only request modified Pokéstops 309 | // since last request time. 310 | Pokestop.get_stops(swLat, swLng, neLat, neLng, lured_only, timestamp).then(function (pokestops) { 311 | // If screen is moved add newly uncovered Pokéstops to the 312 | // ones that were modified since last request time. 313 | if (new_area) { 314 | Pokestop.get_stops(swLat, swLng, neLat, neLng, lured_only, null, oSwLat, oSwLng, oNeLat, oNeLng).then(function (new_pokestops) { 315 | // Add the new ones to the old result and pass to handler. 316 | return foundPokestops(pokestops.concat(new_pokestops)); 317 | }).catch(utils.handle_error); 318 | } else { 319 | // Unchanged viewport. 320 | return foundPokestops(pokestops); 321 | } 322 | }).catch(utils.handle_error); 323 | } 324 | } 325 | 326 | // Handle gyms. 327 | if (show_gyms) { 328 | // Completion handler. 329 | let foundGyms = (gyms) => { 330 | // RM uses an object w/ gym_id as keys. Can't remember why, this might be a case of "old code". 331 | // TODO: Look at RM's code for this and optimize. 332 | const gyms_obj = {}; 333 | 334 | while (gyms.length > 0) { 335 | const gym = gyms.pop(); 336 | 337 | // Attach to result object. 338 | gyms_obj[gym.gym_id] = gym; 339 | } 340 | 341 | response.gyms = gyms_obj; 342 | completed_gyms = true; 343 | 344 | return partialCompleted(completed_pokemon, completed_pokestops, completed_gyms, res, response); 345 | }; 346 | 347 | // First query from client? 348 | if (!last_gyms) { 349 | Gym.get_gyms(swLat, swLng, neLat, neLng).then(foundGyms).catch(utils.handle_error); 350 | } else { 351 | // If map is already populated only request modified Gyms 352 | // since last request time. 353 | Gym.get_gyms(swLat, swLng, neLat, neLng, timestamp).then((gyms) => { 354 | // If screen is moved add newly uncovered Gyms to the 355 | // ones that were modified since last request time. 356 | if (new_area) { 357 | Gym.get_gyms(swLat, swLng, neLat, neLng, null, oSwLat, oSwLng, oNeLat, oNeLng).then((new_gyms) => { 358 | // Add the new ones to the old result and pass to handler. 359 | return foundGyms(gyms.concat(new_gyms)); 360 | }).catch(utils.handle_error); 361 | } else { 362 | // Unchanged viewport. 363 | return foundGyms(gyms); 364 | } 365 | }).catch(utils.handle_error); 366 | } 367 | } 368 | 369 | // A request for nothing? 370 | if (!show_pokemon && !show_pokestops && !show_gyms) { 371 | return res.json(response); 372 | } 373 | }); 374 | }; --------------------------------------------------------------------------------