pyfunda 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfunda-2.0.0/.gitignore +10 -0
- pyfunda-2.0.0/PKG-INFO +445 -0
- pyfunda-2.0.0/README.md +424 -0
- pyfunda-2.0.0/download_20k.py +192 -0
- pyfunda-2.0.0/download_listings.py +243 -0
- pyfunda-2.0.0/example.py +13 -0
- pyfunda-2.0.0/funda/__init__.py +20 -0
- pyfunda-2.0.0/funda/funda.py +463 -0
- pyfunda-2.0.0/funda/listing.py +138 -0
- pyfunda-2.0.0/pyproject.toml +35 -0
- pyfunda-2.0.0/requirements.txt +1 -0
- pyfunda-2.0.0/test_100.json +1 -0
- pyfunda-2.0.0/test_all_flows.py +884 -0
- pyfunda-2.0.0/uv.lock +145 -0
pyfunda-2.0.0/.gitignore
ADDED
pyfunda-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyfunda
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Python API for Funda.nl real estate listings
|
|
5
|
+
Project-URL: Homepage, https://github.com/0xMH/pyfunda
|
|
6
|
+
Project-URL: Repository, https://github.com/0xMH/pyfunda
|
|
7
|
+
Project-URL: Issues, https://github.com/0xMH/pyfunda/issues
|
|
8
|
+
Author: 0xMH
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: api,funda,housing,netherlands,real-estate,scraper
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: requests>=2.28.0
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Funda
|
|
23
|
+
|
|
24
|
+
Python API for [Funda.nl](https://www.funda.nl) real estate listings.
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install -r requirements.txt
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from funda import Funda
|
|
36
|
+
|
|
37
|
+
f = Funda()
|
|
38
|
+
|
|
39
|
+
# Get a listing by ID
|
|
40
|
+
listing = f.get_listing(43117443)
|
|
41
|
+
print(listing['title'], listing['city'])
|
|
42
|
+
# Reehorst 13 Luttenberg
|
|
43
|
+
|
|
44
|
+
# Get a listing by URL
|
|
45
|
+
listing = f.get_listing('https://www.funda.nl/detail/koop/amsterdam/appartement-123/43117443/')
|
|
46
|
+
|
|
47
|
+
# Search listings
|
|
48
|
+
results = f.search_listing('amsterdam', price_max=500000)
|
|
49
|
+
for r in results:
|
|
50
|
+
print(r['title'], r['price'])
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## How It Works
|
|
54
|
+
|
|
55
|
+
This library uses Funda's undocumented mobile app API, which provides clean JSON responses unlike the website that embeds data in Nuxt.js/JavaScript bundles.
|
|
56
|
+
|
|
57
|
+
### Discovery Process
|
|
58
|
+
|
|
59
|
+
The API was reverse engineered by intercepting and analyzing HTTPS traffic from the official Funda Android app:
|
|
60
|
+
|
|
61
|
+
1. Configured an Android device to route traffic through an intercepting proxy
|
|
62
|
+
2. Used the Funda app normally - browsing listings, searching, opening shared URLs
|
|
63
|
+
3. Identified the `*.funda.io` API infrastructure separate from the `www.funda.nl` website
|
|
64
|
+
4. Analyzed request/response patterns to understand the query format and available filters
|
|
65
|
+
5. Discovered how the app resolves URL-based IDs (`tinyId`) to internal IDs (`globalId`)
|
|
66
|
+
|
|
67
|
+
### API Architecture
|
|
68
|
+
|
|
69
|
+
The mobile app communicates with a separate API at `*.funda.io`:
|
|
70
|
+
|
|
71
|
+
| Endpoint | Method | Purpose |
|
|
72
|
+
|----------|--------|---------|
|
|
73
|
+
| `listing-detail-page.funda.io/api/v4/listing/object/nl/{globalId}` | GET | Fetch listing by internal ID |
|
|
74
|
+
| `listing-detail-page.funda.io/api/v4/listing/object/nl/tinyId/{tinyId}` | GET | Fetch listing by URL ID |
|
|
75
|
+
| `listing-search-wonen.funda.io/_msearch/template` | POST | Search listings |
|
|
76
|
+
|
|
77
|
+
### ID System
|
|
78
|
+
|
|
79
|
+
Funda uses two ID systems:
|
|
80
|
+
- **globalId**: Internal numeric ID (7 digits), used in the database
|
|
81
|
+
- **tinyId**: Public-facing ID (8-9 digits), appears in URLs like `funda.nl/detail/koop/amsterdam/.../{tinyId}/`
|
|
82
|
+
|
|
83
|
+
The `tinyId` endpoint was key - it allows fetching any listing directly from a Funda URL without needing to know the internal ID.
|
|
84
|
+
|
|
85
|
+
### Search API
|
|
86
|
+
|
|
87
|
+
Search uses Elasticsearch's [Multi Search Template API](https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-search-template.html) with NDJSON format:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
{"index":"listings-wonen-searcher-alias-prod"}
|
|
91
|
+
{"id":"search_result_20250805","params":{...}}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Search parameters:**
|
|
95
|
+
|
|
96
|
+
| Parameter | Description | Example |
|
|
97
|
+
|-----------|-------------|---------|
|
|
98
|
+
| `selected_area` | Location filter | `["amsterdam"]` |
|
|
99
|
+
| `radius_search` | Radius from location | `{"index": "geo-wonen-alias-prod", "id": "1012AB-0", "path": "area_with_radius.10"}` |
|
|
100
|
+
| `offering_type` | Buy or rent | `"buy"` or `"rent"` |
|
|
101
|
+
| `price.selling_price` | Price range (buy) | `{"from": 200000, "to": 500000}` |
|
|
102
|
+
| `price.rent_price` | Price range (rent) | `{"from": 500, "to": 2000}` |
|
|
103
|
+
| `object_type` | Property types | `["house", "apartment"]` |
|
|
104
|
+
| `floor_area` | Living area m² | `{"from": 50, "to": 150}` |
|
|
105
|
+
| `plot_area` | Plot area m² | `{"from": 100, "to": 500}` |
|
|
106
|
+
| `energy_label` | Energy labels | `["A", "A+", "A++"]` |
|
|
107
|
+
| `sort` | Sort order | `{"field": "publish_date_utc", "order": "desc"}` |
|
|
108
|
+
| `page.from` | Pagination offset | `0`, `15`, `30`... |
|
|
109
|
+
|
|
110
|
+
Results are paginated with 15 listings per page.
|
|
111
|
+
|
|
112
|
+
**Valid radius values:** 1, 2, 5, 10, 15, 30, 50 km (other values are not indexed).
|
|
113
|
+
|
|
114
|
+
### Required Headers
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
User-Agent: Dart/3.9 (dart:io)
|
|
118
|
+
X-Funda-App-Platform: android
|
|
119
|
+
Content-Type: application/json
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Response Data
|
|
123
|
+
|
|
124
|
+
Listing responses include:
|
|
125
|
+
- **Identifiers** - globalId, tinyId
|
|
126
|
+
- **AddressDetails** - title, city, postcode, province, neighbourhood, house number
|
|
127
|
+
- **Price** - numeric and formatted prices (selling or rental), auction flag
|
|
128
|
+
- **FastView** - bedrooms, living area, plot area, energy label
|
|
129
|
+
- **Media** - photos, floorplans, videos, 360° photos, brochure URL (all with CDN base URLs)
|
|
130
|
+
- **KenmerkSections** - detailed property characteristics (70+ fields)
|
|
131
|
+
- **Coordinates** - latitude/longitude
|
|
132
|
+
- **ObjectInsights** - view and save counts
|
|
133
|
+
- **Advertising.TargetingOptions** - boolean features (garden, balcony, solar panels, heat pump, parking, etc.), construction year, room counts
|
|
134
|
+
- **Share** - shareable URL
|
|
135
|
+
- **GoogleMapsObjectUrl** - direct Google Maps link
|
|
136
|
+
- **PublicationDate** - when the listing was published
|
|
137
|
+
- **Tracking.Values.brokers** - broker ID and association
|
|
138
|
+
|
|
139
|
+
## API Reference
|
|
140
|
+
|
|
141
|
+
### Funda
|
|
142
|
+
|
|
143
|
+
Main entry point for the API.
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from funda import Funda
|
|
147
|
+
|
|
148
|
+
f = Funda(timeout=30)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
#### get_listing(listing_id)
|
|
152
|
+
|
|
153
|
+
Get a single listing by ID or URL.
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
# By numeric ID (tinyId or globalId)
|
|
157
|
+
listing = f.get_listing(43117443)
|
|
158
|
+
|
|
159
|
+
# By URL
|
|
160
|
+
listing = f.get_listing('https://www.funda.nl/detail/koop/city/house-name/43117443/')
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### search_listing(location, ...)
|
|
164
|
+
|
|
165
|
+
Search for listings with filters.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
results = f.search_listing(
|
|
169
|
+
location='amsterdam', # City or area name
|
|
170
|
+
offering_type='buy', # 'buy' or 'rent'
|
|
171
|
+
price_min=200000, # Minimum price
|
|
172
|
+
price_max=500000, # Maximum price
|
|
173
|
+
area_min=50, # Minimum living area (m²)
|
|
174
|
+
area_max=150, # Maximum living area (m²)
|
|
175
|
+
plot_min=100, # Minimum plot area (m²)
|
|
176
|
+
plot_max=500, # Maximum plot area (m²)
|
|
177
|
+
object_type=['house'], # Property types (default: house, apartment)
|
|
178
|
+
energy_label=['A', 'A+'], # Energy labels to filter
|
|
179
|
+
sort='newest', # Sort order (see below)
|
|
180
|
+
page=0, # Page number (15 results per page)
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Radius search** - search within a radius from a postcode or city:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
results = f.search_listing(
|
|
188
|
+
location='1012AB', # Postcode or city
|
|
189
|
+
radius_km=10, # Search radius in km
|
|
190
|
+
price_max=750000,
|
|
191
|
+
)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
> **Note:** Valid radius values are 1, 2, 5, 10, 15, 30, and 50 km. Other values are automatically mapped to the nearest valid radius.
|
|
195
|
+
|
|
196
|
+
**Sort options:**
|
|
197
|
+
|
|
198
|
+
| Sort Value | Description |
|
|
199
|
+
|------------|-------------|
|
|
200
|
+
| `newest` | Most recently published first |
|
|
201
|
+
| `oldest` | Oldest listings first |
|
|
202
|
+
| `price_asc` | Lowest price first |
|
|
203
|
+
| `price_desc` | Highest price first |
|
|
204
|
+
| `area_asc` | Smallest living area first |
|
|
205
|
+
| `area_desc` | Largest living area first |
|
|
206
|
+
| `plot_desc` | Largest plot area first |
|
|
207
|
+
| `city` | Alphabetically by city |
|
|
208
|
+
| `postcode` | Alphabetically by postcode |
|
|
209
|
+
|
|
210
|
+
**Multiple locations:**
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
results = f.search_listing(['amsterdam', 'rotterdam', 'utrecht'])
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Listing
|
|
217
|
+
|
|
218
|
+
Listing objects support dict-like access with convenient aliases.
|
|
219
|
+
|
|
220
|
+
**Basic info:**
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
listing['title'] # Property title/address
|
|
224
|
+
listing['city'] # City name
|
|
225
|
+
listing['postcode'] # Postal code
|
|
226
|
+
listing['province'] # Province
|
|
227
|
+
listing['neighbourhood'] # Neighbourhood name
|
|
228
|
+
listing['municipality'] # Municipality (gemeente)
|
|
229
|
+
listing['house_number'] # House number
|
|
230
|
+
listing['house_number_ext'] # House number extension (e.g., "A", "II")
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Price & Status:**
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
listing['price'] # Numeric price
|
|
237
|
+
listing['price_formatted'] # Formatted price string (e.g., "€ 450.000 k.k.")
|
|
238
|
+
listing['price_per_m2'] # Price per m² (from characteristics)
|
|
239
|
+
listing['status'] # "available" or "sold"
|
|
240
|
+
listing['offering_type'] # "Sale" or "Rent"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Property details:**
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
listing['object_type'] # House, Apartment, etc.
|
|
247
|
+
listing['house_type'] # Type of house (e.g., "Tussenwoning")
|
|
248
|
+
listing['construction_type'] # New or existing construction
|
|
249
|
+
listing['construction_year'] # Year built
|
|
250
|
+
listing['bedrooms'] # Number of bedrooms
|
|
251
|
+
listing['rooms'] # Total number of rooms
|
|
252
|
+
listing['living_area'] # Living area in m²
|
|
253
|
+
listing['plot_area'] # Plot area in m²
|
|
254
|
+
listing['energy_label'] # Energy label (A, B, C, etc.)
|
|
255
|
+
listing['description'] # Full description text
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Dates:**
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
listing['publication_date'] # When listed on Funda
|
|
262
|
+
listing['offered_since'] # "Offered since" date (from characteristics)
|
|
263
|
+
listing['acceptance'] # Acceptance terms (e.g., "In overleg")
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Location:**
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
listing['coordinates'] # (lat, lng) tuple
|
|
270
|
+
listing['latitude'] # Latitude
|
|
271
|
+
listing['longitude'] # Longitude
|
|
272
|
+
listing['google_maps_url'] # Direct Google Maps link
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Media:**
|
|
276
|
+
|
|
277
|
+
```python
|
|
278
|
+
listing['photos'] # List of photo IDs
|
|
279
|
+
listing['photo_urls'] # List of full CDN URLs for photos
|
|
280
|
+
listing['photo_count'] # Number of photos
|
|
281
|
+
listing['floorplans'] # List of floorplan IDs
|
|
282
|
+
listing['floorplan_urls'] # List of full CDN URLs for floorplans
|
|
283
|
+
listing['videos'] # List of video IDs
|
|
284
|
+
listing['video_urls'] # List of video URLs
|
|
285
|
+
listing['photos_360'] # List of 360° photo dicts with name, id, url
|
|
286
|
+
listing['brochure_url'] # PDF brochure URL (if available)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
**Property features (booleans):**
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
listing['has_garden'] # Has garden
|
|
293
|
+
listing['has_balcony'] # Has balcony
|
|
294
|
+
listing['has_roof_terrace'] # Has roof terrace
|
|
295
|
+
listing['has_solar_panels'] # Has solar panels
|
|
296
|
+
listing['has_heat_pump'] # Has heat pump
|
|
297
|
+
listing['has_parking_on_site'] # Parking on property
|
|
298
|
+
listing['has_parking_enclosed'] # Enclosed parking
|
|
299
|
+
listing['is_energy_efficient'] # Energy efficient property
|
|
300
|
+
listing['is_monument'] # Listed/protected building
|
|
301
|
+
listing['is_fixer_upper'] # Fixer-upper (kluswoning)
|
|
302
|
+
listing['is_auction'] # Sold via auction
|
|
303
|
+
listing['open_house'] # Has open house scheduled
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Stats & metadata:**
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
listing['views'] # Number of views on Funda
|
|
310
|
+
listing['saves'] # Number of times saved
|
|
311
|
+
listing['highlight'] # Highlight text (blikvanger)
|
|
312
|
+
listing['global_id'] # Internal Funda ID
|
|
313
|
+
listing['tiny_id'] # Public ID (used in URLs)
|
|
314
|
+
listing['url'] # Full Funda URL
|
|
315
|
+
listing['share_url'] # Shareable URL
|
|
316
|
+
listing['broker_id'] # Broker ID
|
|
317
|
+
listing['broker_association'] # Broker association (e.g., "NVM")
|
|
318
|
+
listing['characteristics'] # Dict of all detailed characteristics
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
**Key aliases** - these all work:
|
|
322
|
+
|
|
323
|
+
| Alias | Canonical Key |
|
|
324
|
+
|-------|---------------|
|
|
325
|
+
| `name`, `address` | `title` |
|
|
326
|
+
| `location`, `locality` | `city` |
|
|
327
|
+
| `area`, `size` | `living_area` |
|
|
328
|
+
| `type`, `property_type` | `object_type` |
|
|
329
|
+
| `images`, `pictures`, `media` | `photos` |
|
|
330
|
+
| `agent`, `realtor`, `makelaar` | `broker` |
|
|
331
|
+
| `zip`, `zipcode`, `postal_code` | `postcode` |
|
|
332
|
+
|
|
333
|
+
#### Methods
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
listing.summary() # Text summary of the listing
|
|
337
|
+
listing.to_dict() # Convert to plain dictionary
|
|
338
|
+
listing.keys() # List available keys
|
|
339
|
+
listing.get('key') # Get with default (like dict.get)
|
|
340
|
+
listing.getID() # Get listing ID
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## Examples
|
|
344
|
+
|
|
345
|
+
### Find apartments in Amsterdam under €400k
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
from funda import Funda
|
|
349
|
+
|
|
350
|
+
f = Funda()
|
|
351
|
+
results = f.search_listing('amsterdam', price_max=400000)
|
|
352
|
+
|
|
353
|
+
for listing in results:
|
|
354
|
+
print(f"{listing['title']}")
|
|
355
|
+
print(f" Price: €{listing['price']:,}")
|
|
356
|
+
print(f" Area: {listing.get('living_area', 'N/A')}")
|
|
357
|
+
print(f" Bedrooms: {listing.get('bedrooms', 'N/A')}")
|
|
358
|
+
print()
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Get detailed listing information
|
|
362
|
+
|
|
363
|
+
```python
|
|
364
|
+
from funda import Funda
|
|
365
|
+
|
|
366
|
+
f = Funda()
|
|
367
|
+
listing = f.get_listing(43117443)
|
|
368
|
+
|
|
369
|
+
print(listing.summary())
|
|
370
|
+
|
|
371
|
+
# Access all characteristics
|
|
372
|
+
for key, value in listing['characteristics'].items():
|
|
373
|
+
print(f"{key}: {value}")
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### Search rentals in multiple cities
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
from funda import Funda
|
|
380
|
+
|
|
381
|
+
f = Funda()
|
|
382
|
+
results = f.search_listing(
|
|
383
|
+
location=['amsterdam', 'rotterdam', 'den-haag'],
|
|
384
|
+
offering_type='rent',
|
|
385
|
+
price_max=2000,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
print(f"Found {len(results)} rentals")
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Find energy-efficient homes with a garden
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
from funda import Funda
|
|
395
|
+
|
|
396
|
+
f = Funda()
|
|
397
|
+
listing = f.get_listing(43117443)
|
|
398
|
+
|
|
399
|
+
# Check property features
|
|
400
|
+
if listing['has_garden'] and listing.get('has_solar_panels'):
|
|
401
|
+
print("Energy efficient with garden!")
|
|
402
|
+
|
|
403
|
+
if listing['is_energy_efficient']:
|
|
404
|
+
print(f"Energy label: {listing['energy_label']}")
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Download listing photos
|
|
408
|
+
|
|
409
|
+
```python
|
|
410
|
+
from funda import Funda
|
|
411
|
+
import requests
|
|
412
|
+
|
|
413
|
+
f = Funda()
|
|
414
|
+
listing = f.get_listing(43117443)
|
|
415
|
+
|
|
416
|
+
# Photo URLs are ready to use
|
|
417
|
+
for i, url in enumerate(listing['photo_urls'][:5]):
|
|
418
|
+
response = requests.get(url)
|
|
419
|
+
with open(f"photo_{i}.jpg", "wb") as file:
|
|
420
|
+
file.write(response.content)
|
|
421
|
+
|
|
422
|
+
# Also available: floorplan_urls, video_urls
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Search by radius from postcode
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from funda import Funda
|
|
429
|
+
|
|
430
|
+
f = Funda()
|
|
431
|
+
results = f.search_listing(
|
|
432
|
+
location='1012AB',
|
|
433
|
+
radius_km=15,
|
|
434
|
+
price_max=600000,
|
|
435
|
+
energy_label=['A', 'A+', 'A++'],
|
|
436
|
+
sort='newest',
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
for r in results:
|
|
440
|
+
print(f"{r['title']} - €{r['price']:,}")
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## License
|
|
444
|
+
|
|
445
|
+
MIT
|