import time
from functools import partial
from core.aochat.server_packets import BuddyAdded, CharacterName
from core.chat_blob import ChatBlob
from core.command_param_types import Character, Const, Int
from core.command_request import CommandRequest
from core.db import DB
from core.decorators import instance, command, timerevent
from core.dict_object import DictObject
from core.lookup.pork_service import PorkService
from core.text import Text
from core.igncore import IgnCore
@instance()
class CharacterInfoController:
BUDDY_IS_ONLINE_TYPE = "is_online"
def __init__(self):
self.name_history = []
self.waiting_for_update = {}
def inject(self, registry):
self.bot: IgnCore = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.command_alias_service = registry.get_instance("command_alias_service")
self.util = registry.get_instance("util")
self.account_service = registry.get_instance("account_service")
self.buddy_service = registry.get_instance("buddy_service")
self.alts_controller = registry.get_instance("alts_controller")
def pre_start(self):
self.bot.register_packet_handler(CharacterName.id, self.character_name_update)
def start(self):
self.db.exec("CREATE TABLE IF NOT EXISTS name_history ("
"char_id INT NOT NULL, "
"name VARCHAR(20) NOT NULL, "
"created_at INT NOT NULL, "
"PRIMARY KEY (char_id, name))")
self.db.create_view("name_history")
self.command_alias_service.add_alias("w", "whois")
self.command_alias_service.add_alias("lookup", "whois")
self.command_alias_service.add_alias("is", "whois")
@command(command="whois",
params=[Character("character"),
Int("server_num", is_optional=True),
Const("forceupdate", is_optional=True)],
access_level="member",
description="Get whois information for a character",
extended_description="Use server_num 6 for RK2019 and server_num 5 for live")
def whois_cmd(self, request, char, dimension, force_update):
dimension = dimension or self.bot.dimension
if dimension == self.bot.dimension and char.char_id:
online_status = self.buddy_service.is_online(char.char_id)
if online_status is None:
self.bot.register_packet_handler(BuddyAdded.id, self.handle_buddy_status)
self.waiting_for_update[char.char_id] = \
DictObject({"char_id": char.char_id, "name": char.name,
"callback": partial(self.show_output, char, dimension, force_update, request)})
self.buddy_service.add_buddy(char.char_id, self.BUDDY_IS_ONLINE_TYPE)
else:
self.show_output(char, dimension, force_update, request, online_status)
else:
self.show_output(char, dimension, force_update, request, None)
def show_output(self, char, dimension, force_update, request: CommandRequest, online_status):
max_cache_age = 0 if force_update else 86400
if dimension != self.bot.dimension:
char_info = self.pork_service.request_char_info(char.name, dimension)
else:
char_info = self.pork_service.get_character_info(char.name, max_cache_age)
if char_info and char_info.source != "chat_server":
blob = "Name: %s (%s)\n" % (self.get_full_name(char_info),
self.text.make_tellcmd("History",
f"history {char_info.name} {char_info.dimension}"))
blob += f"Character Id: {char_info.char_id:d}\n"
blob += f"Profession: {char_info.profession}\n"
blob += f"Faction: {self.text.get_formatted_faction(char_info.faction)}\n"
blob += f"Breed: {char_info.breed}\n"
blob += f"Gender: {char_info.gender}\n"
blob += f"Level: {char_info.level}\n"
blob += f"AI Level: {char_info.ai_level:d}\n"
if char_info.org_id:
blob += f"Org: {char_info.org_name} ({char_info.org_id})\n"
blob += f"Org Rank: {char_info.org_rank_name} ({char_info.org_rank_id})\n"
else:
blob += "Org: <None>\n"
blob += "Org Rank: <None>\n"
blob += f"Source: {self.format_source(char_info, max_cache_age)}\n"
blob += f"Dimension: {char_info.dimension}\n"
if dimension == self.bot.dimension:
blob += f"Status: {'Active' if char.char_id else 'Inactive'}\n"
blob += self.get_name_history(char.char_id)
alts = self.account_service.get_alts(char.char_id)
if len(alts) > 1:
blob += f"\nAlts ({len(alts):d})\n"
blob += self.alts_controller.format_alt_list(alts)
more_info = self.text.paginate_single(ChatBlob("More Info", blob))
msg = ChatBlob("More Info", blob, self.text.format_char_info(char_info, online_status, True)+" ")
# msg = self.text.format_char_info(char_info, online_status, True) + " " + more_info
elif char.char_id:
blob = "Note: Could not retrieve detailed info for character.\n\n"
blob += f"Name: {char.name}\n"
blob += f"Character ID: {char.char_id}\n"
if online_status is not None:
blob += f"Online status: {'Online' if online_status else 'Offline'}\n"
blob += self.get_name_history(char.char_id)
msg = ChatBlob(f"Basic Info for {char.name}", blob)
else:
msg = f"Could not find character {char.name} on RK{dimension:d}."
if request.channel == "msg" and request.conn.id == "main":
self.bot.send_mass_message(request.sender.char_id, msg)
else:
request.reply(msg)
def get_name_history(self, char_id):
blob = "\nName History\n"
data = self.db.query("SELECT name, created_at FROM name_history "
"WHERE char_id = ? ORDER BY created_at DESC",
[char_id])
for row in data:
blob += f"[{self.util.format_date(row.created_at)}] {row.name}\n"
return blob
@timerevent(budatime="1min", description="Save name history", is_hidden=True)
def save_name_history_event(self, _, _1):
if not self.name_history:
return
with self.db.pool.get_connection() as conn:
with conn.cursor() as cur:
cur.executemany("INSERT IGNORE INTO name_history (char_id, name, created_at) "
"VALUES (?, ?, ?)",
self.name_history)
self.name_history = []
def get_full_name(self, char_info):
name = ""
if char_info.first_name:
name += char_info.first_name + " "
name += "\"" + char_info.name + "\""
if char_info.last_name:
name += " " + char_info.last_name
return name
def format_source(self, char_info, max_cache_age):
if char_info.cache_age == 0:
return char_info.source
elif char_info.cache_age < max_cache_age:
return f"{char_info.source} (cache; {self.util.time_to_readable(char_info.cache_age)} old)"
elif char_info.cache_age > max_cache_age:
return f"{char_info.source} (old cache; {self.util.time_to_readable(char_info.cache_age)} old)"
def handle_buddy_status(self, _, packet):
obj = self.waiting_for_update.get(packet.char_id)
if obj:
self.buddy_service.remove_buddy(packet.char_id, self.BUDDY_IS_ONLINE_TYPE)
del self.waiting_for_update[packet.char_id]
if not self.waiting_for_update:
self.bot.remove_packet_handler(BuddyAdded.id, self.handle_buddy_status)
obj.callback(packet.online == 1)
def character_name_update(self, _, packet):
self.name_history.append((packet.char_id, packet.name, time.time()))