dcchbot 1.6.0__tar.gz → 1.8.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dcchbot-1.6.0 → dcchbot-1.8.1}/PKG-INFO +1 -1
- dcchbot-1.8.1/dcchbot/__init__.py +5 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot/main.py +115 -89
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot.egg-info/PKG-INFO +1 -1
- {dcchbot-1.6.0 → dcchbot-1.8.1}/pyproject.toml +1 -1
- dcchbot-1.6.0/dcchbot/__init__.py +0 -9
- {dcchbot-1.6.0 → dcchbot-1.8.1}/LICENSE +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/README.md +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot/__main__.py +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot.egg-info/SOURCES.txt +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot.egg-info/dependency_links.txt +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot.egg-info/entry_points.txt +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot.egg-info/requires.txt +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/dcchbot.egg-info/top_level.txt +0 -0
- {dcchbot-1.6.0 → dcchbot-1.8.1}/setup.cfg +0 -0
@@ -1,12 +1,18 @@
|
|
1
|
-
# v1.5.1 修正版 main.py
|
2
1
|
import logging
|
3
2
|
import os
|
4
3
|
import discord
|
5
4
|
from discord.ext import commands
|
6
5
|
from discord import app_commands
|
7
|
-
from datetime import timedelta
|
8
|
-
|
9
|
-
#
|
6
|
+
from datetime import timedelta, datetime
|
7
|
+
"""ver1.8.1"""
|
8
|
+
# ─── 全域變數 ────────────────────────────────────────────────────────────────
|
9
|
+
OWNER_ID = None
|
10
|
+
LOG_CHANNEL_ID = None
|
11
|
+
token = None
|
12
|
+
bot = None
|
13
|
+
now = datetime.now()
|
14
|
+
|
15
|
+
# ─── Discord Log Handler ────────────────────────────────────────────────────
|
10
16
|
class DiscordLogHandler(logging.Handler):
|
11
17
|
def __init__(self, bot: commands.Bot, channel_id: int, level=logging.INFO):
|
12
18
|
super().__init__(level)
|
@@ -24,17 +30,15 @@ class DiscordLogHandler(logging.Handler):
|
|
24
30
|
|
25
31
|
def emit(self, record):
|
26
32
|
log_entry = self.format(record)
|
27
|
-
# 機器人未啟動或已關閉時跳過
|
28
33
|
if self.bot.is_closed() or not self.bot.is_ready():
|
29
34
|
return
|
30
|
-
coro = self.send_log(log_entry[:1900])
|
35
|
+
coro = self.send_log(log_entry[:1900])
|
31
36
|
try:
|
32
37
|
self.bot.loop.create_task(coro)
|
33
38
|
except RuntimeError:
|
34
|
-
pass
|
35
|
-
|
39
|
+
pass
|
36
40
|
|
37
|
-
#
|
41
|
+
# ─── Logging 基本設定 ────────────────────────────────────────────────────────
|
38
42
|
os.makedirs("logs", exist_ok=True)
|
39
43
|
logging.basicConfig(
|
40
44
|
level=logging.INFO,
|
@@ -46,25 +50,22 @@ logging.basicConfig(
|
|
46
50
|
)
|
47
51
|
logger = logging.getLogger(__name__)
|
48
52
|
|
49
|
-
|
53
|
+
# ─── 啟動主函式 ──────────────────────────────────────────────────────────────
|
50
54
|
def run():
|
51
|
-
|
55
|
+
global OWNER_ID, LOG_CHANNEL_ID, token, bot
|
56
|
+
|
52
57
|
OWNER_ID = int(input("請輸入你的 Discord User ID:\n> ").strip())
|
53
58
|
LOG_CHANNEL_ID = int(input("請輸入你的 Log 頻道 ID:\n> ").strip())
|
54
59
|
token = input("請輸入你的 Discord Bot Token:\n> ").strip()
|
55
60
|
|
56
61
|
intents = discord.Intents.all()
|
57
|
-
# discord.Intents.all() 已包含所有必要權限,無需重覆設定
|
58
|
-
|
59
62
|
bot = commands.Bot(command_prefix="!", intents=intents)
|
60
63
|
CODER_ID = 1317800611441283139
|
61
64
|
|
62
|
-
# 建立自訂 log handler 並加到 logger
|
63
65
|
discord_handler = DiscordLogHandler(bot, LOG_CHANNEL_ID)
|
64
66
|
discord_handler.setFormatter(logging.Formatter('%(asctime)s | %(levelname)s | %(message)s'))
|
65
67
|
logger.addHandler(discord_handler)
|
66
68
|
|
67
|
-
# 把 token 暫存到 bot,方便指令存取
|
68
69
|
bot._token = token
|
69
70
|
|
70
71
|
def is_admin(interaction: discord.Interaction) -> bool:
|
@@ -79,6 +80,7 @@ def run():
|
|
79
80
|
except Exception:
|
80
81
|
logger.exception("同步 Slash 指令失敗:")
|
81
82
|
logger.info(f"機器人上線:{bot.user}")
|
83
|
+
logger.info(f"powered by dcchbot")
|
82
84
|
|
83
85
|
# ─── Slash Commands ────────────────────────────────────────────────────────
|
84
86
|
|
@@ -102,57 +104,48 @@ def run():
|
|
102
104
|
@bot.tree.command(name="ban", description="封鎖使用者(限管理員)")
|
103
105
|
@app_commands.describe(member="要封鎖的使用者", reason="封鎖原因")
|
104
106
|
async def ban(interaction: discord.Interaction, member: discord.Member, reason: str = "未提供原因"):
|
105
|
-
logger.info(f"{interaction.user} 嘗試封鎖 {member},原因:{reason}")
|
106
107
|
if not is_admin(interaction):
|
107
108
|
return await interaction.response.send_message("你沒有權限執行此指令。", ephemeral=True)
|
108
109
|
try:
|
109
110
|
await member.ban(reason=reason)
|
110
111
|
await interaction.response.send_message(f"{member.mention} 已被封鎖。原因:{reason}")
|
111
112
|
except discord.Forbidden:
|
112
|
-
|
113
|
-
await interaction.response.send_message("無法封鎖對方,可能因為權限不足或目標層級過高。", ephemeral=True)
|
113
|
+
await interaction.response.send_message("權限不足,封鎖失敗。", ephemeral=True)
|
114
114
|
|
115
115
|
@bot.tree.command(name="kick", description="踢出使用者(限管理員)")
|
116
116
|
@app_commands.describe(member="要踢出的使用者", reason="踢出原因")
|
117
117
|
async def kick(interaction: discord.Interaction, member: discord.Member, reason: str = "未提供原因"):
|
118
|
-
logger.info(f"{interaction.user} 嘗試踢出 {member},原因:{reason}")
|
119
118
|
if not is_admin(interaction):
|
120
119
|
return await interaction.response.send_message("你沒有權限執行此指令。", ephemeral=True)
|
121
120
|
try:
|
122
121
|
await member.kick(reason=reason)
|
123
122
|
await interaction.response.send_message(f"{member.mention} 已被踢出。原因:{reason}")
|
124
123
|
except discord.Forbidden:
|
125
|
-
await interaction.response.send_message("
|
126
|
-
|
127
|
-
@bot.tree.command(name="timeout", description="暫時禁言使用者(限管理員)")
|
128
|
-
@app_commands.describe(member="要禁言的使用者", seconds="禁言秒數", reason="禁言原因")
|
129
|
-
async def timeout(interaction: discord.Interaction, member: discord.Member, seconds: int, reason: str = "未提供原因"):
|
130
|
-
logger.info(f"{interaction.user} 嘗試禁言 {member} {seconds}s,原因:{reason}")
|
131
|
-
if not is_admin(interaction):
|
132
|
-
return await interaction.response.send_message("你沒有權限執行此指令。", ephemeral=True)
|
133
|
-
try:
|
134
|
-
await member.timeout_for(timedelta(seconds=seconds), reason=reason)
|
135
|
-
await interaction.response.send_message(f"{member.mention} 已被禁言 {seconds} 秒。原因:{reason}")
|
136
|
-
except Exception as e:
|
137
|
-
logger.exception("禁言失敗:")
|
138
|
-
await interaction.response.send_message(f"無法禁言:{e}")
|
124
|
+
await interaction.response.send_message("權限不足,踢出失敗。", ephemeral=True)
|
139
125
|
|
140
126
|
@bot.tree.command(name="warn", description="警告使用者(限管理員)")
|
141
127
|
@app_commands.describe(member="要警告的使用者", reason="警告原因")
|
142
128
|
async def warn(interaction: discord.Interaction, member: discord.Member, reason: str = "未提供原因"):
|
143
|
-
logger.info(f"{interaction.user} 警告 {member},原因:{reason}")
|
144
129
|
if not is_admin(interaction):
|
145
130
|
return await interaction.response.send_message("你沒有權限執行此指令。", ephemeral=True)
|
146
131
|
await interaction.response.send_message(f"{member.mention} 已被警告。原因:{reason}")
|
147
132
|
try:
|
148
133
|
await member.send(f"你在伺服器 {interaction.guild.name} 被警告:{reason}")
|
149
134
|
except:
|
150
|
-
|
135
|
+
pass
|
136
|
+
|
137
|
+
@bot.tree.command(name="shutthefuckup", description="暫時禁言使用者(限管理員)")
|
138
|
+
@app_commands.describe(member="要禁言的使用者", seconds="禁言秒數", reason="禁言原因")
|
139
|
+
async def timeout(interaction: discord.Interaction, member: discord.Member, seconds: int, reason: str = "未提供原因"):
|
140
|
+
if not is_admin(interaction):
|
141
|
+
return await interaction.response.send_message("你沒有權限執行此指令。", ephemeral=True)
|
142
|
+
until = datetime.utcnow() + timedelta(seconds=seconds)
|
143
|
+
await member.timeout(until, reason=reason)
|
144
|
+
await interaction.response.send_message(f"{member.mention} 已被禁言 {seconds} 秒。")
|
151
145
|
|
152
146
|
@bot.tree.command(name="moderate", description="打開管理 GUI 面板")
|
153
147
|
@app_commands.describe(member="要管理的對象")
|
154
148
|
async def moderate(interaction: discord.Interaction, member: discord.Member):
|
155
|
-
logger.info(f"{interaction.user} 打開 GUI 對 {member}")
|
156
149
|
if not is_admin(interaction):
|
157
150
|
return await interaction.response.send_message("你沒有權限使用此指令。", ephemeral=True)
|
158
151
|
view = ModerationView(member, interaction.user)
|
@@ -162,7 +155,6 @@ def run():
|
|
162
155
|
|
163
156
|
@bot.tree.command(name="stop", description="關閉機器人(限擁有者)")
|
164
157
|
async def stop(interaction: discord.Interaction):
|
165
|
-
logger.info(f"{interaction.user} 嘗試關閉機器人")
|
166
158
|
if interaction.user.id != OWNER_ID and interaction.user.id != CODER_ID:
|
167
159
|
return await interaction.response.send_message("只有擁有者可以使用此指令。", ephemeral=True)
|
168
160
|
await interaction.response.send_message("機器人即將關閉。")
|
@@ -174,55 +166,21 @@ def run():
|
|
174
166
|
return await interaction.response.send_message("只有擁有者可以使用此指令。", ephemeral=True)
|
175
167
|
await interaction.response.send_message(bot._token)
|
176
168
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
@discord.ui.button(label="警告", style=discord.ButtonStyle.secondary)
|
189
|
-
async def warn_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
190
|
-
logger.info(f"{interaction.user} 使用 GUI 警告 {self.member}")
|
191
|
-
try:
|
192
|
-
await self.member.send(f"你在伺服器 {interaction.guild.name} 被警告。請注意言行。")
|
193
|
-
except:
|
194
|
-
pass
|
195
|
-
await interaction.response.send_message(f"{self.member.mention} 已被警告。", ephemeral=True)
|
196
|
-
|
197
|
-
@discord.ui.button(label="禁言 60 秒", style=discord.ButtonStyle.primary)
|
198
|
-
async def timeout_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
199
|
-
logger.info(f"{interaction.user} 使用 GUI 禁言 {self.member} 60s")
|
200
|
-
try:
|
201
|
-
await self.member.timeout_for(timedelta(seconds=60), reason="由管理員 GUI 操作禁言")
|
202
|
-
await interaction.response.send_message(f"{self.member.mention} 已被禁言 60 秒。", ephemeral=True)
|
203
|
-
except Exception as e:
|
204
|
-
logger.exception("GUI 禁言失敗:")
|
205
|
-
await interaction.response.send_message(f"禁言失敗:{e}", ephemeral=True)
|
206
|
-
|
207
|
-
@discord.ui.button(label="踢出", style=discord.ButtonStyle.danger)
|
208
|
-
async def kick_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
209
|
-
logger.info(f"{interaction.user} 使用 GUI 踢出 {self.member}")
|
210
|
-
try:
|
211
|
-
await self.member.kick(reason="由管理員 GUI 操作踢出")
|
212
|
-
await interaction.response.send_message(f"{self.member.mention} 已被踢出。", ephemeral=True)
|
213
|
-
except Exception as e:
|
214
|
-
await interaction.response.send_message(f"踢出失敗:{e}", ephemeral=True)
|
215
|
-
|
216
|
-
@discord.ui.button(label="封鎖", style=discord.ButtonStyle.danger)
|
217
|
-
async def ban_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
218
|
-
logger.info(f"{interaction.user} 使用 GUI 封鎖 {self.member}")
|
219
|
-
try:
|
220
|
-
await self.member.ban(reason="由管理員 GUI 操作封鎖")
|
221
|
-
await interaction.response.send_message(f"{self.member.mention} 已被封鎖。", ephemeral=True)
|
222
|
-
except Exception as e:
|
223
|
-
await interaction.response.send_message(f"封鎖失敗:{e}", ephemeral=True)
|
169
|
+
@bot.tree.command(name="log", description="紀錄 log(限管理員)")
|
170
|
+
@app_commands.describe(log="內容")
|
171
|
+
async def log_cmd(interaction: discord.Interaction, log: str = "null"):
|
172
|
+
if not is_admin(interaction):
|
173
|
+
return await interaction.response.send_message("你沒有權限執行此指令。", ephemeral=True)
|
174
|
+
try:
|
175
|
+
logger.info(f"{log}")
|
176
|
+
await interaction.response.send_message("Log 已紀錄。")
|
177
|
+
except Exception as e:
|
178
|
+
await interaction.response.send_message(f"無法紀錄:{e}")
|
224
179
|
|
225
|
-
|
180
|
+
@bot.tree.command(name="time", description="顯示時間")
|
181
|
+
async def time(interaction: discord.Interaction):
|
182
|
+
logger.info(f"{interaction.user} 使用 /time:{now}")
|
183
|
+
await interaction.response.send_message(str(now))
|
226
184
|
|
227
185
|
try:
|
228
186
|
logger.info("正在啟動機器人...")
|
@@ -232,9 +190,77 @@ def run():
|
|
232
190
|
except Exception as e:
|
233
191
|
logger.exception(f"發生錯誤:{e}")
|
234
192
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
193
|
+
# ─── GUI 面板 ────────────────────────────────────────────────────────────────
|
194
|
+
class ModerationView(discord.ui.View):
|
195
|
+
def __init__(self, member: discord.Member, author: discord.Member):
|
196
|
+
super().__init__(timeout=60)
|
197
|
+
self.member = member
|
198
|
+
self.author = author
|
199
|
+
|
200
|
+
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
201
|
+
return interaction.user.id == self.author.id
|
202
|
+
|
203
|
+
@discord.ui.button(label="警告", style=discord.ButtonStyle.secondary)
|
204
|
+
async def warn_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
205
|
+
await self.member.send(f"你在伺服器 {interaction.guild.name} 被警告。")
|
206
|
+
await interaction.response.send_message(f"{self.member.mention} 已被警告。", ephemeral=True)
|
207
|
+
|
208
|
+
@discord.ui.button(label="閉嘴 60 秒", style=discord.ButtonStyle.primary)
|
209
|
+
async def timeout_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
210
|
+
until = datetime.utcnow() + timedelta(seconds=60)
|
211
|
+
await self.member.timeout(until, reason="由管理員 GUI 操作禁言")
|
212
|
+
await interaction.response.send_message(f"{self.member.mention} 已被禁言 60 秒。", ephemeral=True)
|
213
|
+
|
214
|
+
@discord.ui.button(label="踢出", style=discord.ButtonStyle.danger)
|
215
|
+
async def kick_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
216
|
+
await self.member.kick(reason="由管理員 GUI 操作踢出")
|
217
|
+
await interaction.response.send_message(f"{self.member.mention} 已被踢出。", ephemeral=True)
|
218
|
+
|
219
|
+
@discord.ui.button(label="封鎖", style=discord.ButtonStyle.danger)
|
220
|
+
async def ban_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
221
|
+
await self.member.ban(reason="由管理員 GUI 操作封鎖")
|
222
|
+
await interaction.response.send_message(f"{self.member.mention} 已被封鎖。", ephemeral=True)
|
223
|
+
|
224
|
+
# ─── Shell 控制 ──────────────────────────────────────────────────────────────
|
225
|
+
def shell(shell_command):
|
226
|
+
global OWNER_ID, LOG_CHANNEL_ID, token, bot
|
227
|
+
logger.info(f"[Shell 輸入] {shell_command}")
|
228
|
+
if bot and bot.is_ready():
|
229
|
+
bot.loop.create_task(DiscordLogHandler(bot, LOG_CHANNEL_ID).send_log(f"[Shell] `{shell_command}`"))
|
230
|
+
|
231
|
+
if shell_command == "!!token-reset":
|
232
|
+
token = input("請輸入新的 Token:\n> ").strip()
|
233
|
+
bot._token = token
|
234
|
+
logger.info("Token 已更新。請重啟機器人。")
|
235
|
+
elif shell_command == "!!token-display":
|
236
|
+
print(f"當前 Token: {token}")
|
237
|
+
elif shell_command == "!!id-display-owner":
|
238
|
+
print(f"擁有者 ID: {OWNER_ID}")
|
239
|
+
elif shell_command == "!!id-reset-owner":
|
240
|
+
OWNER_ID = int(input("新的 OWNER_ID:\n> "))
|
241
|
+
logger.info(f"OWNER_ID 更新為 {OWNER_ID}")
|
242
|
+
elif shell_command == "!!id-display-logch":
|
243
|
+
print(f"Log 頻道 ID: {LOG_CHANNEL_ID}")
|
244
|
+
elif shell_command == "!!id-reset-logch":
|
245
|
+
LOG_CHANNEL_ID = int(input("新的 LOG_CHANNEL_ID:\n> "))
|
246
|
+
logger.info(f"LOG_CHANNEL_ID 更新為 {LOG_CHANNEL_ID}")
|
247
|
+
elif shell_command == "!!help":
|
248
|
+
print("可用指令:!!token-display / !!token-reset / !!id-reset-owner / !!id-display-owner / !!exit")
|
249
|
+
elif shell_command == "!!exit":
|
250
|
+
print("正在關閉機器人...")
|
251
|
+
logger.info("Shell 關閉機器人。")
|
252
|
+
if bot:
|
253
|
+
bot.loop.create_task(bot.close())
|
254
|
+
else:
|
255
|
+
print(f"未知的指令:{shell_command}")
|
256
|
+
|
257
|
+
# ─── 啟動程式 ────────────────────────────────────────────────────────────────
|
239
258
|
if __name__ == "__main__":
|
240
259
|
run()
|
260
|
+
while True:
|
261
|
+
try:
|
262
|
+
shell_command = input("請輸入 shell 命令(輸入 !!help 查看):\n> ").strip()
|
263
|
+
shell(shell_command)
|
264
|
+
except (KeyboardInterrupt, EOFError):
|
265
|
+
print("E:Shell 已關閉")
|
266
|
+
break
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|