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:
parent
da5dbe52c2
commit
7cc1129522
2 changed files with 317 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
310
app/domains/telegram/rugmunchbot/commands/infrastructure.py
Normal file
310
app/domains/telegram/rugmunchbot/commands/infrastructure.py
Normal 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",
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue