blocket-api 0.3.91__tar.gz → 0.3.92__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blocket_api
3
- Version: 0.3.91
3
+ Version: 0.3.92
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
@@ -16,6 +16,7 @@ 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
18
  Requires-Dist: websockets>=15.0.1
19
+ Requires-Dist: beautifulsoup4>=4.14.2
19
20
  Requires-Dist: ruff>=0.14.5
20
21
  Dynamic: license-file
21
22
 
@@ -59,8 +60,8 @@ from blocket_api import (
59
60
 
60
61
  api = BlocketAPI()
61
62
 
62
- # will search all of blocket
63
- result = api.search(
63
+ # search all of blocket
64
+ api.search(
64
65
  "Vinterdäck Audi",
65
66
  sort_order=SortOrder.PRICE_ASC,
66
67
  locations=[Location.STOCKHOLM, Location.UPPSALA],
@@ -68,16 +69,22 @@ result = api.search(
68
69
  )
69
70
 
70
71
  # search for cars only
71
- result = api.search_car(
72
+ api.search_car(
72
73
  "Audi", # query is optional
73
74
  sort_order=CarSortOrder.MILEAGE_ASC,
74
75
  models=[CarModel.AUDI],
75
76
  colors=[CarColor.GULD],
76
77
  price_from=10000,
77
78
  price_to=50000,
78
- transmission=CarTransmission.MANUAL,
79
+ transmissions=[CarTransmission.MANUAL],
79
80
  locations=[Location.STOCKHOLM],
80
81
  )
82
+
83
+ # Get a recommerce or car ad
84
+ from blocket_api import CarAd, RecommerceAd
85
+
86
+ api.get_ad(RecommerceAd(12345678))
87
+ api.get_ad(CarAd(12345678))
81
88
  ```
82
89
 
83
90
  ## 📝 Notes
@@ -38,8 +38,8 @@ from blocket_api import (
38
38
 
39
39
  api = BlocketAPI()
40
40
 
41
- # will search all of blocket
42
- result = api.search(
41
+ # search all of blocket
42
+ api.search(
43
43
  "Vinterdäck Audi",
44
44
  sort_order=SortOrder.PRICE_ASC,
45
45
  locations=[Location.STOCKHOLM, Location.UPPSALA],
@@ -47,16 +47,22 @@ result = api.search(
47
47
  )
48
48
 
49
49
  # search for cars only
50
- result = api.search_car(
50
+ api.search_car(
51
51
  "Audi", # query is optional
52
52
  sort_order=CarSortOrder.MILEAGE_ASC,
53
53
  models=[CarModel.AUDI],
54
54
  colors=[CarColor.GULD],
55
55
  price_from=10000,
56
56
  price_to=50000,
57
- transmission=CarTransmission.MANUAL,
57
+ transmissions=[CarTransmission.MANUAL],
58
58
  locations=[Location.STOCKHOLM],
59
59
  )
60
+
61
+ # Get a recommerce or car ad
62
+ from blocket_api import CarAd, RecommerceAd
63
+
64
+ api.get_ad(RecommerceAd(12345678))
65
+ api.get_ad(CarAd(12345678))
60
66
  ```
61
67
 
62
68
  ## 📝 Notes
@@ -1,3 +1,5 @@
1
+ from .ad_parser import CarAd as CarAd
2
+ from .ad_parser import RecommerceAd as RecommerceAd
1
3
  from .blocket import BlocketAPI as BlocketAPI
2
4
  from .blocket import Location as Location
3
5
  from .constants import CarColor as CarColor
@@ -0,0 +1,182 @@
1
+ import json
2
+ import re
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from bs4 import BeautifulSoup, Tag
7
+ from httpx import Response
8
+
9
+ from .constants import SITE_URL
10
+
11
+
12
+ @dataclass(frozen=True)
13
+ class RecommerceAd:
14
+ id: int
15
+
16
+ @property
17
+ def url(self) -> str:
18
+ return f"{SITE_URL}/recommerce/forsale/item/{self.id}"
19
+
20
+ def parse(self, response: Response) -> Any:
21
+ soup = BeautifulSoup(response.content, "html.parser")
22
+ json_script_tag = soup.select_one(
23
+ 'script:-soup-contains("window.__staticRouterHydrationData")'
24
+ )
25
+ if not json_script_tag:
26
+ return {}
27
+
28
+ if match := re.search(r'JSON\.parse\("(.+)"\)', json_script_tag.text):
29
+ raw_json = match.group(1)
30
+ escaped = raw_json.encode("utf-8").decode("unicode_escape")
31
+ return json.loads(escaped.encode("latin1").decode("utf-8"))
32
+ else:
33
+ return {}
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class CarAd:
38
+ id: int
39
+
40
+ @property
41
+ def url(self) -> str:
42
+ return f"{SITE_URL}/mobility/item/{self.id}"
43
+
44
+ def parse(self, response: Response) -> dict:
45
+ soup = BeautifulSoup(response.content, "html.parser")
46
+ grid = soup.find("div", class_="grid grid-cols-1 md:grid-cols-3 md:gap-x-32")
47
+
48
+ if not grid:
49
+ return {}
50
+
51
+ data: dict[str, Any] = {"url": self.url}
52
+
53
+ self._extract_title_and_subtitle(grid, data)
54
+ self._extract_quick_specs(grid, data)
55
+ self._extract_price(grid, data)
56
+ self._extract_description(grid, data)
57
+ self._extract_specifications(grid, data)
58
+ self._extract_equipment(grid, data)
59
+ self._extract_seller_type(soup, data)
60
+ self._extract_ad_id(soup, data)
61
+
62
+ return data
63
+
64
+ def _extract_title_and_subtitle(self, grid: Tag, data: dict[str, Any]) -> None:
65
+ if title := grid.find("h1", class_=lambda x: x and "t1" in x):
66
+ data["title"] = title.get_text(strip=True)
67
+
68
+ if subtitle := grid.find(
69
+ "p", class_=lambda x: x and "s-text-subtle" in x and "mt-8" in x
70
+ ):
71
+ data["subtitle"] = subtitle.get_text(strip=True)
72
+
73
+ def _extract_quick_specs(self, grid: Tag, data: dict[str, Any]) -> None:
74
+ if specs_grid := grid.find(
75
+ "div", class_=lambda x: x and "grid" in x and "gap-24" in x
76
+ ):
77
+ spec_items = specs_grid.find_all("div", class_="flex gap-16 hyphens-auto")
78
+ for item in spec_items:
79
+ label = item.find("span", class_="s-text-subtle")
80
+ value = item.find("p", class_="m-0 font-bold")
81
+ if label and value:
82
+ label_text = label.get_text(strip=True)
83
+ value_text = value.get_text(strip=True)
84
+
85
+ key_mapping = {
86
+ "Modellår": "model_year",
87
+ "Miltal": "mileage",
88
+ "Växellåda": "transmission",
89
+ "Drivmedel": "fuel",
90
+ }
91
+ key = key_mapping.get(
92
+ label_text, label_text.lower().replace(" ", "_")
93
+ )
94
+ data[key] = value_text
95
+
96
+ def _extract_price(self, grid: Tag, data: dict[str, Any]) -> None:
97
+ if price_section := grid.find("div", class_="border-t pt-40 mt-40"):
98
+ price_labels = price_section.find_all("p", class_="s-text-subtle mb-0")
99
+ for price_label in price_labels:
100
+ label_text = price_label.get_text(strip=True).lower()
101
+ if "pris" in label_text:
102
+ price_elem = price_section.find("span", class_="t2")
103
+ if price_elem:
104
+ data["price"] = price_elem.get_text(strip=True)
105
+ break
106
+ elif "månadskostnad" in label_text:
107
+ monthly_elem = price_section.find("h2", class_="t2")
108
+ if monthly_elem:
109
+ data["monthly_cost"] = monthly_elem.get_text(strip=True)
110
+ break
111
+
112
+ def _extract_description(self, grid: Tag, data: dict[str, Any]) -> None:
113
+ desc_sections = grid.find_all("section", class_="border-t mt-40 pt-40")
114
+ for section in desc_sections:
115
+ h2 = section.find("h2", class_="t3 mb-0")
116
+ if h2 and "beskrivning" in h2.get_text(strip=True).lower():
117
+ desc_div = section.find("div", class_="whitespace-pre-wrap")
118
+ if desc_div:
119
+ data["description"] = desc_div.get_text(strip=True)
120
+ break
121
+
122
+ def _extract_specifications(self, grid: Tag, data: dict[str, Any]) -> None:
123
+ specs_section = grid.find("section", class_="key-info-section")
124
+ if not specs_section:
125
+ return
126
+
127
+ dl = specs_section.find("dl")
128
+ if not dl:
129
+ return
130
+
131
+ specifications: dict[str, str] = {}
132
+ divs = dl.find_all("div", style="break-inside:avoid-column")
133
+ for div in divs:
134
+ dt = div.find("dt")
135
+ dd = div.find("dd")
136
+ if dt and dd:
137
+ key_text = dt.get_text(strip=True)
138
+ value_text = dd.get_text(strip=True)
139
+ specifications[key_text] = value_text
140
+
141
+ if specifications:
142
+ data["specifications"] = specifications
143
+
144
+ def _extract_equipment(self, grid: Tag, data: dict[str, Any]) -> None:
145
+ equipment_section: Tag | None = None
146
+ for section in grid.find_all("section", class_="border-t pt-40 mt-40"):
147
+ h2 = section.find("h2", class_="t3 mb-0")
148
+ if h2 and "utrustning" in h2.get_text(strip=True).lower():
149
+ equipment_section = section
150
+ break
151
+
152
+ if not equipment_section:
153
+ return
154
+
155
+ equipment_list = equipment_section.find("ul")
156
+ if equipment_list:
157
+ equipment_items: list[str] = [
158
+ li.get_text(strip=True) for li in equipment_list.find_all("li")
159
+ ]
160
+ if equipment_items:
161
+ data["equipment"] = equipment_items
162
+
163
+ def _extract_seller_type(self, soup: BeautifulSoup, data: dict[str, Any]) -> None:
164
+ seller_type = (
165
+ "dealer"
166
+ if soup.find("div", class_=lambda x: x and "dealer" in str(x).lower())
167
+ else "private"
168
+ )
169
+ data["seller_type"] = seller_type
170
+
171
+ def _extract_ad_id(self, soup: BeautifulSoup, data: dict[str, Any]) -> None:
172
+ ad_info_divs = soup.find_all(
173
+ "div", class_="text-m flex md:flex-row flex-col md:gap-x-56 gap-y-16"
174
+ )
175
+ for div in ad_info_divs:
176
+ ad_id_labels = div.find_all("p", class_="s-text-subtle mb-0")
177
+ for ad_id_label in ad_id_labels:
178
+ if "Annons-ID" in ad_id_label.get_text(strip=True):
179
+ ad_id_elem = ad_id_label.find_next_sibling("p")
180
+ if ad_id_elem:
181
+ data["ad_id"] = ad_id_elem.get_text(strip=True)
182
+ break
@@ -4,13 +4,16 @@ from dataclasses import dataclass
4
4
  from typing import Any
5
5
 
6
6
  import httpx
7
+ from httpx import Response
7
8
 
9
+ from .ad_parser import CarAd, RecommerceAd
8
10
  from .constants import (
9
11
  HEADERS,
10
12
  SITE_URL,
11
13
  CarColor,
12
14
  CarModel,
13
15
  CarSortOrder,
16
+ CarTransmission,
14
17
  Category,
15
18
  Location,
16
19
  SortOrder,
@@ -28,14 +31,14 @@ def _request(
28
31
  *,
29
32
  url: str,
30
33
  params: list[QueryParam],
31
- ) -> Any:
34
+ ) -> Response:
32
35
  response = httpx.get(
33
36
  url,
34
37
  headers=HEADERS,
35
38
  params=[(param.name, param.value) for param in params],
36
39
  )
37
40
  response.raise_for_status()
38
- return response.json()
41
+ return response
39
42
 
40
43
 
41
44
  @dataclass(frozen=True)
@@ -61,7 +64,7 @@ class BlocketAPI:
61
64
  *([QueryParam("sub_category", sub_category.value)] if sub_category else []),
62
65
  ]
63
66
 
64
- return _request(url=url, params=params)
67
+ return _request(url=url, params=params).json()
65
68
 
66
69
  def search_car(
67
70
  self,
@@ -77,7 +80,7 @@ class BlocketAPI:
77
80
  milage_from: int | None = None,
78
81
  milage_to: int | None = None,
79
82
  colors: list[CarColor] = [],
80
- transmission: int | None = None,
83
+ transmissions: list[CarTransmission] = [],
81
84
  ) -> Any:
82
85
  url = f"{SITE_URL}/mobility/search/api/search/SEARCH_ID_CAR_USED"
83
86
  params = [
@@ -92,7 +95,11 @@ class BlocketAPI:
92
95
  *([QueryParam("milage_from", milage_from)] if milage_from else []),
93
96
  *([QueryParam("milage_to", milage_to)] if milage_to else []),
94
97
  *[QueryParam("exterior_colour", color.value) for color in colors],
95
- *([QueryParam("transmission", transmission)] if transmission else []),
98
+ *[QueryParam("transmission", t.value) for t in transmissions],
96
99
  ]
97
100
 
98
- return _request(url=url, params=params)
101
+ return _request(url=url, params=params).json()
102
+
103
+ def get_ad(self, ad: RecommerceAd | CarAd) -> dict:
104
+ response = _request(url=ad.url, params=[])
105
+ return ad.parse(response)
@@ -48,38 +48,26 @@ class Location(StrEnum):
48
48
  VASTERBOTTEN = "0.300024"
49
49
  VASTERNORRLAND = "0.300022"
50
50
  VASTMANLAND = "0.300019"
51
- VASTRA_GÖTALAND = "0.300014"
51
+ VASTRA_GOTALAND = "0.300014"
52
52
  OREBRO = "0.300018"
53
53
  OSTERGOTLAND = "0.300005"
54
54
 
55
55
 
56
56
  class Category(StrEnum):
57
57
  AFFARSVERKSAMHET = "0.91"
58
- # BIL_OCH_HUSVAGN = None
59
- # BAT = None
60
58
  DJUR_OCH_TILLBEHOR = "0.77"
61
59
  ELEKTRONIK_OCH_VITVAROR = "0.93"
62
- # ENTREPRENAD__OCH_LANTBRUKSMASKINER = None
63
60
  FORDONSTILLBEHOR = "0.90"
64
61
  FRITID_HOBBY_OCH_UNDERHALLNING = "0.86"
65
62
  FORALDRAR_OCH_BARN = "0.68"
66
63
  KLADER_KOSMETIKA_OCH_ACCESSOARER = "0.71"
67
64
  KONST_OCH_ANTIKT = "0.76"
68
- # MOTORCYKLAR = None
69
65
  MOBLER_OCH_INREDNING = "0.78"
70
66
  SPORT_OCH_FRITID = "0.69"
71
67
  TRADGARD_OCH_RENOVERING = "0.67"
72
68
 
73
69
 
74
70
  class SubCategory(StrEnum):
75
- BYGGMATERIAL_OCH_RENOVERING = "1.67.3899"
76
- BILDELAR_OCH_TILLBEHOR = "1.90.82"
77
- CYKEL = "1.69.3963"
78
- JAKT_FISKE_OCH_CAMPING = "1.69.3964"
79
- TRADGARD_OCH_UTEMILJO = "1.67.3901"
80
- MC_UTRUSTNING_OCH_RESERVDELAR = "1.90.20"
81
- HUSVAGNS_OCH_HUSBILSDELAR = "1.90.70"
82
- VERKTYG = "1.67.5219"
83
71
  BUTIK_OCH_DETALJHANDEL = "1.91.3108"
84
72
  CONTAINRAR_OCH_BARACKER = "1.91.3111"
85
73
  DOMANER_OCH_SAJTER = "1.91.3106"
@@ -91,6 +79,8 @@ class SubCategory(StrEnum):
91
79
  SCEN = "1.91.3110"
92
80
  STORKOK_OCH_RESTAURANG = "1.91.3103"
93
81
  VERKSTAD_BYGG_OCH_KONSTRUKTION = "1.91.3102"
82
+ BILDELAR_OCH_TILLBEHOR = "1.90.82"
83
+ HUSVAGNS_OCH_HUSBILSDELAR = "1.90.70"
94
84
  SLAP_OCH_TRAILER = "1.90.80"
95
85
  BATDELAR_OCH_TILLBEHOR = "1.90.30"
96
86
  AKVARIUM = "1.77.3976"
@@ -119,7 +109,79 @@ class SubCategory(StrEnum):
119
109
  TV_SPEL_OCH_SPELKONSOLER = "1.93.3905"
120
110
  VITVAROR = "1.93.3907"
121
111
  ATV_RESERVDELAR = "1.90.3975"
122
- OVRIGT = "1.90.3974"
112
+ MC_UTRUSTNING_OCH_RESERVDELAR = "1.90.20"
113
+ BILJETTER_OCH_RESOR = "1.86.7735"
114
+ BOCKER_OCH_TIDNINGAR = "1.86.5209"
115
+ HANTVERK = "1.86.7734"
116
+ MAT_OCH_DRYCK = "1.86.3972"
117
+ MODELLER_OCH_BYGGSATSER = "1.86.7732"
118
+ MUSIK_OCH_FILM = "1.86.3922"
119
+ MUSIKINSTRUMENT = "1.86.92"
120
+ RADIOSTYRDA_ENHETER = "1.86.7733"
121
+ SAMLAROBJEKT = "1.86.285"
122
+ SALLSKAPS_OCH_BRADSPEL = "1.86.5203"
123
+ BARNBOCKER = "1.68.8369"
124
+ BARNKLADER = "1.68.3913"
125
+ BARNMOBLER = "1.68.3916"
126
+ BARNSKOR = "1.68.3915"
127
+ BARNTILLBEHOR_OCH_SAKERHET = "1.68.3918"
128
+ BARNVAGNAR = "1.68.3914"
129
+ BILBARNSTOLAR_OCH_BABYSKYDD = "1.68.3911"
130
+ GRAVIDKLADER = "1.68.3948"
131
+ INREDNING_TILL_BARNRUM = "1.68.9445"
132
+ LEKSAKER = "1.68.3912"
133
+ ACCESSOARER = "1.71.9481"
134
+ DAMKLADER = "1.71.3941"
135
+ GLASOGON_OCH_SOLGLASOGON = "1.71.8344"
136
+ HERRKLADER = "1.71.3950"
137
+ HUD_HAR_OCH_KROPPSVARD = "1.71.8280"
138
+ KLOCKOR_OCH_ARMBANDSUR = "1.71.3945"
139
+ KOSMETIK = "1.71.8282"
140
+ MASKERADKLADER = "1.71.8349"
141
+ SKOR = "1.71.3949"
142
+ SMYCKEN_OCH_SMYCKESFORVARING = "1.71.7748"
143
+ VASKOR_OCH_PLANBOCKER = "1.71.3946"
144
+ ANTIKA_MOBLER = "1.76.5178"
145
+ KERAMIK_PORSLIN_OCH_GLAS = "1.76.5176"
146
+ KONST = "1.76.5177"
147
+ SILVERFOREMAL_OCH_SILVERBESTICK = "1.76.5179"
148
+ OVRIGA_ANTIKVITETER = "1.76.5175"
149
+ BORD_OCH_STOLAR = "1.78.5196"
150
+ DEKORATION_OCH_PRYDNADER = "1.78.5222"
151
+ GARDEROBER_OCH_FORVARING = "1.78.5198"
152
+ HYLLOR_OCH_BYRAER = "1.78.8345"
153
+ KOKSUTRUSTNING_OCH_PORSLIN = "1.78.5223"
154
+ LAMPOR = "1.78.5181"
155
+ MATTOR_OCH_TEXTILIER = "1.78.5180"
156
+ PYNT_TILL_HOGTIDER_OCH_FEST = "1.78.9760"
157
+ SOFFOR_OCH_FATOLJER = "1.78.7756"
158
+ SANGAR_OCH_MADRASSER = "1.78.5197"
159
+ OVRIGA_MOBLER_OCH_INREDNING = "1.78.3971"
160
+ BOLLSPORTER = "1.69.3961"
161
+ CYKEL = "1.69.3963"
162
+ EXTREMSPORT = "1.69.3938"
163
+ GOLF = "1.69.5164"
164
+ JAKT_FISKE_OCH_CAMPING = "1.69.3964"
165
+ KOSTTILLSKOTT = "1.69.8281"
166
+ RULLSKRIDSKOR_ISHOCKEY_OCH_KONSTAKNING = "1.69.8346"
167
+ SKYTTE = "1.69.3965"
168
+ SUPPORTERPRODUKTER = "1.69.3937"
169
+ TRANINGSKLOCKOR_OCH_AKTIVITETSARMBAND = "1.69.3967"
170
+ TRANINGSKLADER_OCH_SKOR = "1.69.3940"
171
+ TRANINGSUTRUSTNING = "1.69.5166"
172
+ VATTENSPORT = "1.69.7738"
173
+ VINTERSPORT = "1.69.3962"
174
+ OVRIGA_SPORTER = "1.69.3966"
175
+ BADRUM_OCH_BASTU = "1.67.7749"
176
+ BYGGMATERIAL_OCH_RENOVERING = "1.67.3899"
177
+ GARAGEDELAR_OCH_TILLBEHOR = "1.67.8348"
178
+ KOKSINREDNING_OCH_KOKSSTOMMAR = "1.67.3900"
179
+ LARM_OCH_SAKERHET = "1.67.8347"
180
+ TRADGARD_OCH_UTEMILJO = "1.67.3901"
181
+ UTRUSTNING_FOR_FRITIDSHUS = "1.67.3968"
182
+ VERKTYG = "1.67.5219"
183
+ VARME_OCH_VENTILATION = "1.67.5218"
184
+ OVRIGT = "1.67.3969"
123
185
 
124
186
 
125
187
  class CarModel(StrEnum):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: blocket_api
3
- Version: 0.3.91
3
+ Version: 0.3.92
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
@@ -16,6 +16,7 @@ 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
18
  Requires-Dist: websockets>=15.0.1
19
+ Requires-Dist: beautifulsoup4>=4.14.2
19
20
  Requires-Dist: ruff>=0.14.5
20
21
  Dynamic: license-file
21
22
 
@@ -59,8 +60,8 @@ from blocket_api import (
59
60
 
60
61
  api = BlocketAPI()
61
62
 
62
- # will search all of blocket
63
- result = api.search(
63
+ # search all of blocket
64
+ api.search(
64
65
  "Vinterdäck Audi",
65
66
  sort_order=SortOrder.PRICE_ASC,
66
67
  locations=[Location.STOCKHOLM, Location.UPPSALA],
@@ -68,16 +69,22 @@ result = api.search(
68
69
  )
69
70
 
70
71
  # search for cars only
71
- result = api.search_car(
72
+ api.search_car(
72
73
  "Audi", # query is optional
73
74
  sort_order=CarSortOrder.MILEAGE_ASC,
74
75
  models=[CarModel.AUDI],
75
76
  colors=[CarColor.GULD],
76
77
  price_from=10000,
77
78
  price_to=50000,
78
- transmission=CarTransmission.MANUAL,
79
+ transmissions=[CarTransmission.MANUAL],
79
80
  locations=[Location.STOCKHOLM],
80
81
  )
82
+
83
+ # Get a recommerce or car ad
84
+ from blocket_api import CarAd, RecommerceAd
85
+
86
+ api.get_ad(RecommerceAd(12345678))
87
+ api.get_ad(CarAd(12345678))
81
88
  ```
82
89
 
83
90
  ## 📝 Notes
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  blocket_api/__init__.py
5
+ blocket_api/ad_parser.py
5
6
  blocket_api/blocket.py
6
7
  blocket_api/constants.py
7
8
  blocket_api.egg-info/PKG-INFO
@@ -8,4 +8,5 @@ mkdocs>=1.6.1
8
8
  mkdocs-material>=9.6.21
9
9
  mkdocs-swagger-ui-tag>=0.7.2
10
10
  websockets>=15.0.1
11
+ beautifulsoup4>=4.14.2
11
12
  ruff>=0.14.5
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "blocket_api"
3
- version = "0.3.91"
3
+ version = "0.3.92"
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"
@@ -16,6 +16,7 @@ dependencies = [
16
16
  "mkdocs-material>=9.6.21",
17
17
  "mkdocs-swagger-ui-tag>=0.7.2",
18
18
  "websockets>=15.0.1",
19
+ "beautifulsoup4>=4.14.2",
19
20
  "ruff>=0.14.5",
20
21
  ]
21
22
 
File without changes
File without changes