import time import typing 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 if typing.TYPE_CHECKING: from core.job_scheduler import JobScheduler 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") self.jobs: JobScheduler = registry.get_instance("job_scheduler") def pre_start(self): self.bot.register_packet_handler(CharacterName.id, self.character_name_update) self.db.shared.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))") def start(self): 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: if str(char.char_id) == char.name: def a(_): if char.char_id in self.waiting_for_update.keys(): del self.waiting_for_update[char.char_id] if not self.waiting_for_update: self.bot.remove_packet_handler(BuddyAdded.id, self.handle_buddy_status) self.buddy_service.remove_buddy(char.char_id, self.BUDDY_IS_ONLINE_TYPE) request.reply(f"Could not find character {char.name} on RK{dimension}. Lookup failed.") self.jobs.delayed_job(a, 5) 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 (%s, %s, %s)", 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()))