bb445e868d
By default, messages larger than 2.000 characters (sent via tells) get sent via slaves, if these exist. Fixes #3
397 lines
20 KiB
Python
397 lines
20 KiB
Python
import json
|
|
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 IgnCore
|
|
|
|
|
|
# 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: IgnCore = 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 json.loads(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 json.loads(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
|