Initial Release of IGNCore version 2.5

This commit is contained in:
2021-08-09 13:18:56 +02:00
commit a83d98c47e
910 changed files with 224171 additions and 0 deletions
@@ -0,0 +1,58 @@
from core.command_param_types import Int, Character, NamedParameters
from core.decorators import instance, command
from core.lookup.character_history_service import CharacterHistoryService
from core.text import Text
from core.tyrbot import Tyrbot
@instance()
class CharacterHistoryController:
def __init__(self):
self.PAGE_SIZE = 25
pass
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.text: Text = registry.get_instance("text")
self.util = registry.get_instance("util")
self.character_history_service: CharacterHistoryService = registry.get_instance("character_history_service")
self.command_alias_service = registry.get_instance("command_alias_service")
def start(self):
self.command_alias_service.add_alias("h", "history")
@command(command="history",
params=[Character("character"), Int("server_num", is_optional=True), NamedParameters(['page'])],
access_level="member",
description="Get history of character",
extended_description="Use server_num 6 for RK2019 and server_num 5 for live")
def handle_history_cmd1(self, _, char, server_num, named_params):
server_num = server_num or self.bot.dimension
page = int(named_params.page or "1")
offset = (page - 1) * self.PAGE_SIZE
data = self.character_history_service.get_character_history(char.name, server_num)
if data:
data = [x for x in data]
else:
return f"Could not find history for <highlight>{char.name}</highlight> " \
f"on server <highlight>{server_num}</highlight>."
blob = " | ".join(["Date", "Lvl", "AI", "Side", "Breed", "CharId", "Guild (Rank)"]) + "\n"
blob += "__________________________________________________________\n"
return self.text.format_pagination(data, offset, page, self.formatter,
f'History of {char.name} (RK{server_num})',
f"Could not find history for <highlight>{char.name}</highlight> "
f"on server <highlight>{server_num:d}</highlight>.",
f'history {char.name} {server_num} ', self.PAGE_SIZE, blob)
def formatter(self, row, _, data):
if row.guild_name:
org = f"{row.guild_name} ({row.guild_rank_name})"
else:
org = ""
last_changed = self.util.format_date(int(float(row.last_changed)))
if row.deleted == "1": # This value is output as string
return "<red>DELETED</red>\n"
return f"{last_changed} | <white>{self.text.zfill(int(row.level), 220)}</white> | " \
f"<green>{self.text.zfill(int(row.defender_rank or 0), 30)}</green> | {row.faction:^7} | " \
f"{row.breed:^10} | {self.text.zfill(int(row.char_id or 0), int(data[0].char_id or 0))} | " \
f"<white>{org}</white>\n"
@@ -0,0 +1,180 @@
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.tyrbot import Tyrbot
@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: Tyrbot = 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:d}\n"
blob += f"AI Level: <green>{char_info.ai_level:d}</green>\n"
if char_info.org_id:
blob += f"Org: <highlight>{char_info.org_name}</highlight> ({char_info.org_id:d})\n"
blob += f"Org Rank: {char_info.org_rank_name} ({char_info.org_rank_id:d})\n"
else:
blob += "Org: &lt;None&gt;\n"
blob += "Org Rank: &lt;None&gt;\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: {'<green>Active</green>' if char.char_id else '<red>Inactive</red>'}\n"
blob += self.get_name_history(char.char_id)
alts = self.account_service.get_alts(char.char_id)
if len(alts) > 1:
blob += f"\n<header2>Alts ({len(alts):d})</header2>\n"
blob += self.alts_controller.format_alt_list(alts)
more_info = self.text.paginate_single(ChatBlob("More Info", blob))
msg = self.text.format_char_info(char_info, online_status, True) + " " + more_info
elif char.char_id:
blob = "<notice>Note: Could not retrieve detailed info for character.</notice>\n\n"
blob += f"Name: <highlight>{char.name}</highlight>\n"
blob += f"Character ID: <highlight>{char.char_id:d}</highlight>\n"
if online_status is not None:
blob += f"Online status: {'<green>Online</green>' if online_status else '<red>Offline</red>'}\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 <highlight>{char.name}</highlight> on RK{dimension:d}."
if request.channel == "msg":
self.bot.send_mass_message(request.sender.char_id, msg)
else:
request.reply(msg)
def get_name_history(self, char_id):
blob = "\n<header2>Name History</header2>\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 += "\"<highlight>" + char_info.name + "</highlight>\""
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()))
@@ -0,0 +1,254 @@
import requests
from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_param_types import Int, Any, Options
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.registry import Registry
@instance()
class OrgListController:
ORGLIST_BUDDY_TYPE = "orglist"
single_org_uri = "https://people.anarchy-online.com/org/stats/d/5/name/%d/basicstats.xml?data_type=json"
def __init__(self):
self.orglist = None
self.governing_types = DictObject({
"Anarchism": ["Anarchist"],
"Monarchy": ["Monarch", "Counsil", "Follower"],
"Feudalism": ["Lord", "Knight", "Vassal", "Peasant"],
"Republic": ["President", "Advisor", "Veteran", "Member", "Applicant"],
"Faction": ["Director", "Board Member", "Executive", "Member", "Applicant"],
"Department": ["President", "General", "Squad Commander", "Unit Commander",
"Unit Leader", "Unit Member", "Applicant"]
})
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text = registry.get_instance("db")
self.util = registry.get_instance("util")
self.text = registry.get_instance("text")
self.pork_service = registry.get_instance("pork_service")
self.org_pork_service = registry.get_instance("org_pork_service")
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
self.character_service = registry.get_instance("character_service")
@command(command="orglist", params=[Int("org_id")], access_level="member",
description="Show online status of characters in an org")
def orglist_cmd(self, request, org_id):
self.start_orglist_lookup(request.reply, org_id)
@command(command="orglist", params=[Any("character|org_name|org_id")], access_level="member",
description="Show online status of characters in an org")
def orglist_character_cmd(self, request, search):
if search.isdigit():
org_id = int(search)
else:
orgs = self.pork_service.find_orgs(search)
num_orgs = len(orgs)
if num_orgs == 0:
char_info = self.pork_service.get_character_info(search)
if char_info:
if not char_info.org_id:
return "<highlight>%s</highlight> does not appear to belong to an org." % search.capitalize()
else:
org_id = char_info.org_id
else:
return "Could not find character or org <highlight>%s</highlight>." % search
elif num_orgs == 1:
org_id = orgs[0].org_id
else:
blob = ""
for org in orgs:
blob += self.text.make_tellcmd(f"{org.org_name} ({org.org_id:d})",
f"orglist {org.org_id:d}") + "\n"
return ChatBlob(f"Org List ({num_orgs:d})", blob)
self.start_orglist_lookup(request.reply, org_id)
@command(command="orgs",
params=[Options(["info", "detail", "search"]), Any("Organisation")],
access_level="member",
description="View all information about org",
sub_command="info")
def display_org_any(self, _, _1, org):
return self.display_org(org)
def display_org(self, org):
orgs = self.find_org(org)
if len(orgs) == 1:
info = requests.get(self.single_org_uri % orgs[0].org_id).json()
blob = "Name: <notice>{NAME}</notice><br>"
blob += "Org-ID: <notice>{ORG_INSTANCE}</notice><br>"
blob += "Faction: <{side}>{SIDE_NAME}</{side}><br>"
blob += "President: <highlight><president></highlight><br>"
blob += "Government: <highlight>{GOVERNINGNAME}</highlight><br>"
blob += "Number of members: <highlight>{NUMMEMBERS:,}</highlight><br>"
blob += "Description: <{side}>{DESCRIPTION}</{side}><br><br>"
blob += "Objective: <{side}>{OBJECTIVE}</{side}><br><br>"
blob += "History: <{side}>{HISTORY}</{side}><br><br>"
blob += "[{ADD}] - [{REM}]<br>"
if info[0]["GOVERNINGNAME"] == "Department":
blob += "<br><notice>Generals<end><br>"
blob += "<generals>"
blob = blob.format(**info[0], side=info[0]["SIDE_NAME"].lower(),
ADD=self.text.make_chatcmd("Add to our orgs",
"/tell <myname> orgs add %d" % orgs[0].org_id),
REM=self.text.make_chatcmd("Remove from our orgs",
"/tell <myname> orgs rem %d" % orgs[0].org_id))
generals = ""
for member in info[1]:
if member["RANK"] == 0:
blob = blob.replace("<president>", member["NAME"])
if info[0]["GOVERNINGNAME"] == "Department":
if member["RANK"] == 1:
generals += "- {NAME} ({LEVELX}/<green>{ALIENLEVEL}<end>) {PROF}<br>".format(**member)
blob = blob.replace("<generals>", generals)
return ChatBlob(orgs[0].org_name, blob)
elif len(orgs) == 0:
return f"No org with the name <highlight>{org}<end> was found on PoRK."
else:
blob = "Your search had multiple results; please pick an org:<br>"
for org in orgs:
if Registry.get_instance('org_alias_controller', is_optional=True):
blob += f'[{self.text.make_chatcmd("Add", f"/tell <myname> orgs add {org.org_id}")}]'
blob += f'[{self.text.make_chatcmd("More", f"/tell <myname> org info {org.org_id}")}]'
blob += f' <highlight>{org.org_name}<end> ' \
f'(<highlight>{org.org_id}<end>) <{org.faction.lower()}>{org.faction}<end> ' \
f'[<highlight>{org.member_count}<end> members]<br><pagebreak>'
return ChatBlob("Pick an Org", blob)
def start_orglist_lookup(self, reply, org_id):
if self.orglist:
reply("There is an orglist already in progress.")
return
reply(f"Downloading org roster for org id {org_id:d}...")
self.orglist = self.org_pork_service.get_org_info(org_id)
if not self.orglist:
reply(f"Could not find org with ID <highlight>{org_id:d}</highlight>.")
return
self.orglist.org_members = list(self.orglist.org_members.values())
self.orglist.reply = reply
self.orglist.waiting_org_members = {}
self.orglist.finished_org_members = {}
reply(
f"Checking online status for {len(self.orglist.org_members):d} members of "
f"<highlight>{self.orglist.org_info.name}</highlight>...")
# process all name lookups
while self.bot.iterate(1):
pass
self.check_for_orglist_end()
@event(event_type=BuddyService.BUDDY_LOGON_EVENT,
description="Detect online buddies for orglist command", is_hidden=True)
def buddy_logon_event(self, _, event_data):
if self.orglist and event_data.char_id in self.orglist.waiting_org_members:
self.update_online_status(event_data.char_id, True)
self.check_for_orglist_end()
@event(event_type=BuddyService.BUDDY_LOGOFF_EVENT,
description="Detect offline buddies for orglist command", is_hidden=True)
def buddy_logoff_event(self, _, event_data):
if self.orglist and event_data.char_id in self.orglist.waiting_org_members:
self.update_online_status(event_data.char_id, False)
self.check_for_orglist_end()
def update_online_status(self, char_id, status):
self.orglist.finished_org_members[char_id] = self.orglist.waiting_org_members[char_id]
self.orglist.finished_org_members[char_id].online = status
del self.orglist.waiting_org_members[char_id]
def check_for_orglist_end(self):
if self.orglist.org_members:
self.iterate_org_members()
if not self.orglist.waiting_org_members:
self.orglist.reply(self.format_result())
self.orglist = None
def format_result(self):
org_ranks = {}
for rank_name in self.governing_types[self.orglist.org_info.governing_type]:
org_ranks[rank_name] = DictObject({
"online_members": [],
"offline_members": []
})
org_ranks["Inactive"] = DictObject({
"online_members": [],
"offline_members": []
})
for char_id, org_member in self.orglist.finished_org_members.items():
if org_member.online == 2:
org_ranks["Inactive"].offline_members.append(org_member)
elif org_member.online == 1:
org_ranks[org_member.org_rank_name].online_members.append(org_member)
else:
org_ranks[org_member.org_rank_name].offline_members.append(org_member)
blob = ""
num_online = 0
num_total = 0
for rank_name, rank_info in org_ranks.items():
rank_num_online = len(rank_info.online_members)
rank_num_total = len(rank_info.offline_members) + rank_num_online
blob += f"<header2>{rank_name} ({rank_num_online:d} / {rank_num_total:d})</header2>\n"
num_online += rank_num_online
num_total += rank_num_total
for org_member in sorted(rank_info.online_members, key=lambda x: x.name):
level = org_member.level if org_member.ai_level == 0 else \
f"{org_member.level:d}/<green>{org_member.ai_level:d}</green>"
blob += self.format_org(org_member)
# blob += f"{org_member.name} ({level}, " \
# f"{org_member.gender} {org_member.breed} <highlight>{org_member.profession}</highlight>)\n"
if rank_num_total < 200:
blob += "<font color='#555555'>" + ", ".join(
map(lambda x: x.name, sorted(rank_info.offline_members, key=lambda x: x.name))) + "</font>"
blob += "\n"
else:
blob += "<font color='#555555'>Offline members omitted for brevity</font>\n"
blob += "\n"
return ChatBlob(f"Orglist for '{self.orglist.org_info.name}' ({num_online:d} / {num_total:d})", blob)
def format_org(self, player, rank="", main_order=False):
return f" {self.util.get_prof_icon(player.profession)} {rank}{self.text.zfill(player.level, 220)}/<green>{self.text.zfill(player.ai_level, 30)}</green> <{player.faction.lower()}>{player.name}</{player.faction.lower()}>\n"
def iterate_org_members(self):
# add org_members that we don't have online status for as buddies
while self.orglist.org_members and self.buddy_list_has_available_slots():
org_member = self.orglist.org_members.pop()
char_id = org_member.char_id
self.orglist.waiting_org_members[char_id] = org_member
is_online = self.buddy_service.is_online(char_id)
if is_online is None:
if self.character_service.resolve_char_to_id(org_member.name):
self.buddy_service.add_buddy(char_id, self.ORGLIST_BUDDY_TYPE)
self.buddy_service.remove_buddy(char_id, self.ORGLIST_BUDDY_TYPE)
else:
# character is inactive, set as offline
self.update_online_status(char_id, 2)
else:
self.update_online_status(char_id, is_online)
def buddy_list_has_available_slots(self):
return self.buddy_service.buddy_list_size - self.buddy_service.get_buddy_list_size() > 0
def find_org(self, search, table="all_orgs"):
if search.isdigit():
return self.db.query("SELECT * FROM " + table + " where org_id = ?", [search])
elif isinstance(search, str):
return self.db.query("SELECT * FROM " + table + " where org_name LIKE ?", [f"%{search}%"])