allow game noi_tu for multiple server/channel
This commit is contained in:
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user