"""
███ ███ ██ ██ ██████ ██ ██ ██ ██████ ███████ ███████
████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ████ ██ ██ ██ ██████ ██ ██ ██ ██ ██ ███████ █████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██████ ██ ██ ██████ ███████ ██████ ███████ ███████
WynnCraft
"""
# scopes:
# requires: dataclasses-json
# 🔒 Licensed under the GNU AGPLv3
# meta banner: link
# meta desc: Wynncraft API Module
# meta developer: @BruhHikkaModules
from telethon.tl.types import Message
from .. import loader, utils
from ..inline.types import InlineCall
from dataclasses import dataclass
from dataclasses_json import dataclass_json
from typing import Optional, Dict, List, Union
import aiohttp
from aiohttp.web_exceptions import HTTPNotFound
import urllib.parse
import re
from datetime import datetime
@dataclass_json
@dataclass
class Guild:
name: str
prefix: str
rank: str
rankStars: str
@dataclass_json
@dataclass
class LegacyRankColour:
main: str
sub: str
@dataclass_json
@dataclass
class Pvp:
kills: int
deaths: int
@dataclass_json
@dataclass
class Dungeons:
total: int
list: Optional[Dict[str, int]] = None
@dataclass_json
@dataclass
class Raids:
total: int
list: Optional[Dict[str, int]] = None
@dataclass_json
@dataclass
class GlobalData:
wars: int
totalLevel: int
killedMobs: int
chestsFound: int
completedQuests: int
pvp: Pvp
dungeons: Optional[Dungeons] = None
raids: Optional[Raids] = None
@dataclass_json
@dataclass
class PlayerStats:
username: str
online: bool
server: str
activeCharacter: Optional[str]
nickname: Optional[str]
uuid: str
rank: str
rankBadge: Optional[str]
legacyRankColour: Optional[LegacyRankColour]
shortenedRank: Optional[str]
supportRank: Optional[str]
veteran: Optional[bool]
firstJoin: str
lastJoin: str
playtime: float
guild: Optional[Guild]
globalData: GlobalData
forumLink: Optional[int]
ranking: Dict[str, int]
previousRanking: Dict[str, int]
publicProfile: bool
characters: Optional[Dict[str, Dict]] = None
@dataclass_json
@dataclass
class GuildMember:
uuid: str
name: str
rank: str
contributed: int
joined: str
@dataclass_json
@dataclass
class GuildBanner:
base: str
tier: int
structure: str
layers: List[Dict[str, str]]
@dataclass_json
@dataclass
class GuildStats:
name: str
prefix: str
level: int
xpPercent: int
created: str
territories: int
banner: Optional[GuildBanner] = None
wars: Optional[int] = None
members: Optional[Union[Dict[str, Dict[str, Dict]], int]] = None
xp: Optional[int] = None
class WynnCraftAPI:
def __init__(self):
self.v3_url = "https://api.wynncraft.com/v3"
async def get_player_stats(self, identifier: str) -> PlayerStats:
async with aiohttp.ClientSession() as session:
async with session.get(f"{self.v3_url}/player/{identifier}?fullResult") as response:
if response.status == 404:
raise HTTPNotFound
if response.status == 300:
raise ValueError("Multiple players found, please use UUID")
data = await response.json()
if "globalData" in data:
global_data = data["globalData"]
for section in ["dungeons", "raids"]:
if section in global_data and global_data[section] and "list" not in global_data[section]:
global_data[section]["list"] = {
k: v for k, v in global_data[section].items()
if k != "total" and isinstance(v, int)
}
for k in list(global_data[section].keys()):
if k != "total" and k != "list":
del global_data[section][k]
return PlayerStats.from_dict(data.get("data", data))
async def get_guild_stats(self, identifier: str) -> GuildStats:
async with aiohttp.ClientSession() as session:
async with session.get(f"{self.v3_url}/guild/{identifier}") as response:
if response.status == 404:
raise HTTPNotFound
if response.status == 300:
raise ValueError("Multiple guilds found, please use exact name or prefix")
data = await response.json()
return GuildStats.from_dict(data.get("data", data))
async def get_leaderboard(self, type: str, result_limit: int = 100) -> Dict:
async with aiohttp.ClientSession() as session:
url = f"{self.v3_url}/leaderboards/{type}?resultLimit={result_limit}"
async with session.get(url) as response:
if response.status != 200:
raise HTTPNotFound
return await response.json()
async def get_leaderboard_types(self) -> List[str]:
async with aiohttp.ClientSession() as session:
url = f"{self.v3_url}/leaderboards/types"
async with session.get(url) as response:
if response.status != 200:
raise HTTPNotFound
return await response.json()
async def search(self, query: str) -> List[Dict]:
encoded_query = urllib.parse.quote(query)
async with aiohttp.ClientSession() as session:
url = f"{self.v3_url}/search/{encoded_query}"
async with session.get(url) as response:
if response.status != 200:
raise HTTPNotFound
data = await response.json()
results = []
for uuid, name in data.get("players", {}).items():
results.append({"type": "player", "uuid": uuid, "name": name})
for guild_id, guild_data in data.get("guildsPrefix", {}).items():
results.append({"type": "guild", "name": guild_data["name"], "prefix": guild_data["prefix"]})
return results
@loader.tds
class WynnCraft(loader.Module):
"""Wynncraft API Module"""
async def client_ready(self, client, db):
self.api = WynnCraftAPI()
try:
lb_types = await self.api.get_leaderboard_types()
self.leaderboard_types = {t: t.replace("Level", "").replace("Completion", "").title() for t in lb_types}
except Exception:
self.leaderboard_types = {
"guildLevel": "Guild Level",
"guildTerritories": "Guild Territories",
"guildWars": "Guild Wars",
"alchemismLevel": "Alchemism",
"miningLevel": "Mining",
"woodcuttingLevel": "Woodcutting",
"farmingLevel": "Farming",
"fishingLevel": "Fishing",
"armouringLevel": "Armouring",
"tailoringLevel": "Tailoring",
"weaponsmithingLevel": "Weaponsmithing",
"woodworkingLevel": "Woodworking",
"jewelingLevel": "Jeweling",
"scribingLevel": "Scribing",
"cookingLevel": "Cooking",
"professionsGlobalLevel": "Professions Global",
"combatGlobalLevel": "Combat Global",
"totalGlobalLevel": "Total Global",
"playerContent": "Player Content",
"combatSoloLevel": "Combat Solo",
"professionsSoloLevel": "Professions Solo",
"totalSoloLevel": "Total Solo",
"globalPlayerContent": "Global Player Content",
"huntedContent": "Hunted Content",
"grootslangCompletion": "Grootslang",
"colossusCompletion": "Colossus",
"orphionCompletion": "Orphion",
"namelessCompletion": "Nameless",
"warsCompletion": "Wars",
"craftsmanContent": "Craftsman Content",
"huicContent": "Huic Content",
"ironmanContent": "Ironman Content",
"ultimateIronmanContent": "Ultimate Ironman Content",
"hardcoreLegacyLevel": "Hardcore Legacy",
"hardcoreContent": "Hardcore Content",
"huichContent": "Huich Content",
"hicContent": "Hic Content",
"hichContent": "Hich Content",
"grootslangSrPlayers": "Grootslang Sr Players",
"namelessSrPlayers": "Nameless Sr Players",
"colossusSrGuilds": "Colossus Sr Guilds",
"colossusSrPlayers": "Colossus Sr Players",
"namelessSrGuilds": "Nameless Sr Guilds",
"orphionSrPlayers": "Orphion Sr Players",
"grootslangSrGuilds": "Grootslang Sr Guilds",
"orphionSrGuilds": "Orphion Sr Guilds"
}
strings = {
"name": "WynnCraft",
"no_guild": "No guild",
"offline": "Offline",
"stats": (
"👤 Player Stats: {player}\n"
"\n🗄 Server: {server}"
"\n⏳ Playtime: {playtime} hours"
"\n👥 Guild: {guild}"
"\n⭐ Quests Completed: {quests}"
"\n💎 Dungeons Completed: {dungeons}"
"\n📆 First Joined: {joindate}"
"\n📆 Last Joined: {lastjoin}"
),
"guild_stats": (
"👥 Guild Stats: {guild}\n"
"\n🏷 Prefix: {prefix}"
"\n🎚️ Level: {level}"
"\n📈 XP Progress: {xpPercent}%"
"\n🌍 Territories: {territories}"
"\n⚔️ Wars: {wars}"
"\n👥 Members: {members}"
"\n📆 Created: {created}"
),
"guild_members": (
"👥 Guild Members: {guild}\n"
"\n{members_list}"
),
"notfound": "⚠️ Not found",
"extended_info_rankings": (
"👤 Rankings: {player}\n"
"\n🏆 Rankings:"
"\n{ranking}"
),
"extended_info_prev_rankings": (
"👤 Previous Rankings: {player}\n"
"\n📊 Previous Rankings:"
"\n{prev_ranking}"
),
"extended_info_global": (
"👤 Global Data: {player}\n"
"\n⚔️ Wars: {wars}"
"\n🎚️ Total Level: {totalLevel}"
"\n🧟 Killed Mobs: {killedMobs}"
"\n📦 Chests Found: {chestsFound}"
"\n📜 Quests Completed: {quests}"
),
"extended_info_pvp": (
"👤 PvP: {player}\n"
"\n⚔️ Kills: {kills}"
"\n💀 Deaths: {deaths}"
),
"extended_info_dungeons": (
"👤 Dungeons: {player}\n"
"\n💎 Total Dungeons: {total}"
"\n{dungeons_list}"
),
"extended_info_raids": (
"👤 Raids: {player}\n"
"\n⚔️ Total Raids: {total}"
"\n{raids_list}"
),
"extended_info_characters": (
"👤 Characters: {player}\n"
"\n🎭 Characters:"
"\n{characters_list}"
),
"leaderboard": "⭐ {title}\n",
"leaderboard_select": "🏆 Choose Leaderboard",
"search_results": "🔍 Search Results for '{query}'\n",
"no_results": "⚠️ No results found",
"prev_page": "⬅️ Previous",
"next_page": "Next ➡️",
"btn_more": "More Info",
"btn_back": "Back",
"btn_rankings": "Rankings",
"btn_prev_rankings": "Prev Rankings",
"btn_global": "Global Data",
"btn_pvp": "PvP",
"btn_dungeons": "Dungeons",
"btn_raids": "Raids",
"btn_characters": "Characters",
"btn_solo": "Solo",
"btn_global": "Global",
"btn_pvp_leaderboard": "PvP",
"btn_guild": "Guilds",
"btn_gamemodes": "Gamemodes",
"btn_raids": "Raids",
"error_player_notfound": "Player not found",
"error_guild_notfound": "Guild not found",
"error_leaderboard_failed": "Failed to load leaderboard",
"error_no_results": "No results found",
"error_multiple_choices": "Multiple players or guilds found, please use exact name or UUID"
}
strings_ru = {
"no_guild": "Без гильдии",
"offline": "Офлайн",
"stats": (
"👤 Статистика игрока: {player}\n"
"\n🗄 Сервер: {server}"
"\n⏳ Время в игре: {playtime} ч"
"\n👥 Гильдия: {guild}"
"\n⭐ Выполнено квестов: {quests}"
"\n💎 Пройдено подземелий: {dungeons}"
"\n📆 Первый вход: {joindate}"
"\n📆 Последний вход: {lastjoin}"
),
"guild_stats": (
"👥 Статистика гильдии: {guild}\n"
"\n🏷 Префикс: {prefix}"
"\n🎚️ Уровень: {level}"
"\n📈 Прогресс опыта: {xpPercent}%"
"\n🌍 Территории: {territories}"
"\n⚔️ Войны: {wars}"
"\n👥 Участники: {members}"
"\n📆 Создана: {created}"
),
"guild_members": (
"👥 Участники гильдии: {guild}\n"
"\n{members_list}"
),
"notfound": "⚠️ Не найдено",
"extended_info_rankings": (
"👤 Рейтинги: {player}\n"
"\n🏆 Рейтинги:"
"\n{ranking}"
),
"extended_info_prev_rankings": (
"👤 Предыдущие рейтинги: {player}\n"
"\n📊 Предыдущие рейтинги:"
"\n{prev_ranking}"
),
"extended_info_global": (
"👤 Глобальные данные: {player}\n"
"\n⚔️ Войны: {wars}"
"\n🎚️ Общий уровень: {totalLevel}"
"\n🧟 Убито мобов: {killedMobs}"
"\n📦 Найдено сундуков: {chestsFound}"
"\n📜 Выполнено квестов: {quests}"
),
"extended_info_pvp": (
"👤 PvP: {player}\n"
"\n⚔️ Убийства: {kills}"
"\n💀 Смерти: {deaths}"
),
"extended_info_dungeons": (
"👤 Подземелья: {player}\n"
"\n💎 Всего подземелий: {total}"
"\n{dungeons_list}"
),
"extended_info_raids": (
"👤 Рейды: {player}\n"
"\n⚔️ Всего рейдов: {total}"
"\n{raids_list}"
),
"extended_info_characters": (
"👤 Персонажи: {player}\n"
"\n🎭 Персонажи:"
"\n{characters_list}"
),
"leaderboard": "⭐ {title}\n",
"leaderboard_select": "🏆 Выберите лидерборд",
"search_results": "🔍 Результаты поиска для '{query}'\n",
"no_results": "⚠️ Результаты не найдены",
"prev_page": "⬅️ Назад",
"next_page": "Вперёд ➡️",
"btn_more": "Подробнее",
"btn_back": "Назад",
"btn_rankings": "Рейтинги",
"btn_prev_rankings": "Пред. рейтинги",
"btn_global": "Глобальные данные",
"btn_pvp": "PvP",
"btn_dungeons": "Подземелья",
"btn_raids": "Рейды",
"btn_characters": "Персонажи",
"btn_solo": "Соло",
"btn_global": "Глобальный",
"btn_pvp_leaderboard": "PvP",
"btn_guild": "Гильдии",
"btn_gamemodes": "Игровые режимы",
"btn_raids": "Рейды",
"error_player_notfound": "Игрок не найден",
"error_guild_notfound": "Гильдия не найдена",
"error_leaderboard_failed": "Не удалось загрузить лидерборд",
"error_no_results": "Результаты не найдены",
"error_multiple_choices": "Найдено несколько игроков или гильдий, используйте точное название или UUID"
}
def format_ranking(self, ranking: Dict[str, int]) -> str:
formatted = ""
for key, value in ranking.items():
human_key = self.leaderboard_types.get(key.lower(), None)
if not human_key:
key = key.lower()
key = re.sub(r'level$', ' Level', key)
key = re.sub(r'global', 'Global ', key)
key = re.sub(r'solo', 'Solo ', key)
key = re.sub(r'content', 'Content', key)
human_key = ' '.join(word.capitalize() for word in key.split())
formatted += f" - {human_key}: {value}\n"
return formatted or " - None"
def format_dungeons(self, dungeons: Optional[Dungeons]) -> str:
if not dungeons or not dungeons.list:
return " - None"
return "\n".join(f" - {key}: {value}" for key, value in dungeons.list.items())
def format_raids(self, raids: Optional[Raids]) -> str:
if not raids or not raids.list:
return " - None"
return "\n".join(f" - {key}: {value}" for key, value in raids.list.items())
def format_guild_members(self, members: Optional[Union[Dict[str, Dict[str, Dict]], int]]) -> str:
if not members:
return " - None"
if isinstance(members, int):
return f" - Total: {members}"
if not members.get("total"):
return " - None"
formatted = []
for rank, players in members.items():
if rank == "total":
continue
for name, data in players.items():
joined = datetime.fromisoformat(data["joined"].replace('Z', '+00:00')).strftime('%d.%m.%Y')
formatted.append(f" - {name} ({rank.capitalize()}): Joined {joined}")
return "\n".join(formatted[:10]) or " - None"
def format_characters(self, characters: Optional[Dict[str, Dict]]) -> str:
if not characters:
return " - None"
formatted = []
for uuid, char_data in list(characters.items())[:5]: # Limit to 5 characters
char_type = char_data.get("type", "Unknown")
level = char_data.get("level", 0)
xp = char_data.get("xp", 0)
xp_percent = char_data.get("xpPercent", 0)
gamemodes = ", ".join(char_data.get("gamemode", [])) or "None"
died = "Yes" if char_data.get("meta", {}).get("died", False) else "No"
skills = char_data.get("skillPoints", {})
professions = char_data.get("professions", {})
dungeons = char_data.get("dungeons", {}).get("total", 0)
raids = char_data.get("raids", {}).get("total", 0)
formatted.append(
f" - {char_type} (Level {level})\n"
f" - XP: {xp} ({xp_percent}%)\n"
f" - Gamemodes: {gamemodes}\n"
f" - Died: {died}\n"
f" - Skills:\n"
f" - Strength: {skills.get('strength', 0)}\n"
f" - Dexterity: {skills.get('dexterity', 0)}\n"
f" - Intelligence: {skills.get('intelligence', 0)}\n"
f" - Defence: {skills.get('defence', 0)}\n"
f" - Agility: {skills.get('agility', 0)}\n"
f" - Professions:\n"
f" - Fishing: {professions.get('fishing', {}).get('level', 0)}\n"
f" - Mining: {professions.get('mining', {}).get('level', 0)}\n"
f" - Woodcutting: {professions.get('woodcutting', {}).get('level', 0)}\n"
f" - Farming: {professions.get('farming', {}).get('level', 0)}\n"
f" - Dungeons: {dungeons}\n"
f" - Raids: {raids}"
)
return "\n".join(formatted) or " - None"
def format_leaderboard_entry(self, entry: Dict, lb_type: str) -> str:
if "Guild" in lb_type:
return (
f"{entry['num']}. {entry['prefix']} {entry['name']}\n"
f" - Score: {entry.get('score', 'N/A')}\n"
f" - Members: {entry.get('members', 'N/A')}\n"
f" - Level: {entry.get('level', 'N/A')}\n"
)
elif lb_type == "warsCompletion":
return (
f"{entry['num']}. {entry['name']}\n"
f" - Score: {entry.get('score', 'N/A')}\n"
f" - Previous Rank: {entry.get('previousRanking', 'N/A')}\n"
)
elif lb_type in ["grootslangCompletion", "colossusCompletion", "orphionCompletion", "namelessCompletion"]:
metadata = entry.get("metadata", {})
return (
f"{entry['num']}. {entry['name']}\n"
f" - Score: {entry.get('score', 'N/A')}\n"
f" - Completions: {metadata.get('completions', 'N/A')}\n"
f" - Gambits Used: {metadata.get('gambitsUsed', 'N/A')}\n"
)
else:
metadata = entry.get("metadata", {})
return (
f"{entry['num']}. {entry['name']}\n"
f" - Score: {entry.get('score', 'N/A')}\n"
f" - XP: {metadata.get('xp', 'N/A')}\n"
f" - Previous Rank: {entry.get('previousRanking', 'N/A')}\n"
)
def get_extended_info_buttons(self, player_id: str, stats_text: str, current_category: str) -> List[Dict]:
categories = ["rankings", "prev_rankings", "global", "pvp", "dungeons", "raids", "characters"]
buttons = []
for category in categories:
text = f"🟢 {self.strings[f'btn_{category}']}" if category == current_category else self.strings[f"btn_{category}"]
buttons.append({"text": text, "callback": self.show_extended_info, "args": (player_id, stats_text, category)})
buttons.append({"text": self.strings["btn_back"], "callback": self.show_stats, "args": (stats_text, player_id)})
return [
buttons[0:2], # Rankings, Prev Rankings
buttons[2:4], # Global, PvP
buttons[4:6], # Dungeons, Raids
[buttons[6]], # Characters
[buttons[7]] # Back
]
def get_guild_info_buttons(self, guild_id: str, stats_text: str, current_category: str) -> List[Dict]:
categories = ["members"]
buttons = [
{
"text": f"🟢 {self.strings[f'btn_{category}']}" if category == current_category else self.strings[f"btn_{category}"],
"callback": self.show_guild_extended_info,
"args": (guild_id, stats_text, category)
}
for category in categories
]
buttons.append({
"text": self.strings["btn_back"],
"callback": self.show_guild_stats,
"args": (stats_text, guild_id)
})
return [buttons]
async def show_extended_info(self, call: InlineCall, player_id: str, stats_text: str, category: str = "rankings"):
try:
stats = await self.api.get_player_stats(player_id)
if category == "rankings":
text = self.strings["extended_info_rankings"].format(
player=stats.username,
ranking=self.format_ranking(stats.ranking)
)
elif category == "prev_rankings":
text = self.strings["extended_info_prev_rankings"].format(
player=stats.username,
prev_ranking=self.format_ranking(stats.previousRanking)
)
elif category == "global":
text = self.strings["extended_info_global"].format(
player=stats.username,
wars=stats.globalData.wars,
totalLevel=stats.globalData.totalLevel,
killedMobs=stats.globalData.killedMobs,
chestsFound=stats.globalData.chestsFound,
quests=stats.globalData.completedQuests
)
elif category == "pvp":
text = self.strings["extended_info_pvp"].format(
player=stats.username,
kills=stats.globalData.pvp.kills,
deaths=stats.globalData.pvp.deaths
)
elif category == "dungeons":
text = self.strings["extended_info_dungeons"].format(
player=stats.username,
total=stats.globalData.dungeons.total if stats.globalData.dungeons else 0,
dungeons_list=self.format_dungeons(stats.globalData.dungeons)
)
elif category == "raids":
text = self.strings["extended_info_raids"].format(
player=stats.username,
total=stats.globalData.raids.total if stats.globalData.raids else 0,
raids_list=self.format_raids(stats.globalData.raids)
)
elif category == "characters":
text = self.strings["extended_info_characters"].format(
player=stats.username,
characters_list=self.format_characters(stats.characters)
)
await call.edit(
text,
reply_markup=self.get_extended_info_buttons(player_id, stats_text, category)
)
except HTTPNotFound:
await call.answer(self.strings["error_player_notfound"], show_alert=True)
except ValueError:
await call.answer(self.strings["error_multiple_choices"], show_alert=True)
async def show_stats(self, call: InlineCall, stats_text: str, player_id: str):
await call.edit(
stats_text,
reply_markup=[
[{"text": self.strings["btn_more"], "callback": self.show_extended_info, "args": (player_id, stats_text, "rankings")}]
]
)
async def show_guild_extended_info(self, call: InlineCall, guild_id: str, stats_text: str, category: str = "members"):
try:
stats = await self.api.get_guild_stats(guild_id)
if category == "members":
text = self.strings["guild_members"].format(
guild=stats.name,
members_list=self.format_guild_members(stats.members)
)
await call.edit(
text,
reply_markup=self.get_guild_info_buttons(guild_id, stats_text, category)
)
except HTTPNotFound:
await call.answer(self.strings["error_guild_notfound"], show_alert=True)
except ValueError:
await call.answer(self.strings["error_multiple_choices"], show_alert=True)
async def show_guild_stats(self, call: InlineCall, stats_text: str, guild_id: str):
await call.edit(
stats_text,
reply_markup=[
[{"text": self.strings["btn_members"], "callback": self.show_guild_extended_info, "args": (guild_id, stats_text, "members")}]
]
)
@loader.command()
async def wstatscmd(self, message: Message):
"""[Username / uuid] - Player stats"""
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings["notfound"])
return
try:
stats = await self.api.get_player_stats(args)
except HTTPNotFound:
await utils.answer(message, self.strings["notfound"])
return
except ValueError:
await utils.answer(message, self.strings["error_multiple_choices"])
return
guild_text = f"{stats.guild.name} ({stats.guild.rank})" if stats.guild else self.strings["no_guild"]
stats_text = self.strings["stats"].format(
player=args,
server=stats.server or self.strings["offline"],
playtime=f"{stats.playtime:.2f}",
guild=guild_text,
quests=stats.globalData.completedQuests,
dungeons=stats.globalData.dungeons.total if stats.globalData.dungeons else 0,
joindate=datetime.fromisoformat(stats.firstJoin.replace("Z", "+00:00")).strftime("%d.%m.%Y"),
lastjoin=datetime.fromisoformat(stats.lastJoin.replace("Z", "+00:00")).strftime("%d.%m.%Y %H:%M")
)
await self.inline.form(
message=message,
text=stats_text,
reply_markup=[
[{"text": self.strings["btn_more"], "callback": self.show_extended_info, "args": (args, stats_text, "rankings")}]
]
)
@loader.command()
async def wguildcmd(self, message: Message):
"""[GuildName / Prefix] - Guild stats"""
args = utils.get_args_raw(message)
if not args:
await utils.answer(message, self.strings["notfound"])
return
try:
stats = await self.api.get_guild_stats(args)
except HTTPNotFound:
await utils.answer(message, self.strings["notfound"])
return
except ValueError:
await utils.answer(message, self.strings["error_multiple_choices"])
return
members_count = stats.members if isinstance(stats.members, int) else stats.members.get("total", 0)
stats_text = self.strings["guild_stats"].format(
guild=stats.name,
prefix=stats.prefix,
level=stats.level,
xpPercent=stats.xpPercent,
territories=stats.territories,
wars=stats.wars or 0,
members=members_count,
created=datetime.fromisoformat(stats.created.replace("Z", "+00:00")).strftime("%d.%m.%Y")
)
await self.inline.form(
message=message,
text=stats_text,
reply_markup=[
[{"text": self.strings["btn_members"], "callback": self.show_guild_extended_info, "args": (args, stats_text, "members")}]
]
)
@loader.command()
async def wleaderboardcmd(self, message: Message):
"""Show Wynncraft leaderboards"""
await self.inline.form(
message=message,
text=self.strings["leaderboard_select"],
reply_markup=[
[
{"text": self.strings["btn_solo"], "callback": self.show_leaderboard_menu, "args": ("solo",)},
{"text": self.strings["btn_global"], "callback": self.show_leaderboard_menu, "args": ("global",)},
{"text": self.strings["btn_pvp_leaderboard"], "callback": self.show_leaderboard, "args": ("warsCompletion",)},
{"text": self.strings["btn_guild"], "callback": self.show_leaderboard_menu, "args": ("guild",)},
{"text": self.strings["btn_gamemodes"], "callback": self.show_leaderboard_menu, "args": ("gamemodes",)},
{"text": self.strings["btn_raids"], "callback": self.show_leaderboard_menu, "args": ("raids",)}
]
]
)
@loader.command()
async def wsearchcmd(self, message: Message):
"""[Query] - Search for players or guilds"""
query = utils.get_args_raw(message)
if not query:
await utils.answer(message, self.strings["notfound"])
return
try:
results = await self.api.search(query)
if not results:
await utils.answer(message, self.strings["no_results"])
return
await self.show_search_results(None, message, query, results, page=0)
except HTTPNotFound:
await utils.answer(message, self.strings["no_results"])
async def show_leaderboard_menu(self, call: InlineCall, category: str):
try:
lb_types = await self.api.get_leaderboard_types()
buttons = []
if category == "solo":
buttons = [
[
{"text": "Total", "callback": self.show_leaderboard, "args": ("totalSoloLevel",)},
{"text": "Combat", "callback": self.show_leaderboard, "args": ("combatSoloLevel",)}
],
[
{"text": "Professions", "callback": self.show_leaderboard, "args": ("professionsSoloLevel",)},
{"text": "Mining", "callback": self.show_leaderboard, "args": ("miningLevel",)}
],
[
{"text": "Woodcutting", "callback": self.show_leaderboard, "args": ("woodcuttingLevel",)},
{"text": "Farming", "callback": self.show_leaderboard, "args": ("farmingLevel",)}
],
[
{"text": "Fishing", "callback": self.show_leaderboard, "args": ("fishingLevel",)},
{"text": "Armouring", "callback": self.show_leaderboard, "args": ("armouringLevel",)}
],
[
{"text": "Tailoring", "callback": self.show_leaderboard, "args": ("tailoringLevel",)},
{"text": "Weaponsmithing", "callback": self.show_leaderboard, "args": ("weaponsmithingLevel",)}
],
[
{"text": "Woodworking", "callback": self.show_leaderboard, "args": ("woodworkingLevel",)},
{"text": "Jeweling", "callback": self.show_leaderboard, "args": ("jewelingLevel",)}
],
[
{"text": "Scribing", "callback": self.show_leaderboard, "args": ("scribingLevel",)},
{"text": "Cooking", "callback": self.show_leaderboard, "args": ("cookingLevel",)}
],
[
{"text": "Alchemism", "callback": self.show_leaderboard, "args": ("alchemismLevel",)}
]
]
elif category == "global":
buttons = [
[
{"text": "Total", "callback": self.show_leaderboard, "args": ("totalGlobalLevel",)},
{"text": "Combat", "callback": self.show_leaderboard, "args": ("combatGlobalLevel",)}
],
[
{"text": "Professions", "callback": self.show_leaderboard, "args": ("professionsGlobalLevel",)},
{"text": "Player Content", "callback": self.show_leaderboard, "args": ("globalPlayerContent",)}
]
]
elif category == "guild":
buttons = [
[
{"text": "Level", "callback": self.show_leaderboard, "args": ("guildLevel",)},
{"text": "Territories", "callback": self.show_leaderboard, "args": ("guildTerritories",)}
],
[
{"text": "Wars", "callback": self.show_leaderboard, "args": ("guildWars",)},
{"text": "Colossus SR", "callback": self.show_leaderboard, "args": ("colossusSrGuilds",)}
],
[
{"text": "Nameless SR", "callback": self.show_leaderboard, "args": ("namelessSrGuilds",)},
{"text": "Grootslang SR", "callback": self.show_leaderboard, "args": ("grootslangSrGuilds",)}
],
[
{"text": "Orphion SR", "callback": self.show_leaderboard, "args": ("orphionSrGuilds",)}
]
]
elif category == "gamemodes":
gamemode_types = [t for t in lb_types if "Content" in t and "Player" not in t]
buttons = [
[
{"text": t.replace("Content", "").title(), "callback": self.show_leaderboard, "args": (t,)}
for t in gamemode_types[i:i+2]
]
for i in range(0, len(gamemode_types), 2)
]
elif category == "raids":
raid_types = [t for t in lb_types if "Completion" in t or "SrPlayers" in t]
buttons = [
[
{"text": t.replace("Completion", "").replace("SrPlayers", "SR").title(), "callback": self.show_leaderboard, "args": (t,)}
for t in raid_types[i:i+2]
]
for i in range(0, len(raid_types), 2)
]
buttons.append([{"text": self.strings["btn_back"], "callback": self.show_leaderboard_select}])
await call.edit(
self.strings["leaderboard_select"],
reply_markup=buttons
)
except HTTPNotFound:
await call.answer(self.strings["error_leaderboard_failed"], show_alert=True)
async def show_leaderboard(self, call: InlineCall, leaderboard_type: str):
try:
leaderboard = await self.api.get_leaderboard(leaderboard_type)
title = self.leaderboard_types.get(leaderboard_type, leaderboard_type.replace("/", " ").title())
text = self.strings["leaderboard"].format(title=title)
for pos, entry in list(leaderboard.items())[:10]:
entry["num"] = pos
text += self.format_leaderboard_entry(entry, leaderboard_type)
await call.edit(
text,
reply_markup=[
[
{"text": self.strings["btn_solo"], "callback": self.show_leaderboard_menu, "args": ("solo",)},
{"text": self.strings["btn_global"], "callback": self.show_leaderboard_menu, "args": ("global",)},
{"text": self.strings["btn_pvp_leaderboard"], "callback": self.show_leaderboard, "args": ("warsCompletion",)},
{"text": self.strings["btn_guild"], "callback": self.show_leaderboard_menu, "args": ("guild",)},
{"text": self.strings["btn_gamemodes"], "callback": self.show_leaderboard_menu, "args": ("gamemodes",)},
{"text": self.strings["btn_raids"], "callback": self.show_leaderboard_menu, "args": ("raids",)}
],
[{"text": self.strings["btn_back"], "callback": self.show_leaderboard_select}]
]
)
except HTTPNotFound:
await call.answer(self.strings["error_leaderboard_failed"], show_alert=True)
async def show_leaderboard_select(self, call: InlineCall):
await call.edit(
self.strings["leaderboard_select"],
reply_markup=[
[
{"text": self.strings["btn_solo"], "callback": self.show_leaderboard_menu, "args": ("solo",)},
{"text": self.strings["btn_global"], "callback": self.show_leaderboard_menu, "args": ("global",)},
{"text": self.strings["btn_pvp_leaderboard"], "callback": self.show_leaderboard, "args": ("warsCompletion",)},
{"text": self.strings["btn_guild"], "callback": self.show_leaderboard_menu, "args": ("guild",)},
{"text": self.strings["btn_gamemodes"], "callback": self.show_leaderboard_menu, "args": ("gamemodes",)},
{"text": self.strings["btn_raids"], "callback": self.show_leaderboard_menu, "args": ("raids",)}
]
]
)
async def show_search_results(self, call: Optional[InlineCall], message: Message, query: str, results: List[Dict], page: int):
items_per_page = 5
start = page * items_per_page
end = start + items_per_page
results_page = results[start:end]
text = self.strings["search_results"].format(query=query)
for result in results_page:
if result["type"] == "player":
text += (
f"{result['name']} (Player)\n"
f" - UUID: {result.get('uuid', 'N/A')}\n"
)
else:
text += (
f"{result['name']} (Guild)\n"
f" - Prefix: {result.get('prefix', 'N/A')}\n"
)
reply_markup = []
row = []
if page > 0:
row.append({"text": self.strings["prev_page"], "callback": self.show_search_results, "args": (message, query, results, page - 1)})
if end < len(results):
row.append({"text": self.strings["next_page"], "callback": self.show_search_results, "args": (message, query, results, page + 1)})
if row:
reply_markup.append(row)
reply_markup.append([{"text": self.strings["btn_back"], "callback": self.show_search_menu, "args": (query,)}])
if call:
await call.edit(
text,
reply_markup=reply_markup
)
else:
await self.inline.form(
message=message,
text=text,
reply_markup=reply_markup
)
async def show_search_menu(self, call: InlineCall, query: str):
try:
results = await self.api.search(query)
if not results:
await call.edit(self.strings["no_results"])
return
await self.show_search_results(call, call.message, query, results, page=0)
except HTTPNotFound:
await call.edit(self.strings["no_results"])