woltapi 0.0.1__tar.gz → 0.2.0__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.
- {woltapi-0.0.1 → woltapi-0.2.0}/MANIFEST.in +1 -1
- woltapi-0.2.0/PKG-INFO +327 -0
- woltapi-0.2.0/README.md +309 -0
- woltapi-0.2.0/RELEASING.md +91 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/examples/browse.py +86 -17
- woltapi-0.2.0/examples/order.py +419 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/pyproject.toml +1 -1
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/__init__.py +4 -0
- woltapi-0.2.0/src/woltapi/auth.py +201 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/client.py +21 -6
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/models.py +10 -3
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/selection.py +92 -8
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/transport.py +6 -1
- woltapi-0.2.0/src/woltapi.egg-info/PKG-INFO +327 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi.egg-info/SOURCES.txt +5 -1
- woltapi-0.2.0/tests/test_auth.py +553 -0
- woltapi-0.2.0/tests/test_browse.py +294 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/tests/test_client.py +83 -12
- woltapi-0.2.0/tests/test_order_example.py +481 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/tests/test_selection.py +55 -0
- woltapi-0.0.1/PKG-INFO +0 -215
- woltapi-0.0.1/README.md +0 -197
- woltapi-0.0.1/WOLT_API.md +0 -1040
- woltapi-0.0.1/src/woltapi.egg-info/PKG-INFO +0 -215
- woltapi-0.0.1/tests/test_browse.py +0 -123
- {woltapi-0.0.1 → woltapi-0.2.0}/LICENSE +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/examples/check_session.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/setup.cfg +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/credentials.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/errors.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/purchase.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi/services.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi.egg-info/dependency_links.txt +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi.egg-info/requires.txt +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/src/woltapi.egg-info/top_level.txt +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/tests/__init__.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/tests/test_check_session.py +0 -0
- {woltapi-0.0.1 → woltapi-0.2.0}/tests/test_purchase.py +0 -0
woltapi-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: woltapi
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A small synchronous client for observed Wolt ordering flows.
|
|
5
|
+
License-Expression: AGPL-3.0-only
|
|
6
|
+
Project-URL: Repository, https://github.com/skorokithakis/woltapi
|
|
7
|
+
Project-URL: Issues, https://github.com/skorokithakis/woltapi/issues
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Provides-Extra: test
|
|
16
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# woltapi
|
|
20
|
+
|
|
21
|
+
A Python library for looking up restaurants, reading menus, and viewing your
|
|
22
|
+
Wolt orders.
|
|
23
|
+
|
|
24
|
+
This is an **unofficial** project. It also has code for placing orders, but that
|
|
25
|
+
part is unfinished and has not been tested with a real purchase.
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
You need Python 3.10 or newer. Install the library from PyPI:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install woltapi
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
You can then use `from woltapi import WoltClient, SessionCredentials` in your
|
|
36
|
+
Python code. See [Use it in Python](#use-it-in-python) below.
|
|
37
|
+
|
|
38
|
+
### Install from source
|
|
39
|
+
|
|
40
|
+
To run the example scripts or work on the library, clone this repository and
|
|
41
|
+
install an editable copy:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
git clone https://github.com/skorokithakis/woltapi.git
|
|
45
|
+
cd woltapi
|
|
46
|
+
python3 -m venv .venv
|
|
47
|
+
source .venv/bin/activate
|
|
48
|
+
python -m pip install -e .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
These commands create a separate Python environment and install the library in
|
|
52
|
+
it. On Windows, activate it with `.venv\Scripts\activate` instead of `source`.
|
|
53
|
+
|
|
54
|
+
## Try it
|
|
55
|
+
|
|
56
|
+
Run these commands from the project folder after installing from source above.
|
|
57
|
+
The `examples/` scripts are not installed by `pip install woltapi`.
|
|
58
|
+
|
|
59
|
+
The browsing example shows your recent orders, searches for a restaurant, and
|
|
60
|
+
lists some of its menu items. **It will not order anything or change your basket.**
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python examples/browse.py \
|
|
64
|
+
--latitude 60.17 \
|
|
65
|
+
--longitude 24.94 \
|
|
66
|
+
--query "pizza" \
|
|
67
|
+
--token-file ~/.wolt-token
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Replace the two numbers with your location. The example numbers are in Helsinki.
|
|
71
|
+
Latitude and longitude are the two numbers that identify a place on a map.
|
|
72
|
+
|
|
73
|
+
`--token-file` is required. It names the file that holds your Wolt refresh
|
|
74
|
+
token. On the first run the file does not exist yet, so the script asks for the
|
|
75
|
+
token. Paste it and press Enter. Nothing will appear while you paste; that is
|
|
76
|
+
intentional. The script then saves the token to that file, and later runs read
|
|
77
|
+
it from there without asking. See [Get a token](#get-a-token) below.
|
|
78
|
+
|
|
79
|
+
By default, it shows up to 5 recent orders and 20 menu items from the first
|
|
80
|
+
restaurant in the search results. You can change that:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python examples/browse.py \
|
|
84
|
+
--latitude 60.17 \
|
|
85
|
+
--longitude 24.94 \
|
|
86
|
+
--query "burger" \
|
|
87
|
+
--token-file ~/.wolt-token \
|
|
88
|
+
--orders 3 \
|
|
89
|
+
--menu-limit 50 \
|
|
90
|
+
--venue-index 2
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`--venue-index 2` chooses the second search result. You can also search for a
|
|
94
|
+
restaurant by name. Use `--help` to see all the options.
|
|
95
|
+
|
|
96
|
+
Menu prices are converted from cents: for example, `1234` is shown as `12.34 EUR`.
|
|
97
|
+
These are item prices, not a final order total including delivery and other fees.
|
|
98
|
+
The library keeps the original integer amounts; only the display converts them.
|
|
99
|
+
|
|
100
|
+
## Get a token
|
|
101
|
+
|
|
102
|
+
A **refresh token** is a long-lived key that lets the library sign requests
|
|
103
|
+
for your Wolt account. Treat it like a password.
|
|
104
|
+
|
|
105
|
+
1. Sign in to Wolt in your browser.
|
|
106
|
+
2. Open Developer Tools and select **Application** (Firefox: **Storage**).
|
|
107
|
+
3. Under **Cookies**, find the cookie named `__wrtoken`.
|
|
108
|
+
4. Copy its value, without surrounding quotes.
|
|
109
|
+
|
|
110
|
+
Do not share the token, put it in Git, or include it in screenshots.
|
|
111
|
+
|
|
112
|
+
The example scripts read the token from the file named by `--token-file`. If
|
|
113
|
+
that file does not exist, or is empty, they ask with a hidden prompt and then
|
|
114
|
+
save what you paste. The file is created readable only by you.
|
|
115
|
+
|
|
116
|
+
Wolt may replace your refresh token over time. The scripts save each
|
|
117
|
+
replacement to the same file, so later runs keep working without another visit
|
|
118
|
+
to the browser. Point every run at the same file.
|
|
119
|
+
|
|
120
|
+
Because of this, the token file holds a live secret and becomes the only copy
|
|
121
|
+
once Wolt has replaced the original. Keep it out of Git and shared folders. Do
|
|
122
|
+
not point two programs that run at the same time at one token file: whichever
|
|
123
|
+
refreshes second will find its own token already replaced.
|
|
124
|
+
|
|
125
|
+
If a run receives **HTTP 401**, the saved token has expired or was revoked. Put
|
|
126
|
+
a current `__wrtoken` value in the token file, or delete the file and let the
|
|
127
|
+
script ask again. The library cannot sign you in.
|
|
128
|
+
|
|
129
|
+
The scripts exchange this refresh token for short-lived access tokens
|
|
130
|
+
automatically; you never handle access tokens yourself. If you want to supply
|
|
131
|
+
raw `Authorization` headers instead, use `SessionCredentials` from Python
|
|
132
|
+
directly, as described under
|
|
133
|
+
[Manually supplied headers](#manually-supplied-headers).
|
|
134
|
+
|
|
135
|
+
## Use it in Python
|
|
136
|
+
|
|
137
|
+
Here is a complete browsing example. It asks for your refresh token and
|
|
138
|
+
location, then searches for pizza and loads the first result's menu:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from getpass import getpass
|
|
142
|
+
|
|
143
|
+
from woltapi import RefreshTokenCredentials, WoltClient
|
|
144
|
+
|
|
145
|
+
client = WoltClient(
|
|
146
|
+
RefreshTokenCredentials(
|
|
147
|
+
getpass("Wolt refresh token: ").strip(),
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
latitude = float(input("Latitude: "))
|
|
152
|
+
longitude = float(input("Longitude: "))
|
|
153
|
+
|
|
154
|
+
restaurants = client.search_venues(
|
|
155
|
+
"pizza",
|
|
156
|
+
latitude=latitude,
|
|
157
|
+
longitude=longitude,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
for restaurant in restaurants:
|
|
161
|
+
print(restaurant.title or restaurant.slug)
|
|
162
|
+
|
|
163
|
+
if restaurants:
|
|
164
|
+
restaurant = restaurants[0]
|
|
165
|
+
menu = client.get_assortment(restaurant.slug)
|
|
166
|
+
print(f"Found {len(menu.get('items', []))} menu items.")
|
|
167
|
+
else:
|
|
168
|
+
print("No restaurants found. Try another search.")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**The library does not find your token for you.** Your code passes it into
|
|
172
|
+
`RefreshTokenCredentials`. Reading a file, an environment variable, or a prompt
|
|
173
|
+
is the job of your script, not the library.
|
|
174
|
+
|
|
175
|
+
### How token refresh works
|
|
176
|
+
|
|
177
|
+
Only the **consumer refresh token** is needed to start: no access token,
|
|
178
|
+
password, client secret, or browser cookies need to accompany the request. This
|
|
179
|
+
is the `__wrtoken` value from [Get a token](#get-a-token). It is not the access
|
|
180
|
+
token from an Authorization header or the refresh token used by the Converse
|
|
181
|
+
support widget.
|
|
182
|
+
|
|
183
|
+
The first API call exchanges the refresh token at
|
|
184
|
+
`https://authentication.wolt.com/v1/wauth2/access_token`. Subsequent calls reuse
|
|
185
|
+
the access token until shortly before its server-reported expiry (30 minutes in
|
|
186
|
+
the verified response). The access token is supplied to the restaurant, consumer,
|
|
187
|
+
and payment hosts; the refresh token is sent only to the authentication host.
|
|
188
|
+
You can also call `credentials.refresh()` to exchange it explicitly.
|
|
189
|
+
|
|
190
|
+
Wolt may return a replacement refresh token. `credentials.refresh_token` always
|
|
191
|
+
holds the latest successfully validated value. For applications that run across
|
|
192
|
+
restarts, pass `on_refresh=save_refresh_token`, where your function accepts that
|
|
193
|
+
string and saves it to your secret store after each exchange. The library does
|
|
194
|
+
not read or write credential files. Without persistence, a rotated token may be
|
|
195
|
+
lost when your process exits. Do not share one refresh token across independent
|
|
196
|
+
processes that might refresh it concurrently.
|
|
197
|
+
|
|
198
|
+
The callback runs before the API request proceeds and must not call back into
|
|
199
|
+
`credentials.refresh()` or `credentials.headers_for()`. If it raises, the
|
|
200
|
+
exception propagates but the new tokens remain in memory. Later calls retry the
|
|
201
|
+
callback before any further authentication or API request; they remain blocked
|
|
202
|
+
until persistence succeeds. Make your callback safe to repeat with the same token.
|
|
203
|
+
|
|
204
|
+
Optional `restaurant_headers`, `consumer_headers`, and `payment_headers` scope
|
|
205
|
+
extra headers to their respective hosts. `Authorization` is managed by
|
|
206
|
+
`RefreshTokenCredentials`. Its `timeout` controls authentication requests
|
|
207
|
+
separately from `WoltClient`'s API timeout (both default to 10 seconds).
|
|
208
|
+
|
|
209
|
+
Refresh requests are not retried or redirected, and API requests are never
|
|
210
|
+
automatically replayed after a 401, including purchases. An expired or revoked
|
|
211
|
+
refresh token requires a new browser session credential. Authentication failures
|
|
212
|
+
use the existing exceptions, such as `HTTPStatusError` with
|
|
213
|
+
`service == "authentication"`.
|
|
214
|
+
|
|
215
|
+
### Manually supplied headers
|
|
216
|
+
|
|
217
|
+
If you already hold a short-lived access token and want to supply raw headers
|
|
218
|
+
yourself, use `SessionCredentials` instead:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from woltapi import SessionCredentials, WoltClient
|
|
222
|
+
|
|
223
|
+
headers = {"Authorization": "Bearer <access token>"}
|
|
224
|
+
client = WoltClient(
|
|
225
|
+
SessionCredentials(restaurant_headers=headers, consumer_headers=headers)
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Wolt uses different servers for different jobs. `restaurant_headers` supplies
|
|
230
|
+
the login token for searches and saved delivery addresses. `consumer_headers`
|
|
231
|
+
supplies it for menus and order history. The library sends each set of headers
|
|
232
|
+
only to its matching server. With this class, nothing renews the token: after
|
|
233
|
+
about 30 minutes, requests fail with **HTTP 401**. The example scripts no
|
|
234
|
+
longer use this path; prefer `RefreshTokenCredentials`.
|
|
235
|
+
|
|
236
|
+
### Useful methods
|
|
237
|
+
|
|
238
|
+
| Call | What you get |
|
|
239
|
+
| --- | --- |
|
|
240
|
+
| `client.search_venues("pizza", latitude, longitude)` | Restaurant results with names, IDs, and slugs. A slug is the name used in a restaurant's URL. |
|
|
241
|
+
| `client.get_assortment(slug)` | A dictionary containing menu items and their options. |
|
|
242
|
+
| `client.get_venue_static(slug)` | A dictionary of restaurant details. |
|
|
243
|
+
| `client.get_venue_dynamic(slug, latitude, longitude)` | Current opening and delivery information. |
|
|
244
|
+
| `client.get_orders_page()` | A dictionary containing the current page of order history. |
|
|
245
|
+
| `client.list_delivery_targets()` | References to your saved delivery addresses, with saved labels and address details. |
|
|
246
|
+
| `client.get_order_status(purchase_id)` | An order's status and some price information. |
|
|
247
|
+
| `derive_checkout_fields(assortment, item)` | The checkout metadata fields for one menu item, derived from the assortment. Raises an error for items in zero or multiple categories. |
|
|
248
|
+
|
|
249
|
+
For example, after creating `client`:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
history = client.get_orders_page()
|
|
253
|
+
orders = history.get("orders", [])
|
|
254
|
+
print(f"This page contains {len(orders)} orders.")
|
|
255
|
+
|
|
256
|
+
targets = client.list_delivery_targets()
|
|
257
|
+
print(f"You have {len(targets)} saved delivery addresses.")
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Responses can contain personal information. Avoid printing entire responses or
|
|
261
|
+
sending them to shared logs. The examples print saved addresses and card labels
|
|
262
|
+
to the local terminal only. The browsing example prints only selected fields.
|
|
263
|
+
|
|
264
|
+
## Can it order food?
|
|
265
|
+
|
|
266
|
+
Not as a simple, ready-to-use feature yet. There is no `order("pizza")` method.
|
|
267
|
+
|
|
268
|
+
To try **checkout without buying anything**, use the new checkout-only example
|
|
269
|
+
from an editable source installation:
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
python examples/order.py --latitude 60.17 --longitude 24.94 --query pizza \
|
|
273
|
+
--token-file ~/.wolt-token
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Use your own coordinates. It reads and saves your refresh token as described in
|
|
277
|
+
[Get a token](#get-a-token), guides you through selecting an item and a saved
|
|
278
|
+
delivery target/card, then asks before requesting a price. It never submits a
|
|
279
|
+
purchase. Basket saving is off by default and needs a separate confirmation if
|
|
280
|
+
enabled.
|
|
281
|
+
|
|
282
|
+
The checkout example derives `category_id`, `category_ids`, and the three
|
|
283
|
+
checkout exclusion flags from the assortment, so those fields do not need a
|
|
284
|
+
browser context file. It only supports items in exactly one category and stops
|
|
285
|
+
rather than guessing for zero or multiple categories. If it stops, you can
|
|
286
|
+
supply the missing values yourself with `--context-file <path>`, a JSON object
|
|
287
|
+
whose `checkout_fields` entries were copied from your own browser's checkout
|
|
288
|
+
request for the same item. It may still stop if other required catalog data is
|
|
289
|
+
missing. Run `python examples/order.py --help` for available options.
|
|
290
|
+
|
|
291
|
+
The library has methods to choose items, save a basket, ask Wolt for a price,
|
|
292
|
+
and submit a purchase. But some required inputs still need to come from your
|
|
293
|
+
own code, including browser/device information and detailed item data.
|
|
294
|
+
|
|
295
|
+
Order-history and saved-address reads have worked in live checks. **Payment and
|
|
296
|
+
purchase handling have not been verified with a real order.** The purchase code
|
|
297
|
+
only targets one restaurant, immediate home delivery, and one saved card.
|
|
298
|
+
|
|
299
|
+
`submit_prepared_order()` is the call that can place an order and charge you.
|
|
300
|
+
Do not call it unless you intend to buy the exact order you have reviewed. If it
|
|
301
|
+
times out or raises `OrderOutcomeUnknown`, **do not send it again**: Wolt may
|
|
302
|
+
already have received it. Check the order in Wolt instead.
|
|
303
|
+
|
|
304
|
+
Login, adding cards, payment verification screens, scheduled
|
|
305
|
+
orders, cancellation, and refunds are not supported.
|
|
306
|
+
|
|
307
|
+
## Run the tests
|
|
308
|
+
|
|
309
|
+
With your Python environment active:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
python -m pip install -e ".[test]"
|
|
313
|
+
python -m pytest
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The tests use made-up responses. They do not contact Wolt, need your token, or
|
|
317
|
+
place orders.
|
|
318
|
+
|
|
319
|
+
## Releases
|
|
320
|
+
|
|
321
|
+
Maintainers: see [RELEASING.md](RELEASING.md) for tests, release PRs, and automatic
|
|
322
|
+
PyPI publishing.
|
|
323
|
+
|
|
324
|
+
## License
|
|
325
|
+
|
|
326
|
+
Licensed under the GNU Affero General Public License, version 3
|
|
327
|
+
(`AGPL-3.0-only`). See [LICENSE](LICENSE) for the full terms.
|
woltapi-0.2.0/README.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# woltapi
|
|
2
|
+
|
|
3
|
+
A Python library for looking up restaurants, reading menus, and viewing your
|
|
4
|
+
Wolt orders.
|
|
5
|
+
|
|
6
|
+
This is an **unofficial** project. It also has code for placing orders, but that
|
|
7
|
+
part is unfinished and has not been tested with a real purchase.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
You need Python 3.10 or newer. Install the library from PyPI:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install woltapi
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
You can then use `from woltapi import WoltClient, SessionCredentials` in your
|
|
18
|
+
Python code. See [Use it in Python](#use-it-in-python) below.
|
|
19
|
+
|
|
20
|
+
### Install from source
|
|
21
|
+
|
|
22
|
+
To run the example scripts or work on the library, clone this repository and
|
|
23
|
+
install an editable copy:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/skorokithakis/woltapi.git
|
|
27
|
+
cd woltapi
|
|
28
|
+
python3 -m venv .venv
|
|
29
|
+
source .venv/bin/activate
|
|
30
|
+
python -m pip install -e .
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
These commands create a separate Python environment and install the library in
|
|
34
|
+
it. On Windows, activate it with `.venv\Scripts\activate` instead of `source`.
|
|
35
|
+
|
|
36
|
+
## Try it
|
|
37
|
+
|
|
38
|
+
Run these commands from the project folder after installing from source above.
|
|
39
|
+
The `examples/` scripts are not installed by `pip install woltapi`.
|
|
40
|
+
|
|
41
|
+
The browsing example shows your recent orders, searches for a restaurant, and
|
|
42
|
+
lists some of its menu items. **It will not order anything or change your basket.**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python examples/browse.py \
|
|
46
|
+
--latitude 60.17 \
|
|
47
|
+
--longitude 24.94 \
|
|
48
|
+
--query "pizza" \
|
|
49
|
+
--token-file ~/.wolt-token
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Replace the two numbers with your location. The example numbers are in Helsinki.
|
|
53
|
+
Latitude and longitude are the two numbers that identify a place on a map.
|
|
54
|
+
|
|
55
|
+
`--token-file` is required. It names the file that holds your Wolt refresh
|
|
56
|
+
token. On the first run the file does not exist yet, so the script asks for the
|
|
57
|
+
token. Paste it and press Enter. Nothing will appear while you paste; that is
|
|
58
|
+
intentional. The script then saves the token to that file, and later runs read
|
|
59
|
+
it from there without asking. See [Get a token](#get-a-token) below.
|
|
60
|
+
|
|
61
|
+
By default, it shows up to 5 recent orders and 20 menu items from the first
|
|
62
|
+
restaurant in the search results. You can change that:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python examples/browse.py \
|
|
66
|
+
--latitude 60.17 \
|
|
67
|
+
--longitude 24.94 \
|
|
68
|
+
--query "burger" \
|
|
69
|
+
--token-file ~/.wolt-token \
|
|
70
|
+
--orders 3 \
|
|
71
|
+
--menu-limit 50 \
|
|
72
|
+
--venue-index 2
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`--venue-index 2` chooses the second search result. You can also search for a
|
|
76
|
+
restaurant by name. Use `--help` to see all the options.
|
|
77
|
+
|
|
78
|
+
Menu prices are converted from cents: for example, `1234` is shown as `12.34 EUR`.
|
|
79
|
+
These are item prices, not a final order total including delivery and other fees.
|
|
80
|
+
The library keeps the original integer amounts; only the display converts them.
|
|
81
|
+
|
|
82
|
+
## Get a token
|
|
83
|
+
|
|
84
|
+
A **refresh token** is a long-lived key that lets the library sign requests
|
|
85
|
+
for your Wolt account. Treat it like a password.
|
|
86
|
+
|
|
87
|
+
1. Sign in to Wolt in your browser.
|
|
88
|
+
2. Open Developer Tools and select **Application** (Firefox: **Storage**).
|
|
89
|
+
3. Under **Cookies**, find the cookie named `__wrtoken`.
|
|
90
|
+
4. Copy its value, without surrounding quotes.
|
|
91
|
+
|
|
92
|
+
Do not share the token, put it in Git, or include it in screenshots.
|
|
93
|
+
|
|
94
|
+
The example scripts read the token from the file named by `--token-file`. If
|
|
95
|
+
that file does not exist, or is empty, they ask with a hidden prompt and then
|
|
96
|
+
save what you paste. The file is created readable only by you.
|
|
97
|
+
|
|
98
|
+
Wolt may replace your refresh token over time. The scripts save each
|
|
99
|
+
replacement to the same file, so later runs keep working without another visit
|
|
100
|
+
to the browser. Point every run at the same file.
|
|
101
|
+
|
|
102
|
+
Because of this, the token file holds a live secret and becomes the only copy
|
|
103
|
+
once Wolt has replaced the original. Keep it out of Git and shared folders. Do
|
|
104
|
+
not point two programs that run at the same time at one token file: whichever
|
|
105
|
+
refreshes second will find its own token already replaced.
|
|
106
|
+
|
|
107
|
+
If a run receives **HTTP 401**, the saved token has expired or was revoked. Put
|
|
108
|
+
a current `__wrtoken` value in the token file, or delete the file and let the
|
|
109
|
+
script ask again. The library cannot sign you in.
|
|
110
|
+
|
|
111
|
+
The scripts exchange this refresh token for short-lived access tokens
|
|
112
|
+
automatically; you never handle access tokens yourself. If you want to supply
|
|
113
|
+
raw `Authorization` headers instead, use `SessionCredentials` from Python
|
|
114
|
+
directly, as described under
|
|
115
|
+
[Manually supplied headers](#manually-supplied-headers).
|
|
116
|
+
|
|
117
|
+
## Use it in Python
|
|
118
|
+
|
|
119
|
+
Here is a complete browsing example. It asks for your refresh token and
|
|
120
|
+
location, then searches for pizza and loads the first result's menu:
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from getpass import getpass
|
|
124
|
+
|
|
125
|
+
from woltapi import RefreshTokenCredentials, WoltClient
|
|
126
|
+
|
|
127
|
+
client = WoltClient(
|
|
128
|
+
RefreshTokenCredentials(
|
|
129
|
+
getpass("Wolt refresh token: ").strip(),
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
latitude = float(input("Latitude: "))
|
|
134
|
+
longitude = float(input("Longitude: "))
|
|
135
|
+
|
|
136
|
+
restaurants = client.search_venues(
|
|
137
|
+
"pizza",
|
|
138
|
+
latitude=latitude,
|
|
139
|
+
longitude=longitude,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
for restaurant in restaurants:
|
|
143
|
+
print(restaurant.title or restaurant.slug)
|
|
144
|
+
|
|
145
|
+
if restaurants:
|
|
146
|
+
restaurant = restaurants[0]
|
|
147
|
+
menu = client.get_assortment(restaurant.slug)
|
|
148
|
+
print(f"Found {len(menu.get('items', []))} menu items.")
|
|
149
|
+
else:
|
|
150
|
+
print("No restaurants found. Try another search.")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**The library does not find your token for you.** Your code passes it into
|
|
154
|
+
`RefreshTokenCredentials`. Reading a file, an environment variable, or a prompt
|
|
155
|
+
is the job of your script, not the library.
|
|
156
|
+
|
|
157
|
+
### How token refresh works
|
|
158
|
+
|
|
159
|
+
Only the **consumer refresh token** is needed to start: no access token,
|
|
160
|
+
password, client secret, or browser cookies need to accompany the request. This
|
|
161
|
+
is the `__wrtoken` value from [Get a token](#get-a-token). It is not the access
|
|
162
|
+
token from an Authorization header or the refresh token used by the Converse
|
|
163
|
+
support widget.
|
|
164
|
+
|
|
165
|
+
The first API call exchanges the refresh token at
|
|
166
|
+
`https://authentication.wolt.com/v1/wauth2/access_token`. Subsequent calls reuse
|
|
167
|
+
the access token until shortly before its server-reported expiry (30 minutes in
|
|
168
|
+
the verified response). The access token is supplied to the restaurant, consumer,
|
|
169
|
+
and payment hosts; the refresh token is sent only to the authentication host.
|
|
170
|
+
You can also call `credentials.refresh()` to exchange it explicitly.
|
|
171
|
+
|
|
172
|
+
Wolt may return a replacement refresh token. `credentials.refresh_token` always
|
|
173
|
+
holds the latest successfully validated value. For applications that run across
|
|
174
|
+
restarts, pass `on_refresh=save_refresh_token`, where your function accepts that
|
|
175
|
+
string and saves it to your secret store after each exchange. The library does
|
|
176
|
+
not read or write credential files. Without persistence, a rotated token may be
|
|
177
|
+
lost when your process exits. Do not share one refresh token across independent
|
|
178
|
+
processes that might refresh it concurrently.
|
|
179
|
+
|
|
180
|
+
The callback runs before the API request proceeds and must not call back into
|
|
181
|
+
`credentials.refresh()` or `credentials.headers_for()`. If it raises, the
|
|
182
|
+
exception propagates but the new tokens remain in memory. Later calls retry the
|
|
183
|
+
callback before any further authentication or API request; they remain blocked
|
|
184
|
+
until persistence succeeds. Make your callback safe to repeat with the same token.
|
|
185
|
+
|
|
186
|
+
Optional `restaurant_headers`, `consumer_headers`, and `payment_headers` scope
|
|
187
|
+
extra headers to their respective hosts. `Authorization` is managed by
|
|
188
|
+
`RefreshTokenCredentials`. Its `timeout` controls authentication requests
|
|
189
|
+
separately from `WoltClient`'s API timeout (both default to 10 seconds).
|
|
190
|
+
|
|
191
|
+
Refresh requests are not retried or redirected, and API requests are never
|
|
192
|
+
automatically replayed after a 401, including purchases. An expired or revoked
|
|
193
|
+
refresh token requires a new browser session credential. Authentication failures
|
|
194
|
+
use the existing exceptions, such as `HTTPStatusError` with
|
|
195
|
+
`service == "authentication"`.
|
|
196
|
+
|
|
197
|
+
### Manually supplied headers
|
|
198
|
+
|
|
199
|
+
If you already hold a short-lived access token and want to supply raw headers
|
|
200
|
+
yourself, use `SessionCredentials` instead:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from woltapi import SessionCredentials, WoltClient
|
|
204
|
+
|
|
205
|
+
headers = {"Authorization": "Bearer <access token>"}
|
|
206
|
+
client = WoltClient(
|
|
207
|
+
SessionCredentials(restaurant_headers=headers, consumer_headers=headers)
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Wolt uses different servers for different jobs. `restaurant_headers` supplies
|
|
212
|
+
the login token for searches and saved delivery addresses. `consumer_headers`
|
|
213
|
+
supplies it for menus and order history. The library sends each set of headers
|
|
214
|
+
only to its matching server. With this class, nothing renews the token: after
|
|
215
|
+
about 30 minutes, requests fail with **HTTP 401**. The example scripts no
|
|
216
|
+
longer use this path; prefer `RefreshTokenCredentials`.
|
|
217
|
+
|
|
218
|
+
### Useful methods
|
|
219
|
+
|
|
220
|
+
| Call | What you get |
|
|
221
|
+
| --- | --- |
|
|
222
|
+
| `client.search_venues("pizza", latitude, longitude)` | Restaurant results with names, IDs, and slugs. A slug is the name used in a restaurant's URL. |
|
|
223
|
+
| `client.get_assortment(slug)` | A dictionary containing menu items and their options. |
|
|
224
|
+
| `client.get_venue_static(slug)` | A dictionary of restaurant details. |
|
|
225
|
+
| `client.get_venue_dynamic(slug, latitude, longitude)` | Current opening and delivery information. |
|
|
226
|
+
| `client.get_orders_page()` | A dictionary containing the current page of order history. |
|
|
227
|
+
| `client.list_delivery_targets()` | References to your saved delivery addresses, with saved labels and address details. |
|
|
228
|
+
| `client.get_order_status(purchase_id)` | An order's status and some price information. |
|
|
229
|
+
| `derive_checkout_fields(assortment, item)` | The checkout metadata fields for one menu item, derived from the assortment. Raises an error for items in zero or multiple categories. |
|
|
230
|
+
|
|
231
|
+
For example, after creating `client`:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
history = client.get_orders_page()
|
|
235
|
+
orders = history.get("orders", [])
|
|
236
|
+
print(f"This page contains {len(orders)} orders.")
|
|
237
|
+
|
|
238
|
+
targets = client.list_delivery_targets()
|
|
239
|
+
print(f"You have {len(targets)} saved delivery addresses.")
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Responses can contain personal information. Avoid printing entire responses or
|
|
243
|
+
sending them to shared logs. The examples print saved addresses and card labels
|
|
244
|
+
to the local terminal only. The browsing example prints only selected fields.
|
|
245
|
+
|
|
246
|
+
## Can it order food?
|
|
247
|
+
|
|
248
|
+
Not as a simple, ready-to-use feature yet. There is no `order("pizza")` method.
|
|
249
|
+
|
|
250
|
+
To try **checkout without buying anything**, use the new checkout-only example
|
|
251
|
+
from an editable source installation:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
python examples/order.py --latitude 60.17 --longitude 24.94 --query pizza \
|
|
255
|
+
--token-file ~/.wolt-token
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Use your own coordinates. It reads and saves your refresh token as described in
|
|
259
|
+
[Get a token](#get-a-token), guides you through selecting an item and a saved
|
|
260
|
+
delivery target/card, then asks before requesting a price. It never submits a
|
|
261
|
+
purchase. Basket saving is off by default and needs a separate confirmation if
|
|
262
|
+
enabled.
|
|
263
|
+
|
|
264
|
+
The checkout example derives `category_id`, `category_ids`, and the three
|
|
265
|
+
checkout exclusion flags from the assortment, so those fields do not need a
|
|
266
|
+
browser context file. It only supports items in exactly one category and stops
|
|
267
|
+
rather than guessing for zero or multiple categories. If it stops, you can
|
|
268
|
+
supply the missing values yourself with `--context-file <path>`, a JSON object
|
|
269
|
+
whose `checkout_fields` entries were copied from your own browser's checkout
|
|
270
|
+
request for the same item. It may still stop if other required catalog data is
|
|
271
|
+
missing. Run `python examples/order.py --help` for available options.
|
|
272
|
+
|
|
273
|
+
The library has methods to choose items, save a basket, ask Wolt for a price,
|
|
274
|
+
and submit a purchase. But some required inputs still need to come from your
|
|
275
|
+
own code, including browser/device information and detailed item data.
|
|
276
|
+
|
|
277
|
+
Order-history and saved-address reads have worked in live checks. **Payment and
|
|
278
|
+
purchase handling have not been verified with a real order.** The purchase code
|
|
279
|
+
only targets one restaurant, immediate home delivery, and one saved card.
|
|
280
|
+
|
|
281
|
+
`submit_prepared_order()` is the call that can place an order and charge you.
|
|
282
|
+
Do not call it unless you intend to buy the exact order you have reviewed. If it
|
|
283
|
+
times out or raises `OrderOutcomeUnknown`, **do not send it again**: Wolt may
|
|
284
|
+
already have received it. Check the order in Wolt instead.
|
|
285
|
+
|
|
286
|
+
Login, adding cards, payment verification screens, scheduled
|
|
287
|
+
orders, cancellation, and refunds are not supported.
|
|
288
|
+
|
|
289
|
+
## Run the tests
|
|
290
|
+
|
|
291
|
+
With your Python environment active:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
python -m pip install -e ".[test]"
|
|
295
|
+
python -m pytest
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The tests use made-up responses. They do not contact Wolt, need your token, or
|
|
299
|
+
place orders.
|
|
300
|
+
|
|
301
|
+
## Releases
|
|
302
|
+
|
|
303
|
+
Maintainers: see [RELEASING.md](RELEASING.md) for tests, release PRs, and automatic
|
|
304
|
+
PyPI publishing.
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
Licensed under the GNU Affero General Public License, version 3
|
|
309
|
+
(`AGPL-3.0-only`). See [LICENSE](LICENSE) for the full terms.
|