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.
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blocket_api
3
- Version: 0.3.4
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.27.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: ruff>=0.14.1
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 functools import wraps
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 CustomSearchPrice(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
31
+ class CustomSearchCategory(_BaseModel):
28
32
  id: str
29
33
  name: str
30
34
 
31
35
 
32
- class CustomSearchResult(BaseModel):
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(BaseModel):
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(BaseModel):
62
+ class MotorSearchCarEquipment(_BaseModel):
59
63
  label: str
60
64
 
61
65
 
62
- class MotorSearchCarLocation(BaseModel):
66
+ class MotorSearchCarLocation(_BaseModel):
63
67
  region: str
64
68
  municipality: str
65
69
  area: str
66
70
 
67
71
 
68
- class MotorSearchCarImage(BaseModel):
72
+ class MotorSearchCarImage(_BaseModel):
69
73
  height: int
70
74
  width: int
71
75
  image: str
72
76
 
73
77
 
74
- class MotorSearchCar(BaseModel):
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(BaseModel):
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(BaseModel):
94
+ class MotorSearchSeller(_BaseModel):
91
95
  type: str
92
96
  name: str
93
97
  id: str
94
98
 
95
99
 
96
- class MotorSearchResult(BaseModel):
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(BaseModel):
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(BaseModel):
122
+ class StoreSearchLocation(_BaseModel):
119
123
  longitude: float
120
124
  latitude: float
121
125
 
122
126
 
123
- class StoreSearchImage(BaseModel):
127
+ class StoreSearchImage(_BaseModel):
124
128
  height: int
125
129
  width: int
126
130
  url: str
127
131
 
128
132
 
129
- class StoreSearchLogotype(BaseModel):
133
+ class StoreSearchLogotype(_BaseModel):
130
134
  height: int
131
135
  width: int
132
136
  url: str
133
137
 
134
138
 
135
- class StoreSearchResult(BaseModel):
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(BaseModel):
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(BaseModel):
162
+ class HomeSearchGeoPoint(_BaseModel):
159
163
  lat: float
160
164
  lon: float
161
165
 
162
166
 
163
- class HomeSearchLocation(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
231
+ class HomeIndexSearch(_BaseModel):
228
232
  documents: Documents
229
233
 
230
234
 
231
- class DataModel(BaseModel):
235
+ class DataModel(_BaseModel):
232
236
  homeIndexSearch: HomeIndexSearch
233
237
 
234
238
 
235
- class HomeSearchResults(BaseModel):
239
+ class HomeSearchResults(_BaseModel):
236
240
  data: DataModel
237
241
 
238
242
 
239
243
  ### StoreListings ###
240
244
 
241
245
 
242
- class StoreListingContactMethods(BaseModel):
246
+ class StoreListingContactMethods(_BaseModel):
243
247
  phone: bool
244
248
 
245
249
 
246
- class StoreListingAdvertiser(BaseModel):
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(BaseModel):
257
+ class StoreListingAttribute(_BaseModel):
254
258
  header: str
255
259
  id: str
256
260
  items: list[str]
257
261
 
258
262
 
259
- class StoreListingCategory(BaseModel):
263
+ class StoreListingCategory(_BaseModel):
260
264
  id: str
261
265
  name: str
262
266
 
263
267
 
264
- class StoreListingImage(BaseModel):
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(BaseModel):
275
+ class StorListingLocationItem(_BaseModel):
272
276
  id: str
273
277
  name: str
274
278
  query_key: str
275
279
 
276
280
 
277
- class StoreListingParameter(BaseModel):
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(BaseModel):
288
+ class ParameterGroup(_BaseModel):
285
289
  label: str
286
290
  parameters: list[StoreListingParameter]
287
291
  type: str
288
292
 
289
293
 
290
- class StoreListingPartnerInfo(BaseModel):
294
+ class StoreListingPartnerInfo(_BaseModel):
291
295
  external_id: str
292
296
  name: str
293
297
 
294
298
 
295
- class StoreListingPrice(BaseModel):
299
+ class StoreListingPrice(_BaseModel):
296
300
  suffix: str
297
301
  value: int
298
302
 
299
303
 
300
- class StoreListing(BaseModel):
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(BaseModel):
326
+ class StoreListings(_BaseModel):
323
327
  data: list[StoreListing]
324
328
 
325
329
 
326
330
  ### GetAdById ###
327
331
 
328
332
 
329
- class AdByIdReviews(BaseModel):
333
+ class AdByIdReviews(_BaseModel):
330
334
  overall_score: int
331
335
  reviews_received_count: int
332
336
 
333
337
 
334
- class AdByIdPublicProfile(BaseModel):
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(BaseModel):
349
+ class AdByIdContactMethods(_BaseModel):
346
350
  mc: bool
347
351
 
348
352
 
349
- class AdByIdAdvertiser(BaseModel):
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(BaseModel):
361
+ class AdByIdCategory(_BaseModel):
358
362
  id: str
359
363
  name: str
360
364
 
361
365
 
362
- class AdByIdImage(BaseModel):
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(BaseModel):
373
+ class AdByIdLocation(_BaseModel):
370
374
  id: str
371
375
  name: str
372
376
  query_key: str
373
377
 
374
378
 
375
- class AdByIdMap(BaseModel):
379
+ class AdByIdMap(_BaseModel):
376
380
  image_url: str
377
381
  label: str
378
382
  type: str
379
383
 
380
384
 
381
- class AdByIdParameter(BaseModel):
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(BaseModel):
392
+ class AdByIdParameterGroup(_BaseModel):
389
393
  label: str
390
394
  parameters: list[AdByIdParameter]
391
395
  type: str
392
396
 
393
397
 
394
- class AdByIdPrice(BaseModel):
398
+ class AdByIdPrice(_BaseModel):
395
399
  suffix: str
396
400
  value: int
397
401
 
398
402
 
399
- class AdByIdFeatures(BaseModel):
403
+ class AdByIdFeatures(_BaseModel):
400
404
  is_part_of_merge: bool
401
405
 
402
406
 
403
- class AdByIdUrls(BaseModel):
407
+ class AdByIdUrls(_BaseModel):
404
408
  seller_activate: str
405
409
 
406
410
 
407
- class AdByIdBlocketPackage(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
472
+ class AdByIdGamContext(_BaseModel):
469
473
  api_keywords: AdByIdGamApiKeywords
470
474
  client_keywords: AdByIdClientKeywords
471
475
 
472
476
 
473
- class AdByIdContext(BaseModel):
477
+ class AdByIdContext(_BaseModel):
474
478
  gam: AdByIdGamContext
475
479
  network_id: str | None = None
476
480
 
477
481
 
478
- class AdByIdInventory(BaseModel):
482
+ class AdByIdInventory(_BaseModel):
479
483
  context: AdByIdContext
480
484
 
481
485
 
482
- class AdByIdGamKeywords(BaseModel):
486
+ class AdByIdGamKeywords(_BaseModel):
483
487
  adformat: list[str]
484
488
  adsize: list[str]
485
489
 
486
490
 
487
- class AdByIdGamMediaType(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
578
+ class BlocketUserCO2Savings(_BaseModel):
575
579
  date_range: str
576
580
  equivalent: str
577
581
  text: str
578
582
 
579
583
 
580
- class BlocketUserProfileInfo(BaseModel):
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(BaseModel):
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(BaseModel):
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(BaseModel):
613
+ class SavedSearchResponse(_BaseModel):
610
614
  searches: list[SavedSearch]
@@ -1,9 +1,11 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum
3
3
  from typing import Literal
4
+
4
5
  import httpx
5
6
 
6
- QASA_URL = "https://api.qasa.se/graphql"
7
+ from .constants import QASA_URL
8
+
7
9
  HOME_SEARCH_ORDERING = Literal["descending", "ascending"]
8
10
 
9
11
 
@@ -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,4 +1,4 @@
1
- httpx>=0.27.0
1
+ httpx>=0.28.1
2
2
  pre-commit>=3.7.1
3
3
  pytest>=8.2.2
4
4
  respx>=0.21.1
@@ -7,4 +7,5 @@ pydantic>=2.11.7
7
7
  mkdocs>=1.6.1
8
8
  mkdocs-material>=9.6.21
9
9
  mkdocs-swagger-ui-tag>=0.7.2
10
- ruff>=0.14.1
10
+ websockets>=15.0.1
11
+ ruff>=0.14.4
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "blocket_api"
3
- version = "0.3.4"
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.27.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
- "ruff>=0.14.1",
18
+ "websockets>=15.0.1",
19
+ "ruff>=0.14.4",
19
20
  ]
20
21
 
21
22
 
@@ -1,2 +0,0 @@
1
- from .blocket import BlocketAPI as BlocketAPI
2
- from .blocket import Region as Region
File without changes
File without changes