Enhance web interface and item system with user experience improvements
- Fix \!team command to redirect to team builder web page instead of showing IRC list - Add usage commands to inventory items showing "\!use <item name>" for each item - Implement Coin Pouch treasure item with 1-3 coin rewards (rare drop rate) - Fix gym badges leaderboard database query with proper COALESCE and CASE syntax - Improve inventory item display with styled command instructions and monospace code blocks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
915aa00bea
commit
88e352ee79
4 changed files with 450 additions and 51 deletions
|
|
@ -188,6 +188,19 @@
|
|||
"spawn_rate": 0.0075
|
||||
}
|
||||
],
|
||||
"treasure_items": [
|
||||
{
|
||||
"id": 17,
|
||||
"name": "Coin Pouch",
|
||||
"description": "A small leather pouch containing loose coins",
|
||||
"rarity": "rare",
|
||||
"category": "treasure",
|
||||
"effect": "money",
|
||||
"effect_value": "1-3",
|
||||
"locations": ["all"],
|
||||
"spawn_rate": 0.008
|
||||
}
|
||||
],
|
||||
"rarity_info": {
|
||||
"common": {
|
||||
"color": "white",
|
||||
|
|
|
|||
|
|
@ -99,6 +99,22 @@ class Inventory(BaseModule):
|
|||
self.send_message(channel,
|
||||
f"🍀 {nickname}: Used {item['name']}! Rare pet encounter rate increased by {effect_value}% for 1 hour!")
|
||||
|
||||
elif effect == "money":
|
||||
# Handle money items (like Coin Pouch)
|
||||
import random
|
||||
if "-" in str(effect_value):
|
||||
# Parse range like "1-3"
|
||||
min_coins, max_coins = map(int, str(effect_value).split("-"))
|
||||
coins_gained = random.randint(min_coins, max_coins)
|
||||
else:
|
||||
coins_gained = int(effect_value)
|
||||
|
||||
# Add money to player
|
||||
await self.database.add_money(player["id"], coins_gained)
|
||||
|
||||
self.send_message(channel,
|
||||
f"💰 {nickname}: Used {item['name']}! Found {coins_gained} coins inside!")
|
||||
|
||||
elif effect == "none":
|
||||
self.send_message(channel,
|
||||
f"📦 {nickname}: Used {item['name']}! This item has no immediate effect but may be useful later.")
|
||||
|
|
|
|||
|
|
@ -22,51 +22,13 @@ class PetManagement(BaseModule):
|
|||
await self.cmd_nickname(channel, nickname, args)
|
||||
|
||||
async def cmd_team(self, channel, nickname):
|
||||
"""Show active pets (channel display)"""
|
||||
"""Redirect player to their team builder page"""
|
||||
player = await self.require_player(channel, nickname)
|
||||
if not player:
|
||||
return
|
||||
|
||||
pets = await self.database.get_player_pets(player["id"], active_only=False)
|
||||
if not pets:
|
||||
self.send_message(channel, f"{nickname}: You don't have any pets! Use !catch to find some.")
|
||||
return
|
||||
|
||||
# Show active pets first, then others
|
||||
active_pets = [pet for pet in pets if pet.get("is_active")]
|
||||
inactive_pets = [pet for pet in pets if not pet.get("is_active")]
|
||||
|
||||
team_info = []
|
||||
|
||||
# Active pets with star and team position
|
||||
for pet in active_pets:
|
||||
name = pet["nickname"] or pet["species_name"]
|
||||
|
||||
# Calculate EXP progress
|
||||
current_exp = pet.get("experience", 0)
|
||||
next_level_exp = self.database.calculate_exp_for_level(pet["level"] + 1)
|
||||
current_level_exp = self.database.calculate_exp_for_level(pet["level"])
|
||||
exp_needed = next_level_exp - current_exp
|
||||
|
||||
if pet["level"] >= 100:
|
||||
exp_display = "MAX"
|
||||
else:
|
||||
exp_display = f"{exp_needed} to next"
|
||||
|
||||
# Show team position
|
||||
position = pet.get("team_order", "?")
|
||||
team_info.append(f"[{position}]⭐{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP | EXP: {exp_display}")
|
||||
|
||||
# Inactive pets
|
||||
for pet in inactive_pets[:5]: # Show max 5 inactive
|
||||
name = pet["nickname"] or pet["species_name"]
|
||||
team_info.append(f"{name} (Lv.{pet['level']}) - {pet['hp']}/{pet['max_hp']} HP")
|
||||
|
||||
if len(inactive_pets) > 5:
|
||||
team_info.append(f"... and {len(inactive_pets) - 5} more in storage")
|
||||
|
||||
self.send_message(channel, f"🐾 {nickname}'s team: " + " | ".join(team_info))
|
||||
self.send_message(channel, f"🌐 View detailed pet collection at: http://petz.rdx4.com/player/{nickname}#pets")
|
||||
# Redirect to web interface for team management
|
||||
self.send_message(channel, f"⚔️ {nickname}: Manage your team at: http://petz.rdx4.com/teambuilder/{nickname}")
|
||||
|
||||
async def cmd_pets(self, channel, nickname):
|
||||
"""Show link to pet collection web page"""
|
||||
|
|
|
|||
428
webserver.py
428
webserver.py
|
|
@ -1402,12 +1402,405 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
self.wfile.write(html_content.encode())
|
||||
|
||||
def serve_leaderboard(self):
|
||||
"""Serve the leaderboard page - redirect to players for now"""
|
||||
# For now, leaderboard is the same as players page since they're ranked
|
||||
# In the future, this could have different categories
|
||||
self.send_response(302) # Temporary redirect
|
||||
self.send_header('Location', '/players')
|
||||
self.end_headers()
|
||||
"""Serve the enhanced leaderboard page with multiple categories"""
|
||||
import asyncio
|
||||
|
||||
# Check rate limit first
|
||||
allowed, rate_limit_message = self.check_rate_limit()
|
||||
if not allowed:
|
||||
self.send_rate_limit_error(rate_limit_message)
|
||||
return
|
||||
|
||||
# Get database instance
|
||||
database = self.server.database if hasattr(self.server, 'database') else None
|
||||
|
||||
if not database:
|
||||
self.serve_error_page("Leaderboard", "Database not available")
|
||||
return
|
||||
|
||||
try:
|
||||
# Run async database operations in event loop
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# Get all leaderboard data
|
||||
leaderboard_data = loop.run_until_complete(self.get_leaderboard_data(database))
|
||||
|
||||
# Generate HTML content
|
||||
content = self.generate_leaderboard_content(leaderboard_data)
|
||||
|
||||
html_content = self.get_page_template("Leaderboard - PetBot", content, "leaderboard")
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_content.encode())
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error generating leaderboard: {e}")
|
||||
self.serve_error_page("Leaderboard", f"Error loading leaderboard data: {str(e)}")
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
async def get_leaderboard_data(self, database):
|
||||
"""Get all leaderboard data for different categories"""
|
||||
leaderboard_data = {}
|
||||
|
||||
# 1. Top Players by Level
|
||||
leaderboard_data['levels'] = await self.get_level_leaderboard(database)
|
||||
|
||||
# 2. Top Players by Experience
|
||||
leaderboard_data['experience'] = await self.get_experience_leaderboard(database)
|
||||
|
||||
# 3. Richest Players
|
||||
leaderboard_data['money'] = await self.get_money_leaderboard(database)
|
||||
|
||||
# 4. Most Pets Collected
|
||||
leaderboard_data['pet_count'] = await self.get_pet_count_leaderboard(database)
|
||||
|
||||
# 5. Most Achievements
|
||||
leaderboard_data['achievements'] = await self.get_achievement_leaderboard(database)
|
||||
|
||||
# 6. Gym Champions (most gym badges)
|
||||
leaderboard_data['gym_badges'] = await self.get_gym_badge_leaderboard(database)
|
||||
|
||||
# 7. Highest Level Pet
|
||||
leaderboard_data['highest_pet'] = await self.get_highest_pet_leaderboard(database)
|
||||
|
||||
# 8. Most Rare Pets
|
||||
leaderboard_data['rare_pets'] = await self.get_rare_pet_leaderboard(database)
|
||||
|
||||
return leaderboard_data
|
||||
|
||||
async def get_level_leaderboard(self, database):
|
||||
"""Get top players by level"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT nickname, level, experience
|
||||
FROM players
|
||||
ORDER BY level DESC, experience DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "level": p[1], "experience": p[2]} for p in players]
|
||||
|
||||
async def get_experience_leaderboard(self, database):
|
||||
"""Get top players by total experience"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT nickname, level, experience
|
||||
FROM players
|
||||
ORDER BY experience DESC, level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "level": p[1], "experience": p[2]} for p in players]
|
||||
|
||||
async def get_money_leaderboard(self, database):
|
||||
"""Get richest players"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT nickname, money, level
|
||||
FROM players
|
||||
ORDER BY money DESC, level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "money": p[1], "level": p[2]} for p in players]
|
||||
|
||||
async def get_pet_count_leaderboard(self, database):
|
||||
"""Get players with most pets"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT p.nickname, COUNT(pets.id) as pet_count, p.level
|
||||
FROM players p
|
||||
LEFT JOIN pets ON p.id = pets.player_id
|
||||
GROUP BY p.id, p.nickname, p.level
|
||||
ORDER BY pet_count DESC, p.level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "pet_count": p[1], "level": p[2]} for p in players]
|
||||
|
||||
async def get_achievement_leaderboard(self, database):
|
||||
"""Get players with most achievements"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT p.nickname, COUNT(pa.achievement_id) as achievement_count, p.level
|
||||
FROM players p
|
||||
LEFT JOIN player_achievements pa ON p.id = pa.player_id
|
||||
GROUP BY p.id, p.nickname, p.level
|
||||
ORDER BY achievement_count DESC, p.level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "achievement_count": p[1], "level": p[2]} for p in players]
|
||||
|
||||
async def get_gym_badge_leaderboard(self, database):
|
||||
"""Get players with most gym victories (substitute for badges)"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
# Check if player_gym_battles table exists and has data
|
||||
cursor = await db.execute("""
|
||||
SELECT p.nickname,
|
||||
COALESCE(COUNT(DISTINCT CASE WHEN pgb.victories > 0 THEN pgb.gym_id END), 0) as gym_victories,
|
||||
p.level
|
||||
FROM players p
|
||||
LEFT JOIN player_gym_battles pgb ON p.id = pgb.player_id
|
||||
GROUP BY p.id, p.nickname, p.level
|
||||
ORDER BY gym_victories DESC, p.level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "badge_count": p[1], "level": p[2]} for p in players]
|
||||
|
||||
async def get_highest_pet_leaderboard(self, database):
|
||||
"""Get players with highest level pets"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT p.nickname, MAX(pets.level) as highest_pet_level,
|
||||
ps.name as pet_species, p.level as player_level
|
||||
FROM players p
|
||||
JOIN pets ON p.id = pets.player_id
|
||||
JOIN pet_species ps ON pets.species_id = ps.id
|
||||
GROUP BY p.id, p.nickname, p.level
|
||||
ORDER BY highest_pet_level DESC, p.level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "highest_pet_level": p[1], "pet_species": p[2], "player_level": p[3]} for p in players]
|
||||
|
||||
async def get_rare_pet_leaderboard(self, database):
|
||||
"""Get players with most rare pets (epic/legendary)"""
|
||||
import aiosqlite
|
||||
async with aiosqlite.connect(database.db_path) as db:
|
||||
cursor = await db.execute("""
|
||||
SELECT p.nickname, COUNT(pets.id) as rare_pet_count, p.level
|
||||
FROM players p
|
||||
JOIN pets ON p.id = pets.player_id
|
||||
JOIN pet_species ps ON pets.species_id = ps.id
|
||||
WHERE ps.rarity >= 4
|
||||
GROUP BY p.id, p.nickname, p.level
|
||||
ORDER BY rare_pet_count DESC, p.level DESC
|
||||
LIMIT 10
|
||||
""")
|
||||
players = await cursor.fetchall()
|
||||
|
||||
return [{"nickname": p[0], "rare_pet_count": p[1], "level": p[2]} for p in players]
|
||||
|
||||
def generate_leaderboard_content(self, leaderboard_data):
|
||||
"""Generate HTML content for the enhanced leaderboard"""
|
||||
content = """
|
||||
<div class="header">
|
||||
<h1>🏆 PetBot Leaderboards</h1>
|
||||
<p>Compete with trainers across all categories!</p>
|
||||
</div>
|
||||
|
||||
<div class="leaderboard-nav">
|
||||
<button class="category-btn active" onclick="showCategory('levels')">🎯 Levels</button>
|
||||
<button class="category-btn" onclick="showCategory('experience')">⭐ Experience</button>
|
||||
<button class="category-btn" onclick="showCategory('money')">💰 Wealth</button>
|
||||
<button class="category-btn" onclick="showCategory('pet_count')">🐾 Pet Count</button>
|
||||
<button class="category-btn" onclick="showCategory('achievements')">🏅 Achievements</button>
|
||||
<button class="category-btn" onclick="showCategory('gym_badges')">🏛️ Gym Badges</button>
|
||||
<button class="category-btn" onclick="showCategory('highest_pet')">🌟 Highest Pet</button>
|
||||
<button class="category-btn" onclick="showCategory('rare_pets')">💎 Rare Pets</button>
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Generate each leaderboard category
|
||||
content += self.generate_leaderboard_category("levels", "🎯 Level Leaders", leaderboard_data['levels'],
|
||||
["Rank", "Player", "Level", "Experience"],
|
||||
lambda p, i: [i+1, p['nickname'], p['level'], f"{p['experience']:,}"], True)
|
||||
|
||||
content += self.generate_leaderboard_category("experience", "⭐ Experience Champions", leaderboard_data['experience'],
|
||||
["Rank", "Player", "Experience", "Level"],
|
||||
lambda p, i: [i+1, p['nickname'], f"{p['experience']:,}", p['level']])
|
||||
|
||||
content += self.generate_leaderboard_category("money", "💰 Wealthiest Trainers", leaderboard_data['money'],
|
||||
["Rank", "Player", "Money", "Level"],
|
||||
lambda p, i: [i+1, p['nickname'], f"${p['money']:,}", p['level']])
|
||||
|
||||
content += self.generate_leaderboard_category("pet_count", "🐾 Pet Collectors", leaderboard_data['pet_count'],
|
||||
["Rank", "Player", "Pet Count", "Level"],
|
||||
lambda p, i: [i+1, p['nickname'], p['pet_count'], p['level']])
|
||||
|
||||
content += self.generate_leaderboard_category("achievements", "🏅 Achievement Hunters", leaderboard_data['achievements'],
|
||||
["Rank", "Player", "Achievements", "Level"],
|
||||
lambda p, i: [i+1, p['nickname'], p['achievement_count'], p['level']])
|
||||
|
||||
content += self.generate_leaderboard_category("gym_badges", "🏛️ Gym Champions", leaderboard_data['gym_badges'],
|
||||
["Rank", "Player", "Gym Badges", "Level"],
|
||||
lambda p, i: [i+1, p['nickname'], p['badge_count'], p['level']])
|
||||
|
||||
content += self.generate_leaderboard_category("highest_pet", "🌟 Elite Pet Trainers", leaderboard_data['highest_pet'],
|
||||
["Rank", "Player", "Highest Pet", "Species", "Player Level"],
|
||||
lambda p, i: [i+1, p['nickname'], f"Lvl {p['highest_pet_level']}", p['pet_species'], p['player_level']])
|
||||
|
||||
content += self.generate_leaderboard_category("rare_pets", "💎 Rare Pet Masters", leaderboard_data['rare_pets'],
|
||||
["Rank", "Player", "Rare Pets", "Level"],
|
||||
lambda p, i: [i+1, p['nickname'], p['rare_pet_count'], p['level']])
|
||||
|
||||
# Add JavaScript for category switching
|
||||
content += """
|
||||
<script>
|
||||
function showCategory(category) {
|
||||
// Hide all categories
|
||||
const categories = document.querySelectorAll('.leaderboard-category');
|
||||
categories.forEach(cat => cat.style.display = 'none');
|
||||
|
||||
// Show selected category
|
||||
document.getElementById(category).style.display = 'block';
|
||||
|
||||
// Update button states
|
||||
const buttons = document.querySelectorAll('.category-btn');
|
||||
buttons.forEach(btn => btn.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
// Show first category by default
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
showCategory('levels');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.leaderboard-nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 20px 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.category-btn {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 10px 15px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.category-btn:hover {
|
||||
background: var(--hover-color);
|
||||
border-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.category-btn.active {
|
||||
background: var(--text-accent);
|
||||
color: var(--bg-primary);
|
||||
border-color: var(--text-accent);
|
||||
}
|
||||
|
||||
.leaderboard-category {
|
||||
margin: 20px 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.leaderboard-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-dark);
|
||||
}
|
||||
|
||||
.leaderboard-table th {
|
||||
background: var(--gradient-primary);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.leaderboard-table td {
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.leaderboard-table tr:nth-child(even) {
|
||||
background: var(--bg-tertiary);
|
||||
}
|
||||
|
||||
.leaderboard-table tr:hover {
|
||||
background: var(--hover-color);
|
||||
}
|
||||
|
||||
.rank-1 { color: #FFD700; font-weight: bold; }
|
||||
.rank-2 { color: #C0C0C0; font-weight: bold; }
|
||||
.rank-3 { color: #CD7F32; font-weight: bold; }
|
||||
|
||||
.category-title {
|
||||
color: var(--text-accent);
|
||||
margin: 30px 0 15px 0;
|
||||
font-size: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
"""
|
||||
|
||||
return content
|
||||
|
||||
def generate_leaderboard_category(self, category_id, title, data, headers, row_formatter, is_default=False):
|
||||
"""Generate HTML for a single leaderboard category"""
|
||||
display_style = "block" if is_default else "none"
|
||||
|
||||
content = f"""
|
||||
<div id="{category_id}" class="leaderboard-category" style="display: {display_style};">
|
||||
<h2 class="category-title">{title}</h2>
|
||||
"""
|
||||
|
||||
if not data or len(data) == 0:
|
||||
content += '<div class="no-data">No data available for this category yet.</div>'
|
||||
else:
|
||||
content += '<table class="leaderboard-table">'
|
||||
|
||||
# Headers
|
||||
content += '<thead><tr>'
|
||||
for header in headers:
|
||||
content += f'<th>{header}</th>'
|
||||
content += '</tr></thead>'
|
||||
|
||||
# Data rows
|
||||
content += '<tbody>'
|
||||
for i, player in enumerate(data):
|
||||
row_data = row_formatter(player, i)
|
||||
rank_class = f"rank-{i+1}" if i < 3 else ""
|
||||
|
||||
content += f'<tr class="{rank_class}">'
|
||||
for cell in row_data:
|
||||
content += f'<td>{cell}</td>'
|
||||
content += '</tr>'
|
||||
content += '</tbody>'
|
||||
|
||||
content += '</table>'
|
||||
|
||||
content += '</div>'
|
||||
return content
|
||||
|
||||
def serve_locations(self):
|
||||
"""Serve the locations page with real data"""
|
||||
|
|
@ -2314,6 +2707,7 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
</div>
|
||||
<div class="item-description">{item['description']}</div>
|
||||
<div class="item-meta">Category: {item['category'].replace('_', ' ').title()} | Rarity: {item['rarity'].title()}</div>
|
||||
<div class="item-command">💬 Use with: <code>!use {item['name']}</code></div>
|
||||
</div>"""
|
||||
else:
|
||||
inventory_html = """
|
||||
|
|
@ -2765,6 +3159,20 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.item-command {
|
||||
margin-top: 8px;
|
||||
color: var(--text-accent);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.item-command code {
|
||||
background: var(--bg-secondary);
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: monospace;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
|
|
@ -4849,11 +5257,11 @@ class PetBotRequestHandler(BaseHTTPRequestHandler):
|
|||
print(f"🔐 PIN for {nickname}: {pin_code}")
|
||||
|
||||
# Try to send via IRC bot if available
|
||||
if self.bot and hasattr(self.bot, 'send_message'):
|
||||
if self.bot and hasattr(self.bot, 'send_message_sync'):
|
||||
try:
|
||||
# Send PIN via private message
|
||||
self.bot.send_message(nickname, f"🔐 Team Builder PIN: {pin_code}")
|
||||
self.bot.send_message(nickname, f"💡 Enter this PIN on the web page to confirm your team changes. PIN expires in 10 minutes.")
|
||||
# Send PIN via private message using sync wrapper
|
||||
self.bot.send_message_sync(nickname, f"🔐 Team Builder PIN: {pin_code}")
|
||||
self.bot.send_message_sync(nickname, f"💡 Enter this PIN on the web page to confirm your team changes. PIN expires in 10 minutes.")
|
||||
print(f"✅ PIN sent to {nickname} via IRC")
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to send PIN via IRC: {e}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue