Uploading all files.

This commit is contained in:
2025-09-12 18:22:14 +01:00
parent 2034d6b109
commit feb1dc51d2
25 changed files with 3694 additions and 2 deletions

173
README.md
View File

@@ -1,3 +1,172 @@
# duckhunt
# 🦆 DuckHunt IRC Bot
DuckHunt IRC game bot.
A feature-rich IRC game bot where players hunt ducks, upgrade weapons, trade items, and compete on leaderboards!
## 🚀 Features
### 🎯 Core Game Mechanics
- **Different Duck Types**: Common, Rare, Golden, and Armored ducks with varying rewards
- **Weapon System**: Multiple weapon types (Basic Gun, Shotgun, Rifle) with durability
- **Ammunition Types**: Standard, Rubber Bullets, Explosive Rounds
- **Weapon Attachments**: Laser Sight, Extended Magazine, Bipod
- **Accuracy & Reliability**: Skill-based hit/miss and reload failure mechanics
### 🏦 Economy System
- **Shop**: Buy/sell weapons, attachments, and upgrades
- **Banking**: Deposit coins for interest, take loans
- **Trading**: Trade coins and items with other players
- **Insurance**: Protect your equipment from damage
- **Hunting Licenses**: Unlock premium features and bonuses
### 👤 Player Progression
- **Hunter Levels**: Gain XP and level up for better abilities
- **Account System**: Register accounts with password authentication
- **Multiple Auth Methods**: Nick-based, hostmask, or registered account
- **Persistent Stats**: All progress saved to SQLite database
### 🏆 Social Features
- **Leaderboards**: Compete for top rankings
- **Duck Alerts**: Get notified when rare ducks spawn
- **Sabotage**: Interfere with other players (for a cost!)
- **Comprehensive Help**: Detailed command reference
## 📋 Requirements
- Python 3.7+
- asyncio support
- SQLite3 (included with Python)
## 🛠️ Installation
1. Clone or download the bot files
2. Edit `config.json` with your IRC server details:
```json
{
"server": "irc.libera.chat",
"port": 6697,
"nick": "DuckHuntBot",
"channels": ["#yourchannel"],
"ssl": true,
"sasl": false,
"password": "",
"duck_spawn_min": 60,
"duck_spawn_max": 300
}
```
3. Test the bot:
```bash
python test_bot.py
```
4. Run the bot:
```bash
python duckhunt.py
```
## 🎮 Commands
### 🎯 Hunting
- `!bang` - Shoot at a duck (accuracy-based hit/miss)
- `!reload` - Reload weapon (can fail based on reliability)
- `!catch` - Catch a duck with your hands
- `!bef` - Befriend a duck instead of shooting
### 🛒 Economy
- `!shop` - View available items
- `!buy <number>` - Purchase items
- `!sell <number>` - Sell equipment
- `!bank` - Banking services
- `!trade <player> <item> <amount>` - Trade with others
### 📊 Stats & Info
- `!stats` - Detailed combat statistics
- `!duckstats` - Personal hunting record
- `!leaderboard` - Top players ranking
- `!license` - Hunting license management
### ⚙️ Settings
- `!alerts` - Toggle duck spawn notifications
- `!help` - Complete command reference
### 🔐 Account System
- `/msg BotNick register <username> <password>` - Register account
- `/msg BotNick identify <username> <password>` - Login to account
### 🎮 Advanced
- `!sabotage <player>` - Sabotage another hunter's weapon
## 🗂️ File Structure
```
duckhunt/
├── src/
│ ├── duckhuntbot.py # Main IRC bot logic
│ ├── game.py # Game mechanics and commands
│ ├── db.py # SQLite database handling
│ ├── auth.py # Authentication system
│ ├── items.py # Duck types, weapons, attachments
│ ├── logging_utils.py # Colored logging setup
│ └── utils.py # IRC message parsing
├── config.json # Bot configuration
├── duckhunt.py # Main entry point
├── test_bot.py # Test script
└── README.md # This file
```
## 🎯 Game Balance
### Duck Types & Rewards
- **Common Duck** 🦆: 1 coin, 10 XP (70% spawn rate)
- **Rare Duck** 🦆✨: 3 coins, 25 XP (20% spawn rate)
- **Golden Duck** 🥇🦆: 10 coins, 50 XP (8% spawn rate)
- **Armored Duck** 🛡️🦆: 15 coins, 75 XP (2% spawn rate, 3 health)
### Weapon Stats
- **Basic Gun**: 0% accuracy bonus, 100 durability, 1 attachment slot
- **Shotgun**: -10% accuracy, 80 durability, 2 slots, spread shot
- **Rifle**: +20% accuracy, 120 durability, 3 slots
### Progression
- Players start with 100 coins and basic stats
- Level up by gaining XP from successful hunts
- Unlock better equipment and abilities as you progress
## 🔧 Configuration
Edit `config.json` to customize:
- IRC server and channels
- Duck spawn timing (min/max seconds)
- SSL and SASL authentication
- Bot nickname
## 🛡️ Security
- Passwords are hashed with PBKDF2
- Account data stored separately from temporary nick data
- Multiple authentication methods supported
- Database uses prepared statements to prevent injection
## 🐛 Troubleshooting
1. **Bot won't connect**: Check server/port in config.json
2. **Database errors**: Ensure write permissions in bot directory
3. **Commands not working**: Verify bot has joined the channel
4. **Test failures**: Run `python test_bot.py` to diagnose issues
## 🎖️ Contributing
Feel free to add new features:
- More duck types and weapons
- Additional mini-games
- Seasonal events
- Guild/team systems
- Advanced trading mechanics
## 📄 License
This bot is provided as-is for educational and entertainment purposes.
---
🦆 **Happy Hunting!** 🦆

Binary file not shown.

19
config.json Normal file
View File

@@ -0,0 +1,19 @@
{
"server": "irc.rizon.net",
"port": 6697,
"nick": "DuckHunt",
"channels": ["#computertech"],
"ssl": true,
"sasl": {
"enabled": true,
"username": "duckhunt",
"password": "duckhunt//789//"
},
"password": "",
"admins": ["peorth", "computertech"],
"duck_spawn_min": 1800,
"duck_spawn_max": 5400,
"duck_timeout_min": 45,
"duck_timeout_max": 75,
"_comment": "Run with: python3 simple_duckhunt.py | Admins config-only | Private admin: /msg DuckHuntBot restart|quit|launch | Duck timeout: random between min-max seconds"
}

9
config.json.example Normal file
View File

@@ -0,0 +1,9 @@
{
"server": "irc.example.com",
"port": 6697,
"use_ssl": true,
"nick": "DuckHunt",
"channels": ["#duckhunt"],
"sasl_username": "your_nick_here",
"sasl_password": "your_password_here"
}

205
duckhunt.json Normal file
View File

@@ -0,0 +1,205 @@
{
"players": {
"guest44288": {
"current_nick": "Guest44288",
"hostmask": "~Colby@Rizon-FFE0901B.ipcom.comunitel.net",
"coins": 102,
"caught": 1,
"ammo": 9,
"max_ammo": 10,
"chargers": 2,
"max_chargers": 2,
"xp": 15,
"accuracy": 85,
"reliability": 90,
"duck_start_time": null,
"gun_level": 1,
"luck": 0,
"gun_type": "pistol"
},
"colby": {
"coins": 113,
"caught": 9,
"ammo": 10,
"max_ammo": 10,
"chargers": 1,
"max_chargers": 2,
"xp": 44,
"accuracy": 85,
"reliability": 90,
"duck_start_time": null,
"gun_level": 1,
"luck": 0,
"gun_type": "pistol",
"gun_confiscated": false,
"jammed": false,
"jammed_count": 1,
"total_ammo_used": 4,
"shot_at": 3,
"reflex_shots": 3,
"total_reflex_time": 34.926591634750366,
"best_time": 7.363537788391113,
"karma": 3,
"wild_shots": 1
},
"colby_": {
"xp": 0,
"caught": 0,
"befriended": 0,
"missed": 0,
"ammo": 6,
"max_ammo": 6,
"chargers": 2,
"max_chargers": 2,
"accuracy": 65,
"reliability": 70,
"gun_confiscated": false,
"explosive_ammo": false,
"settings": {
"notices": true,
"private_messages": false
},
"golden_ducks": 0,
"karma": 0,
"deflection": 0,
"defense": 0,
"jammed": false,
"jammed_count": 0,
"deaths": 0,
"neutralized": 0,
"deflected": 0,
"best_time": 999.9,
"total_reflex_time": 0.0,
"reflex_shots": 0,
"wild_shots": 0,
"accidents": 0,
"total_ammo_used": 0,
"shot_at": 0,
"lucky_shots": 0,
"luck": 0,
"detector": 0,
"silencer": 0,
"sunglasses": 0,
"clothes": 0,
"grease": 0,
"brush": 0,
"mirror": 0,
"sand": 0,
"water": 0,
"sabotage": 0,
"life_insurance": 0,
"liability": 0,
"decoy": 0,
"bread": 0,
"duck_detector": 0,
"mechanical": 0
},
"computertech": {
"xp": 0,
"caught": 0,
"befriended": 0,
"missed": 0,
"ammo": 6,
"max_ammo": 6,
"chargers": 2,
"max_chargers": 2,
"accuracy": 65,
"reliability": 70,
"weapon": "pistol",
"gun_confiscated": false,
"explosive_ammo": false,
"settings": {
"notices": true,
"private_messages": false
},
"golden_ducks": 0,
"karma": 0,
"deflection": 0,
"defense": 0,
"jammed": false,
"jammed_count": 0,
"deaths": 0,
"neutralized": 0,
"deflected": 0,
"best_time": 999.9,
"total_reflex_time": 0.0,
"reflex_shots": 0,
"wild_shots": 0,
"accidents": 0,
"total_ammo_used": 0,
"shot_at": 0,
"lucky_shots": 0,
"luck": 0,
"detector": 0,
"silencer": 0,
"sunglasses": 0,
"clothes": 0,
"grease": 0,
"brush": 0,
"mirror": 0,
"sand": 0,
"water": 0,
"sabotage": 0,
"life_insurance": 0,
"liability": 0,
"decoy": 0,
"bread": 0,
"duck_detector": 0,
"mechanical": 0
},
"boliver": {
"xp": 6,
"caught": 0,
"befriended": 1,
"missed": 1,
"ammo": 5,
"max_ammo": 6,
"chargers": 2,
"max_chargers": 2,
"accuracy": 65,
"reliability": 70,
"weapon": "pistol",
"gun_confiscated": false,
"explosive_ammo": false,
"settings": {
"notices": true,
"private_messages": false
},
"golden_ducks": 0,
"karma": -1,
"deflection": 0,
"defense": 0,
"jammed": true,
"jammed_count": 1,
"deaths": 0,
"neutralized": 0,
"deflected": 0,
"best_time": 999.9,
"total_reflex_time": 0.0,
"reflex_shots": 0,
"wild_shots": 0,
"accidents": 0,
"total_ammo_used": 1,
"shot_at": 1,
"lucky_shots": 0,
"luck": 0,
"detector": 0,
"silencer": 0,
"sunglasses": 0,
"clothes": 0,
"grease": 0,
"brush": 0,
"mirror": 0,
"sand": 0,
"water": 0,
"sabotage": 0,
"life_insurance": 0,
"liability": 0,
"decoy": 0,
"bread": 0,
"duck_detector": 0,
"mechanical": 0
}
},
"last_save": "1757695515.2473938"
}

93
duckhunt.log Normal file
View File

@@ -0,0 +1,93 @@
[2025-09-11 18:30:40,346] INFO: Loaded 3 players from duckhunt.json
[2025-09-11 18:30:40,346] INFO: DuckHunt Bot initializing...
[2025-09-11 18:30:40,347] INFO: Starting DuckHunt Bot...
[2025-09-11 18:30:40,347] INFO: Loaded 3 players from duckhunt.json
[2025-09-11 18:30:40,420] INFO: Connecting to irc.rizon.net:6697 (SSL: True)
[2025-09-11 18:30:40,579] INFO: Connected successfully!
[2025-09-11 18:30:40,579] INFO: Registering as DuckHuntBot
[2025-09-11 18:30:41,067] INFO: Successfully registered!
[2025-09-11 18:30:41,067] INFO: Joining #colby
[2025-09-11 18:30:41,118] INFO: Successfully joined #colby
[2025-09-11 18:30:41,582] INFO: Starting duck spawning...
[2025-09-11 18:30:46,583] INFO: Admin spawned normal duck 965d7945 in #colby
[2025-09-11 18:30:46,583] INFO: Waiting 56m 37s for next duck
[2025-09-11 18:31:46,591] INFO: Duck 965d7945 timed out in #colby
[2025-09-11 18:38:33,894] INFO: Received SIGINT, initiating graceful shutdown...
[2025-09-11 18:38:34,097] INFO: Shutdown requested, stopping all tasks...
[2025-09-11 18:38:34,097] INFO: Starting cleanup process...
[2025-09-11 18:38:35,211] INFO: IRC connection closed
[2025-09-11 18:38:35,225] INFO: Final database save completed - 3 players saved
[2025-09-11 18:38:35,226] INFO: Cleanup completed successfully
[2025-09-11 18:38:35,234] INFO: DuckHunt Bot shutdown complete
[2025-09-11 18:38:53,536] INFO: Loaded 3 players from duckhunt.json
[2025-09-11 18:38:53,536] INFO: DuckHunt Bot initializing...
[2025-09-11 18:38:53,537] INFO: Starting DuckHunt Bot...
[2025-09-11 18:38:53,537] INFO: Loaded 3 players from duckhunt.json
[2025-09-11 18:38:53,607] INFO: Connecting to irc.rizon.net:6697 (SSL: True)
[2025-09-11 18:38:53,785] INFO: Connected successfully!
[2025-09-11 18:38:53,785] INFO: SASL authentication enabled
[2025-09-11 18:38:54,162] INFO: SASL capability available
[2025-09-11 18:38:54,221] INFO: SASL capability acknowledged
[2025-09-11 18:38:54,221] INFO: Authenticating via SASL as duckhunt
[2025-09-11 18:38:54,645] INFO: Server ready for SASL authentication
[2025-09-11 18:38:54,645] ERROR: SASL authentication failed!
[2025-09-11 18:38:54,645] INFO: Registering as DuckHunt
[2025-09-11 18:39:27,102] WARNING: Connection closed by server
[2025-09-11 18:39:27,103] WARNING: A main task completed unexpectedly
[2025-09-11 18:39:27,103] INFO: Starting cleanup process...
[2025-09-11 18:39:27,105] INFO: Final database save completed - 3 players saved
[2025-09-11 18:39:27,105] INFO: Cleanup completed successfully
[2025-09-11 18:39:27,106] INFO: DuckHunt Bot shutdown complete
[2025-09-11 18:41:03,279] INFO: Loaded 3 players from duckhunt.json
[2025-09-11 18:41:03,279] INFO: DuckHunt Bot initializing...
[2025-09-11 18:41:03,280] INFO: Starting DuckHunt Bot...
[2025-09-11 18:41:03,280] INFO: Loaded 3 players from duckhunt.json
[2025-09-11 18:41:03,354] INFO: Connecting to irc.rizon.net:6697 (SSL: True)
[2025-09-11 18:41:03,516] INFO: Connected successfully!
[2025-09-11 18:41:03,517] INFO: SASL authentication enabled
[2025-09-11 18:41:03,611] INFO: SASL capability available
[2025-09-11 18:41:03,660] INFO: SASL capability acknowledged
[2025-09-11 18:41:03,660] INFO: Authenticating via SASL as duckhunt
[2025-09-11 18:41:04,075] INFO: Server ready for SASL authentication
[2025-09-11 18:41:04,076] ERROR: SASL authentication failed! (904 - Invalid credentials or account not found)
[2025-09-11 18:41:04,076] ERROR: Attempted username: duckhunt
[2025-09-11 18:41:04,076] INFO: Registering as DuckHunt
[2025-09-11 18:41:36,030] WARNING: Connection closed by server
[2025-09-11 18:41:36,031] WARNING: A main task completed unexpectedly
[2025-09-11 18:41:36,031] INFO: Starting cleanup process...
[2025-09-11 18:41:36,032] INFO: Final database save completed - 3 players saved
[2025-09-11 18:41:36,032] INFO: Cleanup completed successfully
[2025-09-11 18:41:36,033] INFO: DuckHunt Bot shutdown complete
[2025-09-12 17:44:59,246] INFO: Loaded 3 players from duckhunt.json
[2025-09-12 17:44:59,247] INFO: DuckHunt Bot initializing...
[2025-09-12 17:44:59,248] INFO: Starting DuckHunt Bot...
[2025-09-12 17:44:59,249] INFO: Loaded 3 players from duckhunt.json
[2025-09-12 17:44:59,329] INFO: Connecting to irc.rizon.net:6697 (SSL: True)
[2025-09-12 17:44:59,997] INFO: Connected successfully!
[2025-09-12 17:44:59,997] INFO: SASL authentication enabled
[2025-09-12 17:45:00,375] INFO: SASL capability available
[2025-09-12 17:45:00,433] INFO: SASL capability acknowledged
[2025-09-12 17:45:00,433] INFO: Authenticating via SASL as duckhunt
[2025-09-12 17:45:00,851] INFO: Server ready for SASL authentication
[2025-09-12 17:45:00,851] ERROR: SASL authentication failed! (904 - Invalid credentials or account not found)
[2025-09-12 17:45:00,852] INFO: Falling back to NickServ identification...
[2025-09-12 17:45:00,852] ERROR: Attempted username: duckhunt
[2025-09-12 17:45:00,852] INFO: Registering as DuckHunt
[2025-09-12 17:45:00,968] ERROR: SASL authentication aborted! (906)
[2025-09-12 17:45:00,968] INFO: Falling back to NickServ identification...
[2025-09-12 17:45:00,969] INFO: Registering as DuckHunt
[2025-09-12 17:45:00,969] INFO: Successfully registered!
[2025-09-12 17:45:00,969] INFO: Attempting NickServ identification for duckhunt
[2025-09-12 17:45:01,971] INFO: NickServ identification commands sent
[2025-09-12 17:45:01,971] INFO: Joining #computertech
[2025-09-12 17:45:02,032] INFO: Successfully joined #computertech
[2025-09-12 17:45:03,001] INFO: Starting duck spawning...
[2025-09-12 17:45:08,003] INFO: Admin spawned normal duck c2950231 in #computertech
[2025-09-12 17:45:08,003] INFO: Waiting 58m 28s for next duck
[2025-09-12 17:45:09,063] INFO: Admin spawned normal duck d30a9198 in #computertech
[2025-09-12 17:45:14,112] WARNING: A main task completed unexpectedly
[2025-09-12 17:45:14,141] INFO: Starting cleanup process...
[2025-09-12 17:45:15,246] INFO: IRC connection closed
[2025-09-12 17:45:15,248] INFO: Final database save completed - 5 players saved
[2025-09-12 17:45:15,248] INFO: Cleanup completed successfully
[2025-09-12 17:45:15,249] INFO: DuckHunt Bot shutdown complete

37
duckhunt.py Normal file
View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""
Main entry point for DuckHunt Bot
"""
import asyncio
import json
import sys
import os
# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
from src.duckhuntbot import IRCBot
def main():
try:
with open('config.json') as f:
config = json.load(f)
bot = IRCBot(config)
bot.logger.info("🦆 Starting DuckHunt Bot...")
# Run the bot
asyncio.run(bot.run())
except KeyboardInterrupt:
print("\n🛑 Bot stopped by user")
except FileNotFoundError:
print("❌ config.json not found!")
sys.exit(1)
except Exception as e:
print(f"❌ Error: {e}")
sys.exit(1)
if __name__ == '__main__':
main()

0
run_bot.sh Normal file
View File

2221
simple_duckhunt.py Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

60
src/auth.py Normal file
View File

@@ -0,0 +1,60 @@
import hashlib
import secrets
from typing import Optional
from src.db import DuckDB
class AuthSystem:
def __init__(self, db: DuckDB):
self.db = db
self.authenticated_users = {} # nick -> account_name
self.pending_registrations = {} # nick -> temp_data
def hash_password(self, password: str, salt: Optional[str] = None) -> tuple:
if salt is None:
salt = secrets.token_hex(16)
hashed = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), 100000)
return hashed.hex(), salt
def verify_password(self, password: str, hashed: str, salt: str) -> bool:
test_hash, _ = self.hash_password(password, salt)
return test_hash == hashed
def register_account(self, username: str, password: str, nick: str, hostmask: str) -> bool:
# Check if account exists
existing = self.db.load_account(username)
if existing:
return False
hashed_pw, salt = self.hash_password(password)
account_data = {
'username': username,
'password_hash': hashed_pw,
'salt': salt,
'primary_nick': nick,
'hostmask': hostmask,
'created_at': None, # Set by DB
'auth_method': 'password' # 'password', 'nickserv', 'hostmask'
}
self.db.save_account(username, account_data)
return True
def authenticate(self, username: str, password: str, nick: str) -> bool:
account = self.db.load_account(username)
if not account:
return False
if self.verify_password(password, account['password_hash'], account['salt']):
self.authenticated_users[nick] = username
return True
return False
def get_account_for_nick(self, nick: str) -> str:
return self.authenticated_users.get(nick, "")
def is_authenticated(self, nick: str) -> bool:
return nick in self.authenticated_users
def logout(self, nick: str):
if nick in self.authenticated_users:
del self.authenticated_users[nick]

97
src/db.py Normal file
View File

@@ -0,0 +1,97 @@
import sqlite3
import json
import datetime
class DuckDB:
def __init__(self, db_path='duckhunt.db'):
self.conn = sqlite3.connect(db_path)
self.create_tables()
def create_tables(self):
with self.conn:
# Player data table
self.conn.execute('''CREATE TABLE IF NOT EXISTS players (
nick TEXT PRIMARY KEY,
data TEXT
)''')
# Account system table
self.conn.execute('''CREATE TABLE IF NOT EXISTS accounts (
username TEXT PRIMARY KEY,
data TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
# Leaderboards table
self.conn.execute('''CREATE TABLE IF NOT EXISTS leaderboard (
account TEXT,
stat_type TEXT,
value INTEGER,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (account, stat_type)
)''')
# Trading table
self.conn.execute('''CREATE TABLE IF NOT EXISTS trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_account TEXT,
to_account TEXT,
trade_data TEXT,
status TEXT DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)''')
def save_player(self, nick, data):
with self.conn:
self.conn.execute('''INSERT OR REPLACE INTO players (nick, data) VALUES (?, ?)''',
(nick, json.dumps(data)))
def load_player(self, nick):
cur = self.conn.cursor()
cur.execute('SELECT data FROM players WHERE nick=?', (nick,))
row = cur.fetchone()
return json.loads(row[0]) if row else None
def get_all_players(self):
cur = self.conn.cursor()
cur.execute('SELECT nick, data FROM players')
return {nick: json.loads(data) for nick, data in cur.fetchall()}
def save_account(self, username, data):
with self.conn:
self.conn.execute('''INSERT OR REPLACE INTO accounts (username, data) VALUES (?, ?)''',
(username, json.dumps(data)))
def load_account(self, username):
cur = self.conn.cursor()
cur.execute('SELECT data FROM accounts WHERE username=?', (username,))
row = cur.fetchone()
return json.loads(row[0]) if row else None
def update_leaderboard(self, account, stat_type, value):
with self.conn:
self.conn.execute('''INSERT OR REPLACE INTO leaderboard (account, stat_type, value) VALUES (?, ?, ?)''',
(account, stat_type, value))
def get_leaderboard(self, stat_type, limit=10):
cur = self.conn.cursor()
cur.execute('SELECT account, value FROM leaderboard WHERE stat_type=? ORDER BY value DESC LIMIT ?',
(stat_type, limit))
return cur.fetchall()
def save_trade(self, from_account, to_account, trade_data):
with self.conn:
cur = self.conn.cursor()
cur.execute('''INSERT INTO trades (from_account, to_account, trade_data) VALUES (?, ?, ?)''',
(from_account, to_account, json.dumps(trade_data)))
return cur.lastrowid
def get_pending_trades(self, account):
cur = self.conn.cursor()
cur.execute('''SELECT id, from_account, trade_data FROM trades
WHERE to_account=? AND status='pending' ''', (account,))
return [(trade_id, from_acc, json.loads(data)) for trade_id, from_acc, data in cur.fetchall()]
def complete_trade(self, trade_id):
with self.conn:
self.conn.execute('UPDATE trades SET status=? WHERE id=?', ('completed', trade_id))

0
src/duckhuntbot.py Normal file
View File

566
src/game.py Normal file
View File

@@ -0,0 +1,566 @@
import asyncio
import random
from src.items import DuckTypes, WeaponTypes, AmmoTypes, Attachments
from src.auth import AuthSystem
class DuckGame:
def __init__(self, bot, db):
self.bot = bot
self.config = bot.config
self.logger = getattr(bot, 'logger', None)
self.db = db
self.auth = AuthSystem(db)
self.duck_spawn_min = self.config.get('duck_spawn_min', 30)
self.duck_spawn_max = self.config.get('duck_spawn_max', 120)
self.ducks = {} # channel: duck dict or None
self.players = {} # nick: player dict
self.duck_alerts = set() # nicks who want duck alerts
def get_player(self, nick):
if nick in self.players:
return self.players[nick]
data = self.db.load_player(nick)
if data:
data['friends'] = set(data.get('friends', []))
self.players[nick] = data
return data
default = {
'ammo': 1, 'max_ammo': 1, 'friends': set(), 'caught': 0, 'coins': 100,
'accuracy': 70, 'reliability': 80, 'gun_oil': 0, 'scope': False,
'silencer': False, 'lucky_charm': False, 'xp': 0, 'level': 1,
'bank_account': 0, 'insurance': {'active': False, 'claims': 0},
'weapon': 'basic_gun', 'weapon_durability': 100, 'ammo_type': 'standard',
'attachments': [], 'hunting_license': {'active': False, 'expires': None},
'duck_alerts': False, 'auth_method': 'nick' # 'nick', 'hostmask', 'account'
}
self.players[nick] = default
return default
def save_player(self, nick, data):
self.players[nick] = data
data_to_save = dict(data)
data_to_save['friends'] = list(data_to_save.get('friends', []))
self.db.save_player(nick, data_to_save)
async def spawn_ducks_loop(self):
while True:
wait_time = random.randint(self.duck_spawn_min, self.duck_spawn_max)
if self.logger:
self.logger.info(f"Waiting {wait_time}s before next duck spawn.")
await asyncio.sleep(wait_time)
for chan in self.bot.channels:
duck = self.ducks.get(chan)
if not (duck and duck.get('alive')):
duck_type = DuckTypes.get_random_duck()
self.ducks[chan] = {
'alive': True,
'type': duck_type,
'health': duck_type['health'],
'max_health': duck_type['health']
}
if self.logger:
self.logger.info(f"{duck_type['name']} spawned in {chan}")
spawn_msg = f'\033[93m{duck_type["emoji"]} A {duck_type["name"]} appears! Type !bang, !catch, !bef, or !reload!\033[0m'
await self.bot.send_message(chan, spawn_msg)
# Alert subscribed players
if self.duck_alerts:
alert_msg = f"🦆 DUCK ALERT: {duck_type['name']} in {chan}!"
for alert_nick in self.duck_alerts:
try:
await self.bot.send_message(alert_nick, alert_msg)
except:
pass # User might be offline
async def handle_command(self, user, channel, message):
nick = user.split('!')[0] if user else 'unknown'
hostmask = user if user else 'unknown'
cmd = message.strip().lower()
if self.logger:
self.logger.info(f"{nick}@{channel}: {cmd}")
# Handle private message commands
if channel == self.bot.nick: # Private message
if cmd.startswith('identify '):
parts = cmd.split(' ', 2)
if len(parts) == 3:
await self.handle_identify(nick, parts[1], parts[2])
else:
await self.bot.send_message(nick, "Usage: identify <username> <password>")
return
elif cmd == 'register':
await self.bot.send_message(nick, "To register: /msg me register <username> <password>")
return
elif cmd.startswith('register '):
parts = cmd.split(' ', 2)
if len(parts) == 3:
await self.handle_register(nick, hostmask, parts[1], parts[2])
else:
await self.bot.send_message(nick, "Usage: register <username> <password>")
return
# Public channel commands
if cmd == '!bang':
await self.handle_bang(nick, channel)
elif cmd == '!reload':
await self.handle_reload(nick, channel)
elif cmd == '!bef':
await self.handle_bef(nick, channel)
elif cmd == '!catch':
await self.handle_catch(nick, channel)
elif cmd == '!shop':
await self.handle_shop(nick, channel)
elif cmd == '!duckstats':
await self.handle_duckstats(nick, channel)
elif cmd.startswith('!buy '):
item_num = cmd.split(' ', 1)[1]
await self.handle_buy(nick, channel, item_num)
elif cmd.startswith('!sell '):
item_num = cmd.split(' ', 1)[1]
await self.handle_sell(nick, channel, item_num)
elif cmd == '!stats':
await self.handle_stats(nick, channel)
elif cmd == '!help':
await self.handle_help(nick, channel)
elif cmd == '!leaderboard' or cmd == '!top':
await self.handle_leaderboard(nick, channel)
elif cmd == '!bank':
await self.handle_bank(nick, channel)
elif cmd == '!license':
await self.handle_license(nick, channel)
elif cmd == '!alerts':
await self.handle_alerts(nick, channel)
elif cmd.startswith('!trade '):
parts = cmd.split(' ', 2)
if len(parts) >= 2:
await self.handle_trade(nick, channel, parts[1:])
elif cmd.startswith('!sabotage '):
target = cmd.split(' ', 1)[1]
await self.handle_sabotage(nick, channel, target)
async def handle_bang(self, nick, channel):
player = self.get_player(nick)
duck = self.ducks.get(channel)
if player['ammo'] <= 0:
await self.bot.send_message(channel, f'\033[91m{nick}, you need to !reload!\033[0m')
return
if duck and duck.get('alive'):
player['ammo'] -= 1
# Calculate hit chance based on accuracy and upgrades
base_accuracy = player['accuracy']
if player['scope']:
base_accuracy += 15
if player['lucky_charm']:
base_accuracy += 10
hit_roll = random.randint(1, 100)
if hit_roll <= base_accuracy:
player['caught'] += 1
coins_earned = 1
if player['silencer']:
coins_earned += 1 # Bonus for silencer
player['coins'] += coins_earned
self.ducks[channel] = {'alive': False}
await self.bot.send_message(channel, f'\033[92m{nick} shot the duck! (+{coins_earned} coin{"s" if coins_earned > 1 else ""})\033[0m')
if self.logger:
self.logger.info(f"{nick} shot a duck in {channel}")
else:
await self.bot.send_message(channel, f'\033[93m{nick} missed the duck!\033[0m')
else:
await self.bot.send_message(channel, f'No duck to shoot, {nick}!')
self.save_player(nick, player)
async def handle_reload(self, nick, channel):
player = self.get_player(nick)
# Check gun reliability - can fail to reload
reliability = player['reliability']
if player['gun_oil'] > 0:
reliability += 15
player['gun_oil'] -= 1 # Gun oil gets used up
reload_roll = random.randint(1, 100)
if reload_roll <= reliability:
player['ammo'] = player['max_ammo']
await self.bot.send_message(channel, f'\033[94m{nick} reloaded successfully!\033[0m')
else:
await self.bot.send_message(channel, f'\033[91m{nick}\'s gun jammed while reloading! Try again.\033[0m')
self.save_player(nick, player)
async def handle_bef(self, nick, channel):
player = self.get_player(nick)
duck = self.ducks.get(channel)
if duck and duck.get('alive'):
player['friends'].add('duck')
self.ducks[channel] = {'alive': False}
await self.bot.send_message(channel, f'\033[96m{nick} befriended the duck!\033[0m')
if self.logger:
self.logger.info(f"{nick} befriended a duck in {channel}")
else:
await self.bot.send_message(channel, f'No duck to befriend, {nick}!')
self.save_player(nick, player)
async def handle_catch(self, nick, channel):
player = self.get_player(nick)
duck = self.ducks.get(channel)
if duck and duck.get('alive'):
player['caught'] += 1
self.ducks[channel] = {'alive': False}
await self.bot.send_message(channel, f'\033[92m{nick} caught the duck!\033[0m')
if self.logger:
self.logger.info(f"{nick} caught a duck in {channel}")
else:
await self.bot.send_message(channel, f'No duck to catch, {nick}!')
self.save_player(nick, player)
async def handle_shop(self, nick, channel):
player = self.get_player(nick)
coins = player['coins']
shop_items = [
"🔫 Scope - Improves accuracy by 15% (Cost: 5 coins)",
"🔇 Silencer - Bonus coin on successful shots (Cost: 8 coins)",
"🛢️ Gun Oil - Improves reload reliability for 3 reloads (Cost: 3 coins)",
"🍀 Lucky Charm - Improves accuracy by 10% (Cost: 10 coins)",
"📦 Ammo Upgrade - Increases max ammo capacity by 1 (Cost: 12 coins)",
"🎯 Accuracy Training - Permanently increases accuracy by 5% (Cost: 15 coins)",
"🔧 Gun Maintenance - Permanently increases reliability by 10% (Cost: 20 coins)"
]
shop_msg = f"\033[95m{nick}'s Shop (Coins: {coins}):\033[0m\n"
for i, item in enumerate(shop_items, 1):
shop_msg += f"{i}. {item}\n"
shop_msg += "Use !buy <number> to purchase an item!\n"
shop_msg += "Use !sell <number> to sell upgrades for coins!"
await self.bot.send_message(channel, shop_msg)
async def handle_duckstats(self, nick, channel):
player = self.get_player(nick)
stats = f"\033[95m{nick}'s Duck Stats:\033[0m\n"
stats += f"Caught: {player['caught']}\n"
stats += f"Coins: {player['coins']}\n"
stats += f"Accuracy: {player['accuracy']}%\n"
stats += f"Reliability: {player['reliability']}%\n"
stats += f"Max Ammo: {player['max_ammo']}\n"
stats += f"Gun Oil: {player['gun_oil']} uses left\n"
upgrades = []
if player['scope']: upgrades.append("Scope")
if player['silencer']: upgrades.append("Silencer")
if player['lucky_charm']: upgrades.append("Lucky Charm")
stats += f"Upgrades: {', '.join(upgrades) if upgrades else 'None'}\n"
stats += f"Friends: {', '.join(player['friends']) if player['friends'] else 'None'}\n"
await self.bot.send_message(channel, stats)
async def handle_buy(self, nick, channel, item_num):
player = self.get_player(nick)
try:
item_id = int(item_num)
except ValueError:
await self.bot.send_message(channel, f'{nick}, please specify a valid item number!')
return
shop_items = {
1: ("scope", 5, "Scope"),
2: ("silencer", 8, "Silencer"),
3: ("gun_oil", 3, "Gun Oil"),
4: ("lucky_charm", 10, "Lucky Charm"),
5: ("ammo_upgrade", 12, "Ammo Upgrade"),
6: ("accuracy_training", 15, "Accuracy Training"),
7: ("gun_maintenance", 20, "Gun Maintenance")
}
if item_id not in shop_items:
await self.bot.send_message(channel, f'{nick}, invalid item number!')
return
item_key, cost, item_name = shop_items[item_id]
if player['coins'] < cost:
await self.bot.send_message(channel, f'\033[91m{nick}, you need {cost} coins for {item_name}! (You have {player["coins"]})\033[0m')
return
# Process purchase
player['coins'] -= cost
if item_key == "scope":
if player['scope']:
await self.bot.send_message(channel, f'{nick}, you already have a scope!')
player['coins'] += cost # Refund
return
player['scope'] = True
elif item_key == "silencer":
if player['silencer']:
await self.bot.send_message(channel, f'{nick}, you already have a silencer!')
player['coins'] += cost
return
player['silencer'] = True
elif item_key == "gun_oil":
player['gun_oil'] += 3
elif item_key == "lucky_charm":
if player['lucky_charm']:
await self.bot.send_message(channel, f'{nick}, you already have a lucky charm!')
player['coins'] += cost
return
player['lucky_charm'] = True
elif item_key == "ammo_upgrade":
player['max_ammo'] += 1
elif item_key == "accuracy_training":
player['accuracy'] = min(95, player['accuracy'] + 5) # Cap at 95%
elif item_key == "gun_maintenance":
player['reliability'] = min(95, player['reliability'] + 10) # Cap at 95%
await self.bot.send_message(channel, f'\033[92m{nick} purchased {item_name}!\033[0m')
self.save_player(nick, player)
async def handle_sell(self, nick, channel, item_num):
player = self.get_player(nick)
try:
item_id = int(item_num)
except ValueError:
await self.bot.send_message(channel, f'{nick}, please specify a valid item number!')
return
sellable_items = {
1: ("scope", 3, "Scope"),
2: ("silencer", 5, "Silencer"),
3: ("gun_oil", 1, "Gun Oil (per use)"),
4: ("lucky_charm", 6, "Lucky Charm")
}
if item_id not in sellable_items:
await self.bot.send_message(channel, f'{nick}, invalid item number! Sellable items: 1-4')
return
item_key, sell_price, item_name = sellable_items[item_id]
if item_key == "scope":
if not player['scope']:
await self.bot.send_message(channel, f'{nick}, you don\'t have a scope to sell!')
return
player['scope'] = False
player['coins'] += sell_price
elif item_key == "silencer":
if not player['silencer']:
await self.bot.send_message(channel, f'{nick}, you don\'t have a silencer to sell!')
return
player['silencer'] = False
player['coins'] += sell_price
elif item_key == "gun_oil":
if player['gun_oil'] <= 0:
await self.bot.send_message(channel, f'{nick}, you don\'t have any gun oil to sell!')
return
player['gun_oil'] -= 1
player['coins'] += sell_price
elif item_key == "lucky_charm":
if not player['lucky_charm']:
await self.bot.send_message(channel, f'{nick}, you don\'t have a lucky charm to sell!')
return
player['lucky_charm'] = False
player['coins'] += sell_price
await self.bot.send_message(channel, f'\033[94m{nick} sold {item_name} for {sell_price} coins!\033[0m')
self.save_player(nick, player)
async def handle_stats(self, nick, channel):
player = self.get_player(nick)
# Calculate effective accuracy and reliability
effective_accuracy = player['accuracy']
if player['scope']:
effective_accuracy += 15
if player['lucky_charm']:
effective_accuracy += 10
effective_accuracy = min(100, effective_accuracy)
effective_reliability = player['reliability']
if player['gun_oil'] > 0:
effective_reliability += 15
effective_reliability = min(100, effective_reliability)
stats = f"\033[96m{nick}'s Combat Stats:\033[0m\n"
stats += f"🎯 Base Accuracy: {player['accuracy']}% (Effective: {effective_accuracy}%)\n"
stats += f"🔧 Base Reliability: {player['reliability']}% (Effective: {effective_reliability}%)\n"
stats += f"🔫 Ammo: {player['ammo']}/{player['max_ammo']}\n"
stats += f"💰 Coins: {player['coins']}\n"
stats += f"🦆 Ducks Caught: {player['caught']}\n"
stats += f"🛢️ Gun Oil: {player['gun_oil']} uses\n"
upgrades = []
if player['scope']: upgrades.append("🔭 Scope")
if player['silencer']: upgrades.append("🔇 Silencer")
if player['lucky_charm']: upgrades.append("🍀 Lucky Charm")
stats += f"⚡ Active Upgrades: {', '.join(upgrades) if upgrades else 'None'}\n"
friends = list(player['friends'])
stats += f"🤝 Friends: {', '.join(friends) if friends else 'None'}"
await self.bot.send_message(channel, stats)
async def handle_register(self, nick, hostmask, username, password):
if self.auth.register_account(username, password, nick, hostmask):
await self.bot.send_message(nick, f"✅ Account '{username}' registered successfully! Use 'identify {username} {password}' to login.")
else:
await self.bot.send_message(nick, f"❌ Account '{username}' already exists!")
async def handle_identify(self, nick, username, password):
if self.auth.authenticate(username, password, nick):
await self.bot.send_message(nick, f"✅ Authenticated as '{username}'!")
# Transfer nick-based data to account if exists
nick_data = self.db.load_player(nick)
if nick_data:
account_data = self.db.load_player(username)
if not account_data:
self.db.save_player(username, nick_data)
await self.bot.send_message(nick, "📊 Your progress has been transferred to your account!")
else:
await self.bot.send_message(nick, "❌ Invalid username or password!")
async def handle_help(self, nick, channel):
help_text = """
🦆 **DuckHunt Bot Commands** 🦆
**🎯 Hunting:**
• !bang - Shoot at a duck (requires ammo)
• !reload - Reload your weapon (can fail based on reliability)
• !catch - Catch a duck with your hands
• !bef - Befriend a duck instead of shooting
**🛒 Economy:**
• !shop - View available items for purchase
• !buy <number> - Purchase an item from the shop
• !sell <number> - Sell equipment for coins
• !bank - Access banking services (deposits, loans)
• !trade <player> <item> <amount> - Trade with other players
**📊 Stats & Info:**
• !stats - View detailed combat statistics
• !duckstats - View personal hunting statistics
• !leaderboard - View top players
• !license - Manage hunting license
**⚙️ Settings:**
• !alerts - Toggle duck spawn notifications
• !register - Register an account (via /msg)
• identify <user> <pass> - Login to account (via /msg)
**🎮 Advanced:**
• !sabotage <player> - Attempt to sabotage another hunter
• !help - Show this help message
💡 **Tips:**
- Different duck types give different rewards
- Weapon durability affects performance
- Insurance protects your equipment
- Level up to unlock better gear!
"""
await self.bot.send_message(nick, help_text)
async def handle_leaderboard(self, nick, channel):
leaderboard_data = self.db.get_leaderboard('caught', 10)
if not leaderboard_data:
await self.bot.send_message(channel, "No leaderboard data available yet!")
return
msg = "🏆 **Duck Hunting Leaderboard** 🏆\n"
for i, (account, caught) in enumerate(leaderboard_data, 1):
emoji = "🥇" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else f"{i}."
msg += f"{emoji} {account}: {caught} ducks\n"
await self.bot.send_message(channel, msg)
async def handle_bank(self, nick, channel):
player = self.get_player(nick)
bank_msg = f"""
🏦 **{nick}'s Bank Account** 🏦
💰 Cash on hand: {player['coins']} coins
🏛️ Bank balance: {player['bank_account']} coins
📈 Total wealth: {player['coins'] + player['bank_account']} coins
**Commands:**
• !bank deposit <amount> - Deposit coins (earns 2% daily interest)
• !bank withdraw <amount> - Withdraw coins
• !bank loan <amount> - Take a loan (10% interest)
"""
await self.bot.send_message(nick, bank_msg)
async def handle_license(self, nick, channel):
player = self.get_player(nick)
license_active = player['hunting_license']['active']
if license_active:
expires = player['hunting_license']['expires']
msg = f"🎫 Your hunting license is active until {expires}\n"
msg += "Licensed hunters get +25% coins and access to rare equipment!"
else:
msg = "🎫 You don't have a hunting license.\n"
msg += "Purchase one for 50 coins to get:\n"
msg += "• +25% coin rewards\n"
msg += "• Access to premium shop items\n"
msg += "• Reduced insurance costs\n"
msg += "Type '!buy license' to purchase"
await self.bot.send_message(channel, msg)
async def handle_alerts(self, nick, channel):
if nick in self.duck_alerts:
self.duck_alerts.remove(nick)
await self.bot.send_message(channel, f"🔕 {nick}: Duck alerts disabled")
else:
self.duck_alerts.add(nick)
await self.bot.send_message(channel, f"🔔 {nick}: Duck alerts enabled! You'll be notified when ducks spawn.")
async def handle_trade(self, nick, channel, args):
if len(args) < 3:
await self.bot.send_message(channel, f"{nick}: Usage: !trade <player> <item> <amount>")
return
target, item, amount = args[0], args[1], args[2]
player = self.get_player(nick)
try:
amount = int(amount)
except ValueError:
await self.bot.send_message(channel, f"{nick}: Amount must be a number!")
return
if item == "coins":
if player['coins'] < amount:
await self.bot.send_message(channel, f"{nick}: You don't have enough coins!")
return
trade_data = {
'type': 'coins',
'amount': amount,
'from_nick': nick
}
trade_id = self.db.save_trade(nick, target, trade_data)
await self.bot.send_message(channel, f"💸 Trade offer sent to {target}: {amount} coins")
await self.bot.send_message(target, f"💰 {nick} wants to trade you {amount} coins. Type '!accept {trade_id}' to accept!")
else:
await self.bot.send_message(channel, f"{nick}: Only coin trading is available currently!")
async def handle_sabotage(self, nick, channel, target):
player = self.get_player(nick)
target_player = self.get_player(target)
if player['coins'] < 5:
await self.bot.send_message(channel, f"{nick}: Sabotage costs 5 coins!")
return
success_chance = 60 + (player['level'] * 5)
if random.randint(1, 100) <= success_chance:
player['coins'] -= 5
target_player['weapon_durability'] = max(0, target_player['weapon_durability'] - 10)
await self.bot.send_message(channel, f"😈 {nick} successfully sabotaged {target}'s weapon!")
self.save_player(nick, player)
self.save_player(target, target_player)
else:
player['coins'] -= 5
await self.bot.send_message(channel, f"😅 {nick}'s sabotage attempt failed!")
self.save_player(nick, player)

124
src/items.py Normal file
View File

@@ -0,0 +1,124 @@
import random
class DuckTypes:
COMMON = {
'name': 'Common Duck',
'emoji': '🦆',
'rarity': 70,
'coins': 1,
'xp': 10,
'health': 1
}
RARE = {
'name': 'Rare Duck',
'emoji': '🦆✨',
'rarity': 20,
'coins': 3,
'xp': 25,
'health': 1
}
GOLDEN = {
'name': 'Golden Duck',
'emoji': '🥇🦆',
'rarity': 8,
'coins': 10,
'xp': 50,
'health': 2
}
ARMORED = {
'name': 'Armored Duck',
'emoji': '🛡️🦆',
'rarity': 2,
'coins': 15,
'xp': 75,
'health': 3
}
@classmethod
def get_random_duck(cls):
roll = random.randint(1, 100)
if roll <= cls.COMMON['rarity']:
return cls.COMMON
elif roll <= cls.COMMON['rarity'] + cls.RARE['rarity']:
return cls.RARE
elif roll <= cls.COMMON['rarity'] + cls.RARE['rarity'] + cls.GOLDEN['rarity']:
return cls.GOLDEN
else:
return cls.ARMORED
class WeaponTypes:
BASIC_GUN = {
'name': 'Basic Gun',
'accuracy_bonus': 0,
'durability': 100,
'max_durability': 100,
'repair_cost': 5,
'attachment_slots': 1
}
SHOTGUN = {
'name': 'Shotgun',
'accuracy_bonus': -10,
'durability': 80,
'max_durability': 80,
'repair_cost': 8,
'attachment_slots': 2,
'spread_shot': True # Can hit multiple ducks
}
RIFLE = {
'name': 'Rifle',
'accuracy_bonus': 20,
'durability': 120,
'max_durability': 120,
'repair_cost': 12,
'attachment_slots': 3
}
class AmmoTypes:
STANDARD = {
'name': 'Standard Ammo',
'damage': 1,
'accuracy_modifier': 0,
'cost': 1
}
RUBBER = {
'name': 'Rubber Bullets',
'damage': 0, # Non-lethal, for catching
'accuracy_modifier': 5,
'cost': 2,
'special': 'stun'
}
EXPLOSIVE = {
'name': 'Explosive Rounds',
'damage': 2,
'accuracy_modifier': -5,
'cost': 5,
'special': 'area_damage'
}
class Attachments:
LASER_SIGHT = {
'name': 'Laser Sight',
'accuracy_bonus': 10,
'cost': 15,
'durability_cost': 2 # Uses weapon durability faster
}
EXTENDED_MAG = {
'name': 'Extended Magazine',
'ammo_bonus': 2,
'cost': 20
}
BIPOD = {
'name': 'Bipod',
'accuracy_bonus': 15,
'reliability_bonus': 5,
'cost': 25
}

28
src/logging_utils.py Normal file
View File

@@ -0,0 +1,28 @@
import logging
import sys
from functools import partial
class ColorFormatter(logging.Formatter):
COLORS = {
'DEBUG': '\033[94m',
'INFO': '\033[92m',
'WARNING': '\033[93m',
'ERROR': '\033[91m',
'CRITICAL': '\033[95m',
'ENDC': '\033[0m',
}
def format(self, record):
color = self.COLORS.get(record.levelname, '')
endc = self.COLORS['ENDC']
msg = super().format(record)
return f"{color}{msg}{endc}"
def setup_logger(name='DuckHuntBot', level=logging.INFO):
logger = logging.getLogger(name)
handler = logging.StreamHandler(sys.stdout)
formatter = ColorFormatter('[%(asctime)s] %(levelname)s: %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(level)
logger.propagate = False
return logger

11
src/utils.py Normal file
View File

@@ -0,0 +1,11 @@
def parse_message(line):
prefix = ''
trailing = ''
if line.startswith(':'):
prefix, line = line[1:].split(' ', 1)
if ' :' in line:
line, trailing = line.split(' :', 1)
parts = line.split()
command = parts[0] if parts else ''
params = parts[1:] if len(parts) > 1 else []
return prefix, command, params, trailing

53
test_bot.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""
Test script for DuckHunt Bot
Run this to test the bot locally
"""
import asyncio
import json
import sys
import os
# Add src directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
from duckhuntbot import IRCBot
async def test_bot():
"""Test the bot initialization and basic functionality"""
try:
# Load config
with open('config.json') as f:
config = json.load(f)
# Create bot instance
bot = IRCBot(config)
print("✅ Bot initialized successfully!")
# Test database
bot.db.save_player("testuser", {"coins": 100, "caught": 5})
data = bot.db.load_player("testuser")
if data and data['coins'] == 100:
print("✅ Database working!")
else:
print("❌ Database test failed!")
# Test game logic
player = bot.game.get_player("testuser")
if player and 'coins' in player:
print("✅ Game logic working!")
else:
print("❌ Game logic test failed!")
print("🦆 DuckHunt Bot is ready to deploy!")
return True
except Exception as e:
print(f"❌ Error: {e}")
return False
if __name__ == '__main__':
success = asyncio.run(test_bot())
if not success:
sys.exit(1)

0
test_connection.py Normal file
View File