From b1068ecd2c1a4fb53b65e12f368a4e3768f6676c Mon Sep 17 00:00:00 2001 From: virtus Date: Fri, 20 Jun 2025 10:31:59 +0700 Subject: [PATCH] adding debt app --- .vscode/launch.json | 16 ++++ apps/experience.py | 14 ++-- apps/home_debt.py | 167 +++++++++++++++----------------------- apps/server.py | 32 +++----- core/bot.py | 4 +- core/events.py | 60 ++------------ core/tasks.py | 34 ++++---- database/base.py | 35 -------- database/channel.py | 45 ---------- database/channel_app.py | 43 ---------- database/server.py | 40 --------- main.py | 6 +- models/__init__.py | 9 +- models/debt_group.py | 12 --- models/home_debt.py | 26 ++++++ repositories/__init__.py | 4 +- repositories/home_debt.py | 140 +++++++++++--------------------- repositories/user.py | 47 ----------- 18 files changed, 208 insertions(+), 526 deletions(-) create mode 100644 .vscode/launch.json delete mode 100644 database/base.py delete mode 100644 database/channel.py delete mode 100644 database/channel_app.py delete mode 100644 database/server.py delete mode 100644 models/debt_group.py create mode 100644 models/home_debt.py delete mode 100644 repositories/user.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bc88455 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Run Bot", + "type": "debugpy", + "request": "launch", + "program": "main.py", + "console": "integratedTerminal", + "envFile": "${workspaceFolder}/.env" + } + ] +} \ No newline at end of file diff --git a/apps/experience.py b/apps/experience.py index c5d6431..358ef75 100644 --- a/apps/experience.py +++ b/apps/experience.py @@ -1,8 +1,8 @@ -import discord -from core.bot import bot, user_repo +# import discord +# from core.bot import bot, user_repo -@bot.command(name='exp') -async def show_exp(ctx): - user_id = ctx.author.id - exp = await user_repo.get_exp(user_id) - await ctx.send(f"{ctx.author.mention} has {exp} experience points!") \ No newline at end of file +# @bot.command(name='exp') +# async def show_exp(ctx): +# user_id = ctx.author.id +# exp = await user_repo.get_exp(user_id) +# await ctx.send(f"{ctx.author.mention} has {exp} experience points!") \ No newline at end of file diff --git a/apps/home_debt.py b/apps/home_debt.py index 11b806f..b1b67c5 100644 --- a/apps/home_debt.py +++ b/apps/home_debt.py @@ -1,131 +1,94 @@ import discord -from datetime import datetime -from core.bot import bot, server_repo, channel_repo, channel_app_repo, home_debt_repo -from models import DiscordServer, DiscordChannel, DiscordChannelApp +from typing import Any +from core.bot import bot, server_repo, channel_repo, home_debt_repo @bot.tree.command(name='set-home-debt', description='Set channel to home debt') async def set_home_debt(interaction: discord.Interaction, channel: discord.TextChannel): try: - # Get or create server + # Kiểm tra xem người dùng có quyền admin không + if not interaction.user.guild_permissions.administrator: + await interaction.response.send_message("Bạn cần có quyền Administrator để sử dụng lệnh này!", ephemeral=True) + return + + # Ensure server is in database server = await server_repo.get(str(interaction.guild_id)) if not server: - server = DiscordServer( - id=str(interaction.guild_id), - name=interaction.guild.name, - created_at=datetime.now(), - updated_at=datetime.now() - ) - server = await server_repo.create(server) + await interaction.response.send_message("Server chưa được khởi tạo trong database! Vui lòng sử dụng lệnh /init_server trước.", ephemeral=True) + return - # Get or create channel - discord_channel = await channel_repo.get(str(channel.id)) + # Ensure channel was registered in database + discord_channel = await channel_repo.get_channel(channel.id) if not discord_channel: - discord_channel = DiscordChannel( - id=str(channel.id), - server_id=str(interaction.guild_id), - name=channel.name, - type='text', - created_at=datetime.now(), - updated_at=datetime.now() - ) - discord_channel = await channel_repo.create(discord_channel) - - # Create or update home debt app - apps = await channel_app_repo.get_by_channel(str(channel.id)) - home_debt_app = next((app for app in apps if app.app_type == 'home_debt'), None) - - if home_debt_app: - home_debt_app.config = {'enabled': True} - await channel_app_repo.update(home_debt_app) + await channel_repo.create_channel(interaction.guild_id, channel.id, 'home_debt') else: - home_debt_app = DiscordChannelApp( - id=f"{channel.id}_home_debt", - channel_id=str(channel.id), - app_type='home_debt', - config={'enabled': True}, - created_at=datetime.now(), - updated_at=datetime.now() - ) - await channel_app_repo.create(home_debt_app) + await channel_repo.update_channel(channel.id, 'home_debt') - await interaction.response.send_message(f"Home debt app has been set up for {channel.mention}") + await interaction.response.send_message(f"home_debt app đã được cấu hình cho {channel.mention}") except Exception as e: - await interaction.response.send_message(f"Error setting up home debt app: {str(e)}") + await interaction.response.send_message(f"Lỗi khi cấu hình home_debt app: {str(e)}") -@bot.tree.command(name="add_member", description="Thêm thành viên mới vào nhà") -async def add_member(interaction: discord.Interaction, member: discord.Member): - """Thêm thành viên mới vào nhà""" +# Decorator to check if the channel is registered and configured for home_debt app +def check_channel_home_debt(): + def decorator(func): + async def wrapper(interaction: discord.Interaction, *args, **kwargs): + channel = await channel_repo.get_channel(interaction.channel_id) + if not channel: + await interaction.response.send_message("Channel chưa được đăng ký!", ephemeral=True) + return + if channel.app != 'home_debt': + await interaction.response.send_message("Lệnh này chỉ có thể sử dụng trong channel đã được cấu hình cho home_debt app!", ephemeral=True) + return + await func(interaction, *args, **kwargs) + return wrapper + return decorator + +@bot.tree.command(name="home-debt-add", description="Thêm khoản chi tiêu mới") +# @check_channel_home_debt() +async def home_debt_add(interaction: discord.Interaction, amount: float, description: str): + """Vì home chỉ có 2 người nên sẽ tự động thêm khoản chi tiêu cho người còn lại""" try: - # Kiểm tra xem thành viên đã tồn tại chưa - existing_member = await home_debt_repo.get_member(member.id) - if existing_member: - await interaction.response.send_message(f"Thành viên {member.name} đã tồn tại trong hệ thống!", ephemeral=True) - return - - # Thêm thành viên mới - await home_debt_repo.add_member(member.id, member.name) - await interaction.response.send_message(f"Đã thêm thành viên {member.name} vào hệ thống!", ephemeral=True) + # Get info other user from home_debt table + other_user = await home_debt_repo.get_other(interaction.user.id) + other_user.value += amount / 2 + await home_debt_repo.update_home_debt(other_user) + await interaction.response.send_message(f"Đã thêm khoản chi tiêu: {description} - {amount / 2}đ cho {other_user.user_id}", ephemeral=False) except Exception as e: await interaction.response.send_message(f"Có lỗi xảy ra: {str(e)}", ephemeral=True) -@bot.tree.command(name="add_expense", description="Thêm khoản chi tiêu mới") -async def add_expense(interaction: discord.Interaction, amount: float, description: str): - """Thêm khoản chi tiêu mới""" +@bot.tree.command(name="home-debt-check", description="Kiểm tra số dư của bạn") +async def home_debt_check(interaction: discord.Interaction): + """Kiểm tra số dư của mọi người""" try: - # Kiểm tra xem người dùng có phải là thành viên không - member = await home_debt_repo.get_member(interaction.user.id) - if not member: - await interaction.response.send_message("Bạn chưa được thêm vào hệ thống! Vui lòng sử dụng lệnh /add_member trước.", ephemeral=True) - return - - # Thêm khoản chi tiêu - expense = await home_debt_repo.add_expense(amount, description, interaction.user.id) - await interaction.response.send_message(f"Đã thêm khoản chi tiêu: {description} - {amount}đ", ephemeral=True) + # Lấy số dư của mọi người + home_debts = await home_debt_repo.get_all() + await interaction.response.send_message(f"Số dư của mọi người: {home_debts}", ephemeral=True) except Exception as e: await interaction.response.send_message(f"Có lỗi xảy ra: {str(e)}", ephemeral=True) -@bot.tree.command(name="check_balance", description="Kiểm tra số dư của bạn") -async def check_balance(interaction: discord.Interaction): - """Kiểm tra số dư của bạn""" +@bot.tree.command(name="vay-debt", description="Vay nợ") +async def vay_debt(interaction: discord.Interaction, amount: float, description: str): + """Vay nợ""" try: - # Kiểm tra xem người dùng có phải là thành viên không - member = await home_debt_repo.get_member(interaction.user.id) - if not member: - await interaction.response.send_message("Bạn chưa được thêm vào hệ thống! Vui lòng sử dụng lệnh /add_member trước.", ephemeral=True) - return + # Get info user from home_debt table + user = await home_debt_repo.get(interaction.user.id) + user.value += amount + await home_debt_repo.update_home_debt(user) - # Lấy số dư - balance = await home_debt_repo.get_balance(interaction.user.id) - await interaction.response.send_message(f"Số dư của bạn: {balance}đ", ephemeral=True) + # Send message to user + await interaction.response.send_message(f"Đã vay nợ: {description} - {amount}đ cho {user.user_id}", ephemeral=False) except Exception as e: await interaction.response.send_message(f"Có lỗi xảy ra: {str(e)}", ephemeral=True) -@bot.tree.command(name="list_expenses", description="Xem danh sách chi tiêu") -async def list_expenses(interaction: discord.Interaction): - """Xem danh sách chi tiêu""" +@bot.tree.command(name="tra-debt", description="Trả nợ") +async def tra_debt(interaction: discord.Interaction, amount: float): + """Trả nợ""" try: - # Kiểm tra xem người dùng có phải là thành viên không - member = await home_debt_repo.get_member(interaction.user.id) - if not member: - await interaction.response.send_message("Bạn chưa được thêm vào hệ thống! Vui lòng sử dụng lệnh /add_member trước.", ephemeral=True) - return + # Get info user from home_debt table + user = await home_debt_repo.get(interaction.user.id) + user.value -= amount + await home_debt_repo.update_home_debt(user) - # Lấy danh sách chi tiêu - expenses = await home_debt_repo.get_expenses() - if not expenses: - await interaction.response.send_message("Chưa có khoản chi tiêu nào!", ephemeral=True) - return - - # Tạo embed message - embed = discord.Embed(title="Danh sách chi tiêu", color=discord.Color.blue()) - for expense in expenses: - member = await home_debt_repo.get_member(expense.member_id) - embed.add_field( - name=f"{expense.description} - {expense.amount}đ", - value=f"Người chi: {member.name}", - inline=False - ) - - await interaction.response.send_message(embed=embed, ephemeral=True) + # Send message to user + await interaction.response.send_message(f"Đã trả nợ: {amount}đ cho {user.user_id}", ephemeral=False) except Exception as e: - await interaction.response.send_message(f"Có lỗi xảy ra: {str(e)}", ephemeral=True) \ No newline at end of file + await interaction.response.send_message(f"Có lỗi xảy ra: {str(e)}", ephemeral=True) \ No newline at end of file diff --git a/apps/server.py b/apps/server.py index bacd0e2..37dc834 100644 --- a/apps/server.py +++ b/apps/server.py @@ -1,28 +1,18 @@ import discord -from datetime import datetime -from core.bot import bot, server_repo, channel_repo, channel_app_repo -from models import DiscordServer, DiscordChannel, DiscordChannelApp - -@bot.tree.command(name='test', description='Test command') -async def test(interaction: discord.Interaction): - try: - # Get server - server = await server_repo.get(str(interaction.guild_id)) - if server: - await interaction.response.send_message(f"Server: {server.name}") - else: - await interaction.response.send_message("Server not found") - except Exception as e: - await interaction.response.send_message(f"Error: {e}") +from core.bot import bot, server_repo @bot.tree.command(name='help', description='Show help') async def help(interaction: discord.Interaction): - embed = discord.Embed(title="Help", description="Here is a list of commands you can use:") - embed.add_field(name="!exp", value="Show your experience points", inline=True) - embed.add_field(name="!test", value="Test command", inline=True) - embed.add_field(name="!help", value="Show help", inline=True) - embed.add_field(name="!set-home-debt", value="Set channel to home debt", inline=True) - await interaction.response.send_message(embed=embed) + """Command để hiển thị danh sách các lệnh""" + # Get all commands + commands = bot.tree.get_commands() + command_list = "\n".join([f"• {c.name}: {c.description}" for c in commands]) + + # Create embed with command list + embed = discord.Embed(title="Help", description="Đây là danh sách các lệnh bạn có thể sử dụng:") + embed.add_field(name="Danh sách lệnh", value=command_list, inline=False) + await interaction.response.send_message(embed=embed, ephemeral=False) + print(f"Đã gửi lệnh help cho {interaction.user.name}") @bot.tree.command(name="init_server", description="Khởi tạo thông tin server trong database") async def init_server(interaction: discord.Interaction): diff --git a/core/bot.py b/core/bot.py index 166bb17..3018f59 100644 --- a/core/bot.py +++ b/core/bot.py @@ -16,12 +16,12 @@ intents.guilds = True bot = commands.Bot(command_prefix='!', intents=intents) # Initialize repositories -from repositories import ServerRepository, ChannelRepository, ChannelAppRepository, UserRepository +from repositories import ServerRepository, ChannelRepository, ChannelAppRepository, HomeDebtRepository server_repo = ServerRepository() channel_repo = ChannelRepository() channel_app_repo = ChannelAppRepository() -user_repo = UserRepository() +home_debt_repo = HomeDebtRepository() # Store user cooldowns from typing import Dict, Set diff --git a/core/events.py b/core/events.py index 35b0030..c34a016 100644 --- a/core/events.py +++ b/core/events.py @@ -1,58 +1,8 @@ -from datetime import datetime -from .bot import bot, user_repo, user_cooldowns, active_voice_users, CHAT_EXP_POINTS, CHAT_COOLDOWN, VOICE_EXP_POINTS_PER_MINUTE +from core.bot import bot +from core.tasks import sync_commands @bot.event async def on_ready(): - print(f'{bot.user} has connected to Discord!') - from .tasks import update_voice_exp - update_voice_exp.start() - try: - synced = await bot.tree.sync() - print(f"Synced {len(synced)} command(s)") - except Exception as e: - print(f"Failed to sync commands: {e}") - -@bot.event -async def on_message(message): - if message.author.bot: - return - - # Handle chat experience points - user_id = message.author.id - current_time = datetime.now() - - if user_id not in user_cooldowns or (current_time - user_cooldowns[user_id]).total_seconds() >= CHAT_COOLDOWN: - current_exp = await user_repo.get_exp(user_id) - new_exp = current_exp + CHAT_EXP_POINTS - await user_repo.update_exp(user_id, new_exp) - user_cooldowns[user_id] = current_time - print(f"Added {CHAT_EXP_POINTS} exp points to {message.author.name}. Total: {new_exp}") - - await bot.process_commands(message) - -@bot.event -async def on_voice_state_update(member, before, after): - if member.bot: - return - - # User joined a voice channel - if before.channel is None and after.channel is not None: - current_time = datetime.now().isoformat() - await user_repo.update_voice_time(member.id, current_time) - active_voice_users.add(member.id) - print(f"{member.name} joined voice channel {after.channel.name}") - - # User left a voice channel - elif before.channel is not None and after.channel is None: - if member.id in active_voice_users: - active_voice_users.remove(member.id) - last_join_time = await user_repo.get_voice_time(member.id) - if last_join_time: - join_time = datetime.fromisoformat(last_join_time) - time_spent = datetime.now() - join_time - minutes = int(time_spent.total_seconds() / 60) - exp_points = minutes * VOICE_EXP_POINTS_PER_MINUTE - current_exp = await user_repo.get_exp(member.id) - new_exp = current_exp + exp_points - await user_repo.update_exp(member.id, new_exp) - print(f"{member.name} spent {minutes} minutes in voice. Added {exp_points} exp points. Total: {new_exp}") \ No newline at end of file + print(f'{bot.user} đã tham gia vào server!') + await sync_commands.start() + print("Đã đồng bộ lệnh!") \ No newline at end of file diff --git a/core/tasks.py b/core/tasks.py index e96bcdc..1c82a50 100644 --- a/core/tasks.py +++ b/core/tasks.py @@ -1,18 +1,22 @@ from discord.ext import tasks -from datetime import datetime -from .bot import bot, user_repo, active_voice_users, VOICE_EXP_POINTS_PER_MINUTE +from core.bot import bot + +# @tasks.loop(minutes=1) +# async def update_voice_exp(): +# current_time = datetime.now() +# for user_id in list(active_voice_users): +# last_join_time = await user_repo.get_voice_time(user_id) +# if last_join_time: +# join_time = datetime.fromisoformat(last_join_time) +# time_spent = current_time - join_time +# if time_spent.total_seconds() >= 60: +# current_exp = await user_repo.get_exp(user_id) +# new_exp = current_exp + VOICE_EXP_POINTS_PER_MINUTE +# await user_repo.update_exp(user_id, new_exp) +# await user_repo.update_voice_time(user_id, current_time.isoformat()) +# print(f"Thêm {VOICE_EXP_POINTS_PER_MINUTE} điểm kinh nghiệm cho user {user_id} cho thời gian voice") @tasks.loop(minutes=1) -async def update_voice_exp(): - current_time = datetime.now() - for user_id in list(active_voice_users): - last_join_time = await user_repo.get_voice_time(user_id) - if last_join_time: - join_time = datetime.fromisoformat(last_join_time) - time_spent = current_time - join_time - if time_spent.total_seconds() >= 60: - current_exp = await user_repo.get_exp(user_id) - new_exp = current_exp + VOICE_EXP_POINTS_PER_MINUTE - await user_repo.update_exp(user_id, new_exp) - await user_repo.update_voice_time(user_id, current_time.isoformat()) - print(f"Added {VOICE_EXP_POINTS_PER_MINUTE} exp points to user {user_id} for voice time") \ No newline at end of file +async def sync_commands(): + await bot.tree.sync() + print("Đã đồng bộ lệnh!") \ No newline at end of file diff --git a/database/base.py b/database/base.py deleted file mode 100644 index 05a3e7d..0000000 --- a/database/base.py +++ /dev/null @@ -1,35 +0,0 @@ -from supabase import create_client, Client -import os -from dotenv import load_dotenv -from infra.db import PostgresConnection - -load_dotenv() - -class Database: - def __init__(self): - uri: str = os.getenv('SUPABASE_URI') - if not uri: - raise ValueError("SUPABASE_URI environment variable is not set") - - # Parse URI to get URL and key - # Format: postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres - parts = uri.split('@') - if len(parts) != 2: - raise ValueError("Invalid SUPABASE_URI format") - - # Extract project reference from the host - host = parts[1].split(':')[0] - project_ref = host.split('.')[1] - - # Construct Supabase URL and key - url = f"https://{project_ref}.supabase.co" - key = os.getenv('SUPABASE_KEY') # Still need the anon key for API access - - if not key: - raise ValueError("SUPABASE_KEY environment variable is not set") - - self.supabase: Client = create_client(url, key) - -class BaseRepository: - def __init__(self): - self.db = PostgresConnection() \ No newline at end of file diff --git a/database/channel.py b/database/channel.py deleted file mode 100644 index ec14bdb..0000000 --- a/database/channel.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Optional, List -from .base import BaseRepository -from models.channel import DiscordChannel - -class ChannelRepository(BaseRepository): - def __init__(self): - super().__init__() - self.table = self.db.get_table('discord_channel') - - async def get_channel(self, channel_id: int) -> Optional[DiscordChannel]: - try: - response = self.table.select('*').eq('channel_id', channel_id).execute() - if response.data: - return DiscordChannel(**response.data[0]) - return None - except Exception as e: - print(f"Error getting channel: {e}") - return None - - async def get_channels_by_server(self, server_id: int) -> List[DiscordChannel]: - try: - response = self.table.select('*').eq('server_id', server_id).execute() - return [DiscordChannel(**channel) for channel in response.data] - except Exception as e: - print(f"Error getting channels: {e}") - return [] - - async def create_channel(self, channel_id: int, server_id: int, channel_name: str) -> Optional[DiscordChannel]: - try: - channel = DiscordChannel(channel_id=channel_id, server_id=server_id, channel_name=channel_name) - response = self.table.insert(channel.dict()).execute() - return DiscordChannel(**response.data[0]) - except Exception as e: - print(f"Error creating channel: {e}") - return None - - async def update_channel(self, channel_id: int, channel_name: str) -> Optional[DiscordChannel]: - try: - response = self.table.update({'channel_name': channel_name}).eq('channel_id', channel_id).execute() - if response.data: - return DiscordChannel(**response.data[0]) - return None - except Exception as e: - print(f"Error updating channel: {e}") - return None \ No newline at end of file diff --git a/database/channel_app.py b/database/channel_app.py deleted file mode 100644 index a662bc7..0000000 --- a/database/channel_app.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Optional, List -from .base import BaseRepository -from models.channel_app import DiscordChannelApp - -class ChannelAppRepository(BaseRepository): - def __init__(self): - super().__init__() - self.table = self.db.get_table('discord_channel_app') - - async def get_channel_app(self, channel_id: int, app_name: str) -> Optional[DiscordChannelApp]: - try: - response = self.table.select('*').eq('channel_id', channel_id).eq('app_name', app_name).execute() - if response.data: - return DiscordChannelApp(**response.data[0]) - return None - except Exception as e: - print(f"Error getting channel app: {e}") - return None - - async def get_channel_apps(self, channel_id: int) -> List[DiscordChannelApp]: - try: - response = self.table.select('*').eq('channel_id', channel_id).execute() - return [DiscordChannelApp(**app) for app in response.data] - except Exception as e: - print(f"Error getting channel apps: {e}") - return [] - - async def create_channel_app(self, channel_id: int, app_name: str) -> Optional[DiscordChannelApp]: - try: - app = DiscordChannelApp(channel_id=channel_id, app_name=app_name) - response = self.table.insert(app.dict()).execute() - return DiscordChannelApp(**response.data[0]) - except Exception as e: - print(f"Error creating channel app: {e}") - return None - - async def delete_channel_app(self, channel_id: int, app_name: str) -> bool: - try: - response = self.table.delete().eq('channel_id', channel_id).eq('app_name', app_name).execute() - return len(response.data) > 0 - except Exception as e: - print(f"Error deleting channel app: {e}") - return False \ No newline at end of file diff --git a/database/server.py b/database/server.py deleted file mode 100644 index 3619ca2..0000000 --- a/database/server.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional -from .base import BaseRepository -from models.server import DiscordServer - -class ServerRepository(BaseRepository): - def __init__(self): - super().__init__() - self.table = self.db.get_table('discord_server') - - async def get_server(self, server_id: int) -> Optional[DiscordServer]: - """Get Discord server by ID""" - try: - response = self.table.select('*').eq('server_id', server_id).execute() - if response.data: - return DiscordServer(**response.data[0]) - return None - except Exception as e: - print(f"Error getting server: {e}") - return None - - async def create_server(self, server_id: int, server_name: str) -> Optional[DiscordServer]: - """Create new Discord server""" - try: - server = DiscordServer(server_id=server_id, server_name=server_name) - response = self.table.insert(server.dict()).execute() - return DiscordServer(**response.data[0]) - except Exception as e: - print(f"Error creating server: {e}") - return None - - async def update_server(self, server_id: int, server_name: str) -> Optional[DiscordServer]: - """Update Discord server""" - try: - response = self.table.update({'server_name': server_name}).eq('server_id', server_id).execute() - if response.data: - return DiscordServer(**response.data[0]) - return None - except Exception as e: - print(f"Error updating server: {e}") - return None \ No newline at end of file diff --git a/main.py b/main.py index d400e4e..a856888 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,11 @@ import os from dotenv import load_dotenv -from core.bot import bot from core import events, tasks -from apps import experience, server +from core.bot import bot +from apps import home_debt, server # Load environment variables load_dotenv() # Run the bot -bot.run(os.getenv('DISCORD_TOKEN')) +bot.run(os.getenv('BOT_TOKEN')) diff --git a/models/__init__.py b/models/__init__.py index 2ac29cd..0cf3792 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,5 +1,6 @@ -from .server import DiscordServer -from .channel import DiscordChannel -from .channel_app import DiscordChannelApp +from models.server import DiscordServer +from models.channel import DiscordChannel +from models.channel_app import DiscordChannelApp +from models.home_debt import DiscordHomeDebt -__all__ = ['DiscordServer', 'DiscordChannel', 'DiscordChannelApp'] \ No newline at end of file +__all__ = ['DiscordServer', 'DiscordChannel', 'DiscordChannelApp', 'DiscordHomeDebt'] \ No newline at end of file diff --git a/models/debt_group.py b/models/debt_group.py deleted file mode 100644 index 73aed77..0000000 --- a/models/debt_group.py +++ /dev/null @@ -1,12 +0,0 @@ -from dataclasses import dataclass -from datetime import datetime -from typing import Optional - -@dataclass -class DiscordDebtGroup: - id: Optional[int] - name: str - description: str - created_at: datetime - updated_at: datetime - members: List[DiscordHomeDebt] \ No newline at end of file diff --git a/models/home_debt.py b/models/home_debt.py new file mode 100644 index 0000000..b0f1c1f --- /dev/null +++ b/models/home_debt.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +@dataclass +class DiscordHomeDebt: + id: Optional[int] + user_id: int + value: int + created_at: datetime + updated_at: datetime + + def __str__(self): + return f"ID: {self.id}, User ID: {self.user_id}, Value: {self.value}, Created At: {self.created_at}, Updated At: {self.updated_at}" + + def __repr__(self): + return self.__str__() + + def to_dict(self): + return { + "id": self.id, + "user_id": self.user_id, + "value": self.value, + "created_at": self.created_at, + "updated_at": self.updated_at + } \ No newline at end of file diff --git a/repositories/__init__.py b/repositories/__init__.py index 3bdce13..313ac3c 100644 --- a/repositories/__init__.py +++ b/repositories/__init__.py @@ -1,6 +1,6 @@ from .server import ServerRepository from .channel import ChannelRepository from .channel_app import ChannelAppRepository -from .user import UserRepository +from .home_debt import HomeDebtRepository -__all__ = ['ServerRepository', 'ChannelRepository', 'ChannelAppRepository', 'UserRepository'] \ No newline at end of file +__all__ = ['ServerRepository', 'ChannelRepository', 'ChannelAppRepository', 'HomeDebtRepository'] \ No newline at end of file diff --git a/repositories/home_debt.py b/repositories/home_debt.py index f5d984a..f3e7cb4 100644 --- a/repositories/home_debt.py +++ b/repositories/home_debt.py @@ -1,103 +1,57 @@ from typing import List, Optional -from datetime import datetime -from models.debt_group import HomeMember, Expense, ExpenseShare +from models.home_debt import DiscordHomeDebt from infra.db import postgres class HomeDebtRepository: - _instance = None - _initialized = False - - def __new__(cls): - if cls._instance is None: - cls._instance = super(HomeDebtRepository, cls).__new__(cls) - return cls._instance - def __init__(self): - if not HomeDebtRepository._initialized: - self.db = postgres.get_table('home_members') - HomeDebtRepository._initialized = True + self.table = postgres.get_table('discord_home_debt') - async def add_member(self, discord_user_id: int, name: str) -> HomeMember: - """Thêm thành viên mới vào nhà""" - data = await self.db.table("home_members").insert({ - "discord_user_id": discord_user_id, - "name": name - }).execute() - return HomeMember(**data.data[0]) - - async def get_member(self, discord_user_id: int) -> Optional[HomeMember]: - """Lấy thông tin thành viên theo discord_user_id""" - data = await self.db.table("home_members").select("*").eq("discord_user_id", discord_user_id).execute() - if not data.data: + async def get(self, discord_user_id: int) -> Optional[DiscordHomeDebt]: + """Lấy thông tin thành viên""" + try: + response = self.table.select('*').eq('user_id', discord_user_id).execute() + if response.data: + return DiscordHomeDebt(**response.data[0]) return None - return HomeMember(**data.data[0]) - - async def get_all_members(self) -> List[HomeMember]: - """Lấy danh sách tất cả thành viên""" - data = await self.db.table("home_members").select("*").execute() - return [HomeMember(**member) for member in data.data] - - async def add_expense(self, amount: float, description: str, paid_by: int) -> Expense: - """Thêm khoản chi tiêu mới""" - # Tạo expense - expense_data = await self.db.table("expenses").insert({ - "amount": amount, - "description": description, - "paid_by": paid_by - }).execute() - expense = Expense(**expense_data.data[0]) - - # Lấy danh sách thành viên - members = await self.get_all_members() - share_amount = amount / len(members) - - # Tạo expense shares cho từng thành viên - for member in members: - await self.db.table("expense_shares").insert({ - "expense_id": expense.id, - "member_id": member.discord_user_id, - "share_amount": share_amount, - "is_paid": member.discord_user_id == paid_by # Người trả tiền được đánh dấu là đã trả - }).execute() - - return expense - - async def get_expense(self, expense_id: int) -> Optional[Expense]: - """Lấy thông tin khoản chi tiêu""" - data = await self.db.table("expenses").select("*").eq("id", expense_id).execute() - if not data.data: + except Exception as e: + print(f"Error getting user: {e}") return None - return Expense(**data.data[0]) - - async def get_all_expenses(self) -> List[Expense]: - """Lấy danh sách tất cả khoản chi tiêu""" - data = await self.db.table("expenses").select("*").order("created_at", desc=True).execute() - return [Expense(**expense) for expense in data.data] - - async def get_expense_shares(self, expense_id: int) -> List[ExpenseShare]: - """Lấy danh sách chia tiền của một khoản chi tiêu""" - data = await self.db.table("expense_shares").select("*").eq("expense_id", expense_id).execute() - return [ExpenseShare(**share) for share in data.data] - - async def mark_share_as_paid(self, share_id: int) -> Optional[ExpenseShare]: - """Đánh dấu một phần chia tiền đã được trả""" - data = await self.db.table("expense_shares").update({ - "is_paid": True - }).eq("id", share_id).execute() - if not data.data: - return None - return ExpenseShare(**data.data[0]) - - async def get_member_balance(self, discord_user_id: int) -> float: - """Tính số dư của một thành viên (số tiền phải trả - số tiền đã trả)""" - # Lấy tất cả expense shares của thành viên - data = await self.db.table("expense_shares").select("*").eq("member_id", discord_user_id).execute() - shares = [ExpenseShare(**share) for share in data.data] - - # Tính tổng số tiền phải trả - total_owed = sum(share.share_amount for share in shares) - # Tính tổng số tiền đã trả - total_paid = sum(share.share_amount for share in shares if share.is_paid) + async def get_other(self, discord_user_id: int) -> Optional[DiscordHomeDebt]: + """Lấy thông tin thành viên khác""" + try: + response = self.table.select('*').neq('user_id', discord_user_id).execute() + if response.data: + return DiscordHomeDebt(**response.data[0]) + return None + except Exception as e: + print(f"Error getting other member: {e}") + return None - return total_owed - total_paid \ No newline at end of file + async def create_home_debt(self, user_id: int, value: int) -> Optional[DiscordHomeDebt]: + """Tạo mới khoản nợ""" + try: + home_debt = DiscordHomeDebt(user_id=user_id, value=value) + response = self.table.insert(home_debt.dict(exclude_none=True)).execute() + return DiscordHomeDebt(**response.data[0]) + except Exception as e: + print(f"Error creating home debt: {e}") + return None + + async def update_home_debt(self, home_debt: DiscordHomeDebt) -> Optional[DiscordHomeDebt]: + """Cập nhật khoản nợ""" + try: + response = self.table.update(home_debt.dict(exclude_none=True)).eq('user_id', home_debt.user_id).execute() + return DiscordHomeDebt(**response.data[0]) + except Exception as e: + print(f"Error updating home debt: {e}") + return None + + async def get_all(self) -> List[DiscordHomeDebt]: + """Lấy tất cả khoản nợ""" + try: + response = self.table.select('*').execute() + return [DiscordHomeDebt(**home_debt) for home_debt in response.data] + except Exception as e: + print(f"Error getting all home debts: {e}") + return [] \ No newline at end of file diff --git a/repositories/user.py b/repositories/user.py deleted file mode 100644 index 05df1f7..0000000 --- a/repositories/user.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Optional -from infra.db import postgres - -class UserRepository: - def __init__(self): - self.exp_table = postgres.get_table('user_exp') - self.voice_table = postgres.get_table('voice_time') - - async def get_exp(self, user_id: int) -> int: - """Get user's experience points from database""" - try: - response = self.exp_table.select('exp_points').eq('user_id', user_id).execute() - if response.data: - return response.data[0]['exp_points'] - return 0 - except Exception as e: - print(f"Error getting user exp: {e}") - return 0 - - async def update_exp(self, user_id: int, exp_points: int) -> bool: - """Update user's experience points in database""" - try: - response = self.exp_table.upsert({'user_id': user_id, 'exp_points': exp_points}).execute() - return len(response.data) > 0 - except Exception as e: - print(f"Error updating user exp: {e}") - return False - - async def get_voice_time(self, user_id: int) -> Optional[str]: - """Get user's voice time from database""" - try: - response = self.voice_table.select('last_join_time').eq('user_id', user_id).execute() - if response.data: - return response.data[0]['last_join_time'] - return None - except Exception as e: - print(f"Error getting user voice time: {e}") - return None - - async def update_voice_time(self, user_id: int, join_time: str) -> bool: - """Update user's voice time in database""" - try: - response = self.voice_table.upsert({'user_id': user_id, 'last_join_time': join_time}).execute() - return len(response.data) > 0 - except Exception as e: - print(f"Error updating user voice time: {e}") - return False \ No newline at end of file