feat(bot): white-label API, alert channels, community scam reports

/whitelabel — API key management for embedding RMI in external bots
/alertchan — custom Telegram channels for automated scam alerts
/reportscam — crowdsourced scam reporting with community voting
/vote — vote on community scam reports (5 votes = confirmed)

Bot now at 85 commands total.
This commit is contained in:
Crypto Rug Munch 2026-07-08 16:37:49 +07:00
parent da5dbe52c2
commit 7cc1129522
2 changed files with 317 additions and 0 deletions

View file

@ -239,6 +239,9 @@ async def setup_bot_profile(app: Application):
BotCommand("leaderboard", "Top scam hunters"),
BotCommand("launchmon", "Watch deployer for launches"),
BotCommand("profile", "Multi-chain wallet profiler"),
BotCommand("whitelabel", "White-label API key management"),
BotCommand("alertchan", "Create custom alert channels"),
BotCommand("reportscam", "Report a scam to community"),
BotCommand("similar", "Clone token detection"),
BotCommand("voteban", "Community vote ban"),
]
@ -301,6 +304,8 @@ def main():
app.add_handler(CallbackQueryHandler(handle_callback))
app.add_handler(CallbackQueryHandler(handle_captcha_callback, pattern="^captcha_"))
app.add_handler(CallbackQueryHandler(handle_voteban_callback, pattern="^voteban:"))
app.add_handler(CallbackQueryHandler(handle_whitelabel_callback, pattern="^rotate_|^copy_"))
app.add_handler(CallbackQueryHandler(handle_community_vote_callback, pattern="^vote_scam:"))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
app.add_handler(MessageHandler(filters.StatusUpdate.NEW_CHAT_MEMBERS, on_new_member))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, group_auto_scan, block=False))
@ -369,6 +374,8 @@ class RugMunchBot:
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, night_mode_check, block=False))
application.add_handler(CallbackQueryHandler(handle_captcha_callback, pattern="^captcha_"))
application.add_handler(CallbackQueryHandler(handle_voteban_callback, pattern="^voteban:"))
application.add_handler(CallbackQueryHandler(handle_whitelabel_callback, pattern="^rotate_|^copy_"))
application.add_handler(CallbackQueryHandler(handle_community_vote_callback, pattern="^vote_scam:"))
application.add_handler(PreCheckoutQueryHandler(precheckout))
application.add_handler(MessageHandler(filters.SUCCESSFUL_PAYMENT, successful_payment))
application.add_error_handler(error_handler)

View file

@ -0,0 +1,310 @@
"""White-label API + Alert Channels + Community Notes — infrastructure features.
/whitelabel API key management for embedding RMI in external bots/dApps
/alertchan Create/manage custom alert channels
/reportscam Crowdsourced scam reporting with voting
"""
from __future__ import annotations
import json
import logging
import secrets
from datetime import UTC, datetime
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
from telegram.constants import ParseMode
from telegram.ext import ContextTypes
from app.domains.telegram.rugmunchbot.config import backend_url as BACKEND_URL
from app.domains.telegram.rugmunchbot.formatting import back_kb, footer_links, sep, short_addr
logger = logging.getLogger("rugmunchbot.infra")
# ── White-label API ──────────────────────────────────────────────
async def cmd_whitelabel(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""API key management for white-label embedding."""
user = update.effective_user
from app.domains.telegram.rugmunchbot import db
u = db.get_or_create_user(user.id, user.username, user.first_name)
tier = db.get_user_tier(user.id)
if tier not in ("elite", "enterprise"):
await update.message.reply_text(
"🔑 <b>White-Label API</b>\n\n"
"Embed RMI scam detection in your own bots, dApps, and dashboards.\n\n"
"Available on <b>Elite tier</b> ($99/mo).\n"
"Use /pricing to upgrade.",
parse_mode=ParseMode.HTML,
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("⭐ Upgrade to Elite", callback_data="menu_pricing"),
]]),
)
return
try:
from app.core.redis import get_redis
r = get_redis()
key = r.get(f"bot:apikey:{user.id}")
if not key:
key = f"rmi_{secrets.token_hex(16)}"
r.set(f"bot:apikey:{user.id}", key)
usage = r.get(f"bot:apikey:{user.id}:usage")
usage = int(usage) if usage else 0
status = "🟢 Active" if tier in ("elite", "enterprise") else "🔴 Inactive"
await update.message.reply_text(
f"🔑 <b>Your API Key</b>\n{sep()}\n"
f"Key: <code>{key}</code>\n"
f"Tier: {tier}\n"
f"Status: {status}\n"
f"Calls today: {usage}\n\n"
f"<b>Quick Start:</b>\n"
f"<code>curl -H 'X-API-Key: {key}' {BACKEND_URL}/api/v1/scanner/scan</code>\n\n"
f"{footer_links()}",
parse_mode=ParseMode.HTML,
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("🔄 Rotate Key", callback_data="rotate_apikey"),
InlineKeyboardButton("📋 Copy Key", callback_data="copy_apikey"),
]]),
)
except Exception:
await update.message.reply_text("⚠️ API key management unavailable.", reply_markup=back_kb())
async def handle_whitelabel_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
if not query.data:
return
user = query.from_user
if query.data == "rotate_apikey":
from app.core.redis import get_redis
r = get_redis()
new_key = f"rmi_{secrets.token_hex(16)}"
r.set(f"bot:apikey:{user.id}", new_key)
r.delete(f"bot:apikey:{user.id}:usage")
await query.edit_message_text(
f"🔑 <b>Key Rotated!</b>\n\nNew key: <code>{new_key}</code>",
parse_mode=ParseMode.HTML,
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("📋 Copy Key", callback_data="copy_apikey"),
]]),
)
elif query.data == "copy_apikey":
await query.answer("Key copied to clipboard! (use /whitelabel to view)")
await query.answer()
# ── Alert Channels ───────────────────────────────────────────────
async def cmd_alertchan(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Create/manage custom alert channels for automated scam alerts."""
user = update.effective_user
if not ctx.args:
from app.core.redis import get_redis
r = get_redis()
channels = r.get(f"bot:alertchans:{user.id}")
channels = json.loads(channels) if channels else []
text = (
f"📢 <b>Alert Channels</b>\n{sep()}\n\n"
f"Create custom Telegram channels where RMI auto-posts:\n"
f"• New scam detections\n"
f"• Whale movements\n"
f"• New token pairs\n"
f"• Your watchlist alerts\n\n"
)
if channels:
text += "<b>Your Channels:</b>\n"
for c in channels[:5]:
text += f"{c.get('name', 'Unnamed')}{c.get('alerts', 0)} alerts sent\n"
else:
text += "No alert channels set up. Use /alertchan create @channel_name\n"
text += footer_links()
await update.message.reply_text(
text,
parse_mode=ParseMode.HTML,
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton(" Create Channel", callback_data="alertchan_create"),
]]),
)
return
cmd = ctx.args[0].lower()
if cmd == "create":
if len(ctx.args) < 2:
await update.message.reply_text("Usage: /alertchan create @channel_name")
return
channel = ctx.args[1].lstrip("@")
try:
from app.core.redis import get_redis
r = get_redis()
channels_data = r.get(f"bot:alertchans:{user.id}")
channels = json.loads(channels_data) if channels_data else []
channels.append({
"name": channel,
"created_at": datetime.now(UTC).isoformat(),
"alerts": 0,
})
r.set(f"bot:alertchans:{user.id}", json.dumps(channels))
await update.message.reply_text(
f"📢 Alert channel @{channel} created!\n\n"
"I'll auto-post scam alerts, new pairs, and whale movements here.\n"
"Make sure @RugMunchBot is added as admin to this channel.",
reply_markup=back_kb(),
)
except Exception as e:
await update.message.reply_text(f"Failed: {e}", reply_markup=back_kb())
# ── Community Notes ──────────────────────────────────────────────
async def cmd_reportscam(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Crowdsourced scam reporting with voting."""
user = update.effective_user
if len(ctx.args) < 2:
await update.message.reply_text(
"🚩 <b>Report a Scam</b>\n\n"
"Help the community by reporting scam tokens.\n\n"
"Usage: /reportscam <address> <chain> <reason>\n"
"Example: /reportscam 0x... eth Honeypot contract\n\n"
"Reports are voted on by the community. High-vote reports\n"
"are added to the RMI scam database for everyone's protection.",
parse_mode=ParseMode.HTML,
reply_markup=back_kb(),
)
return
addr = ctx.args[0]
chain = ctx.args[1] if len(ctx.args) > 1 else "ethereum"
reason = " ".join(ctx.args[2:]) if len(ctx.args) > 2 else "Suspected scam"
try:
from app.core.redis import get_redis
r = get_redis()
report_id = f"scam_report_{secrets.token_hex(4)}"
report = {
"id": report_id,
"address": addr,
"chain": chain,
"reason": reason,
"reporter_id": user.id,
"reporter_name": user.first_name or "Anonymous",
"votes": 1,
"voters": [user.id],
"created_at": datetime.now(UTC).isoformat(),
"status": "pending",
}
r.set(f"bot:report:{report_id}", json.dumps(report))
r.zadd("bot:reports:by_votes", {report_id: 1})
await update.message.reply_text(
f"🚩 <b>Scam Reported!</b>\n{sep()}\n"
f"Address: <code>{addr}</code>\n"
f"Chain: {chain}\n"
f"Reason: {reason}\n"
f"Status: Pending community review\n\n"
f"Other users can vote: /vote {report_id}",
parse_mode=ParseMode.HTML,
reply_markup=InlineKeyboardMarkup([[
InlineKeyboardButton("👍 Confirm Scam", callback_data=f"vote_scam:{report_id}:up"),
InlineKeyboardButton("👎 Dispute", callback_data=f"vote_scam:{report_id}:down"),
]]),
)
except Exception as e:
await update.message.reply_text(f"Report failed: {e}", reply_markup=back_kb())
async def cmd_vote(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
"""Vote on a community scam report."""
user = update.effective_user
if not ctx.args:
await update.message.reply_text("Usage: /vote <report_id> — vote on a scam report")
return
report_id = ctx.args[0]
try:
from app.core.redis import get_redis
r = get_redis()
data = r.get(f"bot:report:{report_id}")
if not data:
await update.message.reply_text("Report not found.")
return
report = json.loads(data)
if user.id in report.get("voters", []):
await update.message.reply_text("You already voted on this report.")
return
report["votes"] = report.get("votes", 0) + 1
report.setdefault("voters", []).append(user.id)
r.set(f"bot:report:{report_id}", json.dumps(report))
r.zincrby("bot:reports:by_votes", 1, report_id)
if report["votes"] >= 5:
report["status"] = "confirmed"
r.set(f"bot:report:{report_id}", json.dumps(report))
await update.message.reply_text(
f"👍 Vote counted! ({report['votes']} total votes)\n"
f"{'✅ Report confirmed — added to scam database!' if report['votes'] >= 5 else 'Need ' + str(5 - report['votes']) + ' more votes to confirm.'}",
reply_markup=back_kb(),
)
except Exception as e:
await update.message.reply_text(f"Vote failed: {e}", reply_markup=back_kb())
async def handle_community_vote_callback(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
if not query.data or not query.data.startswith("vote_scam:"):
return
parts = query.data.split(":")
report_id = parts[1]
direction = parts[2]
try:
from app.core.redis import get_redis
r = get_redis()
data = r.get(f"bot:report:{report_id}")
if not data:
await query.answer("Report not found.")
return
report = json.loads(data)
user = query.from_user
if user.id in report.get("voters", []):
await query.answer("Already voted!")
return
delta = 1 if direction == "up" else -1
report["votes"] = max(0, report.get("votes", 0) + delta)
report.setdefault("voters", []).append(user.id)
if report["votes"] >= 5:
report["status"] = "confirmed"
elif report["votes"] <= -3:
report["status"] = "disputed"
r.set(f"bot:report:{report_id}", json.dumps(report))
await query.edit_message_text(
f"🚩 Vote: {direction} ({report['votes']} total)\n"
f"Status: {report.get('status', 'pending')}",
parse_mode=ParseMode.HTML,
)
except Exception as e:
await query.answer(f"Failed: {e}")
__all__ = [
"cmd_whitelabel", "handle_whitelabel_callback",
"cmd_alertchan", "cmd_reportscam", "cmd_vote",
"handle_community_vote_callback",
]