allow game noi_tu for multiple server/channel

This commit is contained in:
2025-09-10 10:07:27 +07:00
parent aa62667d89
commit 670c124943
4 changed files with 204 additions and 16 deletions
+48
View File
@@ -0,0 +1,48 @@
# Ví dụ cấu hình .env
## Format cơ bản
```bash
# Bot Token
BOT_TOKEN=your_bot_token_here
# Database Configuration
POSTGRES_URL=postgresql+asyncpg://user:password@localhost:5432/database_name
# Channel IDs
CHANNEL_HOME_DEBT_ID=1234567890123456789
# Game Nối Từ - Hỗ trợ nhiều channel với dấu phẩy
CHANNEL_NOI_TU_ID=1383424686708363336,9876543210987654321,5555555555555555555
```
## Các ví dụ khác nhau
### 1 channel
```bash
CHANNEL_NOI_TU_ID=1383424686708363336
```
### 2 channel
```bash
CHANNEL_NOI_TU_ID=1383424686708363336,9876543210987654321
```
### 3+ channel
```bash
CHANNEL_NOI_TU_ID=1383424686708363336,9876543210987654321,5555555555555555555
```
## Lưu ý quan trọng
-**Đúng**: `CHANNEL_NOI_TU_ID=123,456,789`
-**Sai**: `CHANNEL_NOI_TU_ID=123, 456, 789` (có khoảng trắng)
-**Sai**: `CHANNEL_NOI_TU_ID="123,456,789"` (có dấu ngoặc kép)
-**Sai**: `CHANNEL_NOI_TU_ID=123,abc,789` (có ký tự không phải số)
## Cách test
1. Cập nhật file `.env` với format mới
2. Restart bot
3. Kiểm tra log để xem bot có parse đúng channel IDs không
4. Test `!start` trong từng channel để đảm bảo hoạt động
+108
View File
@@ -0,0 +1,108 @@
# Hướng dẫn sử dụng Bot với nhiều Channel
## Tổng quan
Bot đã được refactor để hỗ trợ chạy game nối từ trên nhiều channel đồng thời với cùng một bot token.
## Cấu hình Environment Variables
### Format đơn giản với dấu phẩy
```bash
# Nhiều channel, phân cách bằng dấu phẩy (không có khoảng trắng)
CHANNEL_NOI_TU_ID=1383424686708363336,9876543210987654321,5555555555555555555
```
### Ví dụ thực tế
```bash
# 1 channel
CHANNEL_NOI_TU_ID=1383424686708363336
# 2 channel
CHANNEL_NOI_TU_ID=1383424686708363336,9876543210987654321
# 3 channel
CHANNEL_NOI_TU_ID=1383424686708363336,9876543210987654321,5555555555555555555
```
### Lưu ý
- Không có khoảng trắng xung quanh dấu phẩy
- Mỗi ID phải là số nguyên hợp lệ
- Bot sẽ tự động parse và hỗ trợ tất cả channel trong danh sách
## Cách hoạt động
### Game State riêng biệt
- Mỗi channel có game state hoàn toàn độc lập
- Channel A có thể đang chơi game với từ "âm cao" → "cao độ"
- Channel B có thể đang chơi game khác với từ "mặt trời" → "trời mưa"
- Không có xung đột giữa các game
### Database chung
- Tất cả channel sử dụng chung database PostgreSQL
- Từ điển nối từ được chia sẻ giữa các channel
- Admin có thể thêm/xóa từ từ bất kỳ channel nào
## Lệnh hỗ trợ
### Lệnh cơ bản
- `!start` - Bắt đầu game nối từ
- `!end` - Kết thúc game nối từ
### Lệnh admin (chỉ admin server)
- `!add <từ>` - Thêm từ mới vào database
- `!remove <từ>` - Xóa từ khỏi database
### Lệnh khác
- `/help` - Hiển thị danh sách lệnh cho channel hiện tại
## Ví dụ sử dụng
### Server A (Channel #game-1)
```
User: !start
Bot: 🎮 Trò chơi Nối Từ đã bắt đầu!
Từ đầu tiên: âm cao
User: cao độ
Bot: ✅
User: độ cao
Bot: ✅
```
### Server B (Channel #game-2) - Đồng thời
```
User: !start
Bot: 🎮 Trò chơi Nối Từ đã bắt đầu!
Từ đầu tiên: mặt trời
User: trời mưa
Bot: ✅
User: mưa gió
Bot: ✅
```
## Lưu ý quan trọng
1. **Bot Token**: Chỉ cần 1 bot token duy nhất
2. **Database**: Tất cả channel dùng chung database
3. **Game State**: Mỗi channel có game state riêng biệt
4. **Admin**: Admin của server có thể quản lý từ điển
5. **Timeout**: Mỗi game có timeout 30 giây độc lập
## Troubleshooting
### Bot không phản hồi trong channel
- Kiểm tra channel ID có trong `CHANNEL_NOI_TU_IDS` không
- Kiểm tra bot có quyền gửi tin nhắn trong channel không
### Game không bắt đầu được
- Kiểm tra xem đã có game đang chạy trong channel đó chưa
- Kiểm tra database có từ nào không
### Lỗi database
- Kiểm tra kết nối PostgreSQL
- Kiểm tra biến môi trường database
+26 -11
View File
@@ -1,7 +1,7 @@
import discord import discord
import asyncio import asyncio
from core.bot import bot, CHANNEL_NOI_TU_ID from core.bot import bot, CHANNEL_NOI_TU_IDS
from typing import Set from typing import Set, Dict
from datetime import datetime from datetime import datetime
from apps.score import incr from apps.score import incr
@@ -32,8 +32,14 @@ class NoiTuGame:
self.start_time = None # Thời gian bắt đầu game self.start_time = None # Thời gian bắt đầu game
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
# Khởi tạo game state # Dictionary để lưu game state cho từng channel
game = NoiTuGame() games: Dict[int, NoiTuGame] = {}
def get_game_for_channel(channel_id: int) -> NoiTuGame:
"""Lấy game state cho channel cụ thể"""
if channel_id not in games:
games[channel_id] = NoiTuGame()
return games[channel_id]
def is_admin(ctx): def is_admin(ctx):
"""Kiểm tra xem user có phải là admin không""" """Kiểm tra xem user có phải là admin không"""
@@ -41,7 +47,7 @@ def is_admin(ctx):
def is_correct_channel(ctx): def is_correct_channel(ctx):
"""Kiểm tra xem command có được thực hiện trong đúng channel không""" """Kiểm tra xem command có được thực hiện trong đúng channel không"""
return ctx.channel.id == CHANNEL_NOI_TU_ID return ctx.channel.id in CHANNEL_NOI_TU_IDS
def get_first_word(word: str) -> str: def get_first_word(word: str) -> str:
return word.strip().split()[0] if word else '' return word.strip().split()[0] if word else ''
@@ -58,7 +64,7 @@ def format_time_remaining(seconds: int) -> str:
return "⏰ Hết thời gian!" return "⏰ Hết thời gian!"
return f"⏰ Còn lại: {seconds} giây" return f"⏰ Còn lại: {seconds} giây"
async def update_timer_message(): async def update_timer_message(game: NoiTuGame):
"""Cập nhật tin nhắn thời gian mỗi 1 giây""" """Cập nhật tin nhắn thời gian mỗi 1 giây"""
start_time = game.last_message_time start_time = game.last_message_time
if not start_time: if not start_time:
@@ -101,6 +107,9 @@ async def start_game(ctx):
if not is_correct_channel(ctx): if not is_correct_channel(ctx):
return return
# Lấy game state cho channel này
game = get_game_for_channel(ctx.channel.id)
if game.is_active: if game.is_active:
await ctx.send("❌ Trò chơi đã đang diễn ra!") await ctx.send("❌ Trò chơi đã đang diễn ra!")
return return
@@ -125,7 +134,7 @@ async def start_game(ctx):
game.start_time = datetime.now() game.start_time = datetime.now()
# Tạo timeout task # Tạo timeout task
game.timeout_task = asyncio.create_task(game_timeout()) game.timeout_task = asyncio.create_task(game_timeout(game))
embed = discord.Embed( embed = discord.Embed(
title="🎮 Trò chơi Nối Từ đã bắt đầu!", title="🎮 Trò chơi Nối Từ đã bắt đầu!",
@@ -147,6 +156,9 @@ async def end_game(ctx):
if not is_correct_channel(ctx): if not is_correct_channel(ctx):
return return
# Lấy game state cho channel này
game = get_game_for_channel(ctx.channel.id)
if not game.is_active: if not game.is_active:
await ctx.send("❌ Không có trò chơi nào đang diễn ra!") await ctx.send("❌ Không có trò chơi nào đang diễn ra!")
return return
@@ -257,7 +269,7 @@ async def remove_word(ctx, *, word: str):
else: else:
await ctx.send("❌ Có lỗi xảy ra khi xóa từ!") await ctx.send("❌ Có lỗi xảy ra khi xóa từ!")
async def game_timeout(): async def game_timeout(game: NoiTuGame):
"""Xử lý timeout của game""" """Xử lý timeout của game"""
try: try:
# Đợi đúng 30 giây # Đợi đúng 30 giây
@@ -316,9 +328,12 @@ async def handle_game_message(message):
return return
# Chỉ xử lý trong channel được chỉ định # Chỉ xử lý trong channel được chỉ định
if message.channel.id != CHANNEL_NOI_TU_ID: if message.channel.id not in CHANNEL_NOI_TU_IDS:
return return
# Lấy game state cho channel này
game = get_game_for_channel(message.channel.id)
# Nếu game không active, bỏ qua # Nếu game không active, bỏ qua
if not game.is_active: if not game.is_active:
return return
@@ -372,7 +387,7 @@ async def handle_game_message(message):
# Reset timeout # Reset timeout
if game.timeout_task: if game.timeout_task:
game.timeout_task.cancel() game.timeout_task.cancel()
game.timeout_task = asyncio.create_task(game_timeout()) game.timeout_task = asyncio.create_task(game_timeout(game))
# Dừng timer task cũ nếu có # Dừng timer task cũ nếu có
if game.timer_task: if game.timer_task:
@@ -389,5 +404,5 @@ async def handle_game_message(message):
game.timer_message = await message.channel.send(embed=embed) game.timer_message = await message.channel.send(embed=embed)
# Bắt đầu task cập nhật thời gian # Bắt đầu task cập nhật thời gian
game.timer_task = asyncio.create_task(update_timer_message()) game.timer_task = asyncio.create_task(update_timer_message(game))
+22 -5
View File
@@ -21,7 +21,19 @@ home_debt_repo = HomeDebtRepository()
score_repo = ScoreRepository() score_repo = ScoreRepository()
CHANNEL_HOME_DEBT_ID = int(os.getenv('CHANNEL_HOME_DEBT_ID', 0)) CHANNEL_HOME_DEBT_ID = int(os.getenv('CHANNEL_HOME_DEBT_ID', 0))
CHANNEL_NOI_TU_ID = int(os.getenv('CHANNEL_NOI_TU_ID', 0))
# Hỗ trợ nhiều channel cho game nối từ với format: ID1,ID2,ID3
CHANNEL_NOI_TU_IDS = []
channel_noi_tu_env = os.getenv('CHANNEL_NOI_TU_ID', '')
if channel_noi_tu_env:
for channel_id in channel_noi_tu_env.split(','):
try:
CHANNEL_NOI_TU_IDS.append(int(channel_id.strip()))
except ValueError:
print(f"Invalid channel ID: {channel_id}")
# Giữ lại CHANNEL_NOI_TU_ID cho backward compatibility (lấy ID đầu tiên)
CHANNEL_NOI_TU_ID = CHANNEL_NOI_TU_IDS[0] if CHANNEL_NOI_TU_IDS else 0
@bot.tree.command(name='help', description='Show help') @bot.tree.command(name='help', description='Show help')
@@ -37,12 +49,17 @@ async def help(interaction: discord.Interaction):
"!hdtra <số tiền>", "!hdtra <số tiền>",
"!hdvay <số tiền>" "!hdvay <số tiền>"
], ],
CHANNEL_NOI_TU_ID: [
"!start",
"!end"
],
} }
# Thêm help cho tất cả channel nối từ
for channel_id in CHANNEL_NOI_TU_IDS:
help_commands[channel_id] = [
"!start",
"!end",
"!add <từ> (admin only)",
"!remove <từ> (admin only)"
]
# Kiểm tra xem channel có trong danh sách không # Kiểm tra xem channel có trong danh sách không
if channel.id in help_commands: if channel.id in help_commands:
embed = discord.Embed( embed = discord.Embed(