437 lines
12 KiB
Python
437 lines
12 KiB
Python
"""
|
||
Discord资源搜索机器人 - 独立数据库版
|
||
功能:全局资源搜索、群管理、资源统计、独立数据库
|
||
"""
|
||
import os
|
||
import sys
|
||
import sqlite3
|
||
import discord
|
||
from discord.ext import commands
|
||
from dotenv import load_dotenv
|
||
|
||
# 加载配置
|
||
load_dotenv()
|
||
DISCORD_TOKEN = os.getenv("DISCORD_TOKEN", "")
|
||
|
||
# 独立数据库路径(Discord机器人自己的数据库)
|
||
BOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
DB_PATH = os.path.join(BOT_DIR, "discord_resources.db")
|
||
|
||
# 每页显示数量
|
||
PAGE_SIZE = 8
|
||
|
||
|
||
# ========== 数据库初始化 ==========
|
||
def init_db():
|
||
"""初始化独立数据库"""
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
|
||
# 资源表
|
||
c.execute("""
|
||
CREATE TABLE IF NOT EXISTS resources (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
title TEXT NOT NULL,
|
||
tg_link TEXT DEFAULT '',
|
||
source TEXT DEFAULT '',
|
||
keyword TEXT DEFAULT '',
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
""")
|
||
|
||
# 搜索记录表
|
||
c.execute("""
|
||
CREATE TABLE IF NOT EXISTS search_log (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
user_id INTEGER,
|
||
keyword TEXT,
|
||
result_count INTEGER,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
""")
|
||
|
||
# 群配置表
|
||
c.execute("""
|
||
CREATE TABLE IF NOT EXISTS group_config (
|
||
guild_id INTEGER PRIMARY KEY,
|
||
guild_name TEXT,
|
||
welcome_enabled INTEGER DEFAULT 1,
|
||
search_enabled INTEGER DEFAULT 1,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
)
|
||
""")
|
||
|
||
conn.commit()
|
||
conn.close()
|
||
print(f"✅ 数据库初始化: {DB_PATH}")
|
||
|
||
|
||
# ========== 资源搜索功能 ==========
|
||
def search_resources(keyword, limit=PAGE_SIZE, offset=0):
|
||
"""从独立数据库搜索资源"""
|
||
if not os.path.exists(DB_PATH):
|
||
init_db()
|
||
|
||
conn = sqlite3.connect(DB_PATH)
|
||
conn.row_factory = sqlite3.Row
|
||
c = conn.cursor()
|
||
|
||
query = """
|
||
SELECT id, title, tg_link, source, keyword, created_at
|
||
FROM resources
|
||
WHERE title LIKE ? OR keyword LIKE ? OR source LIKE ?
|
||
ORDER BY (tg_link != '') DESC, created_at DESC
|
||
LIMIT ? OFFSET ?
|
||
"""
|
||
like_keyword = f"%{keyword}%"
|
||
c.execute(query, (like_keyword, like_keyword, like_keyword, limit, offset))
|
||
results = [dict(row) for row in c.fetchall()]
|
||
|
||
c.execute("""
|
||
SELECT COUNT(*) FROM resources
|
||
WHERE title LIKE ? OR keyword LIKE ? OR source LIKE ?
|
||
""", (like_keyword, like_keyword, like_keyword))
|
||
total = c.fetchone()[0]
|
||
|
||
conn.close()
|
||
return results, total
|
||
|
||
|
||
def add_resource(title, tg_link="", source="", keyword=""):
|
||
"""添加资源到数据库"""
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
c.execute("""
|
||
INSERT OR IGNORE INTO resources (title, tg_link, source, keyword)
|
||
VALUES (?, ?, ?, ?)
|
||
""", (title, tg_link, source, keyword))
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
|
||
def get_stats():
|
||
"""获取数据库统计"""
|
||
if not os.path.exists(DB_PATH):
|
||
init_db()
|
||
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
|
||
c.execute("SELECT COUNT(*) FROM resources")
|
||
total = c.fetchone()[0]
|
||
|
||
c.execute("SELECT COUNT(*) FROM resources WHERE tg_link != ''")
|
||
with_link = c.fetchone()[0]
|
||
|
||
c.execute("SELECT COUNT(*) FROM search_log")
|
||
search_count = c.fetchone()[0]
|
||
|
||
conn.close()
|
||
return {"total": total, "with_link": with_link, "search_count": search_count}
|
||
|
||
|
||
def log_search(user_id, keyword, result_count):
|
||
"""记录搜索日志"""
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
c.execute("""
|
||
INSERT INTO search_log (user_id, keyword, result_count)
|
||
VALUES (?, ?, ?)
|
||
""", (user_id, keyword, result_count))
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
|
||
# ========== 格式化输出 ==========
|
||
def format_results(results, keyword, total, page=0):
|
||
"""格式化搜索结果"""
|
||
lines = []
|
||
lines.append(f"🔍 搜索「{keyword}」共找到 {total} 条结果")
|
||
lines.append(f"📄 第 {page+1} 页 / 共 {(total + PAGE_SIZE - 1) // PAGE_SIZE} 页")
|
||
lines.append("")
|
||
|
||
for i, res in enumerate(results, 1):
|
||
idx = page * PAGE_SIZE + i
|
||
title = res.get("title", "")[:80]
|
||
tg_link = res.get("tg_link", "")
|
||
source = res.get("source", "")
|
||
|
||
link_icon = "📎" if tg_link else "🔍"
|
||
lines.append(f"{idx}. {link_icon} {title}")
|
||
|
||
if tg_link:
|
||
lines.append(f" 🔗 {tg_link}")
|
||
|
||
if source:
|
||
lines.append(f" 📡 来源: {source}")
|
||
|
||
lines.append("")
|
||
|
||
if total == 0:
|
||
lines = [f"❌ 未找到与「{keyword}」相关的资源", "", "💡 试试其他关键词"]
|
||
|
||
return "\n".join(lines)
|
||
|
||
|
||
# ========== Discord机器人配置 ==========
|
||
intents = discord.Intents.default()
|
||
intents.message_content = True
|
||
intents.members = True
|
||
|
||
bot = commands.Bot(command_prefix='!', intents=intents, help_command=None)
|
||
|
||
|
||
# ========== 事件处理 ==========
|
||
@bot.event
|
||
async def on_ready():
|
||
"""机器人启动完成"""
|
||
init_db()
|
||
print(f'✅ Discord机器人已登录: {bot.user}')
|
||
print(f'📊 已连接 {len(bot.guilds)} 个服务器')
|
||
print(f'💾 数据库: {DB_PATH}')
|
||
await bot.change_presence(activity=discord.Game(name="资源搜索 | 发关键词自动搜索"))
|
||
|
||
|
||
@bot.event
|
||
async def on_guild_join(guild):
|
||
"""加入新服务器"""
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
c.execute("""
|
||
INSERT OR IGNORE INTO group_config (guild_id, guild_name)
|
||
VALUES (?, ?)
|
||
""", (guild.id, guild.name))
|
||
conn.commit()
|
||
conn.close()
|
||
print(f"➕ 加入新服务器: {guild.name} ({guild.id})")
|
||
|
||
|
||
@bot.event
|
||
async def on_member_join(member):
|
||
"""新成员加入欢迎"""
|
||
# 检查是否启用欢迎
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
c.execute("SELECT welcome_enabled FROM group_config WHERE guild_id = ?", (member.guild.id,))
|
||
result = c.fetchone()
|
||
conn.close()
|
||
|
||
if result and result[0] == 0:
|
||
return
|
||
|
||
# 查找欢迎频道
|
||
welcome_channel = None
|
||
for channel in member.guild.text_channels:
|
||
if 'welcome' in channel.name.lower() or '欢迎' in channel.name or 'general' in channel.name.lower():
|
||
welcome_channel = channel
|
||
break
|
||
|
||
if not welcome_channel and member.guild.text_channels:
|
||
welcome_channel = member.guild.text_channels[0]
|
||
|
||
if welcome_channel:
|
||
welcome_msg = f"""
|
||
👋 欢迎 **{member.mention}** 加入 **{member.guild.name}**!
|
||
|
||
📋 本机器人功能:
|
||
• 直接发送关键词即可搜索资源
|
||
• `!search 关键词` - 搜索资源
|
||
• `!stats` - 查看资源库统计
|
||
• `!help` - 查看帮助
|
||
|
||
💡 试试发送:电影、音乐、软件、游戏
|
||
"""
|
||
await welcome_channel.send(welcome_msg)
|
||
|
||
|
||
# ========== 命令处理 ==========
|
||
@bot.command(name='help')
|
||
async def cmd_help(ctx):
|
||
"""帮助命令"""
|
||
help_text = """
|
||
📋 **Discord资源搜索机器人帮助**
|
||
|
||
**🔍 资源搜索:**
|
||
• 直接发送关键词 - 自动搜索资源库
|
||
• `!search 关键词` - 搜索资源
|
||
• `!stats` - 查看资源库统计
|
||
|
||
**⚙️ 群管理:**
|
||
• `!ping` - 测试机器人状态
|
||
• `!serverinfo` - 查看服务器信息
|
||
• `!welcome on/off` - 开启/关闭欢迎消息
|
||
|
||
**💡 使用示例:**
|
||
• 发送 `电影` - 搜索电影相关资源
|
||
• 发送 `无损音乐` - 搜索无损音乐
|
||
• 发送 `Adobe` - 搜索Adobe软件
|
||
|
||
**📊 数据说明:**
|
||
本机器人使用独立数据库,资源持续更新中...
|
||
"""
|
||
await ctx.send(help_text)
|
||
|
||
|
||
@bot.command(name='search')
|
||
async def cmd_search(ctx, *, keyword: str = None):
|
||
"""搜索资源"""
|
||
if not keyword:
|
||
await ctx.send("❌ 请输入搜索关键词\n示例:`!search 电影`")
|
||
return
|
||
|
||
await do_search(ctx, keyword)
|
||
|
||
|
||
@bot.command(name='stats')
|
||
async def cmd_stats(ctx):
|
||
"""查看资源库统计"""
|
||
stats = get_stats()
|
||
|
||
total = stats['total']
|
||
with_link = stats['with_link']
|
||
search_count = stats['search_count']
|
||
link_percent = (with_link / total * 100) if total > 0 else 0
|
||
|
||
stats_text = f"""
|
||
📊 **资源库统计**
|
||
|
||
• 📦 总资源数:**{total}** 条
|
||
• 🔗 带跳转链接:**{with_link}** 条
|
||
• 📈 链接占比:**{link_percent:.1f}%**
|
||
• 🔍 总搜索次数:**{search_count}** 次
|
||
|
||
💡 发送关键词即可搜索资源
|
||
"""
|
||
await ctx.send(stats_text)
|
||
|
||
|
||
@bot.command(name='ping')
|
||
async def cmd_ping(ctx):
|
||
"""测试机器人状态"""
|
||
latency = bot.latency * 1000
|
||
await ctx.send(f"🏓 Pong! 延迟: {latency:.0f}ms")
|
||
|
||
|
||
@bot.command(name='serverinfo')
|
||
async def cmd_serverinfo(ctx):
|
||
"""查看服务器信息"""
|
||
guild = ctx.guild
|
||
info = f"""
|
||
🏰 **服务器信息**
|
||
|
||
• 📛 名称:{guild.name}
|
||
• 👥 成员数:{guild.member_count}
|
||
• 📅 创建时间:{guild.created_at.strftime('%Y-%m-%d')}
|
||
• 💬 频道数:{len(guild.text_channels)}
|
||
• 🔊 语音频道数:{len(guild.voice_channels)}
|
||
"""
|
||
await ctx.send(info)
|
||
|
||
|
||
@bot.command(name='welcome')
|
||
async def cmd_welcome(ctx, action: str = None):
|
||
"""开启/关闭欢迎消息"""
|
||
if not action or action.lower() not in ['on', 'off']:
|
||
await ctx.send("❌ 用法:`!welcome on` 或 `!welcome off`")
|
||
return
|
||
|
||
enabled = 1 if action.lower() == 'on' else 0
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
c.execute("""
|
||
INSERT OR REPLACE INTO group_config (guild_id, guild_name, welcome_enabled)
|
||
VALUES (?, ?, ?)
|
||
""", (ctx.guild.id, ctx.guild.name, enabled))
|
||
conn.commit()
|
||
conn.close()
|
||
|
||
status = "✅ 已开启" if enabled else "❌ 已关闭"
|
||
await ctx.send(f"{status} 欢迎消息")
|
||
|
||
|
||
# ========== 核心搜索功能 ==========
|
||
async def do_search(ctx, keyword, page=0):
|
||
"""执行搜索并发送结果"""
|
||
# 显示"正在搜索"
|
||
search_msg = await ctx.send(f"🔍 正在搜索「{keyword}」...")
|
||
|
||
try:
|
||
# 搜索资源
|
||
results, total = search_resources(keyword, limit=PAGE_SIZE, offset=page * PAGE_SIZE)
|
||
|
||
# 记录搜索日志
|
||
log_search(ctx.author.id, keyword, total)
|
||
|
||
# 格式化结果
|
||
reply = format_results(results, keyword, total, page=page)
|
||
|
||
# 编辑消息
|
||
await search_msg.edit(content=reply)
|
||
|
||
# 分页反应
|
||
if total > PAGE_SIZE:
|
||
await search_msg.add_reaction("⬅️")
|
||
await search_msg.add_reaction("➡️")
|
||
|
||
except Exception as e:
|
||
await search_msg.edit(content=f"❌ 搜索失败:{str(e)}")
|
||
|
||
|
||
# ========== 全局消息监听 ==========
|
||
@bot.event
|
||
async def on_message(message):
|
||
"""处理所有消息 - 全局资源搜索"""
|
||
# 忽略机器人自己的消息
|
||
if message.author == bot.user:
|
||
return
|
||
|
||
# 处理命令
|
||
await bot.process_commands(message)
|
||
|
||
# 如果是命令,不触发全局搜索
|
||
if message.content.startswith('!'):
|
||
return
|
||
|
||
# 忽略太短的消息
|
||
if len(message.content.strip()) < 2:
|
||
return
|
||
|
||
# 忽略包含URL的消息
|
||
if 'http://' in message.content or 'https://' in message.content:
|
||
return
|
||
|
||
# 只在服务器频道触发全局搜索
|
||
if message.guild is None:
|
||
return
|
||
|
||
# 检查是否启用搜索
|
||
conn = sqlite3.connect(DB_PATH)
|
||
c = conn.cursor()
|
||
c.execute("SELECT search_enabled FROM group_config WHERE guild_id = ?", (message.guild.id,))
|
||
result = c.fetchone()
|
||
conn.close()
|
||
|
||
if result and result[0] == 0:
|
||
return
|
||
|
||
# 全局资源搜索
|
||
keyword = message.content.strip()
|
||
await do_search(message.channel, keyword)
|
||
|
||
|
||
# ========== 主函数 ==========
|
||
def main():
|
||
"""主函数"""
|
||
if not DISCORD_TOKEN:
|
||
print("❌ 错误:未配置DISCORD_TOKEN")
|
||
print("请在.env文件中设置DISCORD_TOKEN")
|
||
print("获取地址:https://discord.com/developers/applications")
|
||
return
|
||
|
||
print("🚀 正在启动Discord机器人...")
|
||
bot.run(DISCORD_TOKEN)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|