inital commit

This commit is contained in:
2026-05-02 10:11:01 +09:00
commit 1de4c6616d
12 changed files with 1088 additions and 0 deletions
+255
View File
@@ -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)