Files
2026-05-02 10:11:01 +09:00

256 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import discord
from discord import app_commands
from discord.ext import commands, tasks
import datetime
import os
import asyncio
from typing import Optional, List, Dict, Any
from database import Database
from dotenv import load_dotenv
load_dotenv()
# --- Views & Modals ---
class TaskModal(discord.ui.Modal, title="新規タスク登録"):
task_title = discord.ui.TextInput(label="タイトル", placeholder="タスク名", required=True, max_length=100)
priority = discord.ui.TextInput(label="優先順位 (SSS-D)", placeholder="S", default="A", min_length=1, max_length=3)
deadline = discord.ui.TextInput(label="締め切り (YYYY-MM-DD HH:MM)", placeholder="2024-12-31 23:59", required=True)
details = discord.ui.TextInput(label="詳細", style=discord.TextStyle.long, required=False, max_length=1000)
def __init__(self, db: Database):
super().__init__()
self.db = db
async def on_submit(self, interaction: discord.Interaction):
prio = self.priority.value.upper()
if prio not in ["SSS", "SS", "S", "A", "B", "C", "D"]:
await interaction.response.send_message(f"❌ 優先順位が無効です。", ephemeral=True)
return
try:
deadline_dt = datetime.datetime.strptime(self.deadline.value, "%Y-%m-%d %H:%M")
except ValueError:
await interaction.response.send_message("❌ 日時の形式が正しくありません。", ephemeral=True)
return
tid = await self.db.add_task(
user_id=interaction.user.id,
user_name=interaction.user.display_name,
guild_id=interaction.guild_id,
channel_id=interaction.channel_id,
title=self.task_title.value,
description=self.details.value,
priority=prio,
deadline=deadline_dt.isoformat()
)
await interaction.response.send_message(f"✅ タスク『{self.task_title.value}』(ID: {tid}) を登録しました!", ephemeral=True)
class EditTaskModal(discord.ui.Modal):
def __init__(self, db: Database, task: dict):
super().__init__(title=f"タスク編集 (ID: {task['id']})")
self.db = db
self.task_id = task['id']
deadline_dt = datetime.datetime.fromisoformat(task['deadline'])
self.task_title = discord.ui.TextInput(label="タイトル", default=task['title'], required=True)
self.priority = discord.ui.TextInput(label="優先順位 (SSS-D)", default=task['priority'], min_length=1, max_length=3)
self.deadline = discord.ui.TextInput(label="締め切り (YYYY-MM-DD HH:MM)", default=deadline_dt.strftime("%Y-%m-%d %H:%M"), required=True)
self.details = discord.ui.TextInput(label="詳細", default=task['description'] or "", style=discord.TextStyle.long, required=False)
self.add_item(self.task_title)
self.add_item(self.priority)
self.add_item(self.deadline)
self.add_item(self.details)
async def on_submit(self, interaction: discord.Interaction):
prio = self.priority.value.upper()
if prio not in ["SSS", "SS", "S", "A", "B", "C", "D"]:
await interaction.response.send_message("❌ 優先順位が無効です。", ephemeral=True)
return
try:
deadline_dt = datetime.datetime.strptime(self.deadline.value, "%Y-%m-%d %H:%M")
except ValueError:
await interaction.response.send_message("❌ 日時の形式が正しくありません。", ephemeral=True)
return
await self.db.update_task(self.task_id, self.task_title.value, self.details.value, prio, deadline_dt.isoformat())
await interaction.response.send_message(f"✨ タスクを更新しました!", ephemeral=True)
class TaskActionView(discord.ui.View):
"""通知やリストに表示する「完了」ボタン"""
def __init__(self, db: Database, task_id: str):
super().__init__(timeout=None)
self.db = db
self.task_id = task_id
@discord.ui.button(label="✅ 完了にする", style=discord.ButtonStyle.success)
async def complete(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.db.complete_task(self.task_id)
# メッセージを「完了済み」に更新
embed = interaction.message.embeds[0]
embed.title = f"🏁 【完了】 {embed.title}"
embed.color = discord.Color.light_grey()
await interaction.response.edit_message(content="お疲れ様でした!", embed=embed, view=None)
class ControlPanel(discord.ui.View):
def __init__(self, db: Database):
super().__init__(timeout=None)
self.db = db
@discord.ui.button(label=" タスク追加", style=discord.ButtonStyle.success, custom_id="panel_add")
async def add_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_modal(TaskModal(self.db))
@discord.ui.button(label="📋 タスク一覧", style=discord.ButtonStyle.primary, custom_id="panel_list")
async def list_btn(self, interaction: discord.Interaction, button: discord.ui.Button):
tasks_list = await self.db.get_user_tasks(interaction.user.id, interaction.channel_id)
if not tasks_list:
await interaction.response.send_message("📋 未完了のタスクはありません。", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
for t in tasks_list:
deadline_fmt = datetime.datetime.fromisoformat(t['deadline']).strftime("%m/%d %H:%M")
embed = discord.Embed(title=t['title'], color=discord.Color.green())
embed.add_field(name="優先度", value=t['priority'], inline=True)
embed.add_field(name="締切", value=deadline_fmt, inline=True)
if t['description']:
embed.description = t['description']
await interaction.followup.send(embed=embed, view=TaskActionView(self.db, t['id']), ephemeral=True)
# --- Bot Core ---
class TaskBot(commands.Bot):
def __init__(self, db: Database):
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
super().__init__(command_prefix="!", intents=intents)
self.db = db
async def setup_hook(self):
await self.db.initialize()
self.daily_notification.start()
self.deadline_monitor.start()
await self.add_cog(TaskCommands(self))
print("Setup hook completed.")
async def on_ready(self):
print(f"Logged in as {self.user} (ID: {self.user.id})")
async def on_message(self, message: discord.Message):
if message.author.bot: return
if self.user.mentioned_in(message):
await message.reply("📋 **タスク管理パネル**", view=ControlPanel(self.db))
await self.process_commands(message)
@tasks.loop(time=datetime.time(hour=7, minute=0))
async def daily_notification(self):
print("Running daily notification...")
tasks_list = await self.db.get_all_active_tasks()
now = datetime.datetime.now()
for t in tasks_list:
deadline = datetime.datetime.fromisoformat(t["deadline"])
diff = deadline - now
new_prio = t["priority"]
if diff.days <= 1: new_prio = "SSS"
elif diff.days <= 3: new_prio = "SS"
elif diff.days <= 7: new_prio = "S"
if new_prio != t["priority"]:
await self.db.update_priority(t["id"], new_prio)
# チャンネルごとの通知
channel_tasks = {}
for t in tasks_list:
cid = t["channel_id"]
if cid not in channel_tasks: channel_tasks[cid] = {}
uid = t["user_id"]
if uid not in channel_tasks[cid]: channel_tasks[cid][uid] = []
channel_tasks[cid][uid].append(t)
for cid, u_dict in channel_tasks.items():
channel = self.get_channel(cid)
if not channel: continue
for uid, t_list in u_dict.items():
embed = discord.Embed(title="☀️ 本日のタスクリマインド", color=discord.Color.gold())
desc = "\n".join([f"• **[{t['priority']}] {t['title']}** ({datetime.datetime.fromisoformat(t['deadline']).strftime('%m/%d %H:%M')})" for t in t_list])
embed.description = desc
await channel.send(content=f"<@{uid}> さん、今日の予定です!", embed=embed)
@tasks.loop(minutes=1)
async def deadline_monitor(self):
now = datetime.datetime.now()
tasks_list = await self.db.get_all_active_tasks()
for t in tasks_list:
deadline = datetime.datetime.fromisoformat(t["deadline"])
diff = deadline - now
seconds_left = diff.total_seconds()
channel = self.get_channel(t["channel_id"])
if not channel: continue
# 1時間前
if 0 < seconds_left <= 3600 and t['last_reminded_type'] != '1h':
embed = discord.Embed(title=f"⏳ 締切1時間前: {t['title']}", color=discord.Color.orange())
await channel.send(content=f"<@{t['user_id']}> 準備はいいですか?あと1時間です!", embed=embed, view=TaskActionView(self.db, t['id']))
await self.db.update_reminded_type(t['id'], '1h')
# 締切
elif seconds_left <= 0 and t['last_reminded_type'] != '0h':
embed = discord.Embed(title=f"⏰ 締切時刻です!: {t['title']}", color=discord.Color.red())
await channel.send(content=f"<@{t['user_id']}> 締切時間になりました!完了しましたか?", embed=embed, view=TaskActionView(self.db, t['id']))
await self.db.update_reminded_type(t['id'], '0h')
class TaskCommands(commands.Cog):
def __init__(self, bot: TaskBot):
self.bot = bot
self.db = bot.db
async def task_autocomplete(self, interaction: discord.Interaction, current: str):
tasks = await self.db.get_user_tasks(interaction.user.id, interaction.channel_id)
return [app_commands.Choice(name=f"[{t['priority']}] {t['title']}", value=t['id']) for t in tasks if current.lower() in t['title'].lower()][:25]
@commands.command(name="sync")
@commands.is_owner()
async def sync_commands(self, ctx: commands.Context, guild_only: bool = True):
try:
if guild_only:
self.bot.tree.copy_global_to(guild=ctx.guild)
await self.bot.tree.sync(guild=ctx.guild)
await ctx.send("✅ 同期完了(サーバー)")
else:
await self.bot.tree.sync()
await ctx.send("✅ 同期完了(グローバル)")
except Exception as e: await ctx.send(f"❌ エラー: {e}")
@app_commands.command(name="add")
async def add_slash(self, interaction: discord.Interaction):
await interaction.response.send_modal(TaskModal(self.db))
@app_commands.command(name="list")
async def list_tasks_slash(self, interaction: discord.Interaction):
tasks_list = await self.db.get_user_tasks(interaction.user.id, interaction.channel_id)
if not tasks_list:
await interaction.response.send_message("📋 未完了のタスクはありません。", ephemeral=True)
return
await interaction.response.defer(ephemeral=True)
for t in tasks_list:
deadline_fmt = datetime.datetime.fromisoformat(t['deadline']).strftime("%m/%d %H:%M")
embed = discord.Embed(title=f"[{t['priority']}] {t['title']}", color=discord.Color.green())
embed.description = f"📅 締切: {deadline_fmt}\n{t['description'] or ''}"
await interaction.followup.send(embed=embed, view=TaskActionView(self.db, t['id']), ephemeral=True)
@app_commands.command(name="done")
@app_commands.autocomplete(task_id=task_autocomplete)
async def done_slash(self, interaction: discord.Interaction, task_id: str):
await self.db.complete_task(task_id)
await interaction.response.send_message(f"✅ 完了にしました!", ephemeral=True)
@app_commands.command(name="edit")
@app_commands.autocomplete(task_id=task_autocomplete)
async def edit_slash(self, interaction: discord.Interaction, task_id: str):
task = await self.db.get_task_by_id(task_id)
if task: await interaction.response.send_modal(EditTaskModal(self.db, task))
else: await interaction.response.send_message("❌ 見つかりません。", ephemeral=True)