import math from core.buddy_service import BuddyService from core.chat_blob import ChatBlob from core.command_param_types import Const, Character, Any, NamedParameters from core.decorators import instance, command, event from core.dict_object import DictObject from core.logger import Logger from core.lookup.character_service import CharacterService from core.lookup.pork_service import PorkService from core.private_channel_service import PrivateChannelService from core.setting_service import SettingService from core.setting_types import TextSettingType from core.text import Text from core.translation_service import TranslationService from core.igncore import Tyrbot # noinspection SqlCaseVsIf,SqlCaseVsCoalesce @instance() class RIAdminController: PAGE_SIZE = 20 UNASSIGNED_RAID_INSTANCE_ID = 0 def __init__(self): self.logger = Logger(__name__) def inject(self, registry): self.bot: Tyrbot = registry.get_instance("bot") self.db = registry.get_instance("db") self.text: Text = registry.get_instance("text") self.setting_service: SettingService = registry.get_instance("setting_service") self.util = registry.get_instance("util") self.character_service: CharacterService = registry.get_instance("character_service") self.private_channel_service: PrivateChannelService = registry.get_instance("private_channel_service") self.ts: TranslationService = registry.get_instance("translation_service") self.getresp = self.ts.get_response self.buddy_service: BuddyService = registry.get_instance("buddy_service") self.pork: PorkService = registry.get_instance("pork_service") def start(self): self.db.exec( "CREATE TABLE IF NOT EXISTS raid_instance (id INT PRIMARY KEY AUTO_INCREMENT, " "name VARCHAR(255) NOT NULL, " "bot varchar(32)) ENGINE MEMORY") self.db.exec( "CREATE TABLE IF NOT EXISTS raid_instance_char (" "raid_instance_id INT NOT NULL, " "char_id INT PRIMARY KEY, " "is_leader TINYINT NOT NULL) ENGINE MEMORY") self.setting_service.register_new(self.module_name, "riadmin_network", "[905848882, 272234, 1923370, 313107, 1217210821, 1134420908]", TextSettingType(), "Allowed bots (charID's)", extended_description="This setting is *NOT* synchronized across the network;" " this needs to be done manually!") @event(event_type="buddy_logoff", description="Track raiders") def raider_logoff(self, _, event_data): if self.bot.is_ready(): buddy = self.buddy_service.get_buddy(event_data.char_id) if not buddy: return if "raider" in buddy.get('types', []): user = self.db.query_single("SELECT * from player where char_id=?", [event_data.char_id]) raid = self.get_raid_instance_by_char(event_data.char_id) if user and raid: if raid.bot != 0: self.bot.send_private_channel_message(f'[RI] Raider logged off: ' f'{user.name} - {user.profession} [{raid.name}]') self.db.exec("DELETE FROM raid_instance_char WHERE char_id = ?", [event_data.char_id]) self.buddy_service.remove_buddy(event_data.char_id, 'raider') @event(event_type=PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, description="Track Raiders") def raider_leave(self, _, event_data): if self.bot.is_ready(): buddy = self.buddy_service.get_buddy(event_data.char_id) if not buddy: return if "raider" in buddy.get('types', []): user = self.pork.get_character_info(event_data.char_id) raid = self.get_raid_instance_by_char(event_data.char_id) part = int(raid.bot) if raid else 0 if part in [self.bot.get_char_id(), 0]: if part != 0: self.bot.send_private_channel_message( f'[RI] Raider left: {user.name} - {user.profession} ' f'[{raid.name if raid else "UNASSIGNED"}]') self.db.exec("DELETE FROM raid_instance_char WHERE char_id = ?", [event_data.char_id]) self.buddy_service.remove_buddy(event_data.char_id, 'raider') @event(event_type="connect", description="Init raider tracking on startup") def connect(self, _, _1): for user in self.get_raid_instance_chars(): self.buddy_service.add_buddy(user.char_id, 'raider') @command(command="riadmin", params=[NamedParameters(['page'])], access_level="leader", description="Show the current RIs") def riadmin(self, _, named_params): refresh = self.text.make_tellcmd("Refresh", "riadmin") send = self.text.make_tellcmd("Apply", "riadmin send") clear = self.text.make_tellcmd("Clear All", "riadmin clear") blob = f"[{refresh}] - Update the RI List\n" \ f"[{send}] - Send all RI's to their bots\n" \ f"[{clear}] - Clear all RI's, and delete them" blob += "\n\n" page = int(named_params.page or "1") offset = (page - 1) * self.PAGE_SIZE data = self.get_raid_instance_chars() return self.format_pagination(data, offset, page, self.compact_char_display, 'Raid Instances', 'No Raidinstances found.', 'riadmin ', self.PAGE_SIZE, blob) def format_pagination(self, data, offset, page, formatter, title, nullmsg, cmd, page_size=10, headline=""): raid_instances = self.get_raid_instances() blob = "" if page == 1: blob = headline selected = data[offset:offset + page_size] if len(data) == 0: return nullmsg num_assigned = 0 num_unassigned = 0 pages = "" if page > 1: pages += "Pages: " + self.text.make_tellcmd("«« Page %d" % (page - 1), f'{cmd} --page={page - 1}') if offset + page_size < len(data): pages += f" Page {page}/{math.ceil(len(data) / page_size)}" pages += " " + self.text.make_tellcmd("Page %d »»" % (page + 1), f'{cmd} --page={page + 1}') pages += "\n" current_raid_instance_id = None for row in data: if row.raid_instance_id == self.UNASSIGNED_RAID_INSTANCE_ID: num_unassigned += 1 else: if row.name: num_assigned += 1 blob += "" + pages for row in selected: if row.raid_instance_id != current_raid_instance_id: name = "" if row.bot: bot = self.pork.get_character_info(row.bot) name = f"[{bot.name}]" if bot else "" blob += f"\n{row.raid_instance_name} {name}\n" current_raid_instance_id = row.raid_instance_id if not row.char_id: continue blob += formatter(row) add_leader_link = (row.raid_instance_id != self.UNASSIGNED_RAID_INSTANCE_ID and not row.is_leader) blob += " " + self.get_assignment_links(raid_instances, row.name, add_leader_link) blob += "\n" blob += "\n" + pages out = ChatBlob(title, blob) out.page_postfix = f" ({num_assigned} Players)" if num_unassigned > 0: out.page_postfix = f" (Assigned: {num_assigned}, " \ f"Unassigned: {num_unassigned})" return out @command(command="ritake", params=[Any("raiders")], access_level="all", description="take the RI from another bot") def ritake(self, request, raiders: str): users = raiders.split(",") if request.sender.char_id not in eval(self.setting_service.get_value('riadmin_network')): return for user in users: char = self.character_service.resolve_char_to_id(user.strip()) if not char: continue if self.private_channel_service.in_private_channel(char): self.buddy_service.add_buddy(char, 'raider') continue self.bot.send_mass_message(char, 'You have been assigned to my RI :: accept the invite!') self.private_channel_service.invite(char) @command(command="riadmin", params=[Const("add"), Any("raid_instance"), Character("char")], access_level="leader", description="Add a character to a RI", sub_command="leader") def riadmin_add(self, _, _1, raid_instance_name, char): if not char.char_id: return self.getresp("global", "char_not_found", {"char": char.name}) raid_instance = self.get_raid_instance(raid_instance_name) if not raid_instance: return f"Raid instance {raid_instance_name} does not exist." self.refresh_raid_instance_chars() row = self.db.query_single("SELECT raid_instance_id FROM raid_instance_char WHERE char_id = ?", [char.char_id]) if row: if raid_instance.id == row.raid_instance_id: if 'raider' not in ( self.buddy_service.get_buddy(char.char_id) or DictObject({'types': []}).get('types', [])): self.buddy_service.add_buddy(char.char_id, 'raider') return f"Character {char.name} is already assigned to " \ f"raid instance {raid_instance.name}." else: if 'raider' not in ( self.buddy_service.get_buddy(char.char_id) or DictObject({'types': []}).get('types', [])): self.buddy_service.add_buddy(char.char_id, 'raider') self.update_char_raid_instance(char.char_id, raid_instance.id) return f"Character {char.name} has been assigned to " \ f"raid instance {raid_instance.name}." else: return f"Character {char.name} is not in the private channel." @command(command="riadmin", params=[Const("clear")], access_level="leader", description="Remove all raids and their players", sub_command="leader") def riadmin_clear(self, _, _1): query = self.db.query("SELECT * FROM raid_instance_char") for user in query: self.buddy_service.remove_buddy(user.char_id, 'raider') self.db.exec("DELETE FROM raid_instance_char where 1") self.db.exec("DELETE FROM raid_instance where 1") return f"All characters have been removed from raid instances." @command(command="riadmin", params=[Const("rem"), Character("char")], access_level="leader", description="remove a character from the RI", sub_command="leader") def riadmin_rem(self, _, _2, char): if not char.char_id: return self.getresp("global", "char_not_found", {"char": char.name}) self.refresh_raid_instance_chars() row = self.db.query_single( "SELECT r2.name FROM raid_instance_char r1 " "JOIN raid_instance r2 ON r1.raid_instance_id = r2.id WHERE r1.char_id = ?", [char.char_id]) if row: self.update_char_raid_instance(char.char_id, self.UNASSIGNED_RAID_INSTANCE_ID) return f"Character {char.name} has been removed from " \ f"raid instance {row.name}." else: return f"Character {char.name} is not assigned to any raid instances." @command(command="riadmin", params=[Const("leader"), Character("char")], access_level="leader", description="Set the leader for a RI", sub_command="leader") def riadmin_leader(self, _, _2, char): if not char.char_id: return self.getresp("global", "char_not_found", {"char": char.name}) raid_instance = self.get_raid_instance_by_char(char.char_id) if not raid_instance: return f"Character {char.name} does not belong to a raid instance." self.set_leader(char.char_id, raid_instance.id) return f"Character {char.name} has been set as the leader for " \ f"raid instance {raid_instance.name}." @command(command="riadmin", params=[Const("send")], access_level="leader", description="Send the RIs to their preset bots", sub_command="leader") def riadmin_send(self, _, _2): self.bot.send_private_channel_message("Exporting raids..") for raid_instance in self.get_raid_instances(): data = self.db.query( "SELECT r.char_id, p.name, r.is_leader FROM raid_instance_char r " "left join player p on r.char_id=p.char_id WHERE raid_instance_id = ?", [raid_instance.id]) if int(raid_instance.bot) == self.bot.get_char_id() or raid_instance.bot == "0" or not raid_instance.bot: continue self.bot.send_private_message(int(raid_instance.bot), "ritake " + ", ".join([k['name'] for k in data]), add_color=False) for char in data: if char.is_leader == 0: self.private_channel_service.kick(char.char_id) return "Raid instance configuration has been applied." @command(command="riadmin", params=[Const("create"), Any("Raid_name"), Character("bot")], access_level="leader", description="Create or change a raid instance", sub_command="leader") def riadmin_create(self, _, _2, raid_instance_name, bot): if not bot.char_id: return self.getresp("global", "char_not_found", {"char": bot.name}) if bot.char_id not in eval(self.setting_service.get_value('riadmin_network')): return "Bot not valid: please ask an Administrator to verify it first." raid_instance = self.get_raid_instance(raid_instance_name) if raid_instance: if raid_instance.name == raid_instance_name and raid_instance.bot == bot.char_id: return f"Raid instance {raid_instance_name} already exists." else: self.db.exec("UPDATE raid_instance SET name = ?, bot = ? WHERE id = ?", [raid_instance_name, bot.char_id, raid_instance.id]) return f"Raid instance {raid_instance_name} has been updated." else: self.db.exec("INSERT INTO raid_instance (name, bot) VALUES (?, ?)", [raid_instance_name, bot.char_id]) return f"Raid instance {raid_instance_name} [{bot.name}] " \ f"has been created." @command(command="riadmin", params=[Const("delete"), Any("raid_instance_name")], access_level="leader", description="Remove a RI", sub_command="leader") def raid_instance_delete_cmd(self, _, _1, raid_instance_name): raid_instance = self.get_raid_instance(raid_instance_name) if not raid_instance: return f"Raid instance {raid_instance_name} does not exist." query = self.db.query("SELECT char_id from raid_instance_char where raid_instance_id=?", [raid_instance.id]) for user in query: self.buddy_service.remove_buddy(user.char_id, 'raider') self.db.exec("DELETE FROM raid_instance_char WHERE raid_instance_id = ?", [raid_instance.id]) self.db.exec("DELETE FROM raid_instance WHERE id = ?", [raid_instance.id]) return f"Raid instance {raid_instance_name} has been deleted." def get_raid_instance_chars(self): self.refresh_raid_instance_chars() data = self.db.query("SELECT * FROM (" "SELECT p.*, r2.id AS raid_instance_id, " "r1.is_leader, r2.name AS raid_instance_name, r2.bot " "FROM raid_instance r2 " "LEFT JOIN raid_instance_char r1 ON r1.raid_instance_id = r2.id " "LEFT JOIN player p ON r1.char_id = p.char_id " "UNION " "SELECT p.*, r3.raid_instance_id, " "r3.is_leader, 'Unassigned' AS raid_instance_name, '' AS bot " "FROM raid_instance_char r3 " "LEFT JOIN player p ON r3.char_id = p.char_id " "WHERE r3.raid_instance_id = ?) as r2r1pr3p " "ORDER BY raid_instance_id != ? DESC, " "raid_instance_name, " "is_leader desc, " "profession, level desc, " "name", [self.UNASSIGNED_RAID_INSTANCE_ID, self.UNASSIGNED_RAID_INSTANCE_ID]) return data def refresh_raid_instance_chars(self): users = self.db.query('SELECT * from online o ' 'left join player p on o.char_id=p.char_id ' 'where o.channel = ?', [self.bot.name]) for user in users: self.db.exec( "INSERT IGNORE INTO raid_instance_char (char_id, raid_instance_id, is_leader) VALUES (?, ?, 0)", [user.char_id, self.UNASSIGNED_RAID_INSTANCE_ID]) self.buddy_service.add_buddy(user.char_id, 'raider') def update_char_raid_instance(self, char_id, raid_instance_id): return self.db.exec("UPDATE raid_instance_char SET raid_instance_id = ?, is_leader = 0 WHERE char_id = ?", [raid_instance_id, char_id]) def set_leader(self, char_id, raid_instance_id): self.db.exec("UPDATE raid_instance_char SET is_leader = 0 WHERE raid_instance_id = ?", [raid_instance_id]) self.db.exec("UPDATE raid_instance_char SET is_leader = 1 WHERE raid_instance_id = ? AND char_id = ?", [raid_instance_id, char_id]) def compact_char_display(self, char_info): if char_info.level: msg = f" {self.util.get_prof_icon(char_info.profession)} " \ f"{self.text.zfill(char_info.level, 220)}/{self.text.zfill(char_info.ai_level, 30)} " \ f"{char_info.name: <13}" elif char_info.name: msg = " %s" % char_info.name else: msg = " Unknown(%d)" % char_info.char_id if char_info.is_leader: msg += " [Leader]" return msg def get_assignment_links(self, raid_instances, char_name, add_leader_link): links = list(map(lambda x: self.text.make_tellcmd(x.name, f"riadmin add {x.name} {char_name}"), raid_instances)) links.insert(0, self.text.make_tellcmd("Rem", f"riadmin rem {char_name}")) if add_leader_link: links.insert(0, self.text.make_tellcmd("L", f"riadmin leader {char_name}")) return f'[{"|".join(links)}]' def get_raid_instances(self): data = self.db.query("SELECT id, name, bot FROM raid_instance ORDER BY name") return data def get_raid_instance(self, raid_instance_name): return self.db.query_single("SELECT id, name, bot FROM raid_instance WHERE name LIKE ?", [raid_instance_name]) def get_raid_instance_by_char(self, char_id): return self.db.query_single("SELECT id, name, CASE WHEN bot IS NOT NULL THEN bot else 0 END as bot " "FROM raid_instance r1 JOIN raid_instance_char r2 ON r1.id = r2.raid_instance_id " "WHERE r2.char_id = ?", [char_id]) def get_conn_by_id(self, bot): conn = self.bot.conns.get(bot) if conn: return conn conns = self.bot.get_conns(lambda x: x.char_name.lower() == bot.lower()) if conns: return conns[0][1] return None