Files
igncore/modules/core/riadmin/riadmin_controller.py
T
Minidodo 9f1da9a00d Fixed warnings caused by non-existing messagehub channels.
Changed the setting registration, removed the warnings.
Loot roll messages are more obvious now.
Superadmins are meant to stay mostily hidden, but are being exposed in !system again.
2021-08-29 17:54:18 +02:00

396 lines
20 KiB
Python

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(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'[<red>RI</red>] 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'[<red>RI</red>] 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<header2>{row.raid_instance_name} {name}</header2>\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" (<highlight>{num_assigned}</highlight> Players)"
if num_unassigned > 0:
out.page_postfix = f" (Assigned: <highlight>{num_assigned}</highlight>, " \
f"Unassigned: <highlight>{num_unassigned}</highlight>)"
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 :: <red>accept the invite!</red>')
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 <highlight>{raid_instance_name}</highlight> 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 <highlight>{char.name}</highlight> is already assigned to " \
f"raid instance <highlight>{raid_instance.name}</highlight>."
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 <highlight>{char.name}</highlight> has been assigned to " \
f"raid instance <highlight>{raid_instance.name}</highlight>."
else:
return f"Character <highlight>{char.name}</highlight> 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 <highlight>{char.name}</highlight> has been removed from " \
f"raid instance <highlight>{row.name}</highlight>."
else:
return f"Character <highlight>{char.name}</highlight> 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 <highlight>{char.name}</highlight> does not belong to a raid instance."
self.set_leader(char.char_id, raid_instance.id)
return f"Character <highlight>{char.name}</highlight> has been set as the leader for " \
f"raid instance <highlight>{raid_instance.name}</highlight>."
@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 <highlight>{raid_instance_name}</highlight> 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 <highlight>{raid_instance_name}</highlight> has been updated."
else:
self.db.exec("INSERT INTO raid_instance (name, bot) VALUES (?, ?)", [raid_instance_name, bot.char_id])
return f"Raid instance <highlight>{raid_instance_name}</highlight> [<highlight>{bot.name}</highlight>] " \
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 <highlight>{raid_instance_name}</highlight> 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 <highlight>{raid_instance_name}</highlight> 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)}/<green>{self.text.zfill(char_info.ai_level, 30)}</green> " \
f"{char_info.name: <13}"
elif char_info.name:
msg = " <highlight>%s</highlight>" % char_info.name
else:
msg = " <highlight>Unknown(%d)</highlight>" % char_info.char_id
if char_info.is_leader:
msg += " [<highlight>Leader</highlight>]"
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