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 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-&gt;Storage for Chrome)-&gt;Local Storage-&gt;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-&gt;Storage for Chrome)-&gt;Local Storage-&gt;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,3 @@
1
+ from . import challenge, generic, geo, login, map, misc, season, user
2
+ from .client import Client
3
+ from .lobby import Lobby
@@ -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,6 @@
1
+ class GeotasticAPIError(Exception):
2
+ pass
3
+
4
+
5
+ class LobbyError(GeotasticAPIError):
6
+ pass
@@ -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-&gt;Storage for Chrome)-&gt;Local Storage-&gt;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,3 @@
1
+ pycryptodome
2
+ requests
3
+ websockets
@@ -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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+