gt-api 0.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gt_api-0.0.4/LICENSE +13 -0
- gt_api-0.0.4/PKG-INFO +141 -0
- gt_api-0.0.4/README.md +106 -0
- gt_api-0.0.4/gt_api/__init__.py +3 -0
- gt_api-0.0.4/gt_api/challenge.py +48 -0
- gt_api-0.0.4/gt_api/client.py +29 -0
- gt_api-0.0.4/gt_api/errors.py +6 -0
- gt_api-0.0.4/gt_api/generic.py +85 -0
- gt_api-0.0.4/gt_api/geo.py +20 -0
- gt_api-0.0.4/gt_api/lobby.py +125 -0
- gt_api-0.0.4/gt_api/login.py +30 -0
- gt_api-0.0.4/gt_api/map.py +260 -0
- gt_api-0.0.4/gt_api/misc.py +35 -0
- gt_api-0.0.4/gt_api/season.py +11 -0
- gt_api-0.0.4/gt_api/user.py +59 -0
- gt_api-0.0.4/gt_api.egg-info/PKG-INFO +141 -0
- gt_api-0.0.4/gt_api.egg-info/SOURCES.txt +20 -0
- gt_api-0.0.4/gt_api.egg-info/dependency_links.txt +1 -0
- gt_api-0.0.4/gt_api.egg-info/requires.txt +3 -0
- gt_api-0.0.4/gt_api.egg-info/top_level.txt +1 -0
- gt_api-0.0.4/pyproject.toml +33 -0
- gt_api-0.0.4/setup.cfg +4 -0
gt_api-0.0.4/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
2
|
+
Version 2, December 2004
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
5
|
+
|
|
6
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
|
7
|
+
copies of this license document, and changing it is allowed as long
|
|
8
|
+
as the name is changed.
|
|
9
|
+
|
|
10
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
11
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
12
|
+
|
|
13
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
gt_api-0.0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gt_api
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Geotastic internal API wrapper
|
|
5
|
+
Author-email: Adam Jenca <jenca.adam@gmail.com>
|
|
6
|
+
Maintainer-email: Adam Jenca <jenca.adam@gmail.com>
|
|
7
|
+
License: DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
8
|
+
Version 2, December 2004
|
|
9
|
+
|
|
10
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
11
|
+
|
|
12
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
|
13
|
+
copies of this license document, and changing it is allowed as long
|
|
14
|
+
as the name is changed.
|
|
15
|
+
|
|
16
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
17
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
18
|
+
|
|
19
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
20
|
+
|
|
21
|
+
Project-URL: Homepage, https://github.com/jenca-adam/geotastic_api
|
|
22
|
+
Project-URL: Bug Reports, https://github.com/jenca-adam/geotastic_api/issues
|
|
23
|
+
Project-URL: Source, https://github.com/jenca-adam/geotastic_api
|
|
24
|
+
Keywords: geotastic,api
|
|
25
|
+
Classifier: Development Status :: 3 - Alpha
|
|
26
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Intended Audience :: Developers
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: pycryptodome
|
|
32
|
+
Requires-Dist: requests
|
|
33
|
+
Requires-Dist: websockets
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# geotastic-api
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Not on PyPI yet.
|
|
41
|
+
Requires Python.
|
|
42
|
+
```
|
|
43
|
+
python3 -m pip install .
|
|
44
|
+
```
|
|
45
|
+
(also installs requirements)
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Logging in
|
|
50
|
+
|
|
51
|
+
Most API requests require logging in. There are two ways to do it:
|
|
52
|
+
|
|
53
|
+
1. mail + password
|
|
54
|
+
1. token
|
|
55
|
+
|
|
56
|
+
#### Mail + Password
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import gt_api
|
|
60
|
+
client=gt_api.Client.login('your.mail@example.com', 'hunter2')
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
And you're in! The big caveat is that this logs you out of any other sessions, and also causes API costs.
|
|
64
|
+
So for most use cases you want to use tokens.
|
|
65
|
+
|
|
66
|
+
#### Token
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import gt_api
|
|
70
|
+
client=gt_api.Client('d8f350a3f557adc253f5003a81d3098c06dea93f84edf10e3fabc1d92acd1771')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
##### How do I get it?
|
|
74
|
+
|
|
75
|
+
Go to geotastic and log in with your account.
|
|
76
|
+
Then open the developer tools (F12 or ctrl-shift-i). Then navigate to Storage(Application->Storage for Chrome)->Local Storage->https://geotastic.net/ and scroll down to *token*. Copy the value.
|
|
77
|
+
|
|
78
|
+
Please note that the token is one per session, so once you log out of the session you got the token from, you'll need to update it.
|
|
79
|
+
#### Without Login
|
|
80
|
+
|
|
81
|
+
Use `Client(None)`.
|
|
82
|
+
Most functions will not work.
|
|
83
|
+
### Creating a drop group
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
map_id=12345
|
|
87
|
+
group = client.create_drop_group(map_id, -12.345, -69.420, 'pe', 'My Drop Group', active=True, bias=5.3)
|
|
88
|
+
print(group["dropGroup"]["id"])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Listing drop groups
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
client.get_drop_groups(12345)
|
|
95
|
+
```
|
|
96
|
+
If you don't own the map, use `get_public_drop_groups()`
|
|
97
|
+
|
|
98
|
+
### Exporting drops
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
client.get_map_drops(12345)
|
|
102
|
+
```
|
|
103
|
+
or
|
|
104
|
+
```python
|
|
105
|
+
client.get_group_drops(12345)
|
|
106
|
+
```
|
|
107
|
+
You DON'T need to own the map or the group.
|
|
108
|
+
|
|
109
|
+
### Importing drops
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
drops=[{"id":1,"style":"streetview","lat":39.28719501248246,"lng":-103.07696260471509,"code":"us","panoId":"Ple0qA2-cNzxc0K-gXgbFA"}]
|
|
113
|
+
client.import_drops(drops, 12345, target_type="map", import_type="merge")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Possible target types are `map` and `group`.
|
|
117
|
+
Possible import types are `merge`, `override` and `update`.
|
|
118
|
+
|
|
119
|
+
### Lobbies
|
|
120
|
+
|
|
121
|
+
Lobbies are handled a little differently.
|
|
122
|
+
To create a lobby, use `Lobby.create(token)`.
|
|
123
|
+
To join a lobby, user `Lobby.join(token, lobby_id)`.
|
|
124
|
+
You then add handlers for events coming in from the Lobby socket using the `lobby.add_handler("event")` decorator. The handler for the event "\*" will be called for every event.
|
|
125
|
+
You can send socket messages to the lobby using `lobby.send_message(type, **kwargs)`. The kwargs will be added to the message json.
|
|
126
|
+
You can make lobby api requests using `lobby.lobby_api_request(url, method *args, **kwargs)`. Args and kwargs will be passed to `request.request()`.
|
|
127
|
+
To run the lobby event loop, use `Lobby.run()`
|
|
128
|
+
To disconnect from the lobby, use `Lobby.disconnect()`
|
|
129
|
+
Look at `examples/auto_lobby.py`
|
|
130
|
+
### Other uses
|
|
131
|
+
|
|
132
|
+
There's loads more I can't be bothered to document. Check the source code.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## Contributing
|
|
136
|
+
|
|
137
|
+
If you'd like to have a feature that's not in the library, just create a github issue and I'll get to it (maybe).
|
|
138
|
+
|
|
139
|
+
If you'd like to add your own, it's pretty easy. Just use developer tools to check which api calls geotastic is making and then remake them as functions. If the calls are encrypted, use `gt_api.generic.decode_encdata`.
|
|
140
|
+
|
|
141
|
+
|
gt_api-0.0.4/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# geotastic-api
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
Not on PyPI yet.
|
|
6
|
+
Requires Python.
|
|
7
|
+
```
|
|
8
|
+
python3 -m pip install .
|
|
9
|
+
```
|
|
10
|
+
(also installs requirements)
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### Logging in
|
|
15
|
+
|
|
16
|
+
Most API requests require logging in. There are two ways to do it:
|
|
17
|
+
|
|
18
|
+
1. mail + password
|
|
19
|
+
1. token
|
|
20
|
+
|
|
21
|
+
#### Mail + Password
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
import gt_api
|
|
25
|
+
client=gt_api.Client.login('your.mail@example.com', 'hunter2')
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
And you're in! The big caveat is that this logs you out of any other sessions, and also causes API costs.
|
|
29
|
+
So for most use cases you want to use tokens.
|
|
30
|
+
|
|
31
|
+
#### Token
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import gt_api
|
|
35
|
+
client=gt_api.Client('d8f350a3f557adc253f5003a81d3098c06dea93f84edf10e3fabc1d92acd1771')
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
##### How do I get it?
|
|
39
|
+
|
|
40
|
+
Go to geotastic and log in with your account.
|
|
41
|
+
Then open the developer tools (F12 or ctrl-shift-i). Then navigate to Storage(Application->Storage for Chrome)->Local Storage->https://geotastic.net/ and scroll down to *token*. Copy the value.
|
|
42
|
+
|
|
43
|
+
Please note that the token is one per session, so once you log out of the session you got the token from, you'll need to update it.
|
|
44
|
+
#### Without Login
|
|
45
|
+
|
|
46
|
+
Use `Client(None)`.
|
|
47
|
+
Most functions will not work.
|
|
48
|
+
### Creating a drop group
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
map_id=12345
|
|
52
|
+
group = client.create_drop_group(map_id, -12.345, -69.420, 'pe', 'My Drop Group', active=True, bias=5.3)
|
|
53
|
+
print(group["dropGroup"]["id"])
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Listing drop groups
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
client.get_drop_groups(12345)
|
|
60
|
+
```
|
|
61
|
+
If you don't own the map, use `get_public_drop_groups()`
|
|
62
|
+
|
|
63
|
+
### Exporting drops
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
client.get_map_drops(12345)
|
|
67
|
+
```
|
|
68
|
+
or
|
|
69
|
+
```python
|
|
70
|
+
client.get_group_drops(12345)
|
|
71
|
+
```
|
|
72
|
+
You DON'T need to own the map or the group.
|
|
73
|
+
|
|
74
|
+
### Importing drops
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
drops=[{"id":1,"style":"streetview","lat":39.28719501248246,"lng":-103.07696260471509,"code":"us","panoId":"Ple0qA2-cNzxc0K-gXgbFA"}]
|
|
78
|
+
client.import_drops(drops, 12345, target_type="map", import_type="merge")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Possible target types are `map` and `group`.
|
|
82
|
+
Possible import types are `merge`, `override` and `update`.
|
|
83
|
+
|
|
84
|
+
### Lobbies
|
|
85
|
+
|
|
86
|
+
Lobbies are handled a little differently.
|
|
87
|
+
To create a lobby, use `Lobby.create(token)`.
|
|
88
|
+
To join a lobby, user `Lobby.join(token, lobby_id)`.
|
|
89
|
+
You then add handlers for events coming in from the Lobby socket using the `lobby.add_handler("event")` decorator. The handler for the event "\*" will be called for every event.
|
|
90
|
+
You can send socket messages to the lobby using `lobby.send_message(type, **kwargs)`. The kwargs will be added to the message json.
|
|
91
|
+
You can make lobby api requests using `lobby.lobby_api_request(url, method *args, **kwargs)`. Args and kwargs will be passed to `request.request()`.
|
|
92
|
+
To run the lobby event loop, use `Lobby.run()`
|
|
93
|
+
To disconnect from the lobby, use `Lobby.disconnect()`
|
|
94
|
+
Look at `examples/auto_lobby.py`
|
|
95
|
+
### Other uses
|
|
96
|
+
|
|
97
|
+
There's loads more I can't be bothered to document. Check the source code.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
## Contributing
|
|
101
|
+
|
|
102
|
+
If you'd like to have a feature that's not in the library, just create a github issue and I'll get to it (maybe).
|
|
103
|
+
|
|
104
|
+
If you'd like to add your own, it's pretty easy. Just use developer tools to check which api calls geotastic is making and then remake them as functions. If the calls are encrypted, use `gt_api.generic.decode_encdata`.
|
|
105
|
+
|
|
106
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from . import generic
|
|
2
|
+
from .client import Client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@Client._register_endpoint
|
|
6
|
+
def get_all_user_challenges(auth_token=None):
|
|
7
|
+
return generic.process_response(
|
|
8
|
+
generic.geotastic_api_request(
|
|
9
|
+
"https://api.geotastic.net/v1/challenge/getAllUserChallenges.php",
|
|
10
|
+
"GET",
|
|
11
|
+
auth_token,
|
|
12
|
+
)
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@Client._register_endpoint
|
|
17
|
+
def get_challenge_drops(challenge_id, auth_token=None):
|
|
18
|
+
return generic.process_response(
|
|
19
|
+
generic.geotastic_api_request(
|
|
20
|
+
"https://api.geotastic.net/v1/challenge/getChallengeDrops.php",
|
|
21
|
+
"GET",
|
|
22
|
+
auth_token,
|
|
23
|
+
params={"id": challenge_id},
|
|
24
|
+
)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@Client._register_endpoint
|
|
29
|
+
def get_challenge_results(challenge_id, auth_token=None):
|
|
30
|
+
return generic.process_response(
|
|
31
|
+
generic.geotastic_api_request(
|
|
32
|
+
"https://api.geotastic.net/v1/challenge/getChallengeResults.php",
|
|
33
|
+
"GET",
|
|
34
|
+
auth_token,
|
|
35
|
+
params={"id": challenge_id},
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@Client._register_endpoint
|
|
41
|
+
def get_own_challenges(auth_token=None):
|
|
42
|
+
return generic.process_response(
|
|
43
|
+
generic.geotastic_api_request(
|
|
44
|
+
"https://api.geotastic.net/v1/challenge/getOwnChallenges.php",
|
|
45
|
+
"GET",
|
|
46
|
+
auth_token,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from . import login
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Client:
|
|
6
|
+
def __init__(self, auth_token, user_data={}):
|
|
7
|
+
self.auth_token = auth_token
|
|
8
|
+
self.user_data = user_data
|
|
9
|
+
|
|
10
|
+
def get_user_data(self):
|
|
11
|
+
return login.login(token=self.auth_token)
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def _register_endpoint(cls, endpoint):
|
|
15
|
+
|
|
16
|
+
@functools.wraps(endpoint)
|
|
17
|
+
def replaced(self, *args, **kwargs):
|
|
18
|
+
return endpoint(*args, **kwargs, auth_token=self.auth_token)
|
|
19
|
+
|
|
20
|
+
setattr(cls, endpoint.__name__, replaced)
|
|
21
|
+
return endpoint
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def login(cls, mail, password):
|
|
25
|
+
login_result = login.login(mail=mail, password=password)
|
|
26
|
+
return cls(login_result["token"], login_result)
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
return f"<Client (auth_token={self.auth_token!r})>"
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import requests, hashlib, os, base64, json
|
|
2
|
+
|
|
3
|
+
from Crypto.Cipher import AES
|
|
4
|
+
from Crypto.Util.Padding import unpad, pad
|
|
5
|
+
|
|
6
|
+
from .errors import GeotasticAPIError
|
|
7
|
+
|
|
8
|
+
PASSWORD = b"4317969d37f68d4f54eae689d8088eba" # static
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def evp_bytes_to_key(salt, password=PASSWORD, key_len=32, iv_len=16):
|
|
12
|
+
d = b""
|
|
13
|
+
while len(d) < key_len + iv_len:
|
|
14
|
+
d_i = (
|
|
15
|
+
hashlib.md5(d[-16:] + password + salt).digest()
|
|
16
|
+
if d
|
|
17
|
+
else hashlib.md5(password + salt).digest()
|
|
18
|
+
)
|
|
19
|
+
d += d_i
|
|
20
|
+
|
|
21
|
+
return d[:key_len], d[key_len : key_len + iv_len]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def aes_decrypt(ct, salt):
|
|
25
|
+
key, iv = evp_bytes_to_key(salt)
|
|
26
|
+
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
27
|
+
return unpad(cipher.decrypt(ct), AES.block_size)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def decode_encdata(plain):
|
|
31
|
+
encdata = json.loads(plain)
|
|
32
|
+
ct = base64.b64decode(encdata["ct"])
|
|
33
|
+
salt = bytes.fromhex(encdata["s"])
|
|
34
|
+
return json.loads(aes_decrypt(ct, salt))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def encode_encdata(data):
|
|
38
|
+
plain = json.dumps(data).encode("utf-8")
|
|
39
|
+
salt = os.urandom(8).hex()
|
|
40
|
+
key, iv = evp_bytes_to_key(bytes.fromhex(salt))
|
|
41
|
+
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
42
|
+
ct = base64.b64encode(cipher.encrypt(pad(plain, AES.block_size))).decode("ascii")
|
|
43
|
+
return json.dumps({"ct": ct, "iv": iv.hex(), "s": salt})
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def decode_websocket(payload):
|
|
47
|
+
decoded = base64.b64decode(payload)
|
|
48
|
+
if not decoded.startswith(b"Salted__"):
|
|
49
|
+
raise ValueError("no salt")
|
|
50
|
+
salt = decoded[8:16]
|
|
51
|
+
ct = decoded[16:]
|
|
52
|
+
return json.loads(aes_decrypt(ct, salt))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def process_response(response):
|
|
56
|
+
if response.ok:
|
|
57
|
+
if not response.content:
|
|
58
|
+
raise GeotasticAPIError("empty response")
|
|
59
|
+
json_response = response.json()
|
|
60
|
+
if json_response["status"] == "success":
|
|
61
|
+
if json_response.get("enc"):
|
|
62
|
+
return decode_encdata(json_response["encData"])
|
|
63
|
+
return json_response.get("data")
|
|
64
|
+
else:
|
|
65
|
+
raise GeotasticAPIError(json_response["message"])
|
|
66
|
+
else:
|
|
67
|
+
raise GeotasticAPIError(f"{response.status_code} {response.reason}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def geotastic_api_request(
|
|
71
|
+
url, method, auth_token=None, extra_headers={}, *args, **kwargs
|
|
72
|
+
):
|
|
73
|
+
# unethical :(
|
|
74
|
+
|
|
75
|
+
headers = {"Referer": "https://geotastic.net/", "Origin": "https://geotastic.net"}
|
|
76
|
+
if auth_token:
|
|
77
|
+
headers["X-Auth-Token"] = auth_token
|
|
78
|
+
headers.update(extra_headers)
|
|
79
|
+
return requests.request(
|
|
80
|
+
method,
|
|
81
|
+
url,
|
|
82
|
+
headers=headers,
|
|
83
|
+
*args,
|
|
84
|
+
**kwargs,
|
|
85
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from . import generic
|
|
2
|
+
from .client import Client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
# reverse geocoding
|
|
6
|
+
@Client._register_endpoint
|
|
7
|
+
def reverse(lat, lng, auth_token=None, omit_border_data=True, skip_state_check=False):
|
|
8
|
+
enc = generic.encode_encdata(
|
|
9
|
+
{
|
|
10
|
+
"latLng": {"lat": lat, "lng": lng},
|
|
11
|
+
"omitBorderData": omit_border_data,
|
|
12
|
+
"skipStateCheck": skip_state_check,
|
|
13
|
+
}
|
|
14
|
+
)
|
|
15
|
+
response = generic.process_response(
|
|
16
|
+
generic.geotastic_api_request(
|
|
17
|
+
"https://api01.geotastic.net/reverseV4", "POST", json={"enc": enc}
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
return response
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from websockets.sync.client import connect
|
|
2
|
+
from websockets import ConnectionClosed
|
|
3
|
+
from . import generic
|
|
4
|
+
from .errors import LobbyError
|
|
5
|
+
from .client import Client
|
|
6
|
+
import threading
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
CLIENT_VERSION = "0.297.17"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Lobby:
|
|
13
|
+
def __init__(self, connection, auth_token):
|
|
14
|
+
self.connection = connection
|
|
15
|
+
self.auth_token = auth_token
|
|
16
|
+
self.thread = threading.Thread(target=self._event_loop)
|
|
17
|
+
self.close_reason = "not yet started"
|
|
18
|
+
self.running = False
|
|
19
|
+
self.lobby_token = None
|
|
20
|
+
self.lobby_id = None
|
|
21
|
+
self.lobby_settings = None
|
|
22
|
+
self.handlers = {"fullLobby": [self.handle_full_lobby]}
|
|
23
|
+
|
|
24
|
+
def event_handler(self, event):
|
|
25
|
+
def decorator(function):
|
|
26
|
+
self.handlers.setdefault(event, [])
|
|
27
|
+
self.handlers[event].append(function)
|
|
28
|
+
return function
|
|
29
|
+
|
|
30
|
+
return decorator
|
|
31
|
+
|
|
32
|
+
def feed_event(self, message):
|
|
33
|
+
for handler in self.handlers.get(message["type"], []) + self.handlers.get(
|
|
34
|
+
"*", []
|
|
35
|
+
):
|
|
36
|
+
threading.Thread(
|
|
37
|
+
target=handler,
|
|
38
|
+
args=(self, message["type"], message.get("data")),
|
|
39
|
+
).start() # run in a separate thread so as to not block the event loop
|
|
40
|
+
|
|
41
|
+
def run(self):
|
|
42
|
+
if self.running:
|
|
43
|
+
raise RuntimeError("already running")
|
|
44
|
+
try:
|
|
45
|
+
ack = generic.decode_websocket(self.connection.recv())
|
|
46
|
+
self.feed_event(ack)
|
|
47
|
+
except ConnectionClosed:
|
|
48
|
+
self.close_reason = self.connection.close_reason
|
|
49
|
+
raise LobbyError(
|
|
50
|
+
f"Disconnected by server: {self.connection.close_reason}"
|
|
51
|
+
) from None
|
|
52
|
+
self.running = True
|
|
53
|
+
self.thread.start()
|
|
54
|
+
|
|
55
|
+
def disconnect(self):
|
|
56
|
+
self.connection.close()
|
|
57
|
+
|
|
58
|
+
def lobby_api_request(self, url, method, *args, **kwargs):
|
|
59
|
+
return generic.geotastic_api_request(
|
|
60
|
+
url,
|
|
61
|
+
method,
|
|
62
|
+
self.auth_token,
|
|
63
|
+
{"Game-Id": self.lobby_id, "Token": self.lobby_token},
|
|
64
|
+
*args,
|
|
65
|
+
**kwargs,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def _event_loop(self):
|
|
69
|
+
while True:
|
|
70
|
+
try:
|
|
71
|
+
self.close_reason = self.connection.close_reason
|
|
72
|
+
message = generic.decode_websocket(self.connection.recv())
|
|
73
|
+
self.feed_event(message)
|
|
74
|
+
except ConnectionClosed:
|
|
75
|
+
self.running = False
|
|
76
|
+
raise LobbyError(
|
|
77
|
+
f"Disconnected by server: {self.connection.close_reason}"
|
|
78
|
+
) from None
|
|
79
|
+
|
|
80
|
+
def send_message(self, type, **kwargs):
|
|
81
|
+
if not self.running:
|
|
82
|
+
raise LobbyError(f"Lobby not running: {self.connection.close_reason}")
|
|
83
|
+
payload = {
|
|
84
|
+
"token": self.lobby_token,
|
|
85
|
+
"gameId": self.lobby_id,
|
|
86
|
+
"type": type,
|
|
87
|
+
**kwargs,
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
self.connection.send(json.dumps(payload))
|
|
91
|
+
|
|
92
|
+
@classmethod
|
|
93
|
+
def create(cls, auth_token, server="multiplayer02"):
|
|
94
|
+
sock = connect(
|
|
95
|
+
f"wss://{server}.geotastic.net/?client_version={CLIENT_VERSION}&t={auth_token}&a=createNewCustomLobby",
|
|
96
|
+
origin="https://geotastic.net",
|
|
97
|
+
subprotocols=["geotastic-protocol"],
|
|
98
|
+
)
|
|
99
|
+
return cls(sock, auth_token)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def join(cls, auth_token, lobby_id, name="", server="multiplayer02"):
|
|
103
|
+
sock = connect(
|
|
104
|
+
f"wss://{server}.geotastic.net/?client_version={CLIENT_VERSION}&t={auth_token}&la={lobby_id}&n={name}&a=joinLobby",
|
|
105
|
+
origin="https://geotastic.net",
|
|
106
|
+
subprotocols=["geotastic-protocol"],
|
|
107
|
+
)
|
|
108
|
+
return cls(sock, auth_token)
|
|
109
|
+
|
|
110
|
+
def handle_full_lobby(self, lobby, type, message):
|
|
111
|
+
lobby.lobby_token = message["token"]
|
|
112
|
+
lobby.lobby_id = message["lobby"]["id"]
|
|
113
|
+
lobby.lobby_settings = message["lobby"]["settingsOptions"]["settings"]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@Client._register_endpoint
|
|
117
|
+
def get_lobby_from_alias(alias, auth_token=None):
|
|
118
|
+
return generic.process_response(
|
|
119
|
+
generic.geotastic_api_request(
|
|
120
|
+
"https://api.geotastic.net/v1/lobby/getLobbyFromAlias.php",
|
|
121
|
+
"GET",
|
|
122
|
+
auth_token,
|
|
123
|
+
params={"alias": alias},
|
|
124
|
+
)
|
|
125
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from .errors import GeotasticAPIError
|
|
2
|
+
from .generic import decode_encdata
|
|
3
|
+
import requests
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def login(mail=None, password=None, token=None, fingerprint=None):
|
|
8
|
+
if fingerprint is None:
|
|
9
|
+
fingerprint = os.urandom(16).hex()
|
|
10
|
+
creds = {}
|
|
11
|
+
if mail:
|
|
12
|
+
creds["mail"] = mail
|
|
13
|
+
if password:
|
|
14
|
+
creds["password"] = password
|
|
15
|
+
if token:
|
|
16
|
+
creds["token"] = token
|
|
17
|
+
response = requests.post(
|
|
18
|
+
"https://api.geotastic.net/v1/user/login.php",
|
|
19
|
+
headers={
|
|
20
|
+
"Origin": "https://geotastic.net",
|
|
21
|
+
"Referer": "https://geotastic.net/",
|
|
22
|
+
},
|
|
23
|
+
json={"credentials": {"fingerprint": fingerprint, **creds}},
|
|
24
|
+
)
|
|
25
|
+
if response.ok:
|
|
26
|
+
json_response = response.json()
|
|
27
|
+
if json_response["status"] == "success":
|
|
28
|
+
return decode_encdata(json_response["encData"])
|
|
29
|
+
raise GeotasticAPIError(json_response["message"])
|
|
30
|
+
raise GeotasticAPIError(f"{response.status} {response.reason}")
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from . import generic
|
|
2
|
+
from .client import Client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@Client._register_endpoint
|
|
6
|
+
def create_tag(tag_name, auth_token=None):
|
|
7
|
+
data = generic.encode_encdata({"tag": tag_name})
|
|
8
|
+
return generic.process_response(
|
|
9
|
+
generic.geotastic_api_request(
|
|
10
|
+
"https://api.geotastic.net/v1/maps/createTagV2.php",
|
|
11
|
+
"POST",
|
|
12
|
+
auth_token,
|
|
13
|
+
json={"enc": data},
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@Client._register_endpoint
|
|
19
|
+
def get_public_drop_groups(map_id, include_tags=True, auth_token=None):
|
|
20
|
+
return generic.process_response(
|
|
21
|
+
generic.geotastic_api_request(
|
|
22
|
+
"https://api.geotastic.net/v1/maps/getPublicDropGroups.php",
|
|
23
|
+
"GET",
|
|
24
|
+
params={"mapId": map_id, "withTags": include_tags},
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@Client._register_endpoint
|
|
30
|
+
def create_drop_group(
|
|
31
|
+
map_id, lat, lng, code, title, active=True, bias=1, auth_token=None, **properties
|
|
32
|
+
):
|
|
33
|
+
data = {
|
|
34
|
+
"mapId": map_id,
|
|
35
|
+
"lat": lat,
|
|
36
|
+
"lng": lng,
|
|
37
|
+
"code": code,
|
|
38
|
+
"title": title,
|
|
39
|
+
"active": active,
|
|
40
|
+
"bias": bias,
|
|
41
|
+
"type": "group",
|
|
42
|
+
**properties,
|
|
43
|
+
}
|
|
44
|
+
response = generic.geotastic_api_request(
|
|
45
|
+
"https://api.geotastic.net/v1/maps/updateDropGroup.php",
|
|
46
|
+
"POST",
|
|
47
|
+
auth_token,
|
|
48
|
+
json=data,
|
|
49
|
+
)
|
|
50
|
+
return generic.process_response(response)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@Client._register_endpoint
|
|
54
|
+
def update_drop_group(group_id, auth_token=None, **properties):
|
|
55
|
+
data = {"id": group_id, **properties}
|
|
56
|
+
response = generic.geotastic_api_request(
|
|
57
|
+
"https://api.geotastic.net/v1/maps/updateDropGroup.php",
|
|
58
|
+
"POST",
|
|
59
|
+
auth_token,
|
|
60
|
+
json=data,
|
|
61
|
+
)
|
|
62
|
+
return generic.process_response(response)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@Client._register_endpoint
|
|
66
|
+
def get_drop_groups(map_id, auth_token=None):
|
|
67
|
+
response = generic.geotastic_api_request(
|
|
68
|
+
"https://api.geotastic.net/v1/maps/getDropGroups.php",
|
|
69
|
+
"GET",
|
|
70
|
+
auth_token,
|
|
71
|
+
params={"mapId": map_id},
|
|
72
|
+
)
|
|
73
|
+
return generic.process_response(response)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@Client._register_endpoint
|
|
77
|
+
def delete_drop_group(drop_group_id, auth_token=None):
|
|
78
|
+
return generic.process_response(
|
|
79
|
+
generic.geotastic_api_request(
|
|
80
|
+
"https://api.geotastic.net/v1/maps/deleteDropGroupV2.php",
|
|
81
|
+
"POST",
|
|
82
|
+
auth_token,
|
|
83
|
+
json={"dropGroupId": drop_group_id},
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@Client._register_endpoint
|
|
89
|
+
def delete_drop(drop_id, auth_token=None):
|
|
90
|
+
return generic.process_response(
|
|
91
|
+
generic.geotastic_api_request(
|
|
92
|
+
"https://api.geotastic.net/v1/maps/deleteDropV2.php",
|
|
93
|
+
"POST",
|
|
94
|
+
auth_token,
|
|
95
|
+
json={"dropId": drop_id},
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@Client._register_endpoint
|
|
101
|
+
def import_drops(drops, target_id, target_type, import_type="merge", auth_token=None):
|
|
102
|
+
return generic.process_response(
|
|
103
|
+
generic.geotastic_api_request(
|
|
104
|
+
"https://api.geotastic.net/v1/drops/importDrops.php",
|
|
105
|
+
"POST",
|
|
106
|
+
auth_token,
|
|
107
|
+
json={
|
|
108
|
+
"drops": drops,
|
|
109
|
+
"params": {
|
|
110
|
+
"targetId": target_id,
|
|
111
|
+
"targetType": target_type,
|
|
112
|
+
"importType": import_type,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@Client._register_endpoint
|
|
120
|
+
def get_map_drops(map_id, auth_token=None):
|
|
121
|
+
return generic.process_response(
|
|
122
|
+
generic.geotastic_api_request(
|
|
123
|
+
"https://api.geotastic.net/v1/maps/getDrops.php",
|
|
124
|
+
"GET",
|
|
125
|
+
auth_token,
|
|
126
|
+
params={"mapId": map_id},
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@Client._register_endpoint
|
|
132
|
+
def get_group_drops(group_id, auth_token=None):
|
|
133
|
+
return generic.process_response(
|
|
134
|
+
generic.geotastic_api_request(
|
|
135
|
+
"https://api.geotastic.net/v1/maps/getDrops.php",
|
|
136
|
+
"GET",
|
|
137
|
+
auth_token,
|
|
138
|
+
params={"groupId": group_id},
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@Client._register_endpoint
|
|
144
|
+
def update_map(map_id, auth_token=None, **properties):
|
|
145
|
+
data = {"id": map_id, **properties}
|
|
146
|
+
return generic.process_response(
|
|
147
|
+
generic.geotastic_api_request(
|
|
148
|
+
"https://api.geotastic.net/v1/maps/updateMapV2.php",
|
|
149
|
+
"POST",
|
|
150
|
+
auth_token,
|
|
151
|
+
json={"enc": generic.encode_encdata(data)},
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@Client._register_endpoint
|
|
157
|
+
def delete_map(map_id, auth_token=None):
|
|
158
|
+
return generic.process_response(
|
|
159
|
+
generic.geotastic_api_request(
|
|
160
|
+
"https://api.geotastic.net/v1/maps/deleteMap.php",
|
|
161
|
+
"POST",
|
|
162
|
+
auth_token,
|
|
163
|
+
data=str(map_id),
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@Client._register_endpoint
|
|
169
|
+
def get_own_maps(auth_token=None):
|
|
170
|
+
return generic.process_response(
|
|
171
|
+
generic.geotastic_api_request(
|
|
172
|
+
"https://api.geotastic.net/v1/maps/getMaps.php", "GET", auth_token
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@Client._register_endpoint
|
|
178
|
+
def get_playable_maps(auth_token=None):
|
|
179
|
+
return generic.process_response(
|
|
180
|
+
generic.geotastic_api_request(
|
|
181
|
+
"https://api.geotastic.net/v1/maps/getPlayableMaps.php", "GET", auth_token
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@Client._register_endpoint
|
|
187
|
+
def random_single_map_drop(map_id, used_ids=[], auth_token=None):
|
|
188
|
+
return generic.process_response(
|
|
189
|
+
generic.geotastic_api_request(
|
|
190
|
+
"https://api.geotastic.net/v1/maps/getRandomDropFromSingleDropMap.php",
|
|
191
|
+
"GET",
|
|
192
|
+
auth_token,
|
|
193
|
+
params={"mapId": map_id, "usedIds": ",".join(map(str, used_ids))},
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@Client._register_endpoint
|
|
199
|
+
def random_grouped_map_drop(
|
|
200
|
+
map_id, removed_groups=[], used_ids=[], picker="balanced", auth_token=None
|
|
201
|
+
):
|
|
202
|
+
response = geotastic_api_request(
|
|
203
|
+
"https://api.geotastic.net/v1/maps/getRandomDropFromGroupedDropMap.php",
|
|
204
|
+
"GET",
|
|
205
|
+
auth_token,
|
|
206
|
+
params={
|
|
207
|
+
"mapId": map_id,
|
|
208
|
+
"rg": ",".join(map(str, removed_groups)),
|
|
209
|
+
"used": ",".join(map(str, used_ids)),
|
|
210
|
+
"sm": picker,
|
|
211
|
+
},
|
|
212
|
+
)
|
|
213
|
+
return process_response(response)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@Client._register_endpoint
|
|
217
|
+
def get_map_tags(map_id, auth_token=None):
|
|
218
|
+
return generic.process_response(
|
|
219
|
+
generic.geotastic_api_request(
|
|
220
|
+
"https://api.geotastic.net/v1/maps/getTagsByMap.php",
|
|
221
|
+
"GET",
|
|
222
|
+
auth_token,
|
|
223
|
+
params={"mapId": map_id},
|
|
224
|
+
)
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@Client._register_endpoint
|
|
229
|
+
def get_maps_by_user(uid, auth_token=None):
|
|
230
|
+
return generic.process_response(
|
|
231
|
+
generic.geotastic_api_request(
|
|
232
|
+
"https://api.geotastic.net/v1/maps/getPlayableMapsByUser.php",
|
|
233
|
+
"GET",
|
|
234
|
+
auth_token,
|
|
235
|
+
params={"uid": uid},
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@Client._register_endpoint
|
|
241
|
+
def get_map_info(map_id, auth_token=None):
|
|
242
|
+
return generic.process_response(
|
|
243
|
+
generic.geotastic_api_request(
|
|
244
|
+
"https://api.geotastic.net/v1/maps/getPlayableMap.php",
|
|
245
|
+
"GET",
|
|
246
|
+
auth_token,
|
|
247
|
+
params={"id": map_id},
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@Client._register_endpoint
|
|
253
|
+
def increase_play_count(map_id, auth_token=None):
|
|
254
|
+
response = generic.geotastic_api_request(
|
|
255
|
+
"https://backend01.geotastic.net/v1/maps/incrementPlayedMapAmountV2.php",
|
|
256
|
+
"POST",
|
|
257
|
+
auth_token,
|
|
258
|
+
json={"enc": generic.encode_encdata({"mapId": map_id})},
|
|
259
|
+
)
|
|
260
|
+
return generic.process_response(response)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from . import generic
|
|
2
|
+
from .client import Client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@Client._register_endpoint
|
|
6
|
+
def get_app_config(auth_token=None):
|
|
7
|
+
return generic.process_response(
|
|
8
|
+
generic.geotastic_api_request(
|
|
9
|
+
"https://api.geotastic.net/v1/config/getAppConfig.php", "GET", auth_token
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@Client._register_endpoint
|
|
15
|
+
def get_community_map_markers(auth_token=None):
|
|
16
|
+
return generic.process_response(
|
|
17
|
+
generic.geotastic_api_request(
|
|
18
|
+
"https://api.geotastic.net/v1/communityMap/getMarkers.php",
|
|
19
|
+
"GET",
|
|
20
|
+
auth_token,
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@Client._register_endpoint
|
|
26
|
+
def request_api_key(auth_token=None):
|
|
27
|
+
data = generic.encode_encdata({})
|
|
28
|
+
return generic.process_response(
|
|
29
|
+
generic.geotastic_api_request(
|
|
30
|
+
"https://api.geotastic.net/v1/config/requestApiKey.php",
|
|
31
|
+
"POST",
|
|
32
|
+
auth_token,
|
|
33
|
+
json={"enc": data},
|
|
34
|
+
)
|
|
35
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from . import generic
|
|
2
|
+
from .client import Client
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@Client._register_endpoint
|
|
6
|
+
def get_season(auth_token=None):
|
|
7
|
+
return generic.process_response(
|
|
8
|
+
generic.geotastic_api_request(
|
|
9
|
+
"https://api.geotastic.net/v1/season/getSeason.php", "GET", auth_token
|
|
10
|
+
)
|
|
11
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from . import generic
|
|
2
|
+
|
|
3
|
+
from .client import Client
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@Client._register_endpoint
|
|
7
|
+
def find_users(nickname=None, uid=None, auth_token=None):
|
|
8
|
+
if not bool(nickname) ^ bool(uid):
|
|
9
|
+
raise ValueError("Specify exactly one of nickname, uid")
|
|
10
|
+
params = {"p": "false"}
|
|
11
|
+
if nickname:
|
|
12
|
+
params.update({"s": nickname, "t": "nickname"})
|
|
13
|
+
elif uid:
|
|
14
|
+
params.udpate({"s": uid, "t": "uid"})
|
|
15
|
+
return generic.process_response(
|
|
16
|
+
generic.geotastic_api_request(
|
|
17
|
+
"https://api.geotastic.net/v1/user/getUserSuggestions.php",
|
|
18
|
+
"GET",
|
|
19
|
+
auth_token,
|
|
20
|
+
params=params,
|
|
21
|
+
)
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@Client._register_endpoint
|
|
26
|
+
def get_public_user_info(uid, auth_token=None):
|
|
27
|
+
return generic.process_response(
|
|
28
|
+
generic.geotastic_api_request(
|
|
29
|
+
"https://api.geotastic.net/v1/user/getPublicUserInfoByUid.php",
|
|
30
|
+
"GET",
|
|
31
|
+
auth_token,
|
|
32
|
+
params={"uid": uid},
|
|
33
|
+
)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@Client._register_endpoint
|
|
38
|
+
def get_achievements(uid, auth_token=None):
|
|
39
|
+
return generic.process_response(
|
|
40
|
+
generic.geotastic_api_request(
|
|
41
|
+
"https://api.geotastic.net/v1/user/getAchievementsByUser.php",
|
|
42
|
+
"GET",
|
|
43
|
+
auth_token,
|
|
44
|
+
params={"uid": uid},
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@Client._register_endpoint
|
|
50
|
+
def get_statistics(uid, auth_token=None):
|
|
51
|
+
data = generic.encode_encdata({"userUid": uid})
|
|
52
|
+
return generic.process_response(
|
|
53
|
+
generic.geotastic_api_request(
|
|
54
|
+
"https://api.geotastic.net/v1/user/getUserStatistics.php",
|
|
55
|
+
"POST",
|
|
56
|
+
auth_token,
|
|
57
|
+
json={"enc": data},
|
|
58
|
+
)
|
|
59
|
+
)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gt_api
|
|
3
|
+
Version: 0.0.4
|
|
4
|
+
Summary: Geotastic internal API wrapper
|
|
5
|
+
Author-email: Adam Jenca <jenca.adam@gmail.com>
|
|
6
|
+
Maintainer-email: Adam Jenca <jenca.adam@gmail.com>
|
|
7
|
+
License: DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
8
|
+
Version 2, December 2004
|
|
9
|
+
|
|
10
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
11
|
+
|
|
12
|
+
Everyone is permitted to copy and distribute verbatim or modified
|
|
13
|
+
copies of this license document, and changing it is allowed as long
|
|
14
|
+
as the name is changed.
|
|
15
|
+
|
|
16
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
17
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
18
|
+
|
|
19
|
+
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
20
|
+
|
|
21
|
+
Project-URL: Homepage, https://github.com/jenca-adam/geotastic_api
|
|
22
|
+
Project-URL: Bug Reports, https://github.com/jenca-adam/geotastic_api/issues
|
|
23
|
+
Project-URL: Source, https://github.com/jenca-adam/geotastic_api
|
|
24
|
+
Keywords: geotastic,api
|
|
25
|
+
Classifier: Development Status :: 3 - Alpha
|
|
26
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
27
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
28
|
+
Classifier: Intended Audience :: Developers
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: pycryptodome
|
|
32
|
+
Requires-Dist: requests
|
|
33
|
+
Requires-Dist: websockets
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# geotastic-api
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Not on PyPI yet.
|
|
41
|
+
Requires Python.
|
|
42
|
+
```
|
|
43
|
+
python3 -m pip install .
|
|
44
|
+
```
|
|
45
|
+
(also installs requirements)
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Logging in
|
|
50
|
+
|
|
51
|
+
Most API requests require logging in. There are two ways to do it:
|
|
52
|
+
|
|
53
|
+
1. mail + password
|
|
54
|
+
1. token
|
|
55
|
+
|
|
56
|
+
#### Mail + Password
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import gt_api
|
|
60
|
+
client=gt_api.Client.login('your.mail@example.com', 'hunter2')
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
And you're in! The big caveat is that this logs you out of any other sessions, and also causes API costs.
|
|
64
|
+
So for most use cases you want to use tokens.
|
|
65
|
+
|
|
66
|
+
#### Token
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import gt_api
|
|
70
|
+
client=gt_api.Client('d8f350a3f557adc253f5003a81d3098c06dea93f84edf10e3fabc1d92acd1771')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
##### How do I get it?
|
|
74
|
+
|
|
75
|
+
Go to geotastic and log in with your account.
|
|
76
|
+
Then open the developer tools (F12 or ctrl-shift-i). Then navigate to Storage(Application->Storage for Chrome)->Local Storage->https://geotastic.net/ and scroll down to *token*. Copy the value.
|
|
77
|
+
|
|
78
|
+
Please note that the token is one per session, so once you log out of the session you got the token from, you'll need to update it.
|
|
79
|
+
#### Without Login
|
|
80
|
+
|
|
81
|
+
Use `Client(None)`.
|
|
82
|
+
Most functions will not work.
|
|
83
|
+
### Creating a drop group
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
map_id=12345
|
|
87
|
+
group = client.create_drop_group(map_id, -12.345, -69.420, 'pe', 'My Drop Group', active=True, bias=5.3)
|
|
88
|
+
print(group["dropGroup"]["id"])
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Listing drop groups
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
client.get_drop_groups(12345)
|
|
95
|
+
```
|
|
96
|
+
If you don't own the map, use `get_public_drop_groups()`
|
|
97
|
+
|
|
98
|
+
### Exporting drops
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
client.get_map_drops(12345)
|
|
102
|
+
```
|
|
103
|
+
or
|
|
104
|
+
```python
|
|
105
|
+
client.get_group_drops(12345)
|
|
106
|
+
```
|
|
107
|
+
You DON'T need to own the map or the group.
|
|
108
|
+
|
|
109
|
+
### Importing drops
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
drops=[{"id":1,"style":"streetview","lat":39.28719501248246,"lng":-103.07696260471509,"code":"us","panoId":"Ple0qA2-cNzxc0K-gXgbFA"}]
|
|
113
|
+
client.import_drops(drops, 12345, target_type="map", import_type="merge")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Possible target types are `map` and `group`.
|
|
117
|
+
Possible import types are `merge`, `override` and `update`.
|
|
118
|
+
|
|
119
|
+
### Lobbies
|
|
120
|
+
|
|
121
|
+
Lobbies are handled a little differently.
|
|
122
|
+
To create a lobby, use `Lobby.create(token)`.
|
|
123
|
+
To join a lobby, user `Lobby.join(token, lobby_id)`.
|
|
124
|
+
You then add handlers for events coming in from the Lobby socket using the `lobby.add_handler("event")` decorator. The handler for the event "\*" will be called for every event.
|
|
125
|
+
You can send socket messages to the lobby using `lobby.send_message(type, **kwargs)`. The kwargs will be added to the message json.
|
|
126
|
+
You can make lobby api requests using `lobby.lobby_api_request(url, method *args, **kwargs)`. Args and kwargs will be passed to `request.request()`.
|
|
127
|
+
To run the lobby event loop, use `Lobby.run()`
|
|
128
|
+
To disconnect from the lobby, use `Lobby.disconnect()`
|
|
129
|
+
Look at `examples/auto_lobby.py`
|
|
130
|
+
### Other uses
|
|
131
|
+
|
|
132
|
+
There's loads more I can't be bothered to document. Check the source code.
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## Contributing
|
|
136
|
+
|
|
137
|
+
If you'd like to have a feature that's not in the library, just create a github issue and I'll get to it (maybe).
|
|
138
|
+
|
|
139
|
+
If you'd like to add your own, it's pretty easy. Just use developer tools to check which api calls geotastic is making and then remake them as functions. If the calls are encrypted, use `gt_api.generic.decode_encdata`.
|
|
140
|
+
|
|
141
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
gt_api/__init__.py
|
|
5
|
+
gt_api/challenge.py
|
|
6
|
+
gt_api/client.py
|
|
7
|
+
gt_api/errors.py
|
|
8
|
+
gt_api/generic.py
|
|
9
|
+
gt_api/geo.py
|
|
10
|
+
gt_api/lobby.py
|
|
11
|
+
gt_api/login.py
|
|
12
|
+
gt_api/map.py
|
|
13
|
+
gt_api/misc.py
|
|
14
|
+
gt_api/season.py
|
|
15
|
+
gt_api/user.py
|
|
16
|
+
gt_api.egg-info/PKG-INFO
|
|
17
|
+
gt_api.egg-info/SOURCES.txt
|
|
18
|
+
gt_api.egg-info/dependency_links.txt
|
|
19
|
+
gt_api.egg-info/requires.txt
|
|
20
|
+
gt_api.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gt_api
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[tool.setuptools]
|
|
7
|
+
packages = ["gt_api"]
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
name = "gt_api"
|
|
11
|
+
version = "0.0.4"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pycryptodome",
|
|
14
|
+
"requests",
|
|
15
|
+
"websockets"
|
|
16
|
+
]
|
|
17
|
+
description = "Geotastic internal API wrapper"
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
license = {file = "LICENSE"}
|
|
20
|
+
keywords = ["geotastic", "api"]
|
|
21
|
+
authors = [{name="Adam Jenca", email="jenca.adam@gmail.com"}]
|
|
22
|
+
maintainers = [{name="Adam Jenca", email="jenca.adam@gmail.com"}]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
"Homepage" = "https://github.com/jenca-adam/geotastic_api"
|
|
32
|
+
"Bug Reports" = "https://github.com/jenca-adam/geotastic_api/issues"
|
|
33
|
+
"Source" = "https://github.com/jenca-adam/geotastic_api"
|
gt_api-0.0.4/setup.cfg
ADDED