diff --git a/.cursor/environment.json b/.cursor/environment.json new file mode 100644 index 0000000..3b21c2d --- /dev/null +++ b/.cursor/environment.json @@ -0,0 +1,3 @@ +{ + "agentCanUpdateSnapshot": true +} \ No newline at end of file diff --git a/README_NOI_TU.md b/README_NOI_TU.md new file mode 100644 index 0000000..0285680 --- /dev/null +++ b/README_NOI_TU.md @@ -0,0 +1,79 @@ +# Trò Chơi Nối Từ - Discord Bot + +## Mô tả +Trò chơi nối từ là một mini-game trong Discord bot, cho phép người chơi nối các từ có 2 chữ cái theo quy tắc: chữ cái đầu của từ mới phải trùng với chữ cái cuối của từ trước. + +## Tính năng + +### 🎮 Game Commands +- `!start` - Bắt đầu trò chơi nối từ +- `!end` - Kết thúc trò chơi + +### 👑 Admin Commands +- `!add [nghĩa]` - Thêm từ mới vào từ điển (chỉ admin) +- `!remove ` - Xóa từ khỏi từ điển (chỉ admin) + +## Luật chơi + +1. **Từ hợp lệ**: Mỗi từ phải có đúng 2 chữ cái +2. **Quy tắc nối**: Chữ cái đầu của từ mới phải trùng với chữ cái cuối của từ trước +3. **Không lặp lại**: Không được sử dụng từ đã được nêu trước đó +4. **Thời gian**: Mỗi lượt có tối đa 30 giây để trả lời +5. **Từ điển**: Từ phải tồn tại trong cơ sở dữ liệu + +## Cách chơi + +1. Admin hoặc bất kỳ ai có thể bắt đầu game bằng lệnh `!start` +2. Bot sẽ chọn một từ ngẫu nhiên để bắt đầu +3. Người chơi gõ từ tiếp theo theo quy tắc nối từ +4. Bot sẽ phản hồi: + - ✅ Nếu từ hợp lệ + - ❌ Nếu từ không hợp lệ +5. Game tiếp tục cho đến khi hết thời gian hoặc không ai trả lời được + +## Ví dụ + +``` +Bot: Từ đầu tiên: "ma" +User1: "an" ✅ +Bot: Từ tiếp theo phải bắt đầu bằng: "N" +User2: "no" ✅ +Bot: Từ tiếp theo phải bắt đầu bằng: "O" +User3: "oi" ✅ +``` + +## Cài đặt + +### Environment Variables +Thêm vào file `.env`: +``` +CHANNEL_NOI_TU_ID=1234567890123456789 +``` + +### Database +Cần có bảng `dictionary_vietnamese_two_words` với cấu trúc: +- `id` (primary key) +- `word` (varchar, unique) +- `meaning` (varchar, nullable) + +## Quản lý từ điển + +### Thêm từ mới +``` +!add ma mẹ +!add an ăn +!add no nói +``` + +### Xóa từ +``` +!remove ma +!remove an +``` + +## Lưu ý + +- Chỉ hoạt động trong channel được chỉ định trong `CHANNEL_NOI_TU_ID` +- Chỉ admin mới có thể thêm/xóa từ +- Game tự động kết thúc sau 30 giây không có người trả lời +- Tất cả từ phải có trong cơ sở dữ liệu để được chấp nhận \ No newline at end of file diff --git a/apps/currency.py b/apps/currency.py new file mode 100644 index 0000000..d5ecc65 --- /dev/null +++ b/apps/currency.py @@ -0,0 +1,20 @@ +import discord +from discord.ext import commands +from core.bot import bot, currency_repo + +@bot.command(name="ncheck", description="Kiểm tra thông tin tài khoản của bạn") +async def currency_check(ctx: commands.Context): + """Kiểm tra thông tin tài khoản của bạn""" + # Check if user is registered in database. If not, create a new one + currency = await currency_repo.get(ctx.author.id) + if not currency: + await ctx.send("Bạn chưa có tài khoản currency") + await currency_repo.create(ctx.author.id, 0) + currency = await currency_repo.get(ctx.author.id) + + # Kiểm tra lại sau khi tạo + if not currency: + await ctx.send("Có lỗi xảy ra khi tạo tài khoản currency") + return + + await ctx.send(f"Thông tin tài khoản của bạn: {currency.balance}") \ No newline at end of file diff --git a/apps/experience.py b/apps/experience.py deleted file mode 100644 index 358ef75..0000000 --- a/apps/experience.py +++ /dev/null @@ -1,8 +0,0 @@ -# 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 diff --git a/apps/noi_tu.py b/apps/noi_tu.py new file mode 100644 index 0000000..1b85f36 --- /dev/null +++ b/apps/noi_tu.py @@ -0,0 +1,389 @@ +import discord +import os +import asyncio +from core.bot import bot +from discord.ext import commands +from typing import Dict, Set, Optional +from datetime import datetime, timedelta + +# Lazy load repository để tránh lỗi database connection +noi_tu_repo = None + +def get_noi_tu_repo(): + """Lazy load repository""" + global noi_tu_repo + if noi_tu_repo is None: + from repositories.noi_tu import NoiTuRepository + noi_tu_repo = NoiTuRepository() + return noi_tu_repo + +# Lấy channel ID từ environment +CHANNEL_NOI_TU_ID = int(os.getenv('CHANNEL_NOI_TU_ID', 0)) + +# Game state +class NoiTuGame: + def __init__(self): + self.is_active = False + self.current_word = "" + self.used_words: Set[str] = set() + self.last_player_id = None + self.last_player_name = None # Thêm tên người chơi cuối + self.last_message_time = None + self.timeout_task = None + self.channel = None + self.timer_message = None # Tin nhắn hiển thị thời gian + self.timer_task = None # Task cập nhật thời gian + self.start_time = None # Thời gian bắt đầu game + +# Khởi tạo game state +game = NoiTuGame() + +def is_admin(ctx): + """Kiểm tra xem user có phải là admin không""" + return ctx.author.guild_permissions.administrator + +def is_correct_channel(ctx): + """Kiểm tra xem command có được thực hiện trong đúng channel không""" + return ctx.channel.id == CHANNEL_NOI_TU_ID + +def get_first_word(word: str) -> str: + return word.strip().split()[0] if word else '' + +def get_last_word(word: str) -> str: + return word.strip().split()[-1] if word else '' + +def format_time_remaining(seconds: int) -> str: + """Format thời gian còn lại""" + if seconds <= 0: + return "⏰ Hết thời gian!" + return f"⏰ Còn lại: {seconds} giây" + +async def update_timer_message(): + """Cập nhật tin nhắn thời gian mỗi 1 giây""" + start_time = game.last_message_time + if not start_time: + return + + for remaining in range(30, -1, -1): # Đếm ngược từ 30 đến 0 + if not game.is_active or not game.timer_message: + break + + try: + # Cập nhật embed + embed = game.timer_message.embeds[0] + embed.title = format_time_remaining(remaining) + + # Thay đổi màu sắc theo thời gian + if remaining <= 5: + embed.color = discord.Color.red() + elif remaining <= 10: + embed.color = discord.Color.orange() + elif remaining <= 20: + embed.color = discord.Color.yellow() + else: + embed.color = discord.Color.blue() + + await game.timer_message.edit(embed=embed) + + # Dừng nếu hết thời gian + if remaining <= 0: + break + + await asyncio.sleep(1) # Đợi đúng 1 giây + + except Exception as e: + print(f"Error updating timer: {e}") + break + +@bot.command(name='start') +async def start_game(ctx): + """Bắt đầu trò chơi nối từ""" + if not is_correct_channel(ctx): + return + + if game.is_active: + await ctx.send("❌ Trò chơi đã đang diễn ra!") + return + + # Lấy repository + repo = get_noi_tu_repo() + + # Lấy từ ngẫu nhiên để bắt đầu + start_word = await repo.get_random_word() + if not start_word: + await ctx.send("❌ Không có từ nào trong cơ sở dữ liệu!") + return + + # Khởi tạo game + game.is_active = True + game.current_word = start_word + game.used_words = {start_word} + game.last_player_id = None + game.last_player_name = None + game.channel = ctx.channel + game.last_message_time = datetime.now() + game.start_time = datetime.now() + + # Tạo timeout task + game.timeout_task = asyncio.create_task(game_timeout()) + + embed = discord.Embed( + title="🎮 Trò chơi Nối Từ đã bắt đầu!", + description=f"Từ đầu tiên: **{start_word}**\n\n" + f"📝 **Luật chơi:**\n" + f"• Mỗi từ gồm 2 từ ghép tiếng Việt (VD: 'âm cao', 'cao độ')\n" + f"• Từ đầu của từ mới phải trùng với từ cuối của từ trước\n" + f"• Không được lặp lại từ đã dùng\n" + f"• Thời gian trả lời tối đa: 30 giây\n\n" + f"⏰ Thời gian bắt đầu: {datetime.now().strftime('%H:%M:%S')}", + color=discord.Color.green() + ) + + await ctx.send(embed=embed) + +@bot.command(name='end') +async def end_game(ctx): + """Kết thúc trò chơi nối từ""" + if not is_correct_channel(ctx): + return + + if not game.is_active: + await ctx.send("❌ Không có trò chơi nào đang diễn ra!") + return + + # Dừng các task + if game.timeout_task: + game.timeout_task.cancel() + if game.timer_task: + game.timer_task.cancel() + + # Tính thời gian chơi + game_duration = "" + if game.start_time: + duration = datetime.now() - game.start_time + minutes = int(duration.total_seconds() // 60) + seconds = int(duration.total_seconds() % 60) + game_duration = f"{minutes} phút {seconds} giây" + + # Tạo thông báo kết thúc + embed = discord.Embed( + title="🏁 Trò chơi Nối Từ đã kết thúc!", + description=f"📊 **Thống kê:**\n" + f"• Số từ đã sử dụng: {len(game.used_words)}\n" + f"• Từ cuối cùng: {game.current_word if game.current_word else 'N/A'}\n" + f"• Thời gian chơi: {game_duration}", + color=discord.Color.red() + ) + + # Thêm thông tin người chiến thắng + if game.last_player_name: + embed.add_field( + name="👑 Người chiến thắng", + value=f"**{game.last_player_name}** - Từ cuối: **{game.current_word}**", + inline=False + ) + + await ctx.send(embed=embed) + + # Reset game state + game.is_active = False + game.current_word = "" + game.used_words.clear() + game.last_player_id = None + game.last_player_name = None + game.channel = None + game.last_message_time = None + game.timeout_task = None + game.timer_message = None + game.timer_task = None + game.start_time = None + +@bot.command(name='add') +async def add_word(ctx, *, word: str, meaning: str = None): + """Thêm từ mới vào cơ sở dữ liệu (chỉ admin)""" + if not is_correct_channel(ctx): + return + + if not is_admin(ctx): + await ctx.send("❌ Chỉ admin mới có thể thêm từ!") + return + + # Kiểm tra từ có hợp lệ không + if not await get_noi_tu_repo().is_valid_word(word): + await ctx.send("❌ Từ phải có đúng 2 từ ghép!") + return + + # Kiểm tra từ đã tồn tại chưa + if await get_noi_tu_repo().is_exist(word): + await ctx.send(f"❌ Từ '{word}' đã tồn tại trong cơ sở dữ liệu!") + return + + # Thêm từ + success = await get_noi_tu_repo().add(word, meaning) + if success: + embed = discord.Embed( + title="✅ Thêm từ thành công!", + description=f"Từ: **{word}**\n" + f"Nghĩa: {meaning if meaning else 'Không có'}", + color=discord.Color.green() + ) + await ctx.send(embed=embed) + else: + await ctx.send("❌ Có lỗi xảy ra khi thêm từ!") + +@bot.command(name='remove') +async def remove_word(ctx, *, word: str): + """Xóa từ khỏi cơ sở dữ liệu (chỉ admin)""" + if not is_correct_channel(ctx): + return + + if not is_admin(ctx): + await ctx.send("❌ Chỉ admin mới có thể xóa từ!") + return + + # Kiểm tra từ có tồn tại không + if not await get_noi_tu_repo().is_exist(word): + await ctx.send(f"❌ Từ '{word}' không tồn tại trong cơ sở dữ liệu!") + return + + # Xóa từ + success = await get_noi_tu_repo().remove(word) + if success: + embed = discord.Embed( + title="✅ Xóa từ thành công!", + description=f"Đã xóa từ: **{word}**", + color=discord.Color.green() + ) + await ctx.send(embed=embed) + else: + await ctx.send("❌ Có lỗi xảy ra khi xóa từ!") + +async def game_timeout(): + """Xử lý timeout của game""" + try: + # Đợi đúng 30 giây + for i in range(30): + if not game.is_active: + return + await asyncio.sleep(1) + + # Kiểm tra lại trước khi timeout + if game.is_active and game.last_message_time: + time_diff = datetime.now() - game.last_message_time + if time_diff.total_seconds() >= 30: + # Game timeout + embed = discord.Embed( + title="⏰ Hết thời gian!", + description=f"Không ai trả lời trong 30 giây.\n" + f"Từ cuối cùng: **{game.current_word}**\n" + f"Trò chơi kết thúc!", + color=discord.Color.orange() + ) + + # Thêm thông tin người chiến thắng + if game.last_player_name: + embed.add_field( + name="👑 Người chiến thắng", + value=f"**{game.last_player_name}** - Từ cuối: **{game.current_word}**", + inline=False + ) + + if game.channel: + await game.channel.send(embed=embed) + + # Reset game + game.is_active = False + game.current_word = "" + game.used_words.clear() + game.last_player_id = None + game.last_player_name = None + game.channel = None + game.last_message_time = None + game.timeout_task = None + game.timer_message = None + game.timer_task = None + game.start_time = None + + except asyncio.CancelledError: + pass + +@bot.listen('on_message') +async def handle_game_message(message): + """Xử lý tin nhắn trong game""" + # Bỏ qua tin nhắn từ bot + if message.author.bot: + return + + # Chỉ xử lý trong channel được chỉ định + if message.channel.id != CHANNEL_NOI_TU_ID: + return + + # Nếu game không active, bỏ qua + if not game.is_active: + return + + # Kiểm tra xem tin nhắn có phải là từ không + word = message.content.strip().lower() + + # Kiểm tra từ có hợp lệ không + if not await get_noi_tu_repo().is_valid_word(word): + return + + # Kiểm tra người vừa trả lời có trả lời tiếp không + if game.last_player_id == message.author.id: + await message.add_reaction('❌') + await message.channel.send(f"❌ **{message.author.display_name}**, hãy để người khác trả lời!") + return + + # Kiểm tra từ có tồn tại trong DB không + if not await get_noi_tu_repo().is_exist(word): + await message.add_reaction('❌') + return + + # Kiểm tra từ đã được sử dụng chưa + if word in game.used_words: + await message.add_reaction('❌') + await message.channel.send(f"❌ Từ '{word}' đã được sử dụng!") + return + + # Kiểm tra quy tắc nối từ ghép + if game.current_word: + last = get_last_word(game.current_word) + first = get_first_word(word) + if first != last: + await message.add_reaction('❌') + await message.channel.send(f"❌ Từ mới phải bắt đầu bằng từ: '{last.upper()}'!") + return + + # Từ hợp lệ + await message.add_reaction('✅') + + # Cập nhật game state + game.current_word = word + game.used_words.add(word) + game.last_player_id = message.author.id + game.last_player_name = message.author.display_name # Lưu tên người chơi + game.last_message_time = datetime.now() + + # Reset timeout + if game.timeout_task: + game.timeout_task.cancel() + game.timeout_task = asyncio.create_task(game_timeout()) + + # Dừng timer task cũ nếu có + if game.timer_task: + game.timer_task.cancel() + + # Thông báo từ tiếp theo + next_hint = get_last_word(word).upper() + embed = discord.Embed( + title="⏰ Còn lại: 30 giây", + color=discord.Color.blue() + ) + + # Gửi tin nhắn mới và lưu reference + game.timer_message = await message.channel.send(embed=embed) + + # Bắt đầu task cập nhật thời gian + game.timer_task = asyncio.create_task(update_timer_message()) + diff --git a/core/bot.py b/core/bot.py index 3018f59..1348afa 100644 --- a/core/bot.py +++ b/core/bot.py @@ -16,12 +16,13 @@ intents.guilds = True bot = commands.Bot(command_prefix='!', intents=intents) # Initialize repositories -from repositories import ServerRepository, ChannelRepository, ChannelAppRepository, HomeDebtRepository +from repositories import ServerRepository, ChannelRepository, ChannelAppRepository, HomeDebtRepository, CurrencyRepository server_repo = ServerRepository() channel_repo = ChannelRepository() channel_app_repo = ChannelAppRepository() home_debt_repo = HomeDebtRepository() +currency_repo = CurrencyRepository() # Store user cooldowns from typing import Dict, Set diff --git a/core/events.py b/core/events.py index 1fc0a17..edfd881 100644 --- a/core/events.py +++ b/core/events.py @@ -1,21 +1,12 @@ import discord from core.bot import bot -from core.tasks import sync_commands +# from core.tasks import sync_commands # Using for sync commands to all guilds @bot.event async def on_ready(): print(f'{bot.user} đã tham gia vào server!') - await sync_commands.start() - print("Đã đồng bộ lệnh!") - -# @bot.event -# async def on_ready(): -# guild_id = 536422615649091595 -# guild_obj = discord.Object(id=guild_id) - -# try: -# synced = await bot.tree.sync(guild=guild_obj) -# print(f"🔁 Synced {len(synced)} command(s) to guild {guild_id}") -# except Exception as e: -# print(f"❌ Failed to sync commands to guild {guild_id}: {e}") \ No newline at end of file + # await sync_commands.start() + TEST_GUILD_ID = discord.Object(id=536422615649091595) + await bot.tree.sync(guild=TEST_GUILD_ID) + print("Đã đồng bộ lệnh!") \ No newline at end of file diff --git a/core/tasks.py b/core/tasks.py index 1c82a50..8c85aff 100644 --- a/core/tasks.py +++ b/core/tasks.py @@ -14,9 +14,4 @@ from core.bot import bot # 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 sync_commands(): - await bot.tree.sync() - print("Đã đồng bộ lệnh!") \ No newline at end of file +# print(f"Thêm {VOICE_EXP_POINTS_PER_MINUTE} điểm kinh nghiệm cho user {user_id} cho thời gian voice") \ No newline at end of file diff --git a/main.py b/main.py index a856888..6dbfc3a 100644 --- a/main.py +++ b/main.py @@ -2,7 +2,7 @@ import os from dotenv import load_dotenv from core import events, tasks from core.bot import bot -from apps import home_debt, server +from apps import home_debt, server, currency, noi_tu # Load environment variables load_dotenv() diff --git a/models/__init__.py b/models/__init__.py index 0cf3792..05728d0 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -2,5 +2,14 @@ from models.server import DiscordServer from models.channel import DiscordChannel from models.channel_app import DiscordChannelApp from models.home_debt import DiscordHomeDebt +from models.currency import DiscordCurrency +from models.noi_tu import DiscordNoiTu -__all__ = ['DiscordServer', 'DiscordChannel', 'DiscordChannelApp', 'DiscordHomeDebt'] \ No newline at end of file +__all__ = [ + 'DiscordServer', + 'DiscordChannel', + 'DiscordChannelApp', + 'DiscordHomeDebt', + 'DiscordCurrency', + 'DiscordNoiTu' +] \ No newline at end of file diff --git a/models/currency.py b/models/currency.py new file mode 100644 index 0000000..4719ad2 --- /dev/null +++ b/models/currency.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +@dataclass +class DiscordCurrency: + id: Optional[int] + user_id: int + balance: int + updated_at: datetime + + def __str__(self): + return f"ID: {self.id}, User ID: {self.user_id}, Balance: {self.balance}, Updated At: {self.updated_at}" + + def __repr__(self): + return self.__str__() + + def to_dict(self): + return { + "id": self.id, + "user_id": self.user_id, + "balance": self.balance, + "updated_at": self.updated_at + } \ No newline at end of file diff --git a/models/noi_tu.py b/models/noi_tu.py new file mode 100644 index 0000000..12c3532 --- /dev/null +++ b/models/noi_tu.py @@ -0,0 +1,19 @@ +from pydantic import BaseModel +from datetime import datetime +from typing import Optional +import json + +class DiscordNoiTu(BaseModel): + id: Optional[int] = None + word: str + meaning: Optional[str] = None + + def to_dict(self): + return { + 'id': self.id, + 'word': self.word, + 'meaning': self.meaning, + } + + def to_json(self): + return json.dumps(self.to_dict()) \ No newline at end of file diff --git a/repositories/__init__.py b/repositories/__init__.py index 313ac3c..2b383b0 100644 --- a/repositories/__init__.py +++ b/repositories/__init__.py @@ -2,5 +2,6 @@ from .server import ServerRepository from .channel import ChannelRepository from .channel_app import ChannelAppRepository from .home_debt import HomeDebtRepository +from .currency import CurrencyRepository -__all__ = ['ServerRepository', 'ChannelRepository', 'ChannelAppRepository', 'HomeDebtRepository'] \ No newline at end of file +__all__ = ['ServerRepository', 'ChannelRepository', 'ChannelAppRepository', 'HomeDebtRepository', 'CurrencyRepository'] \ No newline at end of file diff --git a/repositories/currency.py b/repositories/currency.py new file mode 100644 index 0000000..b901548 --- /dev/null +++ b/repositories/currency.py @@ -0,0 +1,111 @@ +from typing import List, Optional +from models.currency import DiscordCurrency +from infra.db import postgres +from datetime import datetime + +class CurrencyRepository: + def __init__(self): + self.table = postgres.get_table('discord_currency') + + async def get(self, user_id: int) -> Optional[DiscordCurrency]: + """Lấy thông tin thành viên""" + try: + response = self.table.select('*').eq('user_id', user_id).execute() + if response.data: + data = response.data[0] + return DiscordCurrency( + id=data.get('id'), + user_id=data['user_id'], + balance=data['balance'], + updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data['updated_at'] else datetime.now() + ) + return None + except Exception as e: + print(f"Error getting user: {e}") + return None + + async def create(self, user_id: int, balance: int) -> Optional[DiscordCurrency]: + """Tạo thông tin thành viên""" + try: + response = self.table.insert({'user_id': user_id, 'balance': balance}).execute() + if response.data: + data = response.data[0] + return DiscordCurrency( + id=data.get('id'), + user_id=data['user_id'], + balance=data['balance'], + updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data['updated_at'] else datetime.now() + ) + return None + except Exception as e: + print(f"Error creating user: {e}") + return None + + async def update(self, user_id: int, balance: int) -> Optional[DiscordCurrency]: + """Cập nhật thông tin thành viên""" + try: + response = self.table.update({'balance': balance}).eq('user_id', user_id).execute() + if response.data: + data = response.data[0] + return DiscordCurrency( + id=data.get('id'), + user_id=data['user_id'], + balance=data['balance'], + updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data['updated_at'] else datetime.now() + ) + return None + except Exception as e: + print(f"Error updating user: {e}") + return None + + async def get_all(self) -> List[DiscordCurrency]: + """Lấy tất cả thông tin thành viên""" + try: + response = self.table.select('*').execute() + currencies = [] + for data in response.data: + currencies.append(DiscordCurrency( + id=data.get('id'), + user_id=data['user_id'], + balance=data['balance'], + updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data['updated_at'] else datetime.now() + )) + return currencies + except Exception as e: + print(f"Error getting all users: {e}") + return [] + + async def get_all_with_balance(self) -> List[DiscordCurrency]: + """Lấy tất cả thông tin thành viên và số dư""" + try: + response = self.table.select('*').execute() + currencies = [] + for data in response.data: + currencies.append(DiscordCurrency( + id=data.get('id'), + user_id=data['user_id'], + balance=data['balance'], + updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data['updated_at'] else datetime.now() + )) + return currencies + except Exception as e: + print(f"Error getting all users with balance: {e}") + return [] + + # Get all with sort by balance + async def get_all_with_sort_by_balance(self) -> List[DiscordCurrency]: + """Lấy tất cả thông tin thành viên và sắp xếp theo số dư""" + try: + response = self.table.select('*').order('balance', desc=True).execute() + currencies = [] + for data in response.data: + currencies.append(DiscordCurrency( + id=data.get('id'), + user_id=data['user_id'], + balance=data['balance'], + updated_at=datetime.fromisoformat(data['updated_at'].replace('Z', '+00:00')) if data['updated_at'] else datetime.now() + )) + return currencies + except Exception as e: + print(f"Error getting all users with sort by balance: {e}") + return [] \ No newline at end of file diff --git a/repositories/noi_tu.py b/repositories/noi_tu.py new file mode 100644 index 0000000..10f1ebf --- /dev/null +++ b/repositories/noi_tu.py @@ -0,0 +1,86 @@ +from typing import List, Optional +from models.noi_tu import DiscordNoiTu +from infra.db import postgres +from datetime import datetime +import random + +class NoiTuRepository: + def __init__(self): + self.table = postgres.get_table('dictionary_vietnamese_two_words') + + async def is_exist(self, word: str) -> bool: + """Kiểm tra xem từ có tồn tại trong bảng không""" + try: + word = word.strip() + response = self.table.select('*').eq('word', word).execute() + return len(response.data) > 0 + except Exception as e: + print(f"Error checking if word exists: {e}") + return False + + async def add(self, word: str, meaning: Optional[str] = None) -> bool: + """Thêm từ vào bảng""" + try: + response = self.table.insert({ + 'word': word, + 'meaning': meaning + }).execute() + return response.data is not None + except Exception as e: + print(f"Error adding word: {e}") + return False + + async def remove(self, word: str) -> bool: + """Xóa từ khỏi bảng""" + try: + response = self.table.delete().eq('word', word).execute() + return response.data is not None + except Exception as e: + print(f"Error removing word: {e}") + return False + + async def get_random_word(self) -> Optional[str]: + """Lấy một từ ngẫu nhiên từ bảng""" + try: + response = self.table.select('word').execute() + if response.data and len(response.data) > 0: + # Lọc từ có đúng 2 từ ghép, mỗi từ chỉ gồm chữ cái + words = [] + for item in response.data: + word = item['word'].strip() + parts = word.split() + if len(parts) == 2 and all(part.isalpha() for part in parts): + words.append(word) + if words: + return random.choice(words) + return None + except Exception as e: + print(f"Error getting random word: {e}") + return None + + async def get_all_words(self) -> List[str]: + """Lấy tất cả từ trong bảng""" + try: + response = self.table.select('word').execute() + if response.data: + # Lọc từ có đúng 2 từ ghép, mỗi từ chỉ gồm chữ cái + words = [] + for item in response.data: + word = item['word'].strip() + parts = word.split() + if len(parts) == 2 and all(part.isalpha() for part in parts): + words.append(word) + return words + return [] + except Exception as e: + print(f"Error getting all words: {e}") + return [] + + async def is_valid_word(self, word: str) -> bool: + """Kiểm tra từ có hợp lệ không (2 từ ghép, mỗi từ chỉ gồm chữ cái)""" + word = word.strip() + parts = word.split() + if len(parts) != 2: + return False + return all(part.isalpha() for part in parts) + \ No newline at end of file