Initial Marktplaats CLI scaffold
Create mrktplaats-cli from twdehands-cli, updating module/import names, command branding, and token paths while preserving command behavior and payload formats.
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mrktplaats
|
||||||
|
mrktplaats.exe
|
||||||
|
tmp_ocr_*/
|
||||||
40
IDENTITY.md
Normal file
40
IDENTITY.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
Role & Identity:
|
||||||
|
You are Jonathan, a real human user buying computer hardware on the Belgian marketplace "marktplaats.nl". You are NOT an AI, bot, or automated script. You write messages exactly like a normal person typing quickly on a smartphone.
|
||||||
|
|
||||||
|
Language & Tone:
|
||||||
|
|
||||||
|
Language: Casual, conversational Dutch (Flemish nuances are fine).
|
||||||
|
|
||||||
|
Tone: Friendly, polite, but brief and direct. Do not sound corporate, overly formal, or robotic.
|
||||||
|
|
||||||
|
Strict Formatting Rules (CRITICAL):
|
||||||
|
|
||||||
|
NO Emojis: Never use emojis under any circumstances.
|
||||||
|
|
||||||
|
NO Em-dashes or En-dashes: Never use — or –. Use regular commas or periods.
|
||||||
|
|
||||||
|
NO IDs or Metadata: Never include profile IDs (like "40921019"), URNs, reference numbers, or brackets in your messages to sellers.
|
||||||
|
|
||||||
|
NO Bullet points in chat: Write in short, normal paragraphs or single lines.
|
||||||
|
|
||||||
|
NO Passive Voice: Avoid robotic phrases like "Dit bericht is door mij verstuurd." Use active, human phrasing like "Ik stuurde dit per ongeluk."
|
||||||
|
|
||||||
|
Message Structure:
|
||||||
|
|
||||||
|
Greeting: Start with "Hoi", "Hallo", "Beste", or "Dag [Naam]".
|
||||||
|
|
||||||
|
Body: Keep it strictly to 1 or 2 short sentences. Get straight to the point (e.g., asking about SMART data, making an offer, or apologizing).
|
||||||
|
|
||||||
|
Sign-off: End simply with "Groeten, Jonathan", "Mvg, Jonathan", or just "Groeten".
|
||||||
|
|
||||||
|
Examples of GOOD output:
|
||||||
|
|
||||||
|
"Hoi, oei sorry, dat vorige bericht was een foutje en niet voor jou bedoeld. Mag je negeren! Groeten, Jonathan"
|
||||||
|
|
||||||
|
"Hallo, ik heb wel interesse. Heb je toevallig een recente SMART screenshot van de schijf? Mvg, Jonathan"
|
||||||
|
|
||||||
|
Examples of BAD output (NEVER DO THIS):
|
||||||
|
|
||||||
|
"Hi — sorry, dat bericht was vroegtijdig verstuurd door mij. Trek mijn huidige bieding in. (40921019)"
|
||||||
|
|
||||||
|
"Beste verkoper, hierbij wil ik u informeren dat..."
|
||||||
437
README.md
Normal file
437
README.md
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
# mrktplaats CLI
|
||||||
|
|
||||||
|
Command-line interface to [marktplaats.nl](https://www.marktplaats.nl), built on the
|
||||||
|
[mrktplaats Go SDK](../mrktplaats). Designed to be used by both humans and LLM agents.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd mrktplaats-cli
|
||||||
|
go build -o mrktplaats .
|
||||||
|
# optional: install globally
|
||||||
|
go install .
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Run `login` once to authenticate. The token is saved to
|
||||||
|
`~/.config/mrktplaats/token` and reused automatically.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mrktplaats login --email you@example.com --password yourpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
If the account has SMS 2FA enabled the first call returns a verification prompt.
|
||||||
|
Re-run with the code you received:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mrktplaats login --email you@example.com --password yourpassword --code 123456
|
||||||
|
```
|
||||||
|
|
||||||
|
**Alternative: environment variable**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export MRKTPLAATS_TOKEN=your-token-here
|
||||||
|
mrktplaats me
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output format
|
||||||
|
|
||||||
|
All commands write **pretty-printed JSON** to stdout.
|
||||||
|
Errors are written to stderr and exit with code 1.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command reference
|
||||||
|
|
||||||
|
### `login`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats login --email EMAIL --password PASS [--code 2FA_CODE]
|
||||||
|
```
|
||||||
|
|
||||||
|
Authenticate and persist the token. With 2FA enabled, the first call returns
|
||||||
|
`"status": "verification_required"` — re-run with `--code` after receiving the SMS.
|
||||||
|
|
||||||
|
**Output (success)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"token_saved": "/home/you/.config/mrktplaats/token",
|
||||||
|
"access_token": "8fc90f77-..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output (2FA required)**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "verification_required",
|
||||||
|
"method": "sms",
|
||||||
|
"message": "We sent a code to +32 4xx xxx xxx",
|
||||||
|
"request_id": "abc123",
|
||||||
|
"next_step": "re-run with --code CODE after receiving the SMS"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `search`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats search --query Q [--category ID] [--page N] [--size N] [--sort FIELD]
|
||||||
|
```
|
||||||
|
|
||||||
|
Search listings. No authentication required.
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `--query` | *(required)* | Search query |
|
||||||
|
| `--category` | 0 (all) | Category ID filter |
|
||||||
|
| `--page` | 1 | Page number |
|
||||||
|
| `--size` | 20 | Results per page (max 100) |
|
||||||
|
| `--sort` | `SORT_INDEX` | `SORT_INDEX` \| `DATE_DESC` \| `PRICE_ASC` \| `PRICE_DESC` |
|
||||||
|
| `--seller` | — | Filter by seller ID |
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 2573,
|
||||||
|
"page": 1,
|
||||||
|
"size": 3,
|
||||||
|
"listings": [
|
||||||
|
{
|
||||||
|
"urn": "m2372861012",
|
||||||
|
"title": "Seiko SARV001",
|
||||||
|
"price_cents": 25000,
|
||||||
|
"price_label": "€ 250,00",
|
||||||
|
"price_type": "FIXED",
|
||||||
|
"city": "Gent",
|
||||||
|
"seller_name": "Jonathan",
|
||||||
|
"url": "https://..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`price_type` values: `FIXED`, `MIN_BID`, `FAST_BID`, `FREE`, `ON_REQUEST`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `listing`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats listing --urn URN
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch full details for a listing. Requires authentication.
|
||||||
|
|
||||||
|
**Output** — full listing object including:
|
||||||
|
- `adCore.title`, `.description`, `.price`, `.adAddress`, `.pictures`, `.attributes`
|
||||||
|
- `sellerInformation.id`, `.name` — seller's ID is needed for `Relevant.Get`
|
||||||
|
- `bids` — array of current bids
|
||||||
|
- `currentMinimumBid` — minimum next bid in cents
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `conversations`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats conversations [--limit N]
|
||||||
|
```
|
||||||
|
|
||||||
|
List all messaging conversations.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 2,
|
||||||
|
"unread": 0,
|
||||||
|
"conversations": [
|
||||||
|
{
|
||||||
|
"id": "1cgx:5qgvx3h:2p1gxt95d",
|
||||||
|
"item_urn": "m2372861012",
|
||||||
|
"title": "NVIDEA 100000 V2",
|
||||||
|
"other_party": "Mattia",
|
||||||
|
"other_party_id": 57506580,
|
||||||
|
"unread": 0,
|
||||||
|
"latest_message": "Is this still available?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `messages`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats messages --conv CONV_ID [--limit N] [--offset N]
|
||||||
|
```
|
||||||
|
|
||||||
|
Read messages in a conversation. Use `conversations` to find IDs.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 4,
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "abc123",
|
||||||
|
"sender_id": 40921019,
|
||||||
|
"text": "Is this still available?",
|
||||||
|
"date": "2026-03-03T18:48:00Z",
|
||||||
|
"type": "chat",
|
||||||
|
"read": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `send`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats send --urn URN --text TEXT
|
||||||
|
```
|
||||||
|
|
||||||
|
Start a **new** conversation with the seller of a listing.
|
||||||
|
Use `reply` to respond to an existing conversation.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"conversation_id": "1cgx:5qgvx3h:2p1gxt95d",
|
||||||
|
"item_urn": "m2372861012",
|
||||||
|
"status": "sent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `reply`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats reply --conv CONV_ID --text TEXT
|
||||||
|
```
|
||||||
|
|
||||||
|
Send a message in an **existing** conversation.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message_id": "029c3014-...",
|
||||||
|
"conversation_id": "1cgx:5qgvx3h:2p1gxt95d",
|
||||||
|
"status": "sent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `bid`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats bid --urn URN --amount EUROS [--message TEXT]
|
||||||
|
```
|
||||||
|
|
||||||
|
Place a bid. The listing must have `price_type` of `MIN_BID` or `FAST_BID`.
|
||||||
|
Amount is in **euros** (e.g. `--amount 20` for €20.00).
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"bids": [
|
||||||
|
{"id": 1533844324, "value": 2000, "date": "...", "user": {"id": 40921019, "nickname": "Jonathan"}}
|
||||||
|
],
|
||||||
|
"current_minimum_bid_cents": 2000,
|
||||||
|
"current_minimum_bid_euros": 20.0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `remove-bid`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats remove-bid --id BID_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove a bid. `BID_ID` is the numeric `id` field from the `bids` array.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"bids": [],
|
||||||
|
"current_minimum_bid_cents": 2000,
|
||||||
|
"current_minimum_bid_euros": 20.0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `favorites`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats favorites [--limit N]
|
||||||
|
```
|
||||||
|
|
||||||
|
List saved listings.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 3,
|
||||||
|
"more": false,
|
||||||
|
"items": [
|
||||||
|
{"item_urn": "m2372861012", "saved_at": ""}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `add-favorite` / `remove-favorite`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats add-favorite --urn URN
|
||||||
|
mrktplaats remove-favorite --urn URN
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{"status": "added", "item_urn": "m2372861012"}
|
||||||
|
{"status": "removed", "item_urn": "m2372861012"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `me`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats me
|
||||||
|
```
|
||||||
|
|
||||||
|
Get the authenticated user's profile (id, name, email, postcode, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `my-ads`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats my-ads [--status active|inactive|sold] [--limit N]
|
||||||
|
```
|
||||||
|
|
||||||
|
List own listings.
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 1,
|
||||||
|
"ads": [
|
||||||
|
{
|
||||||
|
"item_id": "m2373048475",
|
||||||
|
"title": "Seiko SARV001 - SDK integratietest",
|
||||||
|
"status": "active",
|
||||||
|
"price_cents": 25000,
|
||||||
|
"price_euros": 250.0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `delete-ad`
|
||||||
|
|
||||||
|
```
|
||||||
|
mrktplaats delete-ad --urn URN [--reason N]
|
||||||
|
```
|
||||||
|
|
||||||
|
Delete one of your own listings.
|
||||||
|
|
||||||
|
| Reason | Meaning |
|
||||||
|
|--------|---------|
|
||||||
|
| 1 | Sold on marktplaats.nl |
|
||||||
|
| 2 | Sold elsewhere |
|
||||||
|
| 3 | No longer selling |
|
||||||
|
| 4 | Other (default) |
|
||||||
|
|
||||||
|
**Output**
|
||||||
|
```json
|
||||||
|
{"status": "deleted", "item_urn": "m2373048475"}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LLM agent usage guide
|
||||||
|
|
||||||
|
This tool is designed to be called by an LLM agent as a subprocess. All output
|
||||||
|
is structured JSON so the agent can parse results directly.
|
||||||
|
|
||||||
|
### Typical workflow for buying
|
||||||
|
|
||||||
|
```
|
||||||
|
1. search for items
|
||||||
|
mrktplaats search --query "laptop" --size 10
|
||||||
|
|
||||||
|
2. inspect a promising listing
|
||||||
|
mrktplaats listing --urn <urn from search>
|
||||||
|
|
||||||
|
3. check conversations to see if you already contacted this seller
|
||||||
|
mrktplaats conversations
|
||||||
|
|
||||||
|
4. contact the seller (new conversation)
|
||||||
|
mrktplaats send --urn <urn> --text "Is this still available? Can you do €X?"
|
||||||
|
|
||||||
|
OR reply to an existing conversation
|
||||||
|
mrktplaats reply --conv <conv_id> --text "..."
|
||||||
|
|
||||||
|
5. if the listing supports bidding (price_type = MIN_BID or FAST_BID)
|
||||||
|
mrktplaats bid --urn <urn> --amount <euros>
|
||||||
|
# to cancel: mrktplaats remove-bid --id <bid_id>
|
||||||
|
|
||||||
|
6. save interesting listings for later
|
||||||
|
mrktplaats add-favorite --urn <urn>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Typical workflow for selling
|
||||||
|
|
||||||
|
```
|
||||||
|
1. check own listings
|
||||||
|
mrktplaats my-ads
|
||||||
|
|
||||||
|
2. delete a listing
|
||||||
|
mrktplaats delete-ad --urn <urn>
|
||||||
|
|
||||||
|
3. check and reply to incoming messages
|
||||||
|
mrktplaats conversations
|
||||||
|
mrktplaats messages --conv <conv_id>
|
||||||
|
mrktplaats reply --conv <conv_id> --text "..."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key fields to extract from responses
|
||||||
|
|
||||||
|
| Task | Command | Field to read |
|
||||||
|
|------|---------|---------------|
|
||||||
|
| Get listing URN from search | `search` | `listings[].urn` |
|
||||||
|
| Get seller ID (for bids/contact) | `listing` | `sellerInformation.id` |
|
||||||
|
| Get bid ID (to cancel) | `listing` or `bid` | `bids[].id` |
|
||||||
|
| Get conversation ID | `conversations` or `send` | `conversations[].id` / `conversation_id` |
|
||||||
|
| Check if bid-enabled | `listing` | `adCore.price.priceType == "MIN_BID"` |
|
||||||
|
| Get minimum bid | `listing` | `currentMinimumBid` (cents) |
|
||||||
|
|
||||||
|
### Error handling
|
||||||
|
|
||||||
|
On error the tool exits with code 1 and writes to stderr:
|
||||||
|
|
||||||
|
```
|
||||||
|
error: listing m9999: mrktplaats: API error 404: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent should check the exit code and parse the error message from stderr.
|
||||||
153
SKILLS.md
Normal file
153
SKILLS.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# mrktplaats CLI — Skill Guide for AI Agents
|
||||||
|
|
||||||
|
## What You Can Do
|
||||||
|
|
||||||
|
You have a `mrktplaats` CLI tool to interact with marktplaats.nl (Belgian marketplace). Use it to:
|
||||||
|
- **Search** for items (phones, RAM, electronics, etc.)
|
||||||
|
- **Get details** on listings (price, condition, seller info)
|
||||||
|
- **Check seller ratings** before buying
|
||||||
|
- **Contact sellers** via marktplaats messaging
|
||||||
|
- **Check your messages** and conversations
|
||||||
|
- **Bid** on auction items
|
||||||
|
- **Post listings** to sell items
|
||||||
|
|
||||||
|
## When to Use This Tool
|
||||||
|
|
||||||
|
**Use it when the user asks:**
|
||||||
|
- "Are there any messages?" → Run `conversations`
|
||||||
|
- "Find me a cheap Samsung phone" → Run `search`
|
||||||
|
- "What's the condition of listing X?" → Run `listing --urn X`
|
||||||
|
- "Is seller Y trustworthy?" → Run `reviews --seller Y`
|
||||||
|
- "Message the seller about X" → Run `send` or `reply`
|
||||||
|
- "Send those links to Discord" → Use curl to webhook
|
||||||
|
|
||||||
|
**Don't use it for:**
|
||||||
|
- General web searches (use websearch)
|
||||||
|
- Code questions
|
||||||
|
- Anything unrelated to marktplaats.nl
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search (most common)
|
||||||
|
./mrktplaats search --query "TERMS" --size 50 --sort PRICE_ASC
|
||||||
|
|
||||||
|
# Get details
|
||||||
|
./mrktplaats listing --urn URN
|
||||||
|
|
||||||
|
# Check seller
|
||||||
|
./mrktplaats reviews --seller ID
|
||||||
|
|
||||||
|
# Messages
|
||||||
|
./mrktplaats conversations --limit 10
|
||||||
|
./mrktplaats messages --conv CONV_ID --limit 10
|
||||||
|
|
||||||
|
# Contact seller (NEW listing)
|
||||||
|
./mrktplaats send --urn URN --text "MESSAGE"
|
||||||
|
|
||||||
|
# Contact seller (EXISTING conversation)
|
||||||
|
./mrktplaats reply --conv CONV_ID --text "MESSAGE"
|
||||||
|
|
||||||
|
# Discord webhook
|
||||||
|
curl -X POST WEBHOOK_URL -H "Content-Type: application/json" -d '{"content": "..."}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Writing Messages (IMPORTANT)
|
||||||
|
|
||||||
|
When contacting sellers, follow these rules:
|
||||||
|
|
||||||
|
**Language:** Casual Dutch (Flemish)
|
||||||
|
**Tone:** Friendly, brief, direct — like texting
|
||||||
|
**NO:** emojis, em-dashes (—), IDs/URNs, bullet points
|
||||||
|
|
||||||
|
**Structure:**
|
||||||
|
1. Greeting: "Hoi", "Hallo", "Beste [Naam]"
|
||||||
|
2. Body: 1-2 short sentences
|
||||||
|
3. Sign-off: "Groeten, Jonathan"
|
||||||
|
|
||||||
|
**Good examples:**
|
||||||
|
- "Hoi, is die Samsung S22 nog beschikbaar? Groeten, Jonathan"
|
||||||
|
- "Hoi, kan ik hem morgen ophalen? Groeten, Jonathan"
|
||||||
|
|
||||||
|
**Never do:**
|
||||||
|
- "Hi — I'm interested in your listing (URN: m123456). Please respond."
|
||||||
|
- Include profile IDs, prices in subject, or formal language
|
||||||
|
|
||||||
|
## Finding Deals
|
||||||
|
|
||||||
|
User's benchmarks:
|
||||||
|
- DDR4 RAM: ~€2-3/GB is good
|
||||||
|
- Used phones: depends on model/condition
|
||||||
|
- Always check: price, condition (in description), seller reviews
|
||||||
|
|
||||||
|
**Workflow:**
|
||||||
|
1. Search with broad terms: `./mrktplaats search --query "ram sodimm" --size 50 --sort PRICE_ASC`
|
||||||
|
2. Analyze top results
|
||||||
|
3. Get details: `./mrktplaats listing --urn URN`
|
||||||
|
4. Check seller: `./mrktplaats reviews --seller ID`
|
||||||
|
5. Present findings with links
|
||||||
|
|
||||||
|
**Links format:** `https://link.marktplaats.nl/{URN}`
|
||||||
|
|
||||||
|
## Discord Integration
|
||||||
|
|
||||||
|
**Webhook URL:**
|
||||||
|
```
|
||||||
|
https://discord.com/api/webhooks/1478131681033588859/xjPSaVsePMWrmXI1jGtvnvAaQ2L4jeBVZt9KzXY2LAH3KmHATsLIkqsgjzcMa31oYcnY
|
||||||
|
```
|
||||||
|
|
||||||
|
**Send message:**
|
||||||
|
```bash
|
||||||
|
curl -X POST WEBHOOK_URL \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"content": "Your message here"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Errors & Solutions
|
||||||
|
|
||||||
|
| Error | Meaning | Solution |
|
||||||
|
|-------|---------|----------|
|
||||||
|
| `401 Unauthorized` | Token expired | Ask user to login |
|
||||||
|
| `403 VERIFICATION_NOT_FOUND` | 2FA blocked | Wait hours, then retry login |
|
||||||
|
| `dial tcp: lookup app.marktplaats.nl` | Network error | Retry later |
|
||||||
|
| No results returned | Bad search terms | Try different query |
|
||||||
|
|
||||||
|
## Login Flow
|
||||||
|
|
||||||
|
1. Ask user for email/password (or token)
|
||||||
|
2. Run: `./mrktplaats login --email EMAIL --password PASS`
|
||||||
|
3. If 2FA required → ask for SMS code
|
||||||
|
4. Re-run with code: `./mrktplaats login --email EMAIL --password PASS --code CODE`
|
||||||
|
|
||||||
|
## Complete Workflow Examples
|
||||||
|
|
||||||
|
### Example 1: User wants to find cheap DDR4 RAM
|
||||||
|
```
|
||||||
|
You: ./mrktplaats search --query "DDR4 sodimm" --size 50 --sort PRICE_ASC
|
||||||
|
→ Results: [...]
|
||||||
|
|
||||||
|
You: Analyze and calculate €/GB for top items
|
||||||
|
|
||||||
|
You: Present table with best deals
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: User wants to contact a seller
|
||||||
|
```
|
||||||
|
You: ./mrktplaats send --urn m1234567890 --text "Hoi, is die nog beschikbaar? Groeten, Jonathan"
|
||||||
|
→ {"status": "sent", ...}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: User wants to check messages
|
||||||
|
```
|
||||||
|
You: ./mrktplaats conversations --limit 10
|
||||||
|
→ Shows conversations with unread count
|
||||||
|
|
||||||
|
You: ./mrktplaats messages --conv CONV_ID --limit 5
|
||||||
|
→ Shows message history
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 4: User wants to send deal links to Discord
|
||||||
|
```
|
||||||
|
You: curl -X POST WEBHOOK_URL -H "Content-Type: application/json" \
|
||||||
|
-d '{"content": "DDR4 RAM deals:\n- 16GB - €50: https://link.marktplaats.nl/m123"}'
|
||||||
|
```
|
||||||
291
TOOLS.md
Normal file
291
TOOLS.md
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
# mrktplaats CLI — Technical Reference
|
||||||
|
|
||||||
|
## Installation & Setup
|
||||||
|
|
||||||
|
The CLI is in `/home/joren/dev/marktplaatsApi/mrktplaats-cli/`. Run commands from there or add to PATH.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login (starts 2FA flow)
|
||||||
|
./mrktplaats login --email EMAIL --password PASS
|
||||||
|
|
||||||
|
# If 2FA triggered, re-run with code
|
||||||
|
./mrktplaats login --email EMAIL --password PASS --code 123456
|
||||||
|
|
||||||
|
# Token location: ~/.config/mrktplaats/token
|
||||||
|
# Or set env var: MRKTPLAATS_TOKEN=...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### Search Listings
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats search --query "SEARCH_TERMS" [--category ID] [--size N] [--sort SORT]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Flag | Values | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| `--query` | string | Search terms (required) |
|
||||||
|
| `--category` | ID | Category ID (optional) |
|
||||||
|
| `--size` | 1-100 | Number of results (default 20) |
|
||||||
|
| `--sort` | PRICE_ASC, PRICE_DESC, DATE_DESC | Sort order |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
./mrktplaats search --query "Samsung Galaxy S22" --size 30 --sort PRICE_ASC
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"listings": [
|
||||||
|
{
|
||||||
|
"urn": "m2375361894",
|
||||||
|
"title": "Samsung Galaxy s22, 128g, 8g ram",
|
||||||
|
"price_cents": 16000,
|
||||||
|
"price_label": "€ 160,00",
|
||||||
|
"price_type": "FIXED",
|
||||||
|
"city": "Halle",
|
||||||
|
"seller_name": "Tom"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"page": 1,
|
||||||
|
"size": 30,
|
||||||
|
"total": 403
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Listing Details
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats listing --urn URN
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"adCore": {
|
||||||
|
"urn": "m2375361894",
|
||||||
|
"title": "Samsung Galaxy s22, 128g, 8g ram",
|
||||||
|
"description": "Is ongeveer een tweetal jaar gebruikt...",
|
||||||
|
"price": {"priceAmount": 16000, "priceType": "FIXED"},
|
||||||
|
"pictures": [...],
|
||||||
|
"attributes": [
|
||||||
|
{"key": "condition", "name": "Conditie", "values": [{"name": "Gebruikt"}]},
|
||||||
|
{"key": "Opslagcapaciteit", "values": [{"name": "128 GB"}]}
|
||||||
|
],
|
||||||
|
"link": "https://link.marktplaats.nl/m2375361894"
|
||||||
|
},
|
||||||
|
"sellerInformation": {
|
||||||
|
"id": 34593982,
|
||||||
|
"name": "Tom",
|
||||||
|
"type": "CONSUMER",
|
||||||
|
"activeSince": {"label": "8½ jaar"},
|
||||||
|
"kycState": {"twoFactorVerificationState": "VERIFIED"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Seller Reviews
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats reviews --seller ID
|
||||||
|
# or from a listing
|
||||||
|
./mrktplaats reviews --urn URN
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reviews": [
|
||||||
|
{
|
||||||
|
"id": 125540237,
|
||||||
|
"score": 5,
|
||||||
|
"reviewer_name": "Kakoje",
|
||||||
|
"subject": "Monitoren",
|
||||||
|
"direction": "S2B",
|
||||||
|
"date": "2024-06-26",
|
||||||
|
"details": [{"category": "general", "score": 5, "traits": ["+Reageert snel", "+Vriendelijk"]}]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"seller_id": 34593982,
|
||||||
|
"summary": {"average_score": 4.5, "count": 14}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Conversations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats conversations --limit N
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"conversations": [
|
||||||
|
{
|
||||||
|
"id": "5b6x:2p0q1r6:2p16t6r09",
|
||||||
|
"item_urn": "m2366146019",
|
||||||
|
"title": "Ebike FIETSBATRERIJ",
|
||||||
|
"other_party": "linde.de",
|
||||||
|
"other_party_id": 32557926,
|
||||||
|
"unread": 1,
|
||||||
|
"latest_message": "Wilrijk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 3,
|
||||||
|
"unread": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Messages
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats messages --conv CONV_ID --limit N
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"id": "7ae205d4-18c6-11f1-977b-25f14ffc6a81",
|
||||||
|
"sender_id": 32557926,
|
||||||
|
"text": "Wilrijk",
|
||||||
|
"date": "2026-03-05T19:06:56.942Z",
|
||||||
|
"type": "chat"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 6
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Send Message (New Conversation)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats send --urn URN --text "MESSAGE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"item_urn": "m2372861012",
|
||||||
|
"seller_id": "57506580",
|
||||||
|
"seller_name": "Mattia",
|
||||||
|
"status": "sent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Reply to Conversation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats reply --conv CONV_ID --text "MESSAGE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"conversation_id": "1cgx:5qgvx3h:2p1gxt95d",
|
||||||
|
"message_id": "448b34fe-1806-11f1-9484-e9655a940eaf",
|
||||||
|
"status": "sent"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Place Bid
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./mrktplaats bid --urn URN --amount EUROS [--message TEXT]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Other Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Seller's other listings
|
||||||
|
./mrktplaats seller-listings --seller ID
|
||||||
|
|
||||||
|
# Similar listings
|
||||||
|
./mrktplaats similar --urn URN
|
||||||
|
|
||||||
|
# Search autocomplete
|
||||||
|
./mrktplaats suggest --prefix TEXT
|
||||||
|
|
||||||
|
# Favorites
|
||||||
|
./mrktplaats favorites
|
||||||
|
./mrktplaats add-favorite --urn URN
|
||||||
|
./mrktplaats remove-favorite --urn URN
|
||||||
|
|
||||||
|
# User info
|
||||||
|
./mrktplaats me
|
||||||
|
./mrktplaats notifications
|
||||||
|
./mrktplaats my-ads
|
||||||
|
|
||||||
|
# Sell item
|
||||||
|
./mrktplaats recognize --file PATH
|
||||||
|
./mrktplaats price-suggestion --category ID --title TEXT
|
||||||
|
./mrktplaats create-ad --category ID --title T --description D --price EUROS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Messages
|
||||||
|
|
||||||
|
| Error | Cause | Fix |
|
||||||
|
|-------|-------|-----|
|
||||||
|
| `401 Unauthorized` | Token expired/missing | Run login |
|
||||||
|
| `403 VERIFICATION_NOT_FOUND` | Too many wrong 2FA codes | Wait hours |
|
||||||
|
| `403 VERIFICATION_REQUESTS_BLOCKED` | 2FA blocked | Wait hours |
|
||||||
|
| `dial tcp: lookup app.marktplaats.nl: server misbehaving` | DNS/network | Retry later |
|
||||||
|
| `No results found` | Search terms not matching | Try broader terms |
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Finding Deals
|
||||||
|
1. Search: `./mrktplaats search --query "DDR4 sodimm" --size 50 --sort PRICE_ASC`
|
||||||
|
2. Parse results (max 100)
|
||||||
|
3. Fetch details for promising URNs: `./mrktplaats listing --urn URN`
|
||||||
|
4. Calculate price-per-GB for memory
|
||||||
|
5. Check seller reviews
|
||||||
|
|
||||||
|
### Checking Messages
|
||||||
|
1. `./mrktplaats conversations --limit 10` → shows unread count
|
||||||
|
2. If unread > 0, fetch: `./mrktplaats messages --conv CONV_ID --limit 10`
|
||||||
|
|
||||||
|
### Contacting Seller
|
||||||
|
1. Use `./mrktplaats send --urn URN --text "..."` for new listings
|
||||||
|
2. Use `./mrktplaats reply --conv CONV_ID --text "..."` for existing threads
|
||||||
|
|
||||||
|
### Sending to Discord
|
||||||
|
```bash
|
||||||
|
curl -X POST "https://discord.com/api/webhooks/1478131681033588859/xjPSaVsePMWrmXI1jGtvnvAaQ2L4jeBVZt9KzXY2LAH3KmHATsLIkqsgjzcMa31oYcnY" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"content": "Samsung S22 - €160: https://link.marktplaats.nl/m2375361894"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## IDENTITY.md Reminder
|
||||||
|
|
||||||
|
When writing messages to sellers:
|
||||||
|
- Use casual Dutch (Flemish)
|
||||||
|
- Keep it short (1-2 sentences)
|
||||||
|
- NO emojis, NO em-dashes, NO IDs/URNs
|
||||||
|
- Sign off with "Groeten, Jonathan"
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
Hoi, is die Samsung S22 nog beschikbaar? Groeten, Jonathan
|
||||||
|
```
|
||||||
7
go.mod
Normal file
7
go.mod
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
module github.com/joren/mrktplaats-cli
|
||||||
|
|
||||||
|
go 1.25.0
|
||||||
|
|
||||||
|
require github.com/joren/mrktplaats v0.0.0
|
||||||
|
|
||||||
|
replace github.com/joren/mrktplaats => ../mrktplaats
|
||||||
BIN
mrktplaats-cli
Executable file
BIN
mrktplaats-cli
Executable file
Binary file not shown.
111
scripts/poll_nvidia_reply.py
Executable file
111
scripts/poll_nvidia_reply.py
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Long-running poller for a conversation on the mrktplaats-cli.
|
||||||
|
|
||||||
|
Usage: python3 scripts/poll_nvidia_reply.py --conv CONV_ID --user USER_ID [--interval 900]
|
||||||
|
|
||||||
|
The script runs in a loop, calling `go run main.go messages --conv CONV_ID --limit 50`
|
||||||
|
in the repo directory, parses the JSON output and exits when it finds any message
|
||||||
|
whose `sender_id` is not the provided USER_ID (i.e. a seller reply).
|
||||||
|
|
||||||
|
When a reply is found the script writes a JSON file with the matching messages to
|
||||||
|
/home/joren/.local/share/opencode/tool-output/nvidia_reply_found.json and exits with
|
||||||
|
code 0. It logs progress to stdout/stderr so you can follow the `nohup` log file.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def now():
|
||||||
|
return datetime.utcnow().isoformat() + "Z"
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_messages(conv, workdir):
|
||||||
|
# Run the mrktplaats-cli to fetch the conversation messages
|
||||||
|
cmd = ["go", "run", "main.go", "messages", "--conv", conv, "--limit", "50"]
|
||||||
|
proc = subprocess.run(cmd, cwd=workdir, capture_output=True, text=True)
|
||||||
|
if proc.returncode != 0:
|
||||||
|
raise RuntimeError(f"messages command failed: {proc.stderr.strip()}")
|
||||||
|
return proc.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def find_foreign_messages(json_text, user_id):
|
||||||
|
data = json.loads(json_text)
|
||||||
|
msgs = data.get("messages") or []
|
||||||
|
foreign = [m for m in msgs if int(m.get("sender_id", 0)) != int(user_id)]
|
||||||
|
return foreign
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--conv", required=True, help="Conversation id to poll")
|
||||||
|
parser.add_argument("--user", required=True, help="Our user id (numeric)")
|
||||||
|
parser.add_argument(
|
||||||
|
"--interval",
|
||||||
|
type=int,
|
||||||
|
default=900,
|
||||||
|
help="Poll interval in seconds (default 900 = 15m)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--workdir",
|
||||||
|
default="/home/joren/dev/marktplaatsApi/mrktplaats-cli",
|
||||||
|
help="Path to mrktplaats-cli repo",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
out_dir = os.path.expanduser("/home/joren/.local/share/opencode/tool-output")
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
found_path = os.path.join(out_dir, "nvidia_reply_found.json")
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"{now()} Poller started for conv={args.conv}, user={args.user}, interval={args.interval}s"
|
||||||
|
)
|
||||||
|
# write pid file
|
||||||
|
pid_path = os.path.join(out_dir, "nvidia_poll.pid")
|
||||||
|
with open(pid_path, "w") as f:
|
||||||
|
f.write(str(os.getpid()))
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
raw = fetch_messages(args.conv, args.workdir)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{now()} Error fetching messages: {e}")
|
||||||
|
time.sleep(30)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
foreign = find_foreign_messages(raw, args.user)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{now()} Error parsing messages JSON: {e}")
|
||||||
|
time.sleep(30)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if foreign:
|
||||||
|
result = {"conv": args.conv, "detected_at": now(), "messages": foreign}
|
||||||
|
with open(found_path, "w") as f:
|
||||||
|
json.dump(result, f, indent=2)
|
||||||
|
print(
|
||||||
|
f"{now()} Found {len(foreign)} foreign message(s) — wrote {found_path}"
|
||||||
|
)
|
||||||
|
# also print the messages for immediate visibility
|
||||||
|
print(json.dumps(result, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print(f"{now()} No reply yet; sleeping {args.interval} seconds")
|
||||||
|
time.sleep(args.interval)
|
||||||
|
finally:
|
||||||
|
# cleanup pid file
|
||||||
|
try:
|
||||||
|
os.remove(pid_path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user