Add temp-SMS tool (SMSPool-backed temp-number marketplace) to Pleiades

- sms.py: NumberProvider abstraction + SMSPoolProvider + routes (/sms, rent, poll, release) + admin (/admin/sms)
- Reuses existing sats wallet + BTCPay deposit flow; charges per-rental against balance_sats
- Code detection, lazy expiry/release, per-user session limit, per-IP rate limit
- Verified end-to-end: rented Discord US +1 534 251 1825, wallet 5000->3500 sats, release refunded SMSPool to $7.45
This commit is contained in:
drjones
2026-09-20 19:34:25 -07:00
parent 74268306d0
commit 48305b4ad1
6 changed files with 789 additions and 0 deletions

47
templates/sms_admin.html Normal file
View File

@@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block title %}SMS Admin — Clean Proxys{% endblock %}
{% block body %}
<div style="max-width:900px;margin:0 auto;padding:24px 0">
<h1 style="font-size:1.9rem;letter-spacing:-.5px">SMS <b style="color:#0ff">Admin</b></h1>
<p style="color:#94a3b8;margin:6px 0 16px">SMSPool balance: <b style="color:#0ff">{{ smspool_balance if smspool_balance is not none else 'unavailable' }} USD</b></p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:20px">
<div style="background:rgba(20,28,40,.6);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px"><div style="font-size:.7rem;color:#94a3b8;letter-spacing:.1em">ACTIVE</div><div style="font-size:1.6rem;color:#fff">{{ stats.active }}</div></div>
<div style="background:rgba(20,28,40,.6);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px"><div style="font-size:.7rem;color:#94a3b8;letter-spacing:.1em">SESSIONS</div><div style="font-size:1.6rem;color:#fff">{{ stats.total_sessions }}</div></div>
<div style="background:rgba(20,28,40,.6);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px"><div style="font-size:.7rem;color:#94a3b8;letter-spacing:.1em">SMS RECEIVED</div><div style="font-size:1.6rem;color:#fff">{{ stats.sms_received }}</div></div>
<div style="background:rgba(20,28,40,.6);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px"><div style="font-size:.7rem;color:#94a3b8;letter-spacing:.1em">REVENUE</div><div style="font-size:1.6rem;color:#4ade80">{{ stats.revenue_sats }} sats</div></div>
</div>
<h2 style="font-size:1.2rem;margin:20px 0 10px">Pricing (sats)</h2>
<form method="POST" action="/admin/sms" style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;margin-bottom:24px">
{% for p in pricing %}
<div>
<label style="display:block;font-size:.72rem;color:#94a3b8">{{ p.duration_min }} min</label>
<input name="price_{{ p.duration_min }}" value="{{ p.price_sats }}" style="width:90px;background:rgba(0,0,0,.4);border:1px solid rgba(255,255,255,.15);color:#fff;padding:8px;border-radius:8px;font-family:monospace">
</div>
{% endfor %}
<button type="submit" style="background:linear-gradient(135deg,#0ff,#0af);color:#00141a;border:none;padding:9px 16px;border-radius:8px;font-weight:700;cursor:pointer">Save prices</button>
</form>
<h2 style="font-size:1.2rem;margin:16px 0 10px">Recent sessions</h2>
<table style="width:100%;border-collapse:collapse;font-size:.82rem">
<thead><tr style="text-align:left;color:#94a3b8">
<th style="padding:6px">ID</th><th>User</th><th>Number</th><th>Service</th><th>Status</th><th>Price</th><th>Cost</th><th>Created</th>
</tr></thead>
<tbody>
{% for s in sessions %}
<tr style="border-top:1px solid rgba(255,255,255,.06)">
<td style="padding:6px">{{ s.id }}</td>
<td>{{ s.username }}</td>
<td style="font-family:monospace">{{ s.number }}</td>
<td>{{ s.service_name }}</td>
<td><span style="color:{{ '#4ade80' if s.status == 'sms_received' else '#0ff' if s.status == 'active' else '#94a3b8' }}">{{ s.status }}</span></td>
<td>{{ s.price_sats }}</td>
<td>${{ s.provider_cost }}</td>
<td style="color:#94a3b8">{{ s.created_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}