Fix database corruption handling and auto-creation

- Added datetime import to fix NameError
- Simplified database handling to create new file if missing or corrupted
- Removed backup functionality per user request
- Fixed duplicate method definitions
- Enhanced error handling throughout database operations
- Auto-creates duckhunt.json with proper structure on startup
This commit is contained in:
2025-10-05 19:18:46 +01:00
parent 00e129d2f3
commit 85fa8a9170
15 changed files with 1542 additions and 245 deletions

View File

@@ -1,46 +0,0 @@
# Enhanced Admin Configuration
For better security, update your `config.json` to use hostmask-based admin authentication:
## Current (Less Secure) - Nick Only:
```json
{
"admins": [
"peorth",
"computertech",
"colby"
]
}
```
## Recommended (More Secure) - Hostmask Based:
```json
{
"admins": [
{
"nick": "peorth",
"hostmask": "peorth!*@trusted.domain.com"
},
{
"nick": "computertech",
"hostmask": "computertech!*@*.isp.net"
},
{
"nick": "colby",
"hostmask": "colby!user@192.168.*.*"
}
]
}
```
## Migration Notes:
- The bot supports both formats for backward compatibility
- Nick-only authentication generates security warnings in logs
- Hostmask patterns use shell-style wildcards (* and ?)
- Consider using registered nick services for additional security
## Security Benefits:
- Prevents nick spoofing attacks
- Allows IP/hostname restrictions
- Provides audit logging of admin access
- Maintains backward compatibility during migration

View File

@@ -13,8 +13,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"mysteria": {
"nick": "Mysteria",
@@ -27,21 +40,25 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 5,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {
"1": 1
},
"temporary_effects": [
{
"type": "insurance",
"protection": "friendly_fire",
"expires_at": 1759416116.11089,
"name": "Hunter's Insurance"
}
]
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"wobotkoala": {
"nick": "WobotKoala",
@@ -54,14 +71,25 @@
"gun_confiscated": false,
"current_ammo": 4,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {
"7": 1
},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"boliver": {
"nick": "Boliver",
@@ -74,14 +102,25 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {
"7": 1
},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"papafrog": {
"nick": "PapaFrog",
@@ -96,8 +135,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"leetcode": {
"nick": "leetcode",
@@ -110,12 +162,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"overfl0w": {
"nick": "overfl0w",
@@ -130,8 +193,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"magicalpig": {
"nick": "MagicalPig",
@@ -144,12 +220,23 @@
"gun_confiscated": false,
"current_ammo": 3,
"magazines": 3,
"confiscated_ammo": 3,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 3,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"milambar": {
"nick": "Milambar",
@@ -164,8 +251,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"loulan": {
"nick": "loulan",
@@ -178,12 +278,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 3,
"confiscated_magazines": 2,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 3,
"confiscated_magazines": 2,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"h4": {
"nick": "h4",
@@ -196,12 +307,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"basti": {
"nick": "Basti",
@@ -216,8 +338,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"number1stunna": {
"nick": "Number1Stunna",
@@ -232,8 +367,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"wez": {
"nick": "wez",
@@ -248,8 +396,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"sander": {
"nick": "Sander",
@@ -264,8 +425,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"xternal": {
"nick": "xternal",
@@ -278,12 +452,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 4,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 4,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"wh": {
"nick": "wh",
@@ -298,8 +483,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"updog": {
"nick": "updog",
@@ -314,8 +512,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"hks": {
"nick": "Hks",
@@ -328,12 +539,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"wildting2": {
"nick": "wildting2",
@@ -348,8 +570,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"girafe2": {
"nick": "girafe2",
@@ -364,8 +599,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"kaitphone": {
"nick": "kaitphone",
@@ -378,12 +626,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 4,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 4,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"parabirb": {
"nick": "parabirb",
@@ -396,12 +655,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"deimos": {
"nick": "DEIMOS",
@@ -416,8 +686,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"twentytwo": {
"nick": "twentytwo",
@@ -430,12 +713,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"dg": {
"nick": "DG",
@@ -450,8 +744,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"helderheid": {
"nick": "HelderHeid",
@@ -466,8 +773,21 @@
"magazines": 2,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"neo_nemesis": {
"nick": "Neo_Nemesis",
@@ -482,8 +802,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"jeff_stacey": {
"nick": "jeff_stacey",
@@ -498,8 +831,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"p1x4l": {
"nick": "p1x4l",
@@ -512,12 +858,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 3,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 3,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"deadly": {
"nick": "deadly",
@@ -532,8 +889,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"alterego_": {
"nick": "AlterEgo_",
@@ -548,8 +918,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"peorth": {
"nick": "Peorth",
@@ -564,8 +947,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"magic": {
"nick": "MAGIC",
@@ -580,8 +976,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"koaladinosaur": {
"nick": "KoalaDinosaur",
@@ -596,8 +1005,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"knownsyntax": {
"nick": "KnownSyntax",
@@ -612,8 +1034,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"fifel": {
"nick": "fifel",
@@ -628,8 +1063,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"madafaka": {
"nick": "Madafaka",
@@ -642,12 +1090,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 3,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 3,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"antitheus": {
"nick": "antitheus",
@@ -660,12 +1119,23 @@
"gun_confiscated": false,
"current_ammo": 6,
"magazines": 3,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 5,
"confiscated_magazines": 3,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"l0op": {
"nick": "L0op",
@@ -680,8 +1150,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"norex": {
"nick": "norex",
@@ -696,8 +1179,21 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
},
"brandon": {
"nick": "brandon",
@@ -712,9 +1208,22 @@
"magazines": 3,
"bullets_per_magazine": 6,
"jam_chance": 15,
"confiscated_ammo": 0,
"confiscated_magazines": 0,
"inventory": {},
"temporary_effects": []
"temporary_effects": [],
"best_time": 0.0,
"worst_time": 0.0,
"total_time_hunting": 0.0,
"level": 1,
"xp_gained": 0,
"hp_remaining": 0,
"victim": "",
"xp_lost": 0,
"ammo": 0,
"max_ammo": 0,
"chargers": 0
}
},
"last_save": "1759345603.3433115"
"last_save": "1759518733.8347418"
}

View File

@@ -585,3 +585,241 @@
19:48:26.215 📘 INFO DuckHuntBot 💾 Database saved
19:48:26.422 📘 INFO DuckHuntBot 🔌 IRC connection closed
19:48:26.422 📘 INFO DuckHuntBot ✅ Bot shutdown complete
19:21:05.583 INFO INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log
19:21:05.584 INFO INFO DuckHuntBot Debug mode: ON
19:21:05.585 INFO INFO DuckHuntBot Log everything: YES
19:21:05.585 INFO INFO DuckHuntBot Unified format: YES
19:21:05.586 INFO INFO DuckHuntBot Console level: INFO
19:21:05.587 INFO INFO DuckHuntBot File level: DEBUG
19:21:05.588 INFO INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log
19:21:05.589 INFO INFO DuckHuntBot 🤖 Initializing DuckHunt Bot components...
19:21:05.590 INFO INFO DuckHuntBot.DB Loaded 2 players from duckhunt.json
19:21:05.594 INFO INFO SASL Unified logging system initialized: all logs in duckhunt.log
19:21:05.594 INFO INFO SASL Debug mode: ON
19:21:05.595 INFO INFO SASL Log everything: YES
19:21:05.595 INFO INFO SASL Unified format: YES
19:21:05.595 INFO INFO SASL Console level: INFO
19:21:05.600 INFO INFO SASL File level: DEBUG
19:21:05.603 INFO INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log
19:21:05.605 INFO INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby
19:21:05.608 INFO INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json
19:21:05.610 INFO INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json
19:21:05.610 INFO INFO DuckHuntBot Starting DuckHunt Bot...
19:21:05.758 INFO INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3)
19:21:06.064 INFO INFO DuckHuntBot Successfully connected to irc.rizon.net:6697
19:21:06.065 INFO INFO DuckHuntBot 🔐 Sending server password
19:21:06.066 INFO INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop.
19:21:08.087 INFO INFO DuckHuntBot Successfully registered with IRC server
19:21:10.364 INFO INFO DuckHuntBot 🛑 Received SIGTERM (Ctrl+C), shutting down immediately...
19:21:10.365 INFO INFO DuckHuntBot Cancelled 5 running tasks
19:21:10.386 INFO INFO DuckHuntBot.Game Duck spawning loop cancelled
19:21:10.386 INFO INFO DuckHuntBot.Game Duck timeout loop cancelled
19:21:10.386 INFO INFO DuckHuntBot Message loop cancelled
19:21:10.387 INFO INFO DuckHuntBot Message loop ended
19:21:10.387 INFO INFO DuckHuntBot 🛑 Main loop cancelled
19:21:10.387 INFO INFO DuckHuntBot.Game Game loops cancelled
19:21:10.410 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:21:10.410 INFO INFO DuckHuntBot Database saved
19:21:10.617 INFO INFO DuckHuntBot 🔌 IRC connection closed
19:21:10.620 INFO INFO DuckHuntBot Bot shutdown complete
19:23:31.803 INFO INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log
19:23:31.803 INFO INFO DuckHuntBot Debug mode: ON
19:23:31.803 INFO INFO DuckHuntBot Log everything: YES
19:23:31.804 INFO INFO DuckHuntBot Unified format: YES
19:23:31.804 INFO INFO DuckHuntBot Console level: INFO
19:23:31.804 INFO INFO DuckHuntBot File level: DEBUG
19:23:31.804 INFO INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log
19:23:31.804 INFO INFO DuckHuntBot 🤖 Initializing DuckHunt Bot components...
19:23:31.805 INFO INFO DuckHuntBot.DB Loaded 2 players from duckhunt.json
19:23:31.806 INFO INFO SASL Unified logging system initialized: all logs in duckhunt.log
19:23:31.806 INFO INFO SASL Debug mode: ON
19:23:31.806 INFO INFO SASL Log everything: YES
19:23:31.806 INFO INFO SASL Unified format: YES
19:23:31.806 INFO INFO SASL Console level: INFO
19:23:31.807 INFO INFO SASL File level: DEBUG
19:23:31.807 INFO INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log
19:23:31.807 INFO INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby
19:23:31.807 INFO INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json
19:23:31.808 INFO INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json
19:23:31.808 INFO INFO DuckHuntBot Starting DuckHunt Bot...
19:23:31.894 INFO INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3)
19:23:32.048 INFO INFO DuckHuntBot Successfully connected to irc.rizon.net:6697
19:23:32.048 INFO INFO DuckHuntBot 🔐 Sending server password
19:23:32.049 INFO INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop.
19:23:34.088 INFO INFO DuckHuntBot Successfully registered with IRC server
19:23:38.982 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:23:41.709 INFO INFO DuckHuntBot.Game Duck dropped Single Bullet for player ComputerTech
19:23:41.729 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:23:50.254 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:23:51.613 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:23:53.985 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:23:55.921 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:23:57.354 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:23:58.121 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:36:09.044 INFO INFO DuckHuntBot 🛑 Received SIGINT (Ctrl+C), shutting down immediately...
19:36:09.068 INFO INFO DuckHuntBot Cancelled 5 running tasks
19:36:09.087 INFO INFO DuckHuntBot.Game Duck spawning loop cancelled
19:36:09.088 INFO INFO DuckHuntBot.Game Duck timeout loop cancelled
19:36:09.089 INFO INFO DuckHuntBot Message loop cancelled
19:36:09.089 INFO INFO DuckHuntBot Message loop ended
19:36:09.090 INFO INFO DuckHuntBot 🛑 Main loop cancelled
19:36:09.094 INFO INFO DuckHuntBot.Game Game loops cancelled
19:36:09.121 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:36:09.135 INFO INFO DuckHuntBot Database saved
19:36:09.388 INFO INFO DuckHuntBot 🔌 IRC connection closed
19:36:09.391 INFO INFO DuckHuntBot Bot shutdown complete
20:08:28.271 INFO INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log
20:08:28.272 INFO INFO DuckHuntBot Debug mode: ON
20:08:28.272 INFO INFO DuckHuntBot Log everything: YES
20:08:28.273 INFO INFO DuckHuntBot Unified format: YES
20:08:28.273 INFO INFO DuckHuntBot Console level: INFO
20:08:28.273 INFO INFO DuckHuntBot File level: DEBUG
20:08:28.273 INFO INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log
20:08:28.273 INFO INFO DuckHuntBot 🤖 Initializing DuckHunt Bot components...
20:08:28.274 INFO INFO DuckHuntBot.DB Loaded 2 players from duckhunt.json
20:08:28.275 INFO INFO SASL Unified logging system initialized: all logs in duckhunt.log
20:08:28.275 INFO INFO SASL Debug mode: ON
20:08:28.275 INFO INFO SASL Log everything: YES
20:08:28.275 INFO INFO SASL Unified format: YES
20:08:28.276 INFO INFO SASL Console level: INFO
20:08:28.276 INFO INFO SASL File level: DEBUG
20:08:28.276 INFO INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log
20:08:28.276 INFO INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby
20:08:28.277 INFO INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json
20:08:28.278 INFO INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json
20:08:28.279 INFO INFO DuckHuntBot Starting DuckHunt Bot...
20:08:28.370 INFO INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3)
20:08:28.894 INFO INFO DuckHuntBot Successfully connected to irc.rizon.net:6697
20:08:28.895 INFO INFO DuckHuntBot 🔐 Sending server password
20:08:28.895 INFO INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop.
20:08:30.110 INFO INFO DuckHuntBot Successfully registered with IRC server
20:08:39.693 INFO INFO DuckHuntBot 🛑 Received SIGINT (Ctrl+C), shutting down immediately...
20:08:39.693 INFO INFO DuckHuntBot Cancelled 5 running tasks
20:08:39.746 INFO INFO DuckHuntBot.Game Duck timeout loop cancelled
20:08:39.747 INFO INFO DuckHuntBot Message loop cancelled
20:08:39.747 INFO INFO DuckHuntBot Message loop ended
20:08:39.748 INFO INFO DuckHuntBot 🛑 Main loop cancelled
20:08:39.749 INFO INFO DuckHuntBot.Game Duck spawning loop cancelled
20:08:39.749 INFO INFO DuckHuntBot.Game Game loops cancelled
20:08:39.768 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
20:08:39.768 INFO INFO DuckHuntBot Database saved
20:08:39.971 INFO INFO DuckHuntBot 🔌 IRC connection closed
20:08:39.972 INFO INFO DuckHuntBot Bot shutdown complete
20:08:44.634 INFO INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log
20:08:44.634 INFO INFO DuckHuntBot Debug mode: ON
20:08:44.634 INFO INFO DuckHuntBot Log everything: YES
20:08:44.634 INFO INFO DuckHuntBot Unified format: YES
20:08:44.635 INFO INFO DuckHuntBot Console level: INFO
20:08:44.635 INFO INFO DuckHuntBot File level: DEBUG
20:08:44.635 INFO INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log
20:08:44.635 INFO INFO DuckHuntBot 🤖 Initializing DuckHunt Bot components...
20:08:44.636 INFO INFO DuckHuntBot.DB Loaded 2 players from duckhunt.json
20:08:44.637 INFO INFO SASL Unified logging system initialized: all logs in duckhunt.log
20:08:44.637 INFO INFO SASL Debug mode: ON
20:08:44.637 INFO INFO SASL Log everything: YES
20:08:44.637 INFO INFO SASL Unified format: YES
20:08:44.638 INFO INFO SASL Console level: INFO
20:08:44.638 INFO INFO SASL File level: DEBUG
20:08:44.638 INFO INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log
20:08:44.638 INFO INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby
20:08:44.639 INFO INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json
20:08:44.639 INFO INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json
20:08:44.639 INFO INFO DuckHuntBot Starting DuckHunt Bot...
20:08:44.726 INFO INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3)
20:08:45.038 INFO INFO DuckHuntBot Successfully connected to irc.rizon.net:6697
20:08:45.038 INFO INFO DuckHuntBot 🔐 Sending server password
20:08:45.039 INFO INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop.
20:08:47.194 INFO INFO DuckHuntBot Successfully registered with IRC server
20:08:58.549 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 2 players [save_database:142]
19:38:43.828 INFO INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log
19:38:43.829 INFO INFO DuckHuntBot Debug mode: ON
19:38:43.829 INFO INFO DuckHuntBot Log everything: YES
19:38:43.829 INFO INFO DuckHuntBot Unified format: YES
19:38:43.829 INFO INFO DuckHuntBot Console level: INFO
19:38:43.829 INFO INFO DuckHuntBot File level: DEBUG
19:38:43.829 INFO INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log
19:38:43.830 INFO INFO DuckHuntBot 🤖 Initializing DuckHunt Bot components...
19:38:43.831 INFO INFO DuckHuntBot.DB Loaded 42 players from duckhunt.json
19:38:43.832 INFO INFO SASL Unified logging system initialized: all logs in duckhunt.log
19:38:43.832 INFO INFO SASL Debug mode: ON
19:38:43.832 INFO INFO SASL Log everything: YES
19:38:43.832 INFO INFO SASL Unified format: YES
19:38:43.832 INFO INFO SASL Console level: INFO
19:38:43.832 INFO INFO SASL File level: DEBUG
19:38:43.833 INFO INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log
19:38:43.833 INFO INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby
19:38:43.833 INFO INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json
19:38:43.834 INFO INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json
19:38:43.835 INFO INFO DuckHuntBot Starting DuckHunt Bot...
19:38:43.905 INFO INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3)
19:38:44.350 INFO INFO DuckHuntBot Successfully connected to irc.rizon.net:6697
19:38:44.353 INFO INFO DuckHuntBot 🔐 Sending server password
19:38:44.354 INFO INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop.
19:38:46.114 INFO INFO DuckHuntBot Successfully registered with IRC server
19:38:46.355 DEBUG DEBUG DuckHuntBot.Game Cleaned expired effects for mysteria [_clean_expired_effects:579]
19:38:56.847 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:38:57.430 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:38:57.945 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:38:58.593 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:38:59.335 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:38:59.949 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:39:00.540 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:39:01.158 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:39:01.836 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:15.791 INFO INFO DuckHuntBot 🛑 Received SIGINT (Ctrl+C), shutting down immediately...
19:40:15.794 INFO INFO DuckHuntBot Cancelled 5 running tasks
19:40:15.846 INFO INFO DuckHuntBot.Game Duck spawning loop cancelled
19:40:15.848 INFO INFO DuckHuntBot 🛑 Main loop cancelled
19:40:15.849 INFO INFO DuckHuntBot.Game Duck timeout loop cancelled
19:40:15.851 INFO INFO DuckHuntBot Message loop cancelled
19:40:15.853 INFO INFO DuckHuntBot Message loop ended
19:40:15.857 INFO INFO DuckHuntBot.Game Game loops cancelled
19:40:15.880 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 42 players [save_database:181]
19:40:15.881 INFO INFO DuckHuntBot Database saved
19:40:16.083 INFO INFO DuckHuntBot 🔌 IRC connection closed
19:40:16.084 INFO INFO DuckHuntBot Bot shutdown complete
19:40:16.577 INFO INFO DuckHuntBot Unified logging system initialized: all logs in duckhunt.log
19:40:16.577 INFO INFO DuckHuntBot Debug mode: ON
19:40:16.577 INFO INFO DuckHuntBot Log everything: YES
19:40:16.577 INFO INFO DuckHuntBot Unified format: YES
19:40:16.578 INFO INFO DuckHuntBot Console level: INFO
19:40:16.578 INFO INFO DuckHuntBot File level: DEBUG
19:40:16.578 INFO INFO DuckHuntBot Main log: /home/colby/duckhunt/logs/duckhunt.log
19:40:16.578 INFO INFO DuckHuntBot 🤖 Initializing DuckHunt Bot components...
19:40:16.579 INFO INFO DuckHuntBot.DB Loaded 42 players from duckhunt.json
19:40:16.580 INFO INFO SASL Unified logging system initialized: all logs in duckhunt.log
19:40:16.580 INFO INFO SASL Debug mode: ON
19:40:16.581 INFO INFO SASL Log everything: YES
19:40:16.581 INFO INFO SASL Unified format: YES
19:40:16.581 INFO INFO SASL Console level: INFO
19:40:16.581 INFO INFO SASL File level: DEBUG
19:40:16.581 INFO INFO SASL Main log: /home/colby/duckhunt/logs/duckhunt.log
19:40:16.581 INFO INFO DuckHuntBot Configured 3 admin(s): peorth, computertech, colby
19:40:16.582 INFO INFO DuckHuntBot.Levels Loaded 8 levels from /home/colby/duckhunt/levels.json
19:40:16.582 INFO INFO DuckHuntBot.Shop Loaded 9 shop items from /home/colby/duckhunt/shop.json
19:40:16.583 INFO INFO DuckHuntBot Starting DuckHunt Bot...
19:40:16.677 INFO INFO DuckHuntBot Attempting to connect to irc.rizon.net:6697 (attempt 1/3)
19:40:16.846 INFO INFO DuckHuntBot Successfully connected to irc.rizon.net:6697
19:40:16.847 INFO INFO DuckHuntBot 🔐 Sending server password
19:40:16.848 INFO INFO DuckHuntBot Bot is now running! Press Ctrl+C to stop.
19:40:18.103 INFO INFO DuckHuntBot Successfully registered with IRC server
19:40:29.675 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:30.120 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:30.553 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:30.971 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:31.429 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:31.923 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
19:40:32.519 WARNING WARNING DuckHuntBot Admin access granted via nick-only authentication: ComputerTech!ComputerTe@ComputerTech.Network
20:03:55.179 INFO INFO DuckHuntBot.Game Normal duck spawned in #ct
20:12:13.757 INFO INFO DuckHuntBot 🛑 Received SIGINT (Ctrl+C), shutting down immediately...
20:12:13.761 INFO INFO DuckHuntBot Cancelled 5 running tasks
20:12:13.829 INFO INFO DuckHuntBot.Game Duck timeout loop cancelled
20:12:13.830 INFO INFO DuckHuntBot Message loop cancelled
20:12:13.830 INFO INFO DuckHuntBot Message loop ended
20:12:13.831 INFO INFO DuckHuntBot.Game Duck spawning loop cancelled
20:12:13.831 INFO INFO DuckHuntBot 🛑 Main loop cancelled
20:12:13.834 INFO INFO DuckHuntBot.Game Game loops cancelled
20:12:13.866 DEBUG DEBUG DuckHuntBot.DB Database saved successfully with 42 players [save_database:181]
20:12:13.868 INFO INFO DuckHuntBot Database saved
20:12:14.093 INFO INFO DuckHuntBot 🔌 IRC connection closed
20:12:14.096 INFO INFO DuckHuntBot Bot shutdown complete

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

165
src/db.py
View File

@@ -1,12 +1,14 @@
"""
Simplified Database management for DuckHunt Bot
Focus on fixing missing field errors
Enhanced Database management for DuckHunt Bot
Focus on fixing missing field errors with improved error handling
"""
import json
import logging
import time
import os
from datetime import datetime
from .error_handling import with_retry, RetryConfig, ErrorRecovery, sanitize_user_input
class DuckDB:
@@ -17,45 +19,85 @@ class DuckDB:
self.bot = bot
self.players = {}
self.logger = logging.getLogger('DuckHuntBot.DB')
# Error recovery configuration
self.error_recovery = ErrorRecovery()
self.save_retry_config = RetryConfig(max_attempts=3, base_delay=0.5, max_delay=5.0)
self.load_database()
def load_database(self):
"""Load player data from JSON file with comprehensive error handling"""
def load_database(self) -> dict:
"""Load the database, creating it if it doesn't exist"""
try:
if os.path.exists(self.db_file):
with open(self.db_file, 'r', encoding='utf-8') as f:
data = json.load(f)
if not os.path.exists(self.db_file):
self.logger.info(f"Database file {self.db_file} not found, creating new one")
return self._create_default_database()
# Validate loaded data structure
with open(self.db_file, 'r') as f:
content = f.read().strip()
if not content:
self.logger.warning("Database file is empty, creating new database")
return self._create_default_database()
data = json.loads(content)
# Validate basic structure
if not isinstance(data, dict):
raise ValueError("Database root is not a dictionary")
players_data = data.get('players', {})
if not isinstance(players_data, dict):
raise ValueError("Players data is not a dictionary")
# Initialize metadata if missing
if 'metadata' not in data:
data['metadata'] = {
'version': '1.0',
'created': datetime.now().isoformat(),
'last_modified': datetime.now().isoformat()
}
# Validate each player entry and ensure required fields
valid_players = {}
for nick, player_data in players_data.items():
if isinstance(player_data, dict) and isinstance(nick, str):
# Sanitize and validate player data
valid_players[nick] = self._sanitize_player_data(player_data)
else:
self.logger.warning(f"Skipping invalid player entry: {nick}")
# Initialize players section if missing
if 'players' not in data:
data['players'] = {}
self.players = valid_players
self.logger.info(f"Loaded {len(self.players)} players from {self.db_file}")
# Update last_modified
data['metadata']['last_modified'] = datetime.now().isoformat()
else:
self.players = {}
self.logger.info("No existing database found, starting fresh")
self.logger.info(f"Successfully loaded database with {len(data.get('players', {}))} players")
return data
except (json.JSONDecodeError, UnicodeDecodeError) as e:
self.logger.error(f"Database file corrupted: {e}")
self.players = {}
except (json.JSONDecodeError, ValueError) as e:
self.logger.error(f"Database corruption detected: {e}. Creating new database.")
return self._create_default_database()
except Exception as e:
self.logger.error(f"Error loading database: {e}")
self.players = {}
return self._create_default_database()
def _create_default_database(self) -> dict:
"""Create a new default database file with proper structure"""
try:
default_data = {
"players": {},
"last_save": str(time.time()),
"version": "1.0",
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
"description": "DuckHunt Bot Player Database"
}
with open(self.db_file, 'w', encoding='utf-8') as f:
json.dump(default_data, f, indent=2, ensure_ascii=False, sort_keys=True)
self.logger.info(f"Created new database file: {self.db_file}")
return default_data
except Exception as e:
self.logger.error(f"Failed to create default database: {e}")
# Return a minimal valid structure even if file creation fails
return {
"players": {},
"last_save": str(time.time()),
"version": "1.0",
"created": time.strftime("%Y-%m-%d %H:%M:%S"),
"description": "DuckHunt Bot Player Database"
}
def _sanitize_player_data(self, player_data):
"""Sanitize and validate player data, ensuring ALL required fields exist"""
@@ -146,30 +188,53 @@ class DuckDB:
self.logger.error(f"Error sanitizing player data: {e}")
return self.create_player(player_data.get('nick', 'Unknown') if isinstance(player_data, dict) else 'Unknown')
@with_retry(RetryConfig(max_attempts=3, base_delay=0.5, max_delay=5.0),
exceptions=(OSError, PermissionError, IOError))
def save_database(self):
"""Save all player data to JSON file with comprehensive error handling"""
"""Save all player data to JSON file with retry logic and comprehensive error handling"""
return self._save_database_impl()
def _save_database_impl(self):
"""Internal implementation of database save"""
temp_file = f"{self.db_file}.tmp"
try:
# Prepare data with validation
data = {
'players': {},
'last_save': str(time.time())
'last_save': str(time.time()),
'version': '1.0'
}
# Validate and clean player data before saving
valid_count = 0
for nick, player_data in self.players.items():
if isinstance(nick, str) and isinstance(player_data, dict):
data['players'][nick] = self._sanitize_player_data(player_data)
try:
sanitized_nick = sanitize_user_input(nick, max_length=50)
data['players'][sanitized_nick] = self._sanitize_player_data(player_data)
valid_count += 1
except Exception as e:
self.logger.warning(f"Error processing player {nick} during save: {e}")
else:
self.logger.warning(f"Skipping invalid player data during save: {nick}")
if valid_count == 0:
raise ValueError("No valid player data to save")
# Write to temporary file first (atomic write)
with open(temp_file, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
json.dump(data, f, indent=2, ensure_ascii=False, sort_keys=True)
f.flush()
os.fsync(f.fileno())
# Verify temp file was written correctly
try:
with open(temp_file, 'r', encoding='utf-8') as f:
json.load(f) # Verify it's valid JSON
except json.JSONDecodeError:
raise IOError("Temporary file contains invalid JSON")
# Atomic replace
if os.name == 'nt': # Windows
if os.path.exists(self.db_file):
@@ -178,10 +243,12 @@ class DuckDB:
else: # Unix-like systems
os.rename(temp_file, self.db_file)
self.logger.debug(f"Database saved successfully with {len(data['players'])} players")
self.logger.debug(f"Database saved successfully with {valid_count} players")
return True
except Exception as e:
self.logger.error(f"Error saving database: {e}")
self.logger.error(f"Error in database save implementation: {e}")
raise # Re-raise for retry mechanism
finally:
# Clean up temp file if it still exists
try:
@@ -196,26 +263,42 @@ class DuckDB:
# Validate and sanitize nick
if not isinstance(nick, str) or not nick.strip():
self.logger.warning(f"Invalid nick provided: {nick}")
return None
return self.error_recovery.safe_execute(
lambda: self.create_player('Unknown'),
fallback={'nick': 'Unknown', 'xp': 0, 'ducks_shot': 0},
logger=self.logger
)
nick_lower = nick.lower().strip()[:50]
# Sanitize nick input
nick_clean = sanitize_user_input(nick, max_length=50,
allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-[]{}^`|\\')
nick_lower = nick_clean.lower().strip()
if not nick_lower:
self.logger.warning(f"Empty nick after sanitization: {nick}")
return self.create_player('Unknown')
if nick_lower not in self.players:
self.players[nick_lower] = self.create_player(nick)
self.players[nick_lower] = self.create_player(nick_clean)
else:
# Ensure existing players have all required fields
player = self.players[nick_lower]
if not isinstance(player, dict):
self.logger.warning(f"Invalid player data for {nick_lower}, recreating")
self.players[nick_lower] = self.create_player(nick)
self.players[nick_lower] = self.create_player(nick_clean)
else:
# Migrate and validate existing player data
self.players[nick_lower] = self._migrate_and_validate_player(player, nick)
# Migrate and validate existing player data with error recovery
validated = self.error_recovery.safe_execute(
lambda: self._migrate_and_validate_player(player, nick_clean),
fallback=self.create_player(nick_clean),
logger=self.logger
)
self.players[nick_lower] = validated
return self.players[nick_lower]
except Exception as e:
self.logger.error(f"Error getting player {nick}: {e}")
self.logger.error(f"Critical error getting player {nick}: {e}")
return self.create_player(nick if isinstance(nick, str) else 'Unknown')
def _migrate_and_validate_player(self, player, nick):

View File

@@ -12,6 +12,7 @@ from .game import DuckGame
from .sasl import SASLHandler
from .shop import ShopManager
from .levels import LevelManager
from .error_handling import ErrorRecovery, HealthChecker, sanitize_user_input, safe_format_message
class DuckHuntBot:
@@ -26,12 +27,19 @@ class DuckHuntBot:
self.logger.info("🤖 Initializing DuckHunt Bot components...")
# Initialize error recovery systems
self.error_recovery = ErrorRecovery()
self.health_checker = HealthChecker(check_interval=60.0)
self.db = DuckDB(bot=self)
self.game = DuckGame(self, self.db)
self.messages = MessageManager()
self.sasl_handler = SASLHandler(self, config)
# Set up health checks
self._setup_health_checks()
admins_list = self.get_config('admins', ['colby']) or ['colby']
self.admins = [admin.lower() for admin in admins_list]
self.logger.info(f"Configured {len(self.admins)} admin(s): {', '.join(self.admins)}")
@@ -42,6 +50,34 @@ class DuckHuntBot:
shop_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'shop.json')
self.shop = ShopManager(shop_file, self.levels)
def _setup_health_checks(self):
"""Set up health monitoring checks"""
try:
# Database health check
self.health_checker.add_check(
'database',
lambda: self.db is not None and len(self.db.players) >= 0,
critical=True
)
# IRC connection health check
self.health_checker.add_check(
'irc_connection',
lambda: self.writer is not None and not self.writer.is_closing(),
critical=True
)
# Message system health check
self.health_checker.add_check(
'messages',
lambda: self.messages is not None and len(self.messages.messages) > 0,
critical=False
)
self.logger.debug("Health checks configured")
except Exception as e:
self.logger.error(f"Error setting up health checks: {e}")
def get_config(self, path, default=None):
keys = path.split('.')
value = self.config
@@ -233,16 +269,64 @@ class DuckHuntBot:
return False
def send_message(self, target, msg):
"""Send message to target (channel or user) with error handling"""
"""Send message to target (channel or user) with enhanced error handling"""
if not isinstance(target, str) or not isinstance(msg, str):
self.logger.warning(f"Invalid message parameters: target={type(target)}, msg={type(msg)}")
return False
return self.error_recovery.safe_execute(
lambda: self._send_message_impl(target, msg),
fallback=False,
logger=self.logger
)
def _send_message_impl(self, target, msg):
"""Internal implementation of send_message"""
try:
sanitized_msg = msg.replace('\r', '').replace('\n', ' ').strip()
if not sanitized_msg:
# Sanitize target and message
safe_target = sanitize_user_input(target, max_length=100,
allowed_chars='#&+!abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-[]{}^`|\\')
safe_msg = sanitize_user_input(msg, max_length=400)
if not safe_target or not safe_msg:
self.logger.warning(f"Empty target or message after sanitization")
return False
# Split long messages to prevent IRC limits
max_msg_length = 400 # IRC message limit minus PRIVMSG overhead
if len(safe_msg) <= max_msg_length:
messages = [safe_msg]
else:
# Split into chunks
messages = []
words = safe_msg.split(' ')
current_msg = ''
for word in words:
if len(current_msg + ' ' + word) <= max_msg_length:
current_msg += (' ' if current_msg else '') + word
else:
if current_msg:
messages.append(current_msg)
current_msg = word[:max_msg_length] # Truncate very long words
if current_msg:
messages.append(current_msg)
# Send all message parts
success_count = 0
for i, message_part in enumerate(messages):
if i > 0: # Small delay between messages to avoid flooding
time.sleep(0.1)
if self.send_raw(f"PRIVMSG {safe_target} :{message_part}"):
success_count += 1
else:
self.logger.error(f"Failed to send message part {i+1}/{len(messages)}")
return success_count == len(messages)
return self.send_raw(f"PRIVMSG {target} :{sanitized_msg}")
except Exception as e:
self.logger.error(f"Error sanitizing/sending message: {e}")
@@ -322,8 +406,9 @@ class DuckHuntBot:
self.logger.error(f"Critical error in handle_message: {e}")
async def handle_command(self, user, channel, message):
"""Handle bot commands with comprehensive error handling"""
"""Handle bot commands with enhanced error handling and input validation"""
try:
# Validate input parameters
if not isinstance(message, str) or not message.startswith('!'):
return
@@ -331,8 +416,16 @@ class DuckHuntBot:
self.logger.warning(f"Invalid user/channel types: {type(user)}, {type(channel)}")
return
# Sanitize inputs
safe_message = sanitize_user_input(message, max_length=500)
safe_user = sanitize_user_input(user, max_length=200)
safe_channel = sanitize_user_input(channel, max_length=100)
if not safe_message.startswith('!'):
return
try:
parts = message[1:].split()
parts = safe_message[1:].split()
except Exception as e:
self.logger.warning(f"Error parsing command '{message}': {e}")
return
@@ -343,27 +436,39 @@ class DuckHuntBot:
cmd = parts[0].lower()
args = parts[1:] if len(parts) > 1 else []
try:
nick = user.split('!')[0] if '!' in user else user
if not nick:
self.logger.warning(f"Empty nick from user string: {user}")
return
except Exception as e:
self.logger.error(f"Error extracting nick from '{user}': {e}")
# Extract and validate nick with enhanced error handling
nick = self.error_recovery.safe_execute(
lambda: safe_user.split('!')[0] if '!' in safe_user else safe_user,
fallback='Unknown',
logger=self.logger
)
if not nick or nick == 'Unknown':
self.logger.warning(f"Could not extract valid nick from user string: {user}")
return
try:
player = self.db.get_player(nick)
# Sanitize nick further
nick = sanitize_user_input(nick, max_length=50,
allowed_chars='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-[]{}^`|\\')
# Get player data with error recovery
player = self.error_recovery.safe_execute(
lambda: self.db.get_player(nick),
fallback={'nick': nick, 'xp': 0, 'ducks_shot': 0, 'gun_confiscated': False},
logger=self.logger
)
if player is None:
player = {}
except Exception as e:
self.logger.error(f"Error getting player data for {nick}: {e}")
player = {}
player = {'nick': nick, 'xp': 0, 'ducks_shot': 0, 'gun_confiscated': False}
if channel.startswith('#'):
player['last_activity_channel'] = channel
# Update activity tracking safely
if safe_channel.startswith('#'):
try:
player['last_activity_channel'] = safe_channel
player['last_activity_time'] = time.time()
self.db.players[nick.lower()] = player
except Exception as e:
self.logger.warning(f"Error updating player activity for {nick}: {e}")
try:
if player.get('ignored', False) and not self.is_admin(user):
@@ -372,49 +477,142 @@ class DuckHuntBot:
self.logger.error(f"Error checking admin/ignore status: {e}")
return
await self._execute_command_safely(cmd, nick, channel, player, args, user)
await self._execute_command_safely(cmd, nick, safe_channel, player, args, safe_user)
except Exception as e:
self.logger.error(f"Critical error in handle_command: {e}")
async def _execute_command_safely(self, cmd, nick, channel, player, args, user):
"""Execute individual commands with error isolation"""
"""Execute individual commands with enhanced error isolation and user feedback"""
try:
# Sanitize command arguments
safe_args = []
for arg in args:
safe_arg = sanitize_user_input(str(arg), max_length=100)
if safe_arg:
safe_args.append(safe_arg)
# Execute command with error recovery
command_executed = False
if cmd == "bang":
await self.handle_bang(nick, channel, player)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_bang(nick, channel, player),
fallback=None,
logger=self.logger
)
elif cmd == "bef" or cmd == "befriend":
await self.handle_bef(nick, channel, player)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_bef(nick, channel, player),
fallback=None,
logger=self.logger
)
elif cmd == "reload":
await self.handle_reload(nick, channel, player)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_reload(nick, channel, player),
fallback=None,
logger=self.logger
)
elif cmd == "shop":
await self.handle_shop(nick, channel, player, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_shop(nick, channel, player, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "duckstats":
await self.handle_duckstats(nick, channel, player, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_duckstats(nick, channel, player, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "topduck":
await self.handle_topduck(nick, channel)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_topduck(nick, channel),
fallback=None,
logger=self.logger
)
elif cmd == "use":
await self.handle_use(nick, channel, player, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_use(nick, channel, player, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "give":
await self.handle_give(nick, channel, player, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_give(nick, channel, player, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "duckhelp":
await self.handle_duckhelp(nick, channel, player)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_duckhelp(nick, channel, player),
fallback=None,
logger=self.logger
)
elif cmd == "rearm" and self.is_admin(user):
await self.handle_rearm(nick, channel, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_rearm(nick, channel, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "disarm" and self.is_admin(user):
await self.handle_disarm(nick, channel, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_disarm(nick, channel, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "ignore" and self.is_admin(user):
await self.handle_ignore(nick, channel, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_ignore(nick, channel, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "unignore" and self.is_admin(user):
await self.handle_unignore(nick, channel, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_unignore(nick, channel, safe_args),
fallback=None,
logger=self.logger
)
elif cmd == "ducklaunch" and self.is_admin(user):
await self.handle_ducklaunch(nick, channel, args)
command_executed = True
await self.error_recovery.safe_execute_async(
lambda: self.handle_ducklaunch(nick, channel, safe_args),
fallback=None,
logger=self.logger
)
# If no command was executed, it might be an unknown command
if not command_executed:
self.logger.debug(f"Unknown command '{cmd}' from {nick}")
except Exception as e:
self.logger.error(f"Error executing command '{cmd}' for user {nick}: {e}")
self.logger.error(f"Critical error executing command '{cmd}' for user {nick}: {e}")
# Provide user-friendly error message
try:
error_msg = f"{nick} > An error occurred processing your command. Please try again."
if channel.startswith('#'):
error_msg = safe_format_message(
"{nick} > ⚠️ Something went wrong processing your command. Please try again in a moment.",
nick=nick
)
self.send_message(channel, error_msg)
else:
self.logger.debug("Skipping error message for private channel")
except Exception as send_error:
self.logger.error(f"Error sending error message: {send_error}")
self.logger.error(f"Error sending user error message: {send_error}")
def validate_target_player(self, target_nick, channel):
"""

262
src/error_handling.py Normal file
View File

@@ -0,0 +1,262 @@
"""
Enhanced error handling utilities for DuckHunt Bot
Includes retry mechanisms, circuit breakers, and graceful degradation
"""
import asyncio
import logging
import time
from functools import wraps
from typing import Callable, Any, Optional, Union
class RetryConfig:
"""Configuration for retry mechanisms"""
def __init__(self, max_attempts: int = 3, base_delay: float = 1.0,
max_delay: float = 60.0, exponential: bool = True):
self.max_attempts = max_attempts
self.base_delay = base_delay
self.max_delay = max_delay
self.exponential = exponential
class CircuitBreaker:
"""Circuit breaker pattern for preventing cascading failures"""
def __init__(self, failure_threshold: int = 5, timeout: float = 60.0):
self.failure_threshold = failure_threshold
self.timeout = timeout
self.failure_count = 0
self.last_failure_time = None
self.state = 'closed' # closed, open, half-open
self.logger = logging.getLogger('DuckHuntBot.CircuitBreaker')
def __call__(self, func: Callable) -> Callable:
@wraps(func)
async def wrapper(*args, **kwargs):
if self.state == 'open':
if self.last_failure_time is not None and time.time() - self.last_failure_time > self.timeout:
self.state = 'half-open'
self.logger.info("Circuit breaker moving to half-open state")
else:
raise Exception("Circuit breaker is open - operation blocked")
try:
result = await func(*args, **kwargs)
if self.state == 'half-open':
self.state = 'closed'
self.failure_count = 0
self.logger.info("Circuit breaker closed - service recovered")
return result
except Exception as e:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'open'
self.logger.warning(f"Circuit breaker opened after {self.failure_count} failures")
raise e
return wrapper
def with_retry(config: Optional[RetryConfig] = None,
exceptions: tuple = (Exception,)):
"""Decorator for adding retry logic to functions"""
if config is None:
config = RetryConfig()
def decorator(func: Callable) -> Callable:
@wraps(func)
async def async_wrapper(*args, **kwargs):
logger = logging.getLogger(f'DuckHuntBot.Retry.{func.__name__}')
for attempt in range(config.max_attempts):
try:
return await func(*args, **kwargs)
except exceptions as e:
if attempt == config.max_attempts - 1:
logger.error(f"Function {func.__name__} failed after {config.max_attempts} attempts: {e}")
raise
delay = config.base_delay
if config.exponential:
delay *= (2 ** attempt)
delay = min(delay, config.max_delay)
logger.warning(f"Attempt {attempt + 1}/{config.max_attempts} failed for {func.__name__}: {e}. Retrying in {delay}s")
await asyncio.sleep(delay)
return None
@wraps(func)
def sync_wrapper(*args, **kwargs):
logger = logging.getLogger(f'DuckHuntBot.Retry.{func.__name__}')
for attempt in range(config.max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == config.max_attempts - 1:
logger.error(f"Function {func.__name__} failed after {config.max_attempts} attempts: {e}")
raise
delay = config.base_delay
if config.exponential:
delay *= (2 ** attempt)
delay = min(delay, config.max_delay)
logger.warning(f"Attempt {attempt + 1}/{config.max_attempts} failed for {func.__name__}: {e}. Retrying in {delay}s")
time.sleep(delay)
return None
# Return appropriate wrapper based on whether function is async
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper
return decorator
class ErrorRecovery:
"""Error recovery and graceful degradation utilities"""
@staticmethod
def safe_execute(func: Callable, fallback: Any = None,
log_errors: bool = True, logger: Optional[logging.Logger] = None) -> Any:
"""Safely execute a function with fallback value on error"""
if logger is None:
logger = logging.getLogger('DuckHuntBot.ErrorRecovery')
try:
return func()
except Exception as e:
if log_errors:
logger.error(f"Error executing {func.__name__}: {e}")
return fallback
@staticmethod
async def safe_execute_async(func: Callable, fallback: Any = None,
log_errors: bool = True, logger: Optional[logging.Logger] = None) -> Any:
"""Safely execute an async function with fallback value on error"""
if logger is None:
logger = logging.getLogger('DuckHuntBot.ErrorRecovery')
try:
return await func()
except Exception as e:
if log_errors:
logger.error(f"Error executing {func.__name__}: {e}")
return fallback
@staticmethod
def validate_input(value: Any, validator: Callable, default: Any = None,
field_name: str = "input") -> Any:
"""Validate input with fallback to default"""
try:
if validator(value):
return value
else:
raise ValueError(f"Validation failed for {field_name}")
except Exception:
return default
class HealthChecker:
"""Health monitoring and alerting"""
def __init__(self, check_interval: float = 30.0):
self.check_interval = check_interval
self.checks = {}
self.logger = logging.getLogger('DuckHuntBot.Health')
def add_check(self, name: str, check_func: Callable, critical: bool = False):
"""Add a health check function"""
self.checks[name] = {
'func': check_func,
'critical': critical,
'last_success': None,
'failure_count': 0
}
async def run_checks(self) -> dict:
"""Run all health checks and return results"""
results = {}
for name, check in self.checks.items():
try:
result = await check['func']() if asyncio.iscoroutinefunction(check['func']) else check['func']()
check['last_success'] = time.time()
check['failure_count'] = 0
results[name] = {'status': 'healthy', 'result': result}
except Exception as e:
check['failure_count'] += 1
results[name] = {
'status': 'unhealthy',
'error': str(e),
'failure_count': check['failure_count']
}
if check['critical'] and check['failure_count'] >= 3:
self.logger.error(f"Critical health check '{name}' failed {check['failure_count']} times: {e}")
return results
def safe_format_message(template: str, **kwargs) -> str:
"""Safely format message templates with error handling"""
try:
return template.format(**kwargs)
except KeyError as e:
logger = logging.getLogger('DuckHuntBot.MessageFormat')
logger.error(f"Missing template variable {e} in message: {template[:100]}...")
# Try to provide safe fallback
safe_kwargs = {}
for key, value in kwargs.items():
try:
safe_kwargs[key] = str(value) if value is not None else ''
except Exception:
safe_kwargs[key] = ''
# Replace missing variables with placeholders
import re
def replace_missing(match):
var_name = match.group(1)
if var_name not in safe_kwargs:
return f"[{var_name}]"
return f"{{{var_name}}}"
safe_template = re.sub(r'\{([^}]+)\}', replace_missing, template)
try:
return safe_template.format(**safe_kwargs)
except Exception:
return f"[Message format error in template: {template[:50]}...]"
except Exception as e:
logger = logging.getLogger('DuckHuntBot.MessageFormat')
logger.error(f"Unexpected error formatting message: {e}")
return f"[Message error: {template[:50]}...]"
def sanitize_user_input(value: str, max_length: int = 100,
allowed_chars: Optional[str] = None) -> str:
"""Sanitize user input to prevent injection and errors"""
if not isinstance(value, str):
value = str(value)
# Limit length
value = value[:max_length]
# Remove/replace dangerous characters
value = value.replace('\r', '').replace('\n', ' ')
# Filter to allowed characters if specified
if allowed_chars:
value = ''.join(c for c in value if c in allowed_chars)
return value.strip()

View File

@@ -71,7 +71,8 @@ class MessageManager:
}
def get(self, key: str, **kwargs) -> str:
"""Get a formatted message by key with color placeholder replacement"""
"""Get a formatted message by key with enhanced error handling"""
try:
if key not in self.messages:
return f"[Missing message: {key}]"
@@ -93,13 +94,65 @@ class MessageManager:
placeholder = "{" + color_name + "}"
message = message.replace(placeholder, color_code)
# Format with provided variables
# Sanitize kwargs to prevent injection and ensure all values are safe
safe_kwargs = {}
for k, v in kwargs.items():
try:
return message.format(**kwargs)
# Sanitize key and value
safe_key = str(k)[:50] if k is not None else 'unknown'
if isinstance(v, (int, float)):
safe_kwargs[safe_key] = v
elif v is None:
safe_kwargs[safe_key] = ''
else:
# Sanitize string values
safe_value = str(v)[:200] # Limit length
safe_value = safe_value.replace('\r', '').replace('\n', ' ') # Remove newlines
safe_kwargs[safe_key] = safe_value
except Exception:
safe_kwargs[str(k)] = '[error]'
# Format with provided variables using safe formatting
try:
return message.format(**safe_kwargs)
except KeyError as e:
return f"[Message format error: {e}]"
# Try to identify missing keys and provide defaults
missing_key = str(e).strip("'\"")
# Common defaults for missing keys
defaults = {
'nick': 'Player',
'xp_gained': 0,
'ducks_shot': 0,
'ducks_befriended': 0,
'hp_remaining': 0,
'ammo': 0,
'max_ammo': 0,
'magazines': 0,
'target': 'Unknown',
'victim': 'someone',
'xp_lost': 0,
'xp': 0
}
# Add default for missing key
if missing_key in defaults:
safe_kwargs[missing_key] = defaults[missing_key]
else:
safe_kwargs[missing_key] = f'[{missing_key}]'
try:
return message.format(**safe_kwargs)
except Exception:
return f"[Format error in {key}: missing {missing_key}]"
except ValueError as e:
return f"[Format error in {key}: {e}]"
except Exception as e:
return f"[Message error: {e}]"
return f"[Message error in {key}: {e}]"
except Exception as e:
return f"[Critical message error: {e}]"
def reload(self):
"""Reload messages from file"""