blocket-api 0.3.4__tar.gz → 0.3.6__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.
- {blocket_api-0.3.4/blocket_api.egg-info → blocket_api-0.3.6}/PKG-INFO +61 -3
- blocket_api-0.3.4/PKG-INFO → blocket_api-0.3.6/README.md +57 -20
- blocket_api-0.3.6/blocket_api/__init__.py +11 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api/blocket.py +29 -33
- blocket_api-0.3.6/blocket_api/constants.py +7 -0
- blocket_api-0.3.6/blocket_api/decorators.py +34 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api/models.py +78 -74
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api/qasa.py +3 -1
- blocket_api-0.3.6/blocket_api/sendbird.py +177 -0
- blocket_api-0.3.4/README.md → blocket_api-0.3.6/blocket_api.egg-info/PKG-INFO +78 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api.egg-info/SOURCES.txt +3 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api.egg-info/requires.txt +3 -2
- {blocket_api-0.3.4 → blocket_api-0.3.6}/pyproject.toml +4 -3
- blocket_api-0.3.4/blocket_api/__init__.py +0 -2
- {blocket_api-0.3.4 → blocket_api-0.3.6}/LICENSE +0 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api.egg-info/dependency_links.txt +0 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/blocket_api.egg-info/top_level.txt +0 -0
- {blocket_api-0.3.4 → blocket_api-0.3.6}/setup.cfg +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: blocket_api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: A python API wrapper for blocket.se
|
|
5
5
|
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Dist: httpx>=0.
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
10
|
Requires-Dist: pre-commit>=3.7.1
|
|
11
11
|
Requires-Dist: pytest>=8.2.2
|
|
12
12
|
Requires-Dist: respx>=0.21.1
|
|
@@ -15,7 +15,8 @@ Requires-Dist: pydantic>=2.11.7
|
|
|
15
15
|
Requires-Dist: mkdocs>=1.6.1
|
|
16
16
|
Requires-Dist: mkdocs-material>=9.6.21
|
|
17
17
|
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
18
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: websockets>=15.0.1
|
|
19
|
+
Requires-Dist: ruff>=0.14.4
|
|
19
20
|
Dynamic: license-file
|
|
20
21
|
|
|
21
22
|
<p align="center">
|
|
@@ -80,6 +81,8 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
|
|
|
80
81
|
| [`api.get_user_by_id()`](#get_user_by_id) | 👏 No | Yes | Get user data from id.
|
|
81
82
|
| [`api.save_ad()`](#save_ad) | 🔐 Yes | - | Save ad to "Saved ads".
|
|
82
83
|
| [`api.get_saved_ads()`](#get_saved_ads) | 🔐 Yes | - | List all saved ads.
|
|
84
|
+
| [`api.get_threads()`](#get_threads) | 🔐 Yes | - | List all threads (messages).
|
|
85
|
+
| [`api.get_messages_from_thread()`](#get_messages_from_thread) | 🔐 Yes | - | Get all messages from a thread.
|
|
83
86
|
|
|
84
87
|
|
|
85
88
|
## 🤓 Detailed usage
|
|
@@ -193,6 +196,7 @@ To query listings related to a specific car, supply the following parameters:
|
|
|
193
196
|
- `modelYear` (`Optional[Tuple[int, int]]`) - Set model year range, ex. `(2000, 2020)`.
|
|
194
197
|
- `milage` (`Optional[Tuple[int, int]]`) - Set milage range, ex. `(1000, 2000)`.
|
|
195
198
|
- `gearbox` (`Optional[GEARBOX_OPTIONS]`) - Filter a specific gearbox, ex. `Automat`.
|
|
199
|
+
- `color` (`Optional[List[COLOR_OPTIONS]]`) - Filter a specific color.
|
|
196
200
|
- `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
|
|
197
201
|
|
|
198
202
|
```py
|
|
@@ -200,6 +204,7 @@ To query listings related to a specific car, supply the following parameters:
|
|
|
200
204
|
make=["Audi", "Ford"],
|
|
201
205
|
fuel=["Diesel"],
|
|
202
206
|
chassi=["Cab"],
|
|
207
|
+
chassi=["Blå", "Röd"],
|
|
203
208
|
price=(50000, 100000),
|
|
204
209
|
page=1,
|
|
205
210
|
)
|
|
@@ -366,6 +371,59 @@ Returns all of your saved ads from `https://www.blocket.se/sparade/annonser`.
|
|
|
366
371
|
|
|
367
372
|
```
|
|
368
373
|
|
|
374
|
+
### get_threads
|
|
375
|
+
Returns all of your threads/conversations from Blocket. This api requires a token.
|
|
376
|
+
|
|
377
|
+
- `limit` (`int`, optional) - Number of threads to return, default is 15.
|
|
378
|
+
|
|
379
|
+
```py
|
|
380
|
+
>>> api = BlocketAPI("YourBlocketTokenHere")
|
|
381
|
+
>>> api.get_threads()
|
|
382
|
+
{
|
|
383
|
+
"channels": [
|
|
384
|
+
{
|
|
385
|
+
"channel_url": "sendbird_group_channel_123",
|
|
386
|
+
"name": "from:user-id,ad:1214281684,to:c1-user-id",
|
|
387
|
+
"data": {
|
|
388
|
+
"ad": {
|
|
389
|
+
"id": "1214281684",
|
|
390
|
+
"advertiser_name": "Kalle",
|
|
391
|
+
"subject": "Elsparkcykel",
|
|
392
|
+
"status": "active",
|
|
393
|
+
"url": "https://www.blocket.se/annons/1214281684",
|
|
394
|
+
"price": "2 400 kr"
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
...
|
|
399
|
+
],
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
### get_messages_from_thread
|
|
406
|
+
Returns all of your messages from an existing thread/conversation.
|
|
407
|
+
|
|
408
|
+
- `channel_id` (`str`, required) - The channel id to fetch messages from.
|
|
409
|
+
|
|
410
|
+
```py
|
|
411
|
+
>>> api = BlocketAPI("YourBlocketTokenHere")
|
|
412
|
+
>>> api.get_messages_from_thread("sendbird_group_channel_123")
|
|
413
|
+
{
|
|
414
|
+
"messages": [
|
|
415
|
+
{
|
|
416
|
+
"type": "MESG",
|
|
417
|
+
"message_id": 1354913370,
|
|
418
|
+
"message": "Hey, is your thing still for sale?",
|
|
419
|
+
...
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
369
427
|
## 🔐 Blocket API token
|
|
370
428
|
|
|
371
429
|
There are two ways to acquire your token:
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: blocket_api
|
|
3
|
-
Version: 0.3.4
|
|
4
|
-
Summary: A python API wrapper for blocket.se
|
|
5
|
-
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
|
-
Requires-Python: >=3.10
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Dist: httpx>=0.27.0
|
|
10
|
-
Requires-Dist: pre-commit>=3.7.1
|
|
11
|
-
Requires-Dist: pytest>=8.2.2
|
|
12
|
-
Requires-Dist: respx>=0.21.1
|
|
13
|
-
Requires-Dist: mypy>=1.15.0
|
|
14
|
-
Requires-Dist: pydantic>=2.11.7
|
|
15
|
-
Requires-Dist: mkdocs>=1.6.1
|
|
16
|
-
Requires-Dist: mkdocs-material>=9.6.21
|
|
17
|
-
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
18
|
-
Requires-Dist: ruff>=0.14.1
|
|
19
|
-
Dynamic: license-file
|
|
20
|
-
|
|
21
1
|
<p align="center">
|
|
22
2
|
<img src="https://blocket-api.se/blocket-api.png" />
|
|
23
3
|
</p>
|
|
@@ -80,6 +60,8 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
|
|
|
80
60
|
| [`api.get_user_by_id()`](#get_user_by_id) | 👏 No | Yes | Get user data from id.
|
|
81
61
|
| [`api.save_ad()`](#save_ad) | 🔐 Yes | - | Save ad to "Saved ads".
|
|
82
62
|
| [`api.get_saved_ads()`](#get_saved_ads) | 🔐 Yes | - | List all saved ads.
|
|
63
|
+
| [`api.get_threads()`](#get_threads) | 🔐 Yes | - | List all threads (messages).
|
|
64
|
+
| [`api.get_messages_from_thread()`](#get_messages_from_thread) | 🔐 Yes | - | Get all messages from a thread.
|
|
83
65
|
|
|
84
66
|
|
|
85
67
|
## 🤓 Detailed usage
|
|
@@ -193,6 +175,7 @@ To query listings related to a specific car, supply the following parameters:
|
|
|
193
175
|
- `modelYear` (`Optional[Tuple[int, int]]`) - Set model year range, ex. `(2000, 2020)`.
|
|
194
176
|
- `milage` (`Optional[Tuple[int, int]]`) - Set milage range, ex. `(1000, 2000)`.
|
|
195
177
|
- `gearbox` (`Optional[GEARBOX_OPTIONS]`) - Filter a specific gearbox, ex. `Automat`.
|
|
178
|
+
- `color` (`Optional[List[COLOR_OPTIONS]]`) - Filter a specific color.
|
|
196
179
|
- `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
|
|
197
180
|
|
|
198
181
|
```py
|
|
@@ -200,6 +183,7 @@ To query listings related to a specific car, supply the following parameters:
|
|
|
200
183
|
make=["Audi", "Ford"],
|
|
201
184
|
fuel=["Diesel"],
|
|
202
185
|
chassi=["Cab"],
|
|
186
|
+
chassi=["Blå", "Röd"],
|
|
203
187
|
price=(50000, 100000),
|
|
204
188
|
page=1,
|
|
205
189
|
)
|
|
@@ -366,6 +350,59 @@ Returns all of your saved ads from `https://www.blocket.se/sparade/annonser`.
|
|
|
366
350
|
|
|
367
351
|
```
|
|
368
352
|
|
|
353
|
+
### get_threads
|
|
354
|
+
Returns all of your threads/conversations from Blocket. This api requires a token.
|
|
355
|
+
|
|
356
|
+
- `limit` (`int`, optional) - Number of threads to return, default is 15.
|
|
357
|
+
|
|
358
|
+
```py
|
|
359
|
+
>>> api = BlocketAPI("YourBlocketTokenHere")
|
|
360
|
+
>>> api.get_threads()
|
|
361
|
+
{
|
|
362
|
+
"channels": [
|
|
363
|
+
{
|
|
364
|
+
"channel_url": "sendbird_group_channel_123",
|
|
365
|
+
"name": "from:user-id,ad:1214281684,to:c1-user-id",
|
|
366
|
+
"data": {
|
|
367
|
+
"ad": {
|
|
368
|
+
"id": "1214281684",
|
|
369
|
+
"advertiser_name": "Kalle",
|
|
370
|
+
"subject": "Elsparkcykel",
|
|
371
|
+
"status": "active",
|
|
372
|
+
"url": "https://www.blocket.se/annons/1214281684",
|
|
373
|
+
"price": "2 400 kr"
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
...
|
|
378
|
+
],
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
### get_messages_from_thread
|
|
385
|
+
Returns all of your messages from an existing thread/conversation.
|
|
386
|
+
|
|
387
|
+
- `channel_id` (`str`, required) - The channel id to fetch messages from.
|
|
388
|
+
|
|
389
|
+
```py
|
|
390
|
+
>>> api = BlocketAPI("YourBlocketTokenHere")
|
|
391
|
+
>>> api.get_messages_from_thread("sendbird_group_channel_123")
|
|
392
|
+
{
|
|
393
|
+
"messages": [
|
|
394
|
+
{
|
|
395
|
+
"type": "MESG",
|
|
396
|
+
"message_id": 1354913370,
|
|
397
|
+
"message": "Hey, is your thing still for sale?",
|
|
398
|
+
...
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
|
|
369
406
|
## 🔐 Blocket API token
|
|
370
407
|
|
|
371
408
|
There are two ways to acquire your token:
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .blocket import CHASSI_OPTIONS as CHASSI_OPTIONS
|
|
2
|
+
from .blocket import COLOR_OPTIONS as COLOR_OPTIONS
|
|
3
|
+
from .blocket import FUEL_OPTIONS as FUEL_OPTIONS
|
|
4
|
+
from .blocket import GEARBOX_OPTIONS as GEARBOX_OPTIONS
|
|
5
|
+
from .blocket import MAKE_OPTIONS as MAKE_OPTIONS
|
|
6
|
+
from .blocket import BlocketAPI as BlocketAPI
|
|
7
|
+
from .blocket import Category as Category
|
|
8
|
+
from .blocket import Region as Region
|
|
9
|
+
from .qasa import HOME_SEARCH_ORDERING as HOME_SEARCH_ORDERING
|
|
10
|
+
from .qasa import HomeType as HomeType
|
|
11
|
+
from .qasa import OrderBy as OrderBy
|
|
@@ -4,8 +4,7 @@ import urllib
|
|
|
4
4
|
from collections.abc import Callable
|
|
5
5
|
from dataclasses import dataclass
|
|
6
6
|
from enum import Enum
|
|
7
|
-
from
|
|
8
|
-
from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union, overload
|
|
7
|
+
from typing import TYPE_CHECKING, List, Literal, Optional, Tuple, Union, overload
|
|
9
8
|
|
|
10
9
|
import httpx
|
|
11
10
|
|
|
@@ -21,13 +20,13 @@ from blocket_api.models import (
|
|
|
21
20
|
)
|
|
22
21
|
from blocket_api.qasa import HOME_SEARCH_ORDERING, HomeType, OrderBy, Qasa
|
|
23
22
|
|
|
23
|
+
from .constants import BASE_URL, BYTBIL_URL
|
|
24
|
+
from .decorators import auth_token, public_token
|
|
25
|
+
from .sendbird import Sendbird
|
|
26
|
+
|
|
24
27
|
if TYPE_CHECKING:
|
|
25
28
|
from httpx import Response
|
|
26
29
|
|
|
27
|
-
BASE_URL = "https://api.blocket.se"
|
|
28
|
-
SITE_URL = "https://www.blocket.se"
|
|
29
|
-
BYTBIL_URL = "https://api.bytbil.com"
|
|
30
|
-
|
|
31
30
|
|
|
32
31
|
class Region(Enum):
|
|
33
32
|
hela_sverige = 0
|
|
@@ -130,6 +129,7 @@ CHASSI_OPTIONS = Literal[
|
|
|
130
129
|
"Kombi", "SUV", "Sedan", "Halvkombi", "Coupé", "Cab", "Familjebuss", "Yrkesfordon"
|
|
131
130
|
]
|
|
132
131
|
GEARBOX_OPTIONS = Literal["Automat", "Manuell"]
|
|
132
|
+
COLOR_OPTIONS = Literal["Blå", "Brun", "Grå", "Grön", "Gul", "Röd", "Svart", "Vit"]
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
class APIError(Exception): ...
|
|
@@ -138,33 +138,6 @@ class APIError(Exception): ...
|
|
|
138
138
|
class LimitError(Exception): ...
|
|
139
139
|
|
|
140
140
|
|
|
141
|
-
class TokenError(Exception): ...
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def auth_token(method: Callable) -> Callable:
|
|
145
|
-
@wraps(method)
|
|
146
|
-
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Callable:
|
|
147
|
-
if not self.token:
|
|
148
|
-
raise TokenError("Token is required, see documentation.")
|
|
149
|
-
return method(self, *args, **kwargs)
|
|
150
|
-
|
|
151
|
-
return wrapper
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def public_token(method: Callable) -> Callable:
|
|
155
|
-
@wraps(method)
|
|
156
|
-
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Callable:
|
|
157
|
-
if not self.token:
|
|
158
|
-
response = httpx.get(
|
|
159
|
-
f"{SITE_URL}/api/adout-api-route/refresh-token-and-validate-session"
|
|
160
|
-
)
|
|
161
|
-
response.raise_for_status()
|
|
162
|
-
self.token = response.json()["bearerToken"]
|
|
163
|
-
return method(self, *args, **kwargs)
|
|
164
|
-
|
|
165
|
-
return wrapper
|
|
166
|
-
|
|
167
|
-
|
|
168
141
|
def _make_request(
|
|
169
142
|
*,
|
|
170
143
|
method: Callable[..., Response] = httpx.get,
|
|
@@ -352,6 +325,7 @@ class BlocketAPI:
|
|
|
352
325
|
modelYear: Optional[Tuple[int, int]] = None,
|
|
353
326
|
milage: Optional[Tuple[int, int]] = None,
|
|
354
327
|
gearbox: Optional[GEARBOX_OPTIONS] = None,
|
|
328
|
+
color: Optional[List[COLOR_OPTIONS]] = None,
|
|
355
329
|
*,
|
|
356
330
|
as_objects: Literal[False] = False,
|
|
357
331
|
) -> dict: ...
|
|
@@ -367,6 +341,7 @@ class BlocketAPI:
|
|
|
367
341
|
modelYear: Optional[Tuple[int, int]] = None,
|
|
368
342
|
milage: Optional[Tuple[int, int]] = None,
|
|
369
343
|
gearbox: Optional[GEARBOX_OPTIONS] = None,
|
|
344
|
+
color: Optional[List[COLOR_OPTIONS]] = None,
|
|
370
345
|
*,
|
|
371
346
|
as_objects: bool = False,
|
|
372
347
|
) -> Union[dict, MotorSearchResults]: ...
|
|
@@ -382,6 +357,7 @@ class BlocketAPI:
|
|
|
382
357
|
modelYear: Optional[Tuple[int, int]] = None,
|
|
383
358
|
milage: Optional[Tuple[int, int]] = None,
|
|
384
359
|
gearbox: Optional[GEARBOX_OPTIONS] = None,
|
|
360
|
+
color: Optional[List[COLOR_OPTIONS]] = None,
|
|
385
361
|
*,
|
|
386
362
|
as_objects: bool = False,
|
|
387
363
|
) -> dict | MotorSearchResults:
|
|
@@ -613,3 +589,23 @@ class BlocketAPI:
|
|
|
613
589
|
url = f"{BASE_URL}/search_bff/v1/saved_content?lim={limit}"
|
|
614
590
|
response = _make_request(url=f"{url}", token=self.token).json()
|
|
615
591
|
return response
|
|
592
|
+
|
|
593
|
+
@auth_token
|
|
594
|
+
def get_threads(self, limit: int = 15) -> dict:
|
|
595
|
+
"""
|
|
596
|
+
Returns set number of conversations/threads from Blocket, ordered by latest message.
|
|
597
|
+
"""
|
|
598
|
+
assert self.token
|
|
599
|
+
sendbird = Sendbird.generate(self.token)
|
|
600
|
+
return sendbird.get_threads(limit=limit)
|
|
601
|
+
|
|
602
|
+
@auth_token
|
|
603
|
+
def get_messages_from_thread(self, channel_id: str) -> dict:
|
|
604
|
+
"""
|
|
605
|
+
Returns all messages from a specific channel/thread.
|
|
606
|
+
Requires a channel_id which can be found by using get_threads().
|
|
607
|
+
Ex: sendbird_group_channel_12345_abc123abcde
|
|
608
|
+
"""
|
|
609
|
+
assert self.token
|
|
610
|
+
sendbird = Sendbird.generate(self.token)
|
|
611
|
+
return sendbird.get_messages_from_thread(channel_id)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
BASE_URL = "https://api.blocket.se"
|
|
2
|
+
SITE_URL = "https://www.blocket.se"
|
|
3
|
+
BYTBIL_URL = "https://api.bytbil.com"
|
|
4
|
+
QASA_URL = "https://api.qasa.se/graphql"
|
|
5
|
+
|
|
6
|
+
BLOCKET_SENDBIRD_APP_ID = "162ABE1D-86E9-4C2A-866A-EF1034525DAF"
|
|
7
|
+
SENDBIRD_URL = f"https://api-{BLOCKET_SENDBIRD_APP_ID}.sendbird.com"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .constants import SITE_URL
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TokenError(Exception): ...
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def auth_token(method: Callable) -> Callable:
|
|
14
|
+
@wraps(method)
|
|
15
|
+
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Callable:
|
|
16
|
+
if not self.token:
|
|
17
|
+
raise TokenError("Token is required, see documentation.")
|
|
18
|
+
return method(self, *args, **kwargs)
|
|
19
|
+
|
|
20
|
+
return wrapper
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def public_token(method: Callable) -> Callable:
|
|
24
|
+
@wraps(method)
|
|
25
|
+
def wrapper(self: Any, *args: Any, **kwargs: Any) -> Callable:
|
|
26
|
+
if not self.token:
|
|
27
|
+
response = httpx.get(
|
|
28
|
+
f"{SITE_URL}/api/adout-api-route/refresh-token-and-validate-session"
|
|
29
|
+
)
|
|
30
|
+
response.raise_for_status()
|
|
31
|
+
self.token = response.json()["bearerToken"]
|
|
32
|
+
return method(self, *args, **kwargs)
|
|
33
|
+
|
|
34
|
+
return wrapper
|
|
@@ -1,35 +1,39 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
5
|
|
|
6
6
|
### CustomSearch ###
|
|
7
7
|
|
|
8
8
|
|
|
9
|
-
class
|
|
9
|
+
class _BaseModel(BaseModel):
|
|
10
|
+
model_config = ConfigDict(extra="ignore")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CustomSearchPrice(_BaseModel):
|
|
10
14
|
suffix: str
|
|
11
15
|
value: int
|
|
12
16
|
|
|
13
17
|
|
|
14
|
-
class CustomSearchLocation(
|
|
18
|
+
class CustomSearchLocation(_BaseModel):
|
|
15
19
|
image_url: str
|
|
16
20
|
label: str
|
|
17
21
|
query_key: str | None = None
|
|
18
22
|
|
|
19
23
|
|
|
20
|
-
class CustomSearchImage(
|
|
24
|
+
class CustomSearchImage(_BaseModel):
|
|
21
25
|
height: int
|
|
22
26
|
type: str
|
|
23
27
|
url: str
|
|
24
28
|
width: int
|
|
25
29
|
|
|
26
30
|
|
|
27
|
-
class CustomSearchCategory(
|
|
31
|
+
class CustomSearchCategory(_BaseModel):
|
|
28
32
|
id: str
|
|
29
33
|
name: str
|
|
30
34
|
|
|
31
35
|
|
|
32
|
-
class CustomSearchResult(
|
|
36
|
+
class CustomSearchResult(_BaseModel):
|
|
33
37
|
ad_id: str
|
|
34
38
|
ad_status: str
|
|
35
39
|
body: str
|
|
@@ -47,7 +51,7 @@ class CustomSearchResult(BaseModel):
|
|
|
47
51
|
zipcode: str | None = None
|
|
48
52
|
|
|
49
53
|
|
|
50
|
-
class CustomSearchResults(
|
|
54
|
+
class CustomSearchResults(_BaseModel):
|
|
51
55
|
data: list[CustomSearchResult]
|
|
52
56
|
total_count: int
|
|
53
57
|
|
|
@@ -55,23 +59,23 @@ class CustomSearchResults(BaseModel):
|
|
|
55
59
|
### MotorSearch ###
|
|
56
60
|
|
|
57
61
|
|
|
58
|
-
class MotorSearchCarEquipment(
|
|
62
|
+
class MotorSearchCarEquipment(_BaseModel):
|
|
59
63
|
label: str
|
|
60
64
|
|
|
61
65
|
|
|
62
|
-
class MotorSearchCarLocation(
|
|
66
|
+
class MotorSearchCarLocation(_BaseModel):
|
|
63
67
|
region: str
|
|
64
68
|
municipality: str
|
|
65
69
|
area: str
|
|
66
70
|
|
|
67
71
|
|
|
68
|
-
class MotorSearchCarImage(
|
|
72
|
+
class MotorSearchCarImage(_BaseModel):
|
|
69
73
|
height: int
|
|
70
74
|
width: int
|
|
71
75
|
image: str
|
|
72
76
|
|
|
73
77
|
|
|
74
|
-
class MotorSearchCar(
|
|
78
|
+
class MotorSearchCar(_BaseModel):
|
|
75
79
|
images: list[MotorSearchCarImage]
|
|
76
80
|
location: MotorSearchCarLocation
|
|
77
81
|
fuel: str
|
|
@@ -81,19 +85,19 @@ class MotorSearchCar(BaseModel):
|
|
|
81
85
|
equipment: list[MotorSearchCarEquipment] | None = None
|
|
82
86
|
|
|
83
87
|
|
|
84
|
-
class MotorSearchPrice(
|
|
88
|
+
class MotorSearchPrice(_BaseModel):
|
|
85
89
|
amount: str
|
|
86
90
|
billingPeriod: str
|
|
87
91
|
oldPrice: str | None = None
|
|
88
92
|
|
|
89
93
|
|
|
90
|
-
class MotorSearchSeller(
|
|
94
|
+
class MotorSearchSeller(_BaseModel):
|
|
91
95
|
type: str
|
|
92
96
|
name: str
|
|
93
97
|
id: str
|
|
94
98
|
|
|
95
99
|
|
|
96
|
-
class MotorSearchResult(
|
|
100
|
+
class MotorSearchResult(_BaseModel):
|
|
97
101
|
dealId: str
|
|
98
102
|
link: str
|
|
99
103
|
listTime: datetime.datetime
|
|
@@ -106,7 +110,7 @@ class MotorSearchResult(BaseModel):
|
|
|
106
110
|
description: str
|
|
107
111
|
|
|
108
112
|
|
|
109
|
-
class MotorSearchResults(
|
|
113
|
+
class MotorSearchResults(_BaseModel):
|
|
110
114
|
cars: list[MotorSearchResult]
|
|
111
115
|
hits: int
|
|
112
116
|
pages: int
|
|
@@ -115,24 +119,24 @@ class MotorSearchResults(BaseModel):
|
|
|
115
119
|
### StoreSearch ###
|
|
116
120
|
|
|
117
121
|
|
|
118
|
-
class StoreSearchLocation(
|
|
122
|
+
class StoreSearchLocation(_BaseModel):
|
|
119
123
|
longitude: float
|
|
120
124
|
latitude: float
|
|
121
125
|
|
|
122
126
|
|
|
123
|
-
class StoreSearchImage(
|
|
127
|
+
class StoreSearchImage(_BaseModel):
|
|
124
128
|
height: int
|
|
125
129
|
width: int
|
|
126
130
|
url: str
|
|
127
131
|
|
|
128
132
|
|
|
129
|
-
class StoreSearchLogotype(
|
|
133
|
+
class StoreSearchLogotype(_BaseModel):
|
|
130
134
|
height: int
|
|
131
135
|
width: int
|
|
132
136
|
url: str
|
|
133
137
|
|
|
134
138
|
|
|
135
|
-
class StoreSearchResult(
|
|
139
|
+
class StoreSearchResult(_BaseModel):
|
|
136
140
|
address: str | None = None
|
|
137
141
|
id: str
|
|
138
142
|
logotype: StoreSearchLogotype
|
|
@@ -146,7 +150,7 @@ class StoreSearchResult(BaseModel):
|
|
|
146
150
|
category: str
|
|
147
151
|
|
|
148
152
|
|
|
149
|
-
class StoreSearchResults(
|
|
153
|
+
class StoreSearchResults(_BaseModel):
|
|
150
154
|
data: list[StoreSearchResult]
|
|
151
155
|
total_count: int
|
|
152
156
|
total_page_count: int
|
|
@@ -155,12 +159,12 @@ class StoreSearchResults(BaseModel):
|
|
|
155
159
|
### HomeSearch ###
|
|
156
160
|
|
|
157
161
|
|
|
158
|
-
class HomeSearchGeoPoint(
|
|
162
|
+
class HomeSearchGeoPoint(_BaseModel):
|
|
159
163
|
lat: float
|
|
160
164
|
lon: float
|
|
161
165
|
|
|
162
166
|
|
|
163
|
-
class HomeSearchLocation(
|
|
167
|
+
class HomeSearchLocation(_BaseModel):
|
|
164
168
|
id: int
|
|
165
169
|
locality: str
|
|
166
170
|
countryCode: str
|
|
@@ -169,14 +173,14 @@ class HomeSearchLocation(BaseModel):
|
|
|
169
173
|
route: str
|
|
170
174
|
|
|
171
175
|
|
|
172
|
-
class HomeSearchUpload(
|
|
176
|
+
class HomeSearchUpload(_BaseModel):
|
|
173
177
|
id: int
|
|
174
178
|
order: int | None = None
|
|
175
179
|
type: str
|
|
176
180
|
url: str
|
|
177
181
|
|
|
178
182
|
|
|
179
|
-
class HomeSearchResult(
|
|
183
|
+
class HomeSearchResult(_BaseModel):
|
|
180
184
|
bedroomCount: int | None = None
|
|
181
185
|
blockListing: bool
|
|
182
186
|
rentalLengthSeconds: float | None = None
|
|
@@ -216,7 +220,7 @@ class HomeSearchResult(BaseModel):
|
|
|
216
220
|
uploads: list[HomeSearchUpload]
|
|
217
221
|
|
|
218
222
|
|
|
219
|
-
class Documents(
|
|
223
|
+
class Documents(_BaseModel):
|
|
220
224
|
hasNextPage: bool
|
|
221
225
|
hasPreviousPage: bool
|
|
222
226
|
nodes: list[HomeSearchResult]
|
|
@@ -224,80 +228,80 @@ class Documents(BaseModel):
|
|
|
224
228
|
totalCount: int
|
|
225
229
|
|
|
226
230
|
|
|
227
|
-
class HomeIndexSearch(
|
|
231
|
+
class HomeIndexSearch(_BaseModel):
|
|
228
232
|
documents: Documents
|
|
229
233
|
|
|
230
234
|
|
|
231
|
-
class DataModel(
|
|
235
|
+
class DataModel(_BaseModel):
|
|
232
236
|
homeIndexSearch: HomeIndexSearch
|
|
233
237
|
|
|
234
238
|
|
|
235
|
-
class HomeSearchResults(
|
|
239
|
+
class HomeSearchResults(_BaseModel):
|
|
236
240
|
data: DataModel
|
|
237
241
|
|
|
238
242
|
|
|
239
243
|
### StoreListings ###
|
|
240
244
|
|
|
241
245
|
|
|
242
|
-
class StoreListingContactMethods(
|
|
246
|
+
class StoreListingContactMethods(_BaseModel):
|
|
243
247
|
phone: bool
|
|
244
248
|
|
|
245
249
|
|
|
246
|
-
class StoreListingAdvertiser(
|
|
250
|
+
class StoreListingAdvertiser(_BaseModel):
|
|
247
251
|
contact_methods: StoreListingContactMethods
|
|
248
252
|
name: str
|
|
249
253
|
store_name: str | None = None
|
|
250
254
|
type: str
|
|
251
255
|
|
|
252
256
|
|
|
253
|
-
class StoreListingAttribute(
|
|
257
|
+
class StoreListingAttribute(_BaseModel):
|
|
254
258
|
header: str
|
|
255
259
|
id: str
|
|
256
260
|
items: list[str]
|
|
257
261
|
|
|
258
262
|
|
|
259
|
-
class StoreListingCategory(
|
|
263
|
+
class StoreListingCategory(_BaseModel):
|
|
260
264
|
id: str
|
|
261
265
|
name: str
|
|
262
266
|
|
|
263
267
|
|
|
264
|
-
class StoreListingImage(
|
|
268
|
+
class StoreListingImage(_BaseModel):
|
|
265
269
|
height: int
|
|
266
270
|
type: str
|
|
267
271
|
url: str
|
|
268
272
|
width: int
|
|
269
273
|
|
|
270
274
|
|
|
271
|
-
class StorListingLocationItem(
|
|
275
|
+
class StorListingLocationItem(_BaseModel):
|
|
272
276
|
id: str
|
|
273
277
|
name: str
|
|
274
278
|
query_key: str
|
|
275
279
|
|
|
276
280
|
|
|
277
|
-
class StoreListingParameter(
|
|
281
|
+
class StoreListingParameter(_BaseModel):
|
|
278
282
|
id: str
|
|
279
283
|
label: str
|
|
280
284
|
value: str
|
|
281
285
|
suffix: str | None = None
|
|
282
286
|
|
|
283
287
|
|
|
284
|
-
class ParameterGroup(
|
|
288
|
+
class ParameterGroup(_BaseModel):
|
|
285
289
|
label: str
|
|
286
290
|
parameters: list[StoreListingParameter]
|
|
287
291
|
type: str
|
|
288
292
|
|
|
289
293
|
|
|
290
|
-
class StoreListingPartnerInfo(
|
|
294
|
+
class StoreListingPartnerInfo(_BaseModel):
|
|
291
295
|
external_id: str
|
|
292
296
|
name: str
|
|
293
297
|
|
|
294
298
|
|
|
295
|
-
class StoreListingPrice(
|
|
299
|
+
class StoreListingPrice(_BaseModel):
|
|
296
300
|
suffix: str
|
|
297
301
|
value: int
|
|
298
302
|
|
|
299
303
|
|
|
300
|
-
class StoreListing(
|
|
304
|
+
class StoreListing(_BaseModel):
|
|
301
305
|
ad_id: str
|
|
302
306
|
ad_status: str
|
|
303
307
|
advertiser: StoreListingAdvertiser
|
|
@@ -319,19 +323,19 @@ class StoreListing(BaseModel):
|
|
|
319
323
|
type: str
|
|
320
324
|
|
|
321
325
|
|
|
322
|
-
class StoreListings(
|
|
326
|
+
class StoreListings(_BaseModel):
|
|
323
327
|
data: list[StoreListing]
|
|
324
328
|
|
|
325
329
|
|
|
326
330
|
### GetAdById ###
|
|
327
331
|
|
|
328
332
|
|
|
329
|
-
class AdByIdReviews(
|
|
333
|
+
class AdByIdReviews(_BaseModel):
|
|
330
334
|
overall_score: int
|
|
331
335
|
reviews_received_count: int
|
|
332
336
|
|
|
333
337
|
|
|
334
|
-
class AdByIdPublicProfile(
|
|
338
|
+
class AdByIdPublicProfile(_BaseModel):
|
|
335
339
|
account_created_at: str
|
|
336
340
|
badges: list[Any]
|
|
337
341
|
blocket_account_id: str
|
|
@@ -342,11 +346,11 @@ class AdByIdPublicProfile(BaseModel):
|
|
|
342
346
|
verified_label: str
|
|
343
347
|
|
|
344
348
|
|
|
345
|
-
class AdByIdContactMethods(
|
|
349
|
+
class AdByIdContactMethods(_BaseModel):
|
|
346
350
|
mc: bool
|
|
347
351
|
|
|
348
352
|
|
|
349
|
-
class AdByIdAdvertiser(
|
|
353
|
+
class AdByIdAdvertiser(_BaseModel):
|
|
350
354
|
account_id: str
|
|
351
355
|
contact_methods: AdByIdContactMethods
|
|
352
356
|
name: str
|
|
@@ -354,57 +358,57 @@ class AdByIdAdvertiser(BaseModel):
|
|
|
354
358
|
type: str
|
|
355
359
|
|
|
356
360
|
|
|
357
|
-
class AdByIdCategory(
|
|
361
|
+
class AdByIdCategory(_BaseModel):
|
|
358
362
|
id: str
|
|
359
363
|
name: str
|
|
360
364
|
|
|
361
365
|
|
|
362
|
-
class AdByIdImage(
|
|
366
|
+
class AdByIdImage(_BaseModel):
|
|
363
367
|
height: int
|
|
364
368
|
type: str
|
|
365
369
|
url: str
|
|
366
370
|
width: int
|
|
367
371
|
|
|
368
372
|
|
|
369
|
-
class AdByIdLocation(
|
|
373
|
+
class AdByIdLocation(_BaseModel):
|
|
370
374
|
id: str
|
|
371
375
|
name: str
|
|
372
376
|
query_key: str
|
|
373
377
|
|
|
374
378
|
|
|
375
|
-
class AdByIdMap(
|
|
379
|
+
class AdByIdMap(_BaseModel):
|
|
376
380
|
image_url: str
|
|
377
381
|
label: str
|
|
378
382
|
type: str
|
|
379
383
|
|
|
380
384
|
|
|
381
|
-
class AdByIdParameter(
|
|
385
|
+
class AdByIdParameter(_BaseModel):
|
|
382
386
|
id: str
|
|
383
387
|
label: str
|
|
384
388
|
value: str
|
|
385
389
|
suffix: str | None = None
|
|
386
390
|
|
|
387
391
|
|
|
388
|
-
class AdByIdParameterGroup(
|
|
392
|
+
class AdByIdParameterGroup(_BaseModel):
|
|
389
393
|
label: str
|
|
390
394
|
parameters: list[AdByIdParameter]
|
|
391
395
|
type: str
|
|
392
396
|
|
|
393
397
|
|
|
394
|
-
class AdByIdPrice(
|
|
398
|
+
class AdByIdPrice(_BaseModel):
|
|
395
399
|
suffix: str
|
|
396
400
|
value: int
|
|
397
401
|
|
|
398
402
|
|
|
399
|
-
class AdByIdFeatures(
|
|
403
|
+
class AdByIdFeatures(_BaseModel):
|
|
400
404
|
is_part_of_merge: bool
|
|
401
405
|
|
|
402
406
|
|
|
403
|
-
class AdByIdUrls(
|
|
407
|
+
class AdByIdUrls(_BaseModel):
|
|
404
408
|
seller_activate: str
|
|
405
409
|
|
|
406
410
|
|
|
407
|
-
class AdByIdBlocketPackage(
|
|
411
|
+
class AdByIdBlocketPackage(_BaseModel):
|
|
408
412
|
activated_providers: list[Any]
|
|
409
413
|
ad_id: str
|
|
410
414
|
blocket_payment_backwards_compatability: Any
|
|
@@ -426,7 +430,7 @@ class AdByIdBlocketPackage(BaseModel):
|
|
|
426
430
|
urls: AdByIdUrls
|
|
427
431
|
|
|
428
432
|
|
|
429
|
-
class AdByIdGamApiKeywords(
|
|
433
|
+
class AdByIdGamApiKeywords(_BaseModel):
|
|
430
434
|
adid: list[str]
|
|
431
435
|
adtitle: list[str]
|
|
432
436
|
adtype: list[str]
|
|
@@ -451,7 +455,7 @@ class AdByIdGamApiKeywords(BaseModel):
|
|
|
451
455
|
services: list[Any]
|
|
452
456
|
|
|
453
457
|
|
|
454
|
-
class AdByIdClientKeywords(
|
|
458
|
+
class AdByIdClientKeywords(_BaseModel):
|
|
455
459
|
cmp_advertising: str
|
|
456
460
|
logged_in: str
|
|
457
461
|
page_gen: str
|
|
@@ -465,39 +469,39 @@ class AdByIdClientKeywords(BaseModel):
|
|
|
465
469
|
viewport_width: str
|
|
466
470
|
|
|
467
471
|
|
|
468
|
-
class AdByIdGamContext(
|
|
472
|
+
class AdByIdGamContext(_BaseModel):
|
|
469
473
|
api_keywords: AdByIdGamApiKeywords
|
|
470
474
|
client_keywords: AdByIdClientKeywords
|
|
471
475
|
|
|
472
476
|
|
|
473
|
-
class AdByIdContext(
|
|
477
|
+
class AdByIdContext(_BaseModel):
|
|
474
478
|
gam: AdByIdGamContext
|
|
475
479
|
network_id: str | None = None
|
|
476
480
|
|
|
477
481
|
|
|
478
|
-
class AdByIdInventory(
|
|
482
|
+
class AdByIdInventory(_BaseModel):
|
|
479
483
|
context: AdByIdContext
|
|
480
484
|
|
|
481
485
|
|
|
482
|
-
class AdByIdGamKeywords(
|
|
486
|
+
class AdByIdGamKeywords(_BaseModel):
|
|
483
487
|
adformat: list[str]
|
|
484
488
|
adsize: list[str]
|
|
485
489
|
|
|
486
490
|
|
|
487
|
-
class AdByIdGamMediaType(
|
|
491
|
+
class AdByIdGamMediaType(_BaseModel):
|
|
488
492
|
height: int | None = None
|
|
489
493
|
type: str
|
|
490
494
|
width: int | None = None
|
|
491
495
|
|
|
492
496
|
|
|
493
|
-
class AdByIdGamPlacement(
|
|
497
|
+
class AdByIdGamPlacement(_BaseModel):
|
|
494
498
|
keywords: AdByIdGamKeywords
|
|
495
499
|
media_types: list[AdByIdGamMediaType]
|
|
496
500
|
path: str
|
|
497
501
|
target_id: str
|
|
498
502
|
|
|
499
503
|
|
|
500
|
-
class AdByIdPlacement(
|
|
504
|
+
class AdByIdPlacement(_BaseModel):
|
|
501
505
|
gam: AdByIdGamPlacement | None = None
|
|
502
506
|
type: str
|
|
503
507
|
vendor: str
|
|
@@ -505,7 +509,7 @@ class AdByIdPlacement(BaseModel):
|
|
|
505
509
|
safe_purchase: dict[str, Any] | None = None
|
|
506
510
|
|
|
507
511
|
|
|
508
|
-
class AdByIdData(
|
|
512
|
+
class AdByIdData(_BaseModel):
|
|
509
513
|
ad_id: str
|
|
510
514
|
ad_status: str
|
|
511
515
|
advertiser: AdByIdAdvertiser
|
|
@@ -530,7 +534,7 @@ class AdByIdData(BaseModel):
|
|
|
530
534
|
blocket_package: AdByIdBlocketPackage
|
|
531
535
|
|
|
532
536
|
|
|
533
|
-
class AdByIdResults(
|
|
537
|
+
class AdByIdResults(_BaseModel):
|
|
534
538
|
data: AdByIdData
|
|
535
539
|
inventory: AdByIdInventory
|
|
536
540
|
placements: list[AdByIdPlacement] | None = None
|
|
@@ -539,7 +543,7 @@ class AdByIdResults(BaseModel):
|
|
|
539
543
|
### GetUserById ###
|
|
540
544
|
|
|
541
545
|
|
|
542
|
-
class BlocketUserAccount(
|
|
546
|
+
class BlocketUserAccount(_BaseModel):
|
|
543
547
|
blocket_account_id: str
|
|
544
548
|
created_at: datetime.datetime
|
|
545
549
|
is_verified: bool
|
|
@@ -548,7 +552,7 @@ class BlocketUserAccount(BaseModel):
|
|
|
548
552
|
verified_with: str
|
|
549
553
|
|
|
550
554
|
|
|
551
|
-
class BlocketUserAd(
|
|
555
|
+
class BlocketUserAd(_BaseModel):
|
|
552
556
|
ad_id: str
|
|
553
557
|
ad_url: str
|
|
554
558
|
image_url: str
|
|
@@ -557,13 +561,13 @@ class BlocketUserAd(BaseModel):
|
|
|
557
561
|
subject: str
|
|
558
562
|
|
|
559
563
|
|
|
560
|
-
class BlocketUserActiveAds(
|
|
564
|
+
class BlocketUserActiveAds(_BaseModel):
|
|
561
565
|
ads: list[BlocketUserAd]
|
|
562
566
|
label_primary: str
|
|
563
567
|
label_secondary: str
|
|
564
568
|
|
|
565
569
|
|
|
566
|
-
class BlocketUserBadge(
|
|
570
|
+
class BlocketUserBadge(_BaseModel):
|
|
567
571
|
description: str
|
|
568
572
|
icon: str
|
|
569
573
|
icon_blocket_ui: str
|
|
@@ -571,19 +575,19 @@ class BlocketUserBadge(BaseModel):
|
|
|
571
575
|
label: str
|
|
572
576
|
|
|
573
577
|
|
|
574
|
-
class BlocketUserCO2Savings(
|
|
578
|
+
class BlocketUserCO2Savings(_BaseModel):
|
|
575
579
|
date_range: str
|
|
576
580
|
equivalent: str
|
|
577
581
|
text: str
|
|
578
582
|
|
|
579
583
|
|
|
580
|
-
class BlocketUserProfileInfo(
|
|
584
|
+
class BlocketUserProfileInfo(_BaseModel):
|
|
581
585
|
description: str | None = None
|
|
582
586
|
has_reviews: bool
|
|
583
587
|
name: str
|
|
584
588
|
|
|
585
589
|
|
|
586
|
-
class BlocketUser(
|
|
590
|
+
class BlocketUser(_BaseModel):
|
|
587
591
|
account: BlocketUserAccount
|
|
588
592
|
active_ads: BlocketUserActiveAds
|
|
589
593
|
badges: list[BlocketUserBadge]
|
|
@@ -595,7 +599,7 @@ class BlocketUser(BaseModel):
|
|
|
595
599
|
### SavedSearchesResult ###
|
|
596
600
|
|
|
597
601
|
|
|
598
|
-
class SavedSearch(
|
|
602
|
+
class SavedSearch(_BaseModel):
|
|
599
603
|
id: str
|
|
600
604
|
name: str
|
|
601
605
|
query: str
|
|
@@ -606,5 +610,5 @@ class SavedSearch(BaseModel):
|
|
|
606
610
|
email_enabled: bool | None = None
|
|
607
611
|
|
|
608
612
|
|
|
609
|
-
class SavedSearchResponse(
|
|
613
|
+
class SavedSearchResponse(_BaseModel):
|
|
610
614
|
searches: list[SavedSearch]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import urllib.parse
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
from pydantic import BaseModel, ConfigDict
|
|
10
|
+
from websockets.sync.client import connect
|
|
11
|
+
|
|
12
|
+
from .constants import BASE_URL, BLOCKET_SENDBIRD_APP_ID, SENDBIRD_URL
|
|
13
|
+
|
|
14
|
+
USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class SendbirdResponse(BaseModel):
|
|
18
|
+
user_id: str
|
|
19
|
+
nickname: str
|
|
20
|
+
profile_url: str
|
|
21
|
+
ekey: str
|
|
22
|
+
metadata: dict
|
|
23
|
+
|
|
24
|
+
ping_interval: int
|
|
25
|
+
pong_timeout: int
|
|
26
|
+
login_ts: int
|
|
27
|
+
|
|
28
|
+
key: str # Session key
|
|
29
|
+
expires_at: int
|
|
30
|
+
|
|
31
|
+
unread_cnt: dict
|
|
32
|
+
premium_feature_list: list[str]
|
|
33
|
+
application_attributes: list[str]
|
|
34
|
+
services: list[str]
|
|
35
|
+
|
|
36
|
+
model_config = ConfigDict(extra="ignore")
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def session_key(self) -> str:
|
|
40
|
+
return self.key
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_blocket_tokens(bearer_token: str) -> dict:
|
|
44
|
+
token_response = httpx.get(
|
|
45
|
+
f"{BASE_URL}/messaging-sendbird-communication/v1/users/tokens",
|
|
46
|
+
headers={
|
|
47
|
+
"Authorization": f"Bearer {bearer_token}",
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
token_response.raise_for_status()
|
|
51
|
+
return token_response.json()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SendbirdClient:
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
app_id: str,
|
|
58
|
+
user_id: str,
|
|
59
|
+
access_token: str,
|
|
60
|
+
):
|
|
61
|
+
self.app_id = app_id
|
|
62
|
+
self.user_id = user_id
|
|
63
|
+
self.access_token = access_token
|
|
64
|
+
|
|
65
|
+
def _build_wss_url(self) -> str:
|
|
66
|
+
base_url = f"wss://ws-{self.app_id.lower()}.sendbird.com/"
|
|
67
|
+
timestamp = str(int(time.time() * 1000))
|
|
68
|
+
|
|
69
|
+
params = {
|
|
70
|
+
"p": "JS",
|
|
71
|
+
"pv": USER_AGENT,
|
|
72
|
+
"sv": "4.10.8",
|
|
73
|
+
"ai": self.app_id,
|
|
74
|
+
"pmce": "1",
|
|
75
|
+
"active": "1",
|
|
76
|
+
"device_token_types": "gcm,huawei,apns",
|
|
77
|
+
"SB-User-Agent": "JS/c4.10.8///oweb",
|
|
78
|
+
"SB-SDK-User-Agent": "main_sdk_info=chat/js/4.10.8&device_os_platform=web&os_version=Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0",
|
|
79
|
+
"Request-Sent-Timestamp": timestamp,
|
|
80
|
+
"include_extra_data": "premium_feature_list,file_upload_size_limit,application_attributes,emoji_hash,multiple_file_send_max_size,notifications",
|
|
81
|
+
"use_local_cache": "0",
|
|
82
|
+
"include_poll_details": "1",
|
|
83
|
+
"user_id": self.user_id,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return base_url + "?" + urllib.parse.urlencode(params)
|
|
87
|
+
|
|
88
|
+
def get_data(self) -> str:
|
|
89
|
+
with connect(
|
|
90
|
+
self._build_wss_url(),
|
|
91
|
+
subprotocols=[urllib.parse.quote(json.dumps({"token": self.access_token}))], # type: ignore[list-item]
|
|
92
|
+
additional_headers={
|
|
93
|
+
"User-Agent": USER_AGENT,
|
|
94
|
+
"Origin": "https://www.blocket.se",
|
|
95
|
+
},
|
|
96
|
+
open_timeout=5,
|
|
97
|
+
) as websocket:
|
|
98
|
+
message: str = websocket.recv(timeout=5) # type: ignore[assignment]
|
|
99
|
+
return str(message.split("LOGI")[1])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass(frozen=True)
|
|
103
|
+
class Sendbird:
|
|
104
|
+
sendbird_client: SendbirdClient
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def generate(cls, blocket_token: str) -> Sendbird:
|
|
108
|
+
tokens = get_blocket_tokens(blocket_token)
|
|
109
|
+
sb_token = tokens["sb_session_token"]
|
|
110
|
+
sb_user_id = tokens["sb_user_id"]
|
|
111
|
+
return cls(
|
|
112
|
+
sendbird_client=SendbirdClient(
|
|
113
|
+
app_id=BLOCKET_SENDBIRD_APP_ID,
|
|
114
|
+
user_id=sb_user_id,
|
|
115
|
+
access_token=sb_token,
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def get_threads(self, limit: int) -> dict:
|
|
120
|
+
sendbird = SendbirdResponse.model_validate_json(self.sendbird_client.get_data())
|
|
121
|
+
response = httpx.get(
|
|
122
|
+
f"{SENDBIRD_URL}/v3/users/{self.sendbird_client.user_id}/my_group_channels",
|
|
123
|
+
params={
|
|
124
|
+
"token": "",
|
|
125
|
+
"limit": limit,
|
|
126
|
+
"order": "latest_last_message",
|
|
127
|
+
"show_member": True,
|
|
128
|
+
"show_read_receipt": True,
|
|
129
|
+
"show_delivery_receipt": True,
|
|
130
|
+
"show_empty": False,
|
|
131
|
+
"member_state_filter": "all",
|
|
132
|
+
"super_mode": "all",
|
|
133
|
+
"public_mode": "all",
|
|
134
|
+
"unread_filter": "all",
|
|
135
|
+
"name_contains": "",
|
|
136
|
+
"custom_type_startswith": "",
|
|
137
|
+
"hidden_mode": "unhidden_only",
|
|
138
|
+
"show_frozen": True,
|
|
139
|
+
"show_metadata": True,
|
|
140
|
+
"include_chat_notification": False,
|
|
141
|
+
"include_left_channel": False,
|
|
142
|
+
},
|
|
143
|
+
headers={
|
|
144
|
+
"Session-Key": sendbird.session_key,
|
|
145
|
+
"Access-Token": self.sendbird_client.access_token,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
response.raise_for_status()
|
|
149
|
+
return response.json()
|
|
150
|
+
|
|
151
|
+
def get_messages_from_thread(self, channel_id: str) -> dict:
|
|
152
|
+
sendbird = SendbirdResponse.model_validate_json(self.sendbird_client.get_data())
|
|
153
|
+
response = httpx.get(
|
|
154
|
+
f"{SENDBIRD_URL}/v3/group_channels/{channel_id}/messages",
|
|
155
|
+
params={
|
|
156
|
+
"is_sdk": True,
|
|
157
|
+
"prev_limit": 30,
|
|
158
|
+
"next_limit": 0,
|
|
159
|
+
"include": False,
|
|
160
|
+
"reverse": False,
|
|
161
|
+
"message_ts": 9007199254740991,
|
|
162
|
+
"message_type": "",
|
|
163
|
+
"include_reply_type": "none",
|
|
164
|
+
"with_sorted_meta_array": False,
|
|
165
|
+
"include_reactions": False,
|
|
166
|
+
"include_thread_info": False,
|
|
167
|
+
"include_parent_message_info": False,
|
|
168
|
+
"show_subchannel_message_only": False,
|
|
169
|
+
"include_poll_details": True,
|
|
170
|
+
},
|
|
171
|
+
headers={
|
|
172
|
+
"Session-Key": sendbird.session_key,
|
|
173
|
+
"Access-Token": self.sendbird_client.access_token,
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
response.raise_for_status()
|
|
177
|
+
return response.json()
|
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blocket_api
|
|
3
|
+
Version: 0.3.6
|
|
4
|
+
Summary: A python API wrapper for blocket.se
|
|
5
|
+
Author-email: dunderrrrrr <emil.bjorkroth@gmail.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: pre-commit>=3.7.1
|
|
11
|
+
Requires-Dist: pytest>=8.2.2
|
|
12
|
+
Requires-Dist: respx>=0.21.1
|
|
13
|
+
Requires-Dist: mypy>=1.15.0
|
|
14
|
+
Requires-Dist: pydantic>=2.11.7
|
|
15
|
+
Requires-Dist: mkdocs>=1.6.1
|
|
16
|
+
Requires-Dist: mkdocs-material>=9.6.21
|
|
17
|
+
Requires-Dist: mkdocs-swagger-ui-tag>=0.7.2
|
|
18
|
+
Requires-Dist: websockets>=15.0.1
|
|
19
|
+
Requires-Dist: ruff>=0.14.4
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
1
22
|
<p align="center">
|
|
2
23
|
<img src="https://blocket-api.se/blocket-api.png" />
|
|
3
24
|
</p>
|
|
@@ -60,6 +81,8 @@ Some calls require a `bearerToken`. However, some calls are public and don't req
|
|
|
60
81
|
| [`api.get_user_by_id()`](#get_user_by_id) | 👏 No | Yes | Get user data from id.
|
|
61
82
|
| [`api.save_ad()`](#save_ad) | 🔐 Yes | - | Save ad to "Saved ads".
|
|
62
83
|
| [`api.get_saved_ads()`](#get_saved_ads) | 🔐 Yes | - | List all saved ads.
|
|
84
|
+
| [`api.get_threads()`](#get_threads) | 🔐 Yes | - | List all threads (messages).
|
|
85
|
+
| [`api.get_messages_from_thread()`](#get_messages_from_thread) | 🔐 Yes | - | Get all messages from a thread.
|
|
63
86
|
|
|
64
87
|
|
|
65
88
|
## 🤓 Detailed usage
|
|
@@ -173,6 +196,7 @@ To query listings related to a specific car, supply the following parameters:
|
|
|
173
196
|
- `modelYear` (`Optional[Tuple[int, int]]`) - Set model year range, ex. `(2000, 2020)`.
|
|
174
197
|
- `milage` (`Optional[Tuple[int, int]]`) - Set milage range, ex. `(1000, 2000)`.
|
|
175
198
|
- `gearbox` (`Optional[GEARBOX_OPTIONS]`) - Filter a specific gearbox, ex. `Automat`.
|
|
199
|
+
- `color` (`Optional[List[COLOR_OPTIONS]]`) - Filter a specific color.
|
|
176
200
|
- `as_objects` (`bool`, optional) - Return results as pydantic models, default is `False`.
|
|
177
201
|
|
|
178
202
|
```py
|
|
@@ -180,6 +204,7 @@ To query listings related to a specific car, supply the following parameters:
|
|
|
180
204
|
make=["Audi", "Ford"],
|
|
181
205
|
fuel=["Diesel"],
|
|
182
206
|
chassi=["Cab"],
|
|
207
|
+
chassi=["Blå", "Röd"],
|
|
183
208
|
price=(50000, 100000),
|
|
184
209
|
page=1,
|
|
185
210
|
)
|
|
@@ -346,6 +371,59 @@ Returns all of your saved ads from `https://www.blocket.se/sparade/annonser`.
|
|
|
346
371
|
|
|
347
372
|
```
|
|
348
373
|
|
|
374
|
+
### get_threads
|
|
375
|
+
Returns all of your threads/conversations from Blocket. This api requires a token.
|
|
376
|
+
|
|
377
|
+
- `limit` (`int`, optional) - Number of threads to return, default is 15.
|
|
378
|
+
|
|
379
|
+
```py
|
|
380
|
+
>>> api = BlocketAPI("YourBlocketTokenHere")
|
|
381
|
+
>>> api.get_threads()
|
|
382
|
+
{
|
|
383
|
+
"channels": [
|
|
384
|
+
{
|
|
385
|
+
"channel_url": "sendbird_group_channel_123",
|
|
386
|
+
"name": "from:user-id,ad:1214281684,to:c1-user-id",
|
|
387
|
+
"data": {
|
|
388
|
+
"ad": {
|
|
389
|
+
"id": "1214281684",
|
|
390
|
+
"advertiser_name": "Kalle",
|
|
391
|
+
"subject": "Elsparkcykel",
|
|
392
|
+
"status": "active",
|
|
393
|
+
"url": "https://www.blocket.se/annons/1214281684",
|
|
394
|
+
"price": "2 400 kr"
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
...
|
|
399
|
+
],
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
### get_messages_from_thread
|
|
406
|
+
Returns all of your messages from an existing thread/conversation.
|
|
407
|
+
|
|
408
|
+
- `channel_id` (`str`, required) - The channel id to fetch messages from.
|
|
409
|
+
|
|
410
|
+
```py
|
|
411
|
+
>>> api = BlocketAPI("YourBlocketTokenHere")
|
|
412
|
+
>>> api.get_messages_from_thread("sendbird_group_channel_123")
|
|
413
|
+
{
|
|
414
|
+
"messages": [
|
|
415
|
+
{
|
|
416
|
+
"type": "MESG",
|
|
417
|
+
"message_id": 1354913370,
|
|
418
|
+
"message": "Hey, is your thing still for sale?",
|
|
419
|
+
...
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
349
427
|
## 🔐 Blocket API token
|
|
350
428
|
|
|
351
429
|
There are two ways to acquire your token:
|
|
@@ -3,8 +3,11 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
blocket_api/__init__.py
|
|
5
5
|
blocket_api/blocket.py
|
|
6
|
+
blocket_api/constants.py
|
|
7
|
+
blocket_api/decorators.py
|
|
6
8
|
blocket_api/models.py
|
|
7
9
|
blocket_api/qasa.py
|
|
10
|
+
blocket_api/sendbird.py
|
|
8
11
|
blocket_api.egg-info/PKG-INFO
|
|
9
12
|
blocket_api.egg-info/SOURCES.txt
|
|
10
13
|
blocket_api.egg-info/dependency_links.txt
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "blocket_api"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.6"
|
|
4
4
|
description = "A python API wrapper for blocket.se"
|
|
5
5
|
authors = [{name = "dunderrrrrr", email = "emil.bjorkroth@gmail.com"}]
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.10"
|
|
8
8
|
dependencies = [
|
|
9
|
-
"httpx>=0.
|
|
9
|
+
"httpx>=0.28.1",
|
|
10
10
|
"pre-commit>=3.7.1",
|
|
11
11
|
"pytest>=8.2.2",
|
|
12
12
|
"respx>=0.21.1",
|
|
@@ -15,7 +15,8 @@ dependencies = [
|
|
|
15
15
|
"mkdocs>=1.6.1",
|
|
16
16
|
"mkdocs-material>=9.6.21",
|
|
17
17
|
"mkdocs-swagger-ui-tag>=0.7.2",
|
|
18
|
-
"
|
|
18
|
+
"websockets>=15.0.1",
|
|
19
|
+
"ruff>=0.14.4",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|