inital commit
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user