Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions revault-reseller-watchdog/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
.vercel
79 changes: 79 additions & 0 deletions revault-reseller-watchdog/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Revault - The Autonomous Watchdog for Resellers

A 24/7 price watchdog powered by [TinyFish](https://tinyfish.ai) browser agents. Add items you want to flip, set your target margin and max buy price, and ResaleRadar monitors reseller platforms every 5 minutes — alerting you on Discord the moment a deal hits your target.

Built for the [TinyFish Take-Home Challenge] *"The Autonomous Watchdog"*

## How It Works

1. **Add an item** to your Watchdog List (e.g., "Louis Vuitton Neverfull MM")
2. **Set your target** — max buy price + target margin %
3. **TinyFish agents** scrape reseller platforms every 15 minutes
4. **Discord alert** fires instantly when a listing meets your criteria and then after every 15 minutes.

## Categories Supported

| Category | Platforms Scraped |
|----------|-------------------|
| Watches | Chrono24, WatchBox, Bob's Watches, eBay |
| Bags | Fashionphile, Vestiaire, Rebag, GoogleShopping |
| Trading Cards | TCGPlayer, eBay, StockX, PWCC |
| Sports & Fan Gear | Fanatics, eBay Sports, PWCC, Goldin, StockX, SidelineSwap |
| Sneakers | StockX, GOAT, eBay, Alias |

## Setup — Run Your Own Instance

### 1. Clone and install

```bash
git clone <your-repo-url>
cd revault-reseller-watchdog
npm install
```

### 2. Get your API keys

- **TinyFish API Key**: Sign up at [tinyfish.ai](https://tinyfish.ai), go to Dashboard → Settings → API Keys
- **Discord Webhook URL**: In your Discord server, go to Server Settings → Integrations → Webhooks → New Webhook → Copy URL

### 3. Configure

Create a `.env` file in the project root:

```env
VITE_TINYFISH_API_KEY=sk-tinyfish-your-key-here
VITE_DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/your-webhook-here
```

Or edit `src/config.js` directly with your keys.

### 4. Run locally

```bash
npm run dev
```

### 5. Deploy


Set the environment variables in Vercel Dashboard → Project Settings → Environment Variables.

## Architecture

```
User adds item → TinyFish browser agents scrape reseller sites
Extract: price, condition, listing URL
Compare to user's max buy price + target margin
If target hit → Discord webhook alert
If not → update watchlist, check again in 5 min
```

## Tech Stack

- **Frontend**: React + Vite
- **Scraping**: TinyFish Web Agent API (handles anti-bot, JS-heavy sites)
- **Notifications**: Discord Webhooks
- **Hosting**: Vercel
30 changes: 30 additions & 0 deletions revault-reseller-watchdog/api/discord.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Vercel serverless function to proxy Discord webhook calls
// (Discord webhooks block browser CORS)
export default async function handler(req, res) {
if (req.method !== "POST") {
return res.status(405).json({ error: "Method not allowed" });
}

const webhookUrl = process.env.DISCORD_WEBHOOK_URL || process.env.VITE_DISCORD_WEBHOOK_URL;

if (!webhookUrl) {
return res.status(500).json({ error: "DISCORD_WEBHOOK_URL not configured" });
}

try {
const response = await fetch(webhookUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req.body),
});

if (!response.ok) {
const text = await response.text();
return res.status(response.status).json({ error: text });
}

return res.status(200).json({ ok: true });
} catch (err) {
return res.status(500).json({ error: err.message });
}
}
29 changes: 29 additions & 0 deletions revault-reseller-watchdog/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
extends: [
js.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaFeatures: { jsx: true },
sourceType: 'module',
},
},
rules: {
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
},
},
])
13 changes: 13 additions & 0 deletions revault-reseller-watchdog/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Revault</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading