Update async io to connect the bot
Browse files
app.py
CHANGED
|
@@ -3,6 +3,7 @@ import discord
|
|
| 3 |
import time
|
| 4 |
from fastapi import FastAPI
|
| 5 |
import uvicorn
|
|
|
|
| 6 |
from threading import Thread
|
| 7 |
from discord import app_commands, ui
|
| 8 |
# 导入数据库基础类
|
|
@@ -15,28 +16,28 @@ from material import (
|
|
| 15 |
delete_material_logic,
|
| 16 |
list_materials_logic
|
| 17 |
)
|
|
|
|
| 18 |
# 初始化数据库管理器
|
| 19 |
-
# 假设你已经设置了环境变量 MONGO_URL
|
| 20 |
db_manager = MongoManager(os.getenv("MONGO_URL"), "tonyd")
|
| 21 |
|
| 22 |
# 身份组权限列表
|
| 23 |
mod = [1357078628969746462, 1357079285113819146, 1436537252317626449, 1357079707539214417, 1357079889160835183]
|
| 24 |
|
| 25 |
-
# --- 1.
|
| 26 |
app = FastAPI()
|
| 27 |
@app.get('/')
|
| 28 |
def home(): return {"Stauts": "Alive"}
|
| 29 |
|
| 30 |
def run_flask():
|
|
|
|
| 31 |
port = int(os.environ.get('PORT', 7860))
|
| 32 |
uvicorn.run(app, host='0.0.0.0', port=port)
|
| 33 |
|
| 34 |
-
# --- 2. 辅助函数
|
| 35 |
def format_pings(role_input: str):
|
| 36 |
pass
|
| 37 |
|
| 38 |
# --- 3. Modal 表单定义 ---
|
| 39 |
-
|
| 40 |
class AnnounceModal(ui.Modal):
|
| 41 |
def __init__(self, channel: discord.TextChannel, ping_roles_str: str = None):
|
| 42 |
super().__init__(title="New Announcement")
|
|
@@ -47,21 +48,15 @@ class AnnounceModal(ui.Modal):
|
|
| 47 |
announcement_body = ui.TextInput(label="Body", placeholder="Enter message...", style=discord.TextStyle.long)
|
| 48 |
|
| 49 |
async def on_submit(self, interaction: discord.Interaction):
|
| 50 |
-
# 准备正文
|
| 51 |
content = self.announcement_body.value
|
| 52 |
pings = self.ping_roles_str
|
| 53 |
-
|
| 54 |
-
# 按照你的要求:Ping 放在文本最下面,但是在 --- 上面
|
| 55 |
-
if pings:
|
| 56 |
-
pass
|
| 57 |
|
| 58 |
embed = discord.Embed(
|
| 59 |
title=self.announcement_title.value,
|
| 60 |
description=content,
|
| 61 |
color=discord.Color.blue()
|
| 62 |
)
|
| 63 |
-
|
| 64 |
-
# Footer 只留发送人和时间
|
| 65 |
footer = f"\n\n||{pings}||\n\n---\n**Sent by:** ||{interaction.user.mention}||\n**Time:** <t:{int(time.time())}:f>"
|
| 66 |
embed.description += footer
|
| 67 |
|
|
@@ -75,7 +70,6 @@ class EditAnnounceModal(ui.Modal):
|
|
| 75 |
self.new_ping_roles_str = new_ping_roles_str
|
| 76 |
|
| 77 |
old_title = message.embeds[0].title if message.embeds else ""
|
| 78 |
-
# 裁掉旧的 Footer
|
| 79 |
old_body = message.embeds[0].description.split("\n\n---")[0] if message.embeds else ""
|
| 80 |
|
| 81 |
self.edit_title = ui.TextInput(label="Title", default=old_title, style=discord.TextStyle.short)
|
|
@@ -87,21 +81,18 @@ class EditAnnounceModal(ui.Modal):
|
|
| 87 |
async def on_submit(self, interaction: discord.Interaction):
|
| 88 |
content = self.edit_body.value
|
| 89 |
pings = self.new_ping_roles_str
|
| 90 |
-
|
| 91 |
-
if pings:
|
| 92 |
-
pass
|
| 93 |
|
| 94 |
new_embed = discord.Embed(
|
| 95 |
title=self.edit_title.value,
|
| 96 |
description=content,
|
| 97 |
color=discord.Color.blue()
|
| 98 |
)
|
| 99 |
-
|
| 100 |
footer = f"\n\n||{pings}||\n\n---\n**Edited by:** ||{interaction.user.mention}||\n**Time:** <t:{int(time.time())}:f>"
|
| 101 |
new_embed.description += footer
|
| 102 |
|
| 103 |
await self.message.edit(embed=new_embed)
|
| 104 |
-
await interaction.response.send_message(f"✅ Updated announcement {message.id} !", ephemeral=False)
|
| 105 |
|
| 106 |
# --- 4. 机器人主体 ---
|
| 107 |
class MyBot(discord.Client):
|
|
@@ -114,16 +105,10 @@ class MyBot(discord.Client):
|
|
| 114 |
print(f"✅ Slash commands synced")
|
| 115 |
|
| 116 |
client = MyBot()
|
| 117 |
-
# 如果你使用的是 client.tree,请确保将 db_manager 挂载到 client 上
|
| 118 |
-
# 这样在指令内部可以通过 interaction.client.db 访问
|
| 119 |
client.db = db_manager
|
| 120 |
-
# --- 5. 指令定义 ---
|
| 121 |
|
|
|
|
| 122 |
@client.tree.command(name="announce", description="Post announcement")
|
| 123 |
-
@app_commands.describe(
|
| 124 |
-
channel="Target channel",
|
| 125 |
-
role_ids="Input Role Pings"
|
| 126 |
-
)
|
| 127 |
async def announce(interaction: discord.Interaction, channel: discord.TextChannel, role_ids: str = None):
|
| 128 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 129 |
await interaction.response.send_message("❌ **Access Denied**", ephemeral=True)
|
|
@@ -131,145 +116,108 @@ async def announce(interaction: discord.Interaction, channel: discord.TextChanne
|
|
| 131 |
await interaction.response.send_modal(AnnounceModal(channel, role_ids))
|
| 132 |
|
| 133 |
@client.tree.command(name="announce_edit", description="Edit announcement")
|
| 134 |
-
@app_commands.describe(
|
| 135 |
-
channel="The channel",
|
| 136 |
-
message_id="The ID of the message",
|
| 137 |
-
new_role_ids="New Role Pings to add to the bottom"
|
| 138 |
-
)
|
| 139 |
async def announce_edit(interaction: discord.Interaction, channel: discord.TextChannel, message_id: str, new_role_ids: str = None):
|
| 140 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 141 |
await interaction.response.send_message("❌ **Access Denied**", ephemeral=True)
|
| 142 |
return
|
| 143 |
-
|
| 144 |
try:
|
| 145 |
message = await channel.fetch_message(int(message_id))
|
| 146 |
await interaction.response.send_modal(EditAnnounceModal(message, new_role_ids))
|
| 147 |
except Exception as e:
|
| 148 |
await interaction.response.send_message(f"❌ Error: {e}", ephemeral=False)
|
| 149 |
|
| 150 |
-
@client.tree.command(name="wismer_commands", description="Show all avaliable commands
|
| 151 |
async def wismer_commands(interaction: discord.Interaction):
|
| 152 |
await interaction.response.send_message(
|
| 153 |
'''**Avaliable Commands**:
|
| 154 |
-
`/announce`: Post an announcement
|
| 155 |
-
`/announce_edit`: Edit an announcement
|
| 156 |
`/mats`: Show all materials.
|
| 157 |
`/uploadmat`: Upload a material.''',
|
| 158 |
ephemeral=False
|
| 159 |
)
|
| 160 |
|
|
|
|
| 161 |
|
| 162 |
-
|
| 163 |
-
ADMIN_LOG_CHANNEL_ID = 1468391869686878455 # Replace with your Admin/Staff channel ID
|
| 164 |
-
|
| 165 |
-
# --- 1. Upload Material ---
|
| 166 |
-
# --- 1. Upload Material ---
|
| 167 |
-
@client.tree.command(name="uploadmat", description="Upload a study link (Requires Approval)")
|
| 168 |
-
@app_commands.describe(category="Subject", title="Resource Title", url="Resource Link")
|
| 169 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 170 |
async def uploadmat(interaction: discord.Interaction, category: str, title: str, url: str):
|
| 171 |
-
# 改为 ephemeral=True,因为你的反馈消息是发给个人看的
|
| 172 |
await interaction.response.defer(ephemeral=True)
|
| 173 |
-
|
| 174 |
if not url.startswith(("http://", "https://")):
|
| 175 |
-
return await interaction.followup.send("❌ Invalid URL!
|
| 176 |
-
|
| 177 |
try:
|
| 178 |
-
# 执行逻辑
|
| 179 |
new_id = await upload_material_logic(client.db, category, title, url, interaction.user.id)
|
| 180 |
-
|
| 181 |
-
# 1. Feedback to User (保持和 defer 一致的 ephemeral)
|
| 182 |
-
await interaction.followup.send(
|
| 183 |
-
f"✅ Submitted! **{category}** ID: `#{new_id}`. Please wait for Moderator approval.",
|
| 184 |
-
ephemeral=True
|
| 185 |
-
)
|
| 186 |
-
|
| 187 |
-
# 2. Alert to Staff Channel (这是发送到频道,不涉及交互反馈)
|
| 188 |
log_channel = client.get_channel(ADMIN_LOG_CHANNEL_ID)
|
| 189 |
if log_channel:
|
| 190 |
-
embed = discord.Embed(title="🔔 New Material Pending
|
| 191 |
embed.add_field(name="Subject", value=category, inline=True)
|
| 192 |
embed.add_field(name="Index ID", value=f"`#{new_id}`", inline=True)
|
| 193 |
-
embed.add_field(name="Title", value=title, inline=False)
|
| 194 |
-
embed.add_field(name="URL", value=url, inline=False)
|
| 195 |
-
embed.add_field(name="Uploader", value=interaction.user.mention, inline=False)
|
| 196 |
-
embed.set_footer(text="Use /approvemat to authorize this link.")
|
| 197 |
await log_channel.send(embed=embed)
|
| 198 |
-
|
| 199 |
except Exception as e:
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
await interaction.followup.send(f"❌ An error occurred: {e}", ephemeral=True)
|
| 204 |
-
except:
|
| 205 |
-
pass
|
| 206 |
-
|
| 207 |
-
# --- 2. Approve Material ---
|
| 208 |
-
@client.tree.command(name="approvemat", description="[Mod Only] Approve a pending material")
|
| 209 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 210 |
async def approvemat(interaction: discord.Interaction, category: str, index_id: str):
|
| 211 |
await interaction.response.defer(ephemeral=False)
|
| 212 |
-
# Changed MOD_ROLES to mod
|
| 213 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 214 |
-
return await interaction.followup.send("❌ Access Denied
|
| 215 |
-
|
| 216 |
await approve_material_logic(client.db, category, index_id)
|
| 217 |
-
await interaction.followup.send(
|
| 218 |
-
f"✅ Approved **{category}** `#{index_id}`. It is now visible to everyone.",
|
| 219 |
-
ephemeral=False
|
| 220 |
-
)
|
| 221 |
|
| 222 |
-
|
| 223 |
-
@client.tree.command(name="deletemat", description="[Mod Only] Delete a material")
|
| 224 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 225 |
async def deletemat(interaction: discord.Interaction, category: str, index_id: str):
|
| 226 |
-
# Changed MOD_ROLES to mod
|
| 227 |
await interaction.response.defer(ephemeral=False)
|
| 228 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 229 |
-
return await interaction.followup.send("❌ Access Denied
|
| 230 |
-
|
| 231 |
await delete_material_logic(client.db, category, index_id)
|
| 232 |
-
await interaction.followup.send(
|
| 233 |
-
f"🗑️ Deleted ID `#{index_id}` from **{category}**.",
|
| 234 |
-
ephemeral=False
|
| 235 |
-
)
|
| 236 |
|
| 237 |
-
|
| 238 |
-
@client.tree.command(name="mats", description="View approved study materials")
|
| 239 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 240 |
async def mats(interaction: discord.Interaction, category: str):
|
| 241 |
await interaction.response.defer(ephemeral=False)
|
| 242 |
materials = await list_materials_logic(client.db, category, status="approved")
|
| 243 |
if not materials:
|
| 244 |
-
return await interaction.followup.send(f"📭 No
|
| 245 |
-
|
| 246 |
list_text = "\n".join([f"`#{m['_id']}` [{m['title']}]({m['url']})" for m in materials])
|
| 247 |
-
embed = discord.Embed(title=f"📚 {category}
|
| 248 |
await interaction.followup.send(embed=embed)
|
| 249 |
|
| 250 |
-
|
| 251 |
-
@client.tree.command(name="unappmats", description="[Mod Only] View pending materials")
|
| 252 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 253 |
async def unappmats(interaction: discord.Interaction, category: str):
|
| 254 |
-
# Changed MOD_ROLES to mod
|
| 255 |
await interaction.response.defer(ephemeral=False)
|
| 256 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 257 |
-
return await interaction.followup.send("❌ Access Denied
|
| 258 |
-
|
| 259 |
materials = await list_materials_logic(client.db, category, status="pending")
|
| 260 |
if not materials:
|
| 261 |
-
return await interaction.followup.send(f"✅ No pending items
|
| 262 |
-
|
| 263 |
list_text = "\n".join([f"`#{m['_id']}` **{m['title']}**\n🔗 {m['url']}" for m in materials])
|
| 264 |
-
embed = discord.Embed(title=f"⏳ {category} Pending
|
| 265 |
await interaction.followup.send(embed=embed)
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
|
| 268 |
-
|
| 269 |
-
# --- 启动 ---
|
| 270 |
if __name__ == "__main__":
|
|
|
|
| 271 |
t = Thread(target=run_flask)
|
| 272 |
t.daemon = True
|
| 273 |
t.start()
|
| 274 |
-
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
import time
|
| 4 |
from fastapi import FastAPI
|
| 5 |
import uvicorn
|
| 6 |
+
import asyncio
|
| 7 |
from threading import Thread
|
| 8 |
from discord import app_commands, ui
|
| 9 |
# 导入数据库基础类
|
|
|
|
| 16 |
delete_material_logic,
|
| 17 |
list_materials_logic
|
| 18 |
)
|
| 19 |
+
|
| 20 |
# 初始化数据库管理器
|
|
|
|
| 21 |
db_manager = MongoManager(os.getenv("MONGO_URL"), "tonyd")
|
| 22 |
|
| 23 |
# 身份组权限列表
|
| 24 |
mod = [1357078628969746462, 1357079285113819146, 1436537252317626449, 1357079707539214417, 1357079889160835183]
|
| 25 |
|
| 26 |
+
# --- 1. FastAPI 保活 ---
|
| 27 |
app = FastAPI()
|
| 28 |
@app.get('/')
|
| 29 |
def home(): return {"Stauts": "Alive"}
|
| 30 |
|
| 31 |
def run_flask():
|
| 32 |
+
# Hugging Face 默认使用 7860 端口
|
| 33 |
port = int(os.environ.get('PORT', 7860))
|
| 34 |
uvicorn.run(app, host='0.0.0.0', port=port)
|
| 35 |
|
| 36 |
+
# --- 2. 辅助函数 ---
|
| 37 |
def format_pings(role_input: str):
|
| 38 |
pass
|
| 39 |
|
| 40 |
# --- 3. Modal 表单定义 ---
|
|
|
|
| 41 |
class AnnounceModal(ui.Modal):
|
| 42 |
def __init__(self, channel: discord.TextChannel, ping_roles_str: str = None):
|
| 43 |
super().__init__(title="New Announcement")
|
|
|
|
| 48 |
announcement_body = ui.TextInput(label="Body", placeholder="Enter message...", style=discord.TextStyle.long)
|
| 49 |
|
| 50 |
async def on_submit(self, interaction: discord.Interaction):
|
|
|
|
| 51 |
content = self.announcement_body.value
|
| 52 |
pings = self.ping_roles_str
|
| 53 |
+
if pings: pass
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
embed = discord.Embed(
|
| 56 |
title=self.announcement_title.value,
|
| 57 |
description=content,
|
| 58 |
color=discord.Color.blue()
|
| 59 |
)
|
|
|
|
|
|
|
| 60 |
footer = f"\n\n||{pings}||\n\n---\n**Sent by:** ||{interaction.user.mention}||\n**Time:** <t:{int(time.time())}:f>"
|
| 61 |
embed.description += footer
|
| 62 |
|
|
|
|
| 70 |
self.new_ping_roles_str = new_ping_roles_str
|
| 71 |
|
| 72 |
old_title = message.embeds[0].title if message.embeds else ""
|
|
|
|
| 73 |
old_body = message.embeds[0].description.split("\n\n---")[0] if message.embeds else ""
|
| 74 |
|
| 75 |
self.edit_title = ui.TextInput(label="Title", default=old_title, style=discord.TextStyle.short)
|
|
|
|
| 81 |
async def on_submit(self, interaction: discord.Interaction):
|
| 82 |
content = self.edit_body.value
|
| 83 |
pings = self.new_ping_roles_str
|
| 84 |
+
if pings: pass
|
|
|
|
|
|
|
| 85 |
|
| 86 |
new_embed = discord.Embed(
|
| 87 |
title=self.edit_title.value,
|
| 88 |
description=content,
|
| 89 |
color=discord.Color.blue()
|
| 90 |
)
|
|
|
|
| 91 |
footer = f"\n\n||{pings}||\n\n---\n**Edited by:** ||{interaction.user.mention}||\n**Time:** <t:{int(time.time())}:f>"
|
| 92 |
new_embed.description += footer
|
| 93 |
|
| 94 |
await self.message.edit(embed=new_embed)
|
| 95 |
+
await interaction.response.send_message(f"✅ Updated announcement {self.message.id} !", ephemeral=False)
|
| 96 |
|
| 97 |
# --- 4. 机器人主体 ---
|
| 98 |
class MyBot(discord.Client):
|
|
|
|
| 105 |
print(f"✅ Slash commands synced")
|
| 106 |
|
| 107 |
client = MyBot()
|
|
|
|
|
|
|
| 108 |
client.db = db_manager
|
|
|
|
| 109 |
|
| 110 |
+
# --- 5. 指令定义 ---
|
| 111 |
@client.tree.command(name="announce", description="Post announcement")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
async def announce(interaction: discord.Interaction, channel: discord.TextChannel, role_ids: str = None):
|
| 113 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 114 |
await interaction.response.send_message("❌ **Access Denied**", ephemeral=True)
|
|
|
|
| 116 |
await interaction.response.send_modal(AnnounceModal(channel, role_ids))
|
| 117 |
|
| 118 |
@client.tree.command(name="announce_edit", description="Edit announcement")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
async def announce_edit(interaction: discord.Interaction, channel: discord.TextChannel, message_id: str, new_role_ids: str = None):
|
| 120 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 121 |
await interaction.response.send_message("❌ **Access Denied**", ephemeral=True)
|
| 122 |
return
|
|
|
|
| 123 |
try:
|
| 124 |
message = await channel.fetch_message(int(message_id))
|
| 125 |
await interaction.response.send_modal(EditAnnounceModal(message, new_role_ids))
|
| 126 |
except Exception as e:
|
| 127 |
await interaction.response.send_message(f"❌ Error: {e}", ephemeral=False)
|
| 128 |
|
| 129 |
+
@client.tree.command(name="wismer_commands", description="Show all avaliable commands")
|
| 130 |
async def wismer_commands(interaction: discord.Interaction):
|
| 131 |
await interaction.response.send_message(
|
| 132 |
'''**Avaliable Commands**:
|
| 133 |
+
`/announce`: Post an announcement.
|
| 134 |
+
`/announce_edit`: Edit an announcement.
|
| 135 |
`/mats`: Show all materials.
|
| 136 |
`/uploadmat`: Upload a material.''',
|
| 137 |
ephemeral=False
|
| 138 |
)
|
| 139 |
|
| 140 |
+
ADMIN_LOG_CHANNEL_ID = 1468391869686878455
|
| 141 |
|
| 142 |
+
@client.tree.command(name="uploadmat", description="Upload a study link")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 144 |
async def uploadmat(interaction: discord.Interaction, category: str, title: str, url: str):
|
|
|
|
| 145 |
await interaction.response.defer(ephemeral=True)
|
|
|
|
| 146 |
if not url.startswith(("http://", "https://")):
|
| 147 |
+
return await interaction.followup.send("❌ Invalid URL!", ephemeral=True)
|
|
|
|
| 148 |
try:
|
|
|
|
| 149 |
new_id = await upload_material_logic(client.db, category, title, url, interaction.user.id)
|
| 150 |
+
await interaction.followup.send(f"✅ Submitted! ID: `#{new_id}`.", ephemeral=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
log_channel = client.get_channel(ADMIN_LOG_CHANNEL_ID)
|
| 152 |
if log_channel:
|
| 153 |
+
embed = discord.Embed(title="🔔 New Material Pending", color=discord.Color.orange())
|
| 154 |
embed.add_field(name="Subject", value=category, inline=True)
|
| 155 |
embed.add_field(name="Index ID", value=f"`#{new_id}`", inline=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
await log_channel.send(embed=embed)
|
|
|
|
| 157 |
except Exception as e:
|
| 158 |
+
await interaction.followup.send(f"❌ Error: {e}", ephemeral=True)
|
| 159 |
+
|
| 160 |
+
@client.tree.command(name="approvemat", description="[Mod Only] Approve material")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 162 |
async def approvemat(interaction: discord.Interaction, category: str, index_id: str):
|
| 163 |
await interaction.response.defer(ephemeral=False)
|
|
|
|
| 164 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 165 |
+
return await interaction.followup.send("❌ Access Denied", ephemeral=True)
|
|
|
|
| 166 |
await approve_material_logic(client.db, category, index_id)
|
| 167 |
+
await interaction.followup.send(f"✅ Approved **{category}** `#{index_id}`.")
|
|
|
|
|
|
|
|
|
|
| 168 |
|
| 169 |
+
@client.tree.command(name="deletemat", description="[Mod Only] Delete material")
|
|
|
|
| 170 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 171 |
async def deletemat(interaction: discord.Interaction, category: str, index_id: str):
|
|
|
|
| 172 |
await interaction.response.defer(ephemeral=False)
|
| 173 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 174 |
+
return await interaction.followup.send("❌ Access Denied", ephemeral=True)
|
|
|
|
| 175 |
await delete_material_logic(client.db, category, index_id)
|
| 176 |
+
await interaction.followup.send(f"🗑️ Deleted ID `#{index_id}`.")
|
|
|
|
|
|
|
|
|
|
| 177 |
|
| 178 |
+
@client.tree.command(name="mats", description="View approved materials")
|
|
|
|
| 179 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 180 |
async def mats(interaction: discord.Interaction, category: str):
|
| 181 |
await interaction.response.defer(ephemeral=False)
|
| 182 |
materials = await list_materials_logic(client.db, category, status="approved")
|
| 183 |
if not materials:
|
| 184 |
+
return await interaction.followup.send(f"📭 No materials in **{category}**.")
|
|
|
|
| 185 |
list_text = "\n".join([f"`#{m['_id']}` [{m['title']}]({m['url']})" for m in materials])
|
| 186 |
+
embed = discord.Embed(title=f"📚 {category}", description=list_text, color=discord.Color.blue())
|
| 187 |
await interaction.followup.send(embed=embed)
|
| 188 |
|
| 189 |
+
@client.tree.command(name="unappmats", description="[Mod Only] View pending")
|
|
|
|
| 190 |
@app_commands.choices(category=[app_commands.Choice(name=c, value=c) for c in CATEGORIES])
|
| 191 |
async def unappmats(interaction: discord.Interaction, category: str):
|
|
|
|
| 192 |
await interaction.response.defer(ephemeral=False)
|
| 193 |
if not any(role.id in mod for role in interaction.user.roles):
|
| 194 |
+
return await interaction.followup.send("❌ Access Denied", ephemeral=True)
|
|
|
|
| 195 |
materials = await list_materials_logic(client.db, category, status="pending")
|
| 196 |
if not materials:
|
| 197 |
+
return await interaction.followup.send(f"✅ No pending items.")
|
|
|
|
| 198 |
list_text = "\n".join([f"`#{m['_id']}` **{m['title']}**\n🔗 {m['url']}" for m in materials])
|
| 199 |
+
embed = discord.Embed(title=f"⏳ {category} Pending", description=list_text, color=discord.Color.orange())
|
| 200 |
await interaction.followup.send(embed=embed)
|
| 201 |
|
| 202 |
+
# --- 6. 启动逻辑 ---
|
| 203 |
+
async def start_bot():
|
| 204 |
+
token = os.getenv('DISCORD_TOKEN')
|
| 205 |
+
while True:
|
| 206 |
+
try:
|
| 207 |
+
print("正在尝试连接 Discord...")
|
| 208 |
+
await client.start(token)
|
| 209 |
+
except Exception as e:
|
| 210 |
+
print(f"❌ 连接失败: {e},15秒后重试...")
|
| 211 |
+
await asyncio.sleep(15)
|
| 212 |
|
|
|
|
|
|
|
| 213 |
if __name__ == "__main__":
|
| 214 |
+
# 启动 Web 保活
|
| 215 |
t = Thread(target=run_flask)
|
| 216 |
t.daemon = True
|
| 217 |
t.start()
|
| 218 |
+
|
| 219 |
+
# 启动机器人异步循环
|
| 220 |
+
try:
|
| 221 |
+
asyncio.run(start_bot())
|
| 222 |
+
except KeyboardInterrupt:
|
| 223 |
+
pass
|