@rbillon59/vinted-mcp-server 0.1.0
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.
- package/README.md +232 -0
- package/dist/api/browser-utils.d.ts +21 -0
- package/dist/api/browser-utils.d.ts.map +1 -0
- package/dist/api/browser-utils.js +64 -0
- package/dist/api/browser-utils.js.map +1 -0
- package/dist/api/client.d.ts +45 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +220 -0
- package/dist/api/client.js.map +1 -0
- package/dist/api/session-provider.d.ts +30 -0
- package/dist/api/session-provider.d.ts.map +1 -0
- package/dist/api/session-provider.js +69 -0
- package/dist/api/session-provider.js.map +1 -0
- package/dist/api/types.d.ts +83 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +12 -0
- package/dist/api/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +22 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/brands.d.ts +3 -0
- package/dist/tools/brands.d.ts.map +1 -0
- package/dist/tools/brands.js +34 -0
- package/dist/tools/brands.js.map +1 -0
- package/dist/tools/categories.d.ts +3 -0
- package/dist/tools/categories.d.ts.map +1 -0
- package/dist/tools/categories.js +28 -0
- package/dist/tools/categories.js.map +1 -0
- package/dist/tools/item.d.ts +3 -0
- package/dist/tools/item.d.ts.map +1 -0
- package/dist/tools/item.js +155 -0
- package/dist/tools/item.js.map +1 -0
- package/dist/tools/search.d.ts +3 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +73 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/user-items.d.ts +3 -0
- package/dist/tools/user-items.d.ts.map +1 -0
- package/dist/tools/user-items.js +50 -0
- package/dist/tools/user-items.js.map +1 -0
- package/dist/tools/user.d.ts +3 -0
- package/dist/tools/user.d.ts.map +1 -0
- package/dist/tools/user.js +38 -0
- package/dist/tools/user.js.map +1 -0
- package/dist/utils/cache.d.ts +17 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +65 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/mcp-error.d.ts +11 -0
- package/dist/utils/mcp-error.d.ts.map +1 -0
- package/dist/utils/mcp-error.js +11 -0
- package/dist/utils/mcp-error.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +22 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +47 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# vinted-mcp-server
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Rbillon59/vinted-mcp-server/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for interacting with the Vinted marketplace. Enables AI assistants to search, browse, and discover second-hand items on Vinted.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Search items** — Full-text search with filters (price, brand, size, color, condition, sort)
|
|
10
|
+
- **Item details** — Full item info including description, photos, seller profile, and condition
|
|
11
|
+
- **User profiles** — Seller ratings, reviews, item counts, and activity
|
|
12
|
+
- **User items** — Browse all items listed by a specific user
|
|
13
|
+
- **Brand search** — Find brand IDs for use in search filters
|
|
14
|
+
- **Categories** — Browse the Vinted category tree
|
|
15
|
+
- **Rate limiting** — Token bucket rate limiter to avoid API bans
|
|
16
|
+
- **Caching** — In-memory LRU cache with TTL for fast repeated queries
|
|
17
|
+
- **Retry logic** — Exponential backoff for transient errors
|
|
18
|
+
- **Session management** — Puppeteer-based with Cloudflare bypass
|
|
19
|
+
- **Token-efficient** — Concise markdown responses optimized for LLM consumption
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Prerequisites
|
|
24
|
+
- Node.js 20+
|
|
25
|
+
|
|
26
|
+
### Using npx (zero-install)
|
|
27
|
+
|
|
28
|
+
This server communicates via **stdio** (JSON-RPC) and is designed to be launched by an MCP client, not run directly in a terminal. Configure it in your MCP client as shown below.
|
|
29
|
+
|
|
30
|
+
### Install from source
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/Rbillon59/vinted-mcp-server.git
|
|
34
|
+
cd vinted-mcp-server
|
|
35
|
+
npm install
|
|
36
|
+
npm run build
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Usage with Claude Desktop
|
|
40
|
+
|
|
41
|
+
Add to your `claude_desktop_config.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"vinted": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["vinted-mcp-server"],
|
|
49
|
+
"env": {
|
|
50
|
+
"VINTED_DOMAIN": "www.vinted.fr"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
<details>
|
|
58
|
+
<summary>Alternative: using a local build</summary>
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcpServers": {
|
|
63
|
+
"vinted": {
|
|
64
|
+
"command": "node",
|
|
65
|
+
"args": ["/absolute/path/to/vinted-mcp-server/dist/index.js"],
|
|
66
|
+
"env": {
|
|
67
|
+
"VINTED_DOMAIN": "www.vinted.fr"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
</details>
|
|
74
|
+
|
|
75
|
+
### Usage with Claude Code CLI
|
|
76
|
+
|
|
77
|
+
Add to your Claude Code settings (`~/.claude/settings.json` or project `.mcp.json`):
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"vinted": {
|
|
83
|
+
"command": "npx",
|
|
84
|
+
"args": ["vinted-mcp-server"],
|
|
85
|
+
"env": {
|
|
86
|
+
"VINTED_DOMAIN": "www.vinted.fr"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Usage with Cursor
|
|
94
|
+
|
|
95
|
+
Add to `.cursor/mcp.json` in your project:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"mcpServers": {
|
|
100
|
+
"vinted": {
|
|
101
|
+
"command": "npx",
|
|
102
|
+
"args": ["vinted-mcp-server"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Usage with Docker
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
docker build -t vinted-mcp-server .
|
|
112
|
+
docker run -i --rm -e VINTED_DOMAIN=www.vinted.fr vinted-mcp-server
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Available Tools
|
|
116
|
+
|
|
117
|
+
### `search_items`
|
|
118
|
+
Search the Vinted catalog with filters.
|
|
119
|
+
|
|
120
|
+
| Parameter | Type | Default | Description |
|
|
121
|
+
|-----------|------|---------|-------------|
|
|
122
|
+
| `query` | string | *(required)* | Search text (e.g., "nike air max", "robe vintage") |
|
|
123
|
+
| `page` | number | 1 | Page number |
|
|
124
|
+
| `per_page` | number | 20 | Results per page (max: 96) |
|
|
125
|
+
| `order` | string | "relevance" | Sort: `relevance`, `price_low_to_high`, `price_high_to_low`, `newest_first` |
|
|
126
|
+
| `price_from` | number | — | Minimum price filter |
|
|
127
|
+
| `price_to` | number | — | Maximum price filter |
|
|
128
|
+
| `brand_ids` | string | — | Brand IDs (comma-separated) |
|
|
129
|
+
| `size_ids` | string | — | Size IDs (comma-separated) |
|
|
130
|
+
| `color_ids` | string | — | Color IDs (comma-separated) |
|
|
131
|
+
| `catalog_ids` | string | — | Category IDs (comma-separated) |
|
|
132
|
+
| `status_ids` | string | — | Condition: `6`=New with tags, `1`=New, `2`=Very good, `3`=Good, `4`=Satisfactory |
|
|
133
|
+
|
|
134
|
+
### `get_item_details`
|
|
135
|
+
Get detailed information about a specific item.
|
|
136
|
+
|
|
137
|
+
| Parameter | Type | Description |
|
|
138
|
+
|-----------|------|-------------|
|
|
139
|
+
| `item_id` | number | The Vinted item ID (from search results) |
|
|
140
|
+
|
|
141
|
+
Returns: title, price, description, brand, size, condition, colors, seller info (rating, location), photos, stats (views, favorites).
|
|
142
|
+
|
|
143
|
+
### `get_user_profile`
|
|
144
|
+
Get a seller's profile information.
|
|
145
|
+
|
|
146
|
+
| Parameter | Type | Description |
|
|
147
|
+
|-----------|------|-------------|
|
|
148
|
+
| `user_id` | number | The Vinted user ID (from search results or item details) |
|
|
149
|
+
|
|
150
|
+
Returns: username, rating, review breakdown, items listed/sold, location, member since, last active.
|
|
151
|
+
|
|
152
|
+
### `get_user_items`
|
|
153
|
+
Browse all items listed by a specific user.
|
|
154
|
+
|
|
155
|
+
| Parameter | Type | Default | Description |
|
|
156
|
+
|-----------|------|---------|-------------|
|
|
157
|
+
| `user_id` | number | *(required)* | The Vinted user ID |
|
|
158
|
+
| `page` | number | 1 | Page number |
|
|
159
|
+
| `per_page` | number | 20 | Results per page |
|
|
160
|
+
|
|
161
|
+
### `search_brands`
|
|
162
|
+
Search for brand names and get their IDs for use in search filters.
|
|
163
|
+
|
|
164
|
+
| Parameter | Type | Description |
|
|
165
|
+
|-----------|------|-------------|
|
|
166
|
+
| `query` | string | Brand name to search (e.g., "Nike", "Zara") |
|
|
167
|
+
|
|
168
|
+
### `get_categories`
|
|
169
|
+
Browse the Vinted category tree.
|
|
170
|
+
|
|
171
|
+
| Parameter | Type | Description |
|
|
172
|
+
|-----------|------|-------------|
|
|
173
|
+
| `parent_id` | number | *(optional)* Parent category ID to list children of |
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
| Environment Variable | Default | Description |
|
|
178
|
+
|---------------------|---------|-------------|
|
|
179
|
+
| `VINTED_DOMAIN` | `www.vinted.fr` | Vinted domain (e.g., `www.vinted.de`, `www.vinted.es`, `www.vinted.it`) |
|
|
180
|
+
| `PUPPETEER_EXECUTABLE_PATH` | — | Custom Chrome/Chromium path for Puppeteer |
|
|
181
|
+
| `BROWSER_TIMEOUT_MS` | `30000` | Timeout for Cloudflare challenge resolution (ms) |
|
|
182
|
+
|
|
183
|
+
### Supported Domains
|
|
184
|
+
| Domain | Country |
|
|
185
|
+
|--------|---------|
|
|
186
|
+
| `www.vinted.fr` | France |
|
|
187
|
+
| `www.vinted.de` | Germany |
|
|
188
|
+
| `www.vinted.es` | Spain |
|
|
189
|
+
| `www.vinted.it` | Italy |
|
|
190
|
+
| `www.vinted.nl` | Netherlands |
|
|
191
|
+
| `www.vinted.be` | Belgium |
|
|
192
|
+
| `www.vinted.pl` | Poland |
|
|
193
|
+
| `www.vinted.pt` | Portugal |
|
|
194
|
+
| `www.vinted.lt` | Lithuania |
|
|
195
|
+
| `www.vinted.cz` | Czech Republic |
|
|
196
|
+
| `www.vinted.co.uk` | United Kingdom |
|
|
197
|
+
|
|
198
|
+
## Architecture
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
src/
|
|
202
|
+
index.ts # Entry point, stdio transport
|
|
203
|
+
server.ts # MCP server config & tool registration
|
|
204
|
+
tools/
|
|
205
|
+
search.ts # search_items tool
|
|
206
|
+
item.ts # get_item_details tool
|
|
207
|
+
user.ts # get_user_profile tool
|
|
208
|
+
user-items.ts # get_user_items tool
|
|
209
|
+
brands.ts # search_brands tool
|
|
210
|
+
categories.ts # get_categories tool
|
|
211
|
+
api/
|
|
212
|
+
client.ts # HTTP client (session, cache, rate limit, retry)
|
|
213
|
+
session-provider.ts # Browser-based session (Cloudflare bypass)
|
|
214
|
+
browser-utils.ts # Shared Puppeteer/stealth utilities
|
|
215
|
+
types.ts # Vinted API response types
|
|
216
|
+
utils/
|
|
217
|
+
cache.ts # TTL cache with LRU eviction
|
|
218
|
+
rate-limiter.ts # Token bucket rate limiter
|
|
219
|
+
mcp-error.ts # Shared MCP error response builder
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Reliability Features
|
|
223
|
+
- **Rate limiting**: Token bucket (10 req/10s) prevents API bans
|
|
224
|
+
- **Caching**: LRU cache (3min TTL, 200 entries max) reduces redundant requests
|
|
225
|
+
- **Retry**: Exponential backoff (1s, 2s) on 429/5xx errors
|
|
226
|
+
- **Session recovery**: Automatic cookie refresh on 401/403 with request coalescing
|
|
227
|
+
- **Request deduplication**: Concurrent identical requests share a single API call
|
|
228
|
+
- **Cloudflare bypass**: Puppeteer with stealth plugin for session acquisition
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared browser constants and utilities for session providers.
|
|
3
|
+
*/
|
|
4
|
+
import type { Browser } from "puppeteer";
|
|
5
|
+
export declare const BROWSER_ARGS: readonly ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu", "--disable-extensions", "--disable-background-networking", "--disable-sync", "--no-first-run"];
|
|
6
|
+
/** Cookie names that indicate a successful Vinted session. */
|
|
7
|
+
export declare const SESSION_COOKIE_REGEX: RegExp;
|
|
8
|
+
export declare const ACCESS_TOKEN_COOKIE = "access_token_web";
|
|
9
|
+
/** Additional cookies to include in API requests. */
|
|
10
|
+
export declare const EXTRA_COOKIE_NAMES: readonly ["anon_id", "anonymous-locale", "v_udt", "__cf_bm"];
|
|
11
|
+
export declare function buildCookieString(cookies: ReadonlyArray<{
|
|
12
|
+
name: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}>): string;
|
|
15
|
+
export declare function hasSessionCookie(cookies: ReadonlyArray<{
|
|
16
|
+
name: string;
|
|
17
|
+
value: string;
|
|
18
|
+
}>): boolean;
|
|
19
|
+
export declare function launchStealthBrowser(executablePath: string): Promise<Browser>;
|
|
20
|
+
export declare function parseTimeoutEnv(fallback: number): number;
|
|
21
|
+
//# sourceMappingURL=browser-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-utils.d.ts","sourceRoot":"","sources":["../../src/api/browser-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,eAAO,MAAM,YAAY,kMASf,CAAC;AAEX,8DAA8D;AAC9D,eAAO,MAAM,oBAAoB,QAA0B,CAAC;AAC5D,eAAO,MAAM,mBAAmB,qBAAqB,CAAC;AAEtD,qDAAqD;AACrD,eAAO,MAAM,kBAAkB,8DAKrB,CAAC;AAEX,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACtD,MAAM,CAoBR;AAED,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GACtD,OAAO,CAIT;AAED,wBAAsB,oBAAoB,CACxC,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared browser constants and utilities for session providers.
|
|
3
|
+
*/
|
|
4
|
+
export const BROWSER_ARGS = [
|
|
5
|
+
"--no-sandbox",
|
|
6
|
+
"--disable-setuid-sandbox",
|
|
7
|
+
"--disable-dev-shm-usage",
|
|
8
|
+
"--disable-gpu",
|
|
9
|
+
"--disable-extensions",
|
|
10
|
+
"--disable-background-networking",
|
|
11
|
+
"--disable-sync",
|
|
12
|
+
"--no-first-run",
|
|
13
|
+
];
|
|
14
|
+
/** Cookie names that indicate a successful Vinted session. */
|
|
15
|
+
export const SESSION_COOKIE_REGEX = /^_vinted_\w+_session$/;
|
|
16
|
+
export const ACCESS_TOKEN_COOKIE = "access_token_web";
|
|
17
|
+
/** Additional cookies to include in API requests. */
|
|
18
|
+
export const EXTRA_COOKIE_NAMES = [
|
|
19
|
+
"anon_id",
|
|
20
|
+
"anonymous-locale",
|
|
21
|
+
"v_udt",
|
|
22
|
+
"__cf_bm",
|
|
23
|
+
];
|
|
24
|
+
export function buildCookieString(cookies) {
|
|
25
|
+
const extraSet = new Set(EXTRA_COOKIE_NAMES);
|
|
26
|
+
const parts = [];
|
|
27
|
+
for (const cookie of cookies) {
|
|
28
|
+
const isSession = cookie.name === ACCESS_TOKEN_COOKIE
|
|
29
|
+
|| SESSION_COOKIE_REGEX.test(cookie.name);
|
|
30
|
+
const isExtra = extraSet.has(cookie.name);
|
|
31
|
+
if (isSession || isExtra) {
|
|
32
|
+
parts.push(`${cookie.name}=${cookie.value}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (parts.length === 0) {
|
|
36
|
+
throw new Error("No usable cookies found after session resolution");
|
|
37
|
+
}
|
|
38
|
+
return parts.join("; ");
|
|
39
|
+
}
|
|
40
|
+
export function hasSessionCookie(cookies) {
|
|
41
|
+
return cookies.some((c) => c.name === ACCESS_TOKEN_COOKIE || SESSION_COOKIE_REGEX.test(c.name));
|
|
42
|
+
}
|
|
43
|
+
export async function launchStealthBrowser(executablePath) {
|
|
44
|
+
const puppeteerExtraMod = (await import("puppeteer-extra"));
|
|
45
|
+
const stealthMod = (await import("puppeteer-extra-plugin-stealth"));
|
|
46
|
+
const puppeteer = puppeteerExtraMod.default;
|
|
47
|
+
puppeteer.use(stealthMod.default());
|
|
48
|
+
const launchOptions = {
|
|
49
|
+
headless: true,
|
|
50
|
+
args: [...BROWSER_ARGS],
|
|
51
|
+
};
|
|
52
|
+
if (executablePath) {
|
|
53
|
+
launchOptions["executablePath"] = executablePath;
|
|
54
|
+
}
|
|
55
|
+
return puppeteer.launch(launchOptions);
|
|
56
|
+
}
|
|
57
|
+
export function parseTimeoutEnv(fallback) {
|
|
58
|
+
const envTimeout = process.env["BROWSER_TIMEOUT_MS"];
|
|
59
|
+
if (!envTimeout)
|
|
60
|
+
return fallback;
|
|
61
|
+
const parsed = parseInt(envTimeout, 10);
|
|
62
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=browser-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-utils.js","sourceRoot":"","sources":["../../src/api/browser-utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc;IACd,0BAA0B;IAC1B,yBAAyB;IACzB,eAAe;IACf,sBAAsB;IACtB,iCAAiC;IACjC,gBAAgB;IAChB,gBAAgB;CACR,CAAC;AAEX,8DAA8D;AAC9D,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,kBAAkB,CAAC;AAEtD,qDAAqD;AACrD,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,SAAS;IACT,kBAAkB;IAClB,OAAO;IACP,SAAS;CACD,CAAC;AAEX,MAAM,UAAU,iBAAiB,CAC/B,OAAuD;IAEvD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,kBAAkB,CAAC,CAAC;IACrD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,SAAS,GACb,MAAM,CAAC,IAAI,KAAK,mBAAmB;eAChC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAE1C,IAAI,SAAS,IAAI,OAAO,EAAE,CAAC;YACzB,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,OAAuD;IAEvD,OAAO,OAAO,CAAC,IAAI,CACjB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,mBAAmB,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAC3E,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,cAAsB;IAEtB,MAAM,iBAAiB,GAAG,CAAC,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAKzD,CAAC;IACF,MAAM,UAAU,GAAG,CAAC,MAAM,MAAM,CAAC,gCAAgC,CAAC,CAEjE,CAAC;IAEF,MAAM,SAAS,GAAG,iBAAiB,CAAC,OAAO,CAAC;IAC5C,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpC,MAAM,aAAa,GAA4B;QAC7C,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,CAAC,GAAG,YAAY,CAAC;KACxB,CAAC;IACF,IAAI,cAAc,EAAE,CAAC;QACnB,aAAa,CAAC,gBAAgB,CAAC,GAAG,cAAc,CAAC;IACnD,CAAC;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACrD,IAAI,CAAC,UAAU;QAAE,OAAO,QAAQ,CAAC;IACjC,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vinted API HTTP client with session management, rate limiting, and caching.
|
|
3
|
+
*/
|
|
4
|
+
import { type SessionProvider } from "./session-provider.js";
|
|
5
|
+
export declare const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
6
|
+
export declare class VintedClient {
|
|
7
|
+
private readonly domain;
|
|
8
|
+
private session;
|
|
9
|
+
private refreshPromise;
|
|
10
|
+
private readonly cache;
|
|
11
|
+
private readonly rateLimiter;
|
|
12
|
+
private readonly inflight;
|
|
13
|
+
private readonly sessionProvider;
|
|
14
|
+
constructor(domain?: string, sessionProvider?: SessionProvider);
|
|
15
|
+
get baseUrl(): string;
|
|
16
|
+
private doRefreshSession;
|
|
17
|
+
/**
|
|
18
|
+
* Coalesce concurrent session refreshes into a single request.
|
|
19
|
+
*/
|
|
20
|
+
private refreshSession;
|
|
21
|
+
private isSessionValid;
|
|
22
|
+
private ensureSession;
|
|
23
|
+
/**
|
|
24
|
+
* Fetch with retry logic for transient errors.
|
|
25
|
+
*/
|
|
26
|
+
private fetchWithRetry;
|
|
27
|
+
/**
|
|
28
|
+
* Make an authenticated, cached, rate-limited GET request to the Vinted API.
|
|
29
|
+
* Deduplicates concurrent requests for the same URL.
|
|
30
|
+
*/
|
|
31
|
+
get<T>(path: string, params?: Record<string, string>): Promise<T>;
|
|
32
|
+
private fetchAndCache;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch an HTML page with session, rate limiting, and retry.
|
|
35
|
+
* Used for scraping item detail pages where the JSON API is unavailable.
|
|
36
|
+
*/
|
|
37
|
+
getHtml(url: string): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Release browser resources held by session providers.
|
|
40
|
+
*/
|
|
41
|
+
destroy(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export declare function getClient(): VintedClient;
|
|
44
|
+
export declare function destroyClient(): Promise<void>;
|
|
45
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,KAAK,eAAe,EAAyB,MAAM,uBAAuB,CAAC;AAcpF,eAAO,MAAM,UAAU,oHAC4F,CAAC;AASpH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA8D;IAC1F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAuC;IAChE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,MAAM,CAAC,EAAE,MAAM,EAAE,eAAe,CAAC,EAAE,eAAe;IAK9D,IAAI,OAAO,IAAI,MAAM,CAEpB;YAEa,gBAAgB;IAI9B;;OAEG;YACW,cAAc;IAS5B,OAAO,CAAC,cAAc;YAKR,aAAa;IAU3B;;OAEG;YACW,cAAc;IAsE5B;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;YAkCzD,aAAa;IAyB3B;;;OAGG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA6B3C;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAI/B;AAKD,wBAAgB,SAAS,IAAI,YAAY,CAKxC;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAKnD"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vinted API HTTP client with session management, rate limiting, and caching.
|
|
3
|
+
*/
|
|
4
|
+
import { TtlCache } from "../utils/cache.js";
|
|
5
|
+
import { RateLimiter } from "../utils/rate-limiter.js";
|
|
6
|
+
import { createSessionProvider } from "./session-provider.js";
|
|
7
|
+
const DEFAULT_DOMAIN = "www.vinted.fr";
|
|
8
|
+
const SESSION_TTL_MS = 10 * 60 * 1000; // 10 minutes
|
|
9
|
+
const REQUEST_TIMEOUT_MS = 15_000;
|
|
10
|
+
const CACHE_TTL_MS = 3 * 60 * 1000; // 3 minutes
|
|
11
|
+
const MAX_RETRIES = 2;
|
|
12
|
+
const RETRY_BASE_DELAY_MS = 1000;
|
|
13
|
+
// Rate limit: 10 requests per 10 seconds (conservative to avoid bans)
|
|
14
|
+
const RATE_LIMIT_REQUESTS = 10;
|
|
15
|
+
const RATE_LIMIT_WINDOW_MS = 10_000;
|
|
16
|
+
export const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
17
|
+
export class VintedClient {
|
|
18
|
+
domain;
|
|
19
|
+
session = null;
|
|
20
|
+
refreshPromise = null;
|
|
21
|
+
cache = new TtlCache(CACHE_TTL_MS);
|
|
22
|
+
rateLimiter = new RateLimiter(RATE_LIMIT_REQUESTS, RATE_LIMIT_WINDOW_MS);
|
|
23
|
+
inflight = new Map();
|
|
24
|
+
sessionProvider;
|
|
25
|
+
constructor(domain, sessionProvider) {
|
|
26
|
+
this.domain = domain ?? process.env["VINTED_DOMAIN"] ?? DEFAULT_DOMAIN;
|
|
27
|
+
this.sessionProvider = sessionProvider ?? createSessionProvider();
|
|
28
|
+
}
|
|
29
|
+
get baseUrl() {
|
|
30
|
+
return `https://${this.domain}`;
|
|
31
|
+
}
|
|
32
|
+
async doRefreshSession() {
|
|
33
|
+
this.session = await this.sessionProvider.refreshSession(this.domain);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Coalesce concurrent session refreshes into a single request.
|
|
37
|
+
*/
|
|
38
|
+
async refreshSession() {
|
|
39
|
+
if (!this.refreshPromise) {
|
|
40
|
+
this.refreshPromise = this.doRefreshSession().finally(() => {
|
|
41
|
+
this.refreshPromise = null;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return this.refreshPromise;
|
|
45
|
+
}
|
|
46
|
+
isSessionValid() {
|
|
47
|
+
if (!this.session)
|
|
48
|
+
return false;
|
|
49
|
+
return Date.now() - this.session.fetchedAt < SESSION_TTL_MS;
|
|
50
|
+
}
|
|
51
|
+
async ensureSession() {
|
|
52
|
+
if (!this.isSessionValid()) {
|
|
53
|
+
await this.refreshSession();
|
|
54
|
+
}
|
|
55
|
+
if (!this.session) {
|
|
56
|
+
throw new Error("Failed to establish Vinted session");
|
|
57
|
+
}
|
|
58
|
+
return this.session.cookie;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Fetch with retry logic for transient errors.
|
|
62
|
+
*/
|
|
63
|
+
async fetchWithRetry(url, cookie, method = "GET", body) {
|
|
64
|
+
let lastError = null;
|
|
65
|
+
let retryDelay = RETRY_BASE_DELAY_MS;
|
|
66
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
67
|
+
if (attempt > 0) {
|
|
68
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
73
|
+
try {
|
|
74
|
+
const headers = {
|
|
75
|
+
"User-Agent": USER_AGENT,
|
|
76
|
+
Accept: "application/json, text/plain, */*",
|
|
77
|
+
Cookie: cookie,
|
|
78
|
+
Referer: `https://${this.domain}/`,
|
|
79
|
+
Origin: `https://${this.domain}`,
|
|
80
|
+
};
|
|
81
|
+
if (body !== undefined) {
|
|
82
|
+
headers["Content-Type"] = "application/json";
|
|
83
|
+
}
|
|
84
|
+
const response = await fetch(url, {
|
|
85
|
+
method,
|
|
86
|
+
headers,
|
|
87
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
88
|
+
signal: controller.signal,
|
|
89
|
+
});
|
|
90
|
+
// Return 2xx/3xx immediately
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
// Don't retry client errors (except 429; auth handled by caller)
|
|
95
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 429) {
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
// Retry on 429 (rate limited) or 5xx - consume body to free connection
|
|
99
|
+
const retryAfter = response.headers.get("retry-after");
|
|
100
|
+
retryDelay = retryAfter
|
|
101
|
+
? Math.min(parseInt(retryAfter, 10) * 1000, 30_000)
|
|
102
|
+
: RETRY_BASE_DELAY_MS * Math.pow(2, attempt);
|
|
103
|
+
await response.body?.cancel();
|
|
104
|
+
lastError = new Error(`Vinted API error: ${response.status} ${response.statusText}`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
113
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
114
|
+
lastError = new Error("Request timed out");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
throw lastError ?? new Error("Request failed after retries");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Make an authenticated, cached, rate-limited GET request to the Vinted API.
|
|
122
|
+
* Deduplicates concurrent requests for the same URL.
|
|
123
|
+
*/
|
|
124
|
+
async get(path, params) {
|
|
125
|
+
const url = new URL(`/api/v2${path}`, this.baseUrl);
|
|
126
|
+
if (params) {
|
|
127
|
+
for (const [key, value] of Object.entries(params)) {
|
|
128
|
+
if (value !== undefined && value !== "") {
|
|
129
|
+
url.searchParams.set(key, value);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const cacheKey = url.toString();
|
|
134
|
+
// Check cache first
|
|
135
|
+
const cached = this.cache.get(cacheKey);
|
|
136
|
+
if (cached !== undefined) {
|
|
137
|
+
return cached;
|
|
138
|
+
}
|
|
139
|
+
// Deduplicate concurrent requests for the same URL
|
|
140
|
+
const existing = this.inflight.get(cacheKey);
|
|
141
|
+
if (existing) {
|
|
142
|
+
return existing;
|
|
143
|
+
}
|
|
144
|
+
const promise = this.fetchAndCache(cacheKey);
|
|
145
|
+
this.inflight.set(cacheKey, promise);
|
|
146
|
+
try {
|
|
147
|
+
return await promise;
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
this.inflight.delete(cacheKey);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async fetchAndCache(cacheKey) {
|
|
154
|
+
// Ensure session before consuming a rate limit token
|
|
155
|
+
const cookie = await this.ensureSession();
|
|
156
|
+
await this.rateLimiter.acquire();
|
|
157
|
+
let response = await this.fetchWithRetry(cacheKey, cookie);
|
|
158
|
+
// Handle expired session
|
|
159
|
+
if (response.status === 401 || response.status === 403) {
|
|
160
|
+
await response.body?.cancel();
|
|
161
|
+
this.session = null;
|
|
162
|
+
const newCookie = await this.ensureSession();
|
|
163
|
+
response = await this.fetchWithRetry(cacheKey, newCookie);
|
|
164
|
+
}
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
throw new Error(`Vinted API error: ${response.status} ${response.statusText}`);
|
|
167
|
+
}
|
|
168
|
+
const data = (await response.json());
|
|
169
|
+
this.cache.set(cacheKey, data);
|
|
170
|
+
return data;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Fetch an HTML page with session, rate limiting, and retry.
|
|
174
|
+
* Used for scraping item detail pages where the JSON API is unavailable.
|
|
175
|
+
*/
|
|
176
|
+
async getHtml(url) {
|
|
177
|
+
const cacheKey = `html:${url}`;
|
|
178
|
+
const cached = this.cache.get(cacheKey);
|
|
179
|
+
if (cached !== undefined) {
|
|
180
|
+
return cached;
|
|
181
|
+
}
|
|
182
|
+
const cookie = await this.ensureSession();
|
|
183
|
+
await this.rateLimiter.acquire();
|
|
184
|
+
let response = await this.fetchWithRetry(url, cookie);
|
|
185
|
+
if (response.status === 401 || response.status === 403) {
|
|
186
|
+
await response.body?.cancel();
|
|
187
|
+
this.session = null;
|
|
188
|
+
const newCookie = await this.ensureSession();
|
|
189
|
+
response = await this.fetchWithRetry(url, newCookie);
|
|
190
|
+
}
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
throw new Error(`Vinted page error: ${response.status} ${response.statusText}`);
|
|
193
|
+
}
|
|
194
|
+
const html = await response.text();
|
|
195
|
+
this.cache.set(cacheKey, html);
|
|
196
|
+
return html;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Release browser resources held by session providers.
|
|
200
|
+
*/
|
|
201
|
+
async destroy() {
|
|
202
|
+
this.cache.destroy();
|
|
203
|
+
await this.sessionProvider.destroy();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Singleton client instance */
|
|
207
|
+
let clientInstance = null;
|
|
208
|
+
export function getClient() {
|
|
209
|
+
if (!clientInstance) {
|
|
210
|
+
clientInstance = new VintedClient();
|
|
211
|
+
}
|
|
212
|
+
return clientInstance;
|
|
213
|
+
}
|
|
214
|
+
export async function destroyClient() {
|
|
215
|
+
if (clientInstance) {
|
|
216
|
+
await clientInstance.destroy();
|
|
217
|
+
clientInstance = null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-based session provider for Vinted.
|
|
3
|
+
*
|
|
4
|
+
* Uses puppeteer-extra with the stealth plugin to solve Cloudflare challenges,
|
|
5
|
+
* extract cookies, and return them for use with regular fetch calls.
|
|
6
|
+
* The browser is launched only during session refresh and closed immediately after.
|
|
7
|
+
*/
|
|
8
|
+
export interface SessionCookies {
|
|
9
|
+
readonly cookie: string;
|
|
10
|
+
readonly fetchedAt: number;
|
|
11
|
+
}
|
|
12
|
+
export interface SessionProviderConfig {
|
|
13
|
+
readonly timeoutMs?: number;
|
|
14
|
+
readonly executablePath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SessionProvider {
|
|
17
|
+
refreshSession(domain: string): Promise<SessionCookies>;
|
|
18
|
+
destroy(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare class BrowserSessionProvider implements SessionProvider {
|
|
21
|
+
private readonly config;
|
|
22
|
+
private browser;
|
|
23
|
+
constructor(config?: SessionProviderConfig);
|
|
24
|
+
refreshSession(domain: string): Promise<SessionCookies>;
|
|
25
|
+
destroy(): Promise<void>;
|
|
26
|
+
private solveChallenge;
|
|
27
|
+
private closeBrowser;
|
|
28
|
+
}
|
|
29
|
+
export declare function createSessionProvider(config?: SessionProviderConfig): SessionProvider;
|
|
30
|
+
//# sourceMappingURL=session-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-provider.d.ts","sourceRoot":"","sources":["../../src/api/session-provider.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACxD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAWD,qBAAa,sBAAuB,YAAW,eAAe;IAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,OAAO,CAAwB;gBAE3B,MAAM,CAAC,EAAE,qBAAqB;IAIpC,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAavD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAIhB,cAAc;YAsBd,YAAY;CAW3B;AAED,wBAAgB,qBAAqB,CAAC,MAAM,CAAC,EAAE,qBAAqB,GAAG,eAAe,CAErF"}
|