Files
igncore/modules/onlinebot/online/org_controller.py
T
Minidodo d3461ef462 Moved org finder to the core
Allowed viewing the most recent account logs by using !account log recent
Allowed removing of chatbots
Moved the so far hardcoded org prefixing into the DB, which allows dynamically changing them.
Fix for orgrosters; org leaves are being detected now.
2022-02-11 15:43:48 +01:00

363 lines
20 KiB
Python

import json
import re
import time
from threading import Thread
import requests
from core.buddy_service import BuddyService
from core.cache_service import CacheService
from core.chat_blob import ChatBlob
from core.command_alias_service import CommandAliasService
from core.command_param_types import Const, Any
from core.command_request import CommandRequest
from core.db import DB
from core.decorators import instance, command, timerevent, event
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.org_pork_service import OrgPorkService
from core.lookup.pork_service import PorkService
from core.message_hub_service import MessageHubService
from core.text import Text
from core.igncore import IgnCore
from core.util import Util
from modules.core.accounting.services.access_service import AccessService
# noinspection PyAttributeOutsideInit,SpellCheckingInspection,DuplicatedCode
from modules.core.accounting.services.account_service import AccountService
from modules.onlinebot.online.org_alias_controller import OrgAliasController
# noinspection DuplicatedCode
@instance()
class OrgController:
threads = {}
letters = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u",
"v", "w", "x", "y", "z", "others", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
pork_uri = "https://people.anarchy-online.com/people/lookup/orgs.html?l=%s"
single_org_uri = "https://people.anarchy-online.com/org/stats/d/5/name/%d/basicstats.xml?data_type=json"
def inject(self, registry):
self.logger = Logger(__name__)
self.bot: IgnCore = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.util: Util = registry.get_instance("util")
self.pork: PorkService = registry.get_instance("pork_service")
self.org_pork: OrgPorkService = registry.get_instance("org_pork_service")
self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service")
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
self.access_service: AccessService = registry.get_instance("access_service")
self.relay_hub_service: MessageHubService = registry.get_instance("message_hub_service")
self.alias_controller: OrgAliasController = registry.get_instance("org_alias_controller")
self.account_service: AccountService = registry.get_instance("account_service")
self.cache: CacheService = registry.get_instance("cache_service")
self.org_alias: OrgAliasController = registry.get_instance("org_alias_controller")
def start(self):
self.db.exec("CREATE TABLE IF NOT EXISTS orgs(org_id int primary key not null)")
self.command_alias_service.add_alias("org", "orgs")
@event("connect", "Adds all members to buddylist")
def connect(self, _, _1):
query = self.db.query("SELECT char_id, member from account where member IN (SELECT org_id from orgs)")
if query:
for player in query:
self.buddy_service.add_buddy(player.char_id, "member")
@command(command="orgs", params=[Const("add"), Any("Organisation")], access_level="admin",
description="Add an org to the online list", sub_command="moderate")
def orgs_add_any(self, sender, _, org):
return self.orgs_add(org, sender)
def orgs_add(self, search, sender):
orgs = self.org_pork.find_org(search)
if len(orgs) == 1:
orgs = orgs[0]
if self.db.exec("REPLACE INTO orgs(org_id) VALUES(?)",
[orgs.org_id]) == 1:
sender.reply(f"Adding the organisation <highlight>{orgs.org_name}<end> to the roster...")
org_adder = Thread(name=orgs.org_id, target=self.fetch_single,
args=(orgs.org_id, orgs.org_name, sender))
self.threads[orgs.org_id] = org_adder
org_adder.start()
else:
return f"The organisation <highlight>{orgs.org_name}<end> is in the roster already."
elif len(orgs) == 0:
return f"No org with the name <highlight>{search}<end> was found on PoRK."
else:
blob = "Your search had multiple results; please pick an org:<br>"
for org in orgs:
blob += f'[{self.text.make_chatcmd("Add", f"/tell <myname> orgs add {org.org_id}")}]' \
f'[{self.text.make_chatcmd("More", f"/tell <myname> org info {org.org_id}")}]' \
f' <highlight>{org.org_name}<end> (<highlight>{org.org_id}<end>) ' \
f'<{org.faction.lower()}>{org.faction}<end> [<highlight>{org.member_count}<end> members]' \
f'<br><pagebreak>'
return ChatBlob("Pick an Org", blob)
@command(command="orgs", params=[Const("rem"), Any("Organisation")], access_level="admin",
description="Remove an org from the online list", sub_command="moderate")
def orgs_rem_any(self, _, _1, org):
return self.orgs_rem(org)
def orgs_rem(self, search):
orgs = self.org_pork.find_org(search)
if len(orgs) == 1:
orgs = orgs[0]
if self.db.exec("DELETE FROM orgs where org_id = ?", [orgs.org_id]) == 1:
org_remover = Thread(name=orgs.org_id, target=self.remove_single, args=(orgs.org_id, orgs.org_name))
self.threads[orgs.org_id] = org_remover
org_remover.start()
return f"Removed the organisation <highlight>{orgs.org_name}<end> from the roster."
else:
return f"The organisation <highlight>{orgs.org_name}<end> is not on the roster list."
elif len(orgs) == 0:
return f"The organisation <highlight>{search}<end> is not on the roster list."
else:
blob = "Your search had multiple results; please pick an org:<br>"
for org in orgs:
blob += f'[{self.text.make_chatcmd("Remove", f"/tell <myname> orgs remove {org.org_id}")}]' \
f'[{self.text.make_chatcmd("More", f"/tell <myname> org info {org.org_id}")}]' \
f' <highlight>{org.org_name}<end> (<highlight>{org.org_id}<end>) ' \
f'<{org.faction.lower()}>{org.faction}<end> [<highlight>{org.member_count}<end> members]' \
f'<br><pagebreak>'
return ChatBlob("Pick an Org", blob)
@command(command="orgs", params=[Const("list", is_optional=True)], access_level="member",
description="View all orgs on the online list", sub_command="online_info")
def orgs_list(self, sender: CommandRequest, _):
head = "<header>Organisations registered as Members<end>"
blob = ""
for org in self.db.query("SELECT o.*, a.*, oa.org_alias from orgs o "
"left join all_orgs a on o.org_id = a.org_id "
"left join org_alias oa on o.org_id = oa.org_id order by a.org_name"):
org = DictObject(org)
blob += "- %s%s%s<highlight>%s<end> (%d) with <highlight>%d<end> members\n" % (
"[<highlight>" + self.text.make_chatcmd("Info", "/tell <myname> orgs info %d" % org.org_id,
style="style='text-decoration:none'") + "</highlight>] ",
"[<red>" + self.text.make_chatcmd("Remove", "/tell <myname> orgs rem %d" % org.org_id,
style="style='text-decoration:none'") + "</red>] " if
sender.sender.access_level["level"] <= 10 else "",
f"[<highlight>{self.org_alias.get_alias(org.org_id)}</highlight>] ",
org.org_name, org.org_id,
self.db.query_single("SELECT member_count from all_orgs where org_id=?", [org.org_id]).member_count)
return ChatBlob(head, blob)
@timerevent(budatime="48h", description="pull list of all orgs from PoRK")
def discover_orgs(self, _, _1):
def discover():
start = time.time()
self.logger.info("Fetching global orgdata..")
count = 0
data = []
for letter in self.letters:
result = requests.get(self.pork_uri % letter)
# noinspection RegExpRepeatedSpace
matches = re.findall("""<tr>
<td align="left">
<a href="//people.anarchy-online.com/org/stats/d/5/name/(\d+)">
(.+)</a></td>
<td align="right">(\d+)</td>
<td align="right">(\d+)</td>
<td align="left">(\w+)</td>
<td align="left">(\w+)</td>
<td align="left" class="dim">RK5</td>
</tr>""", result.text)
for match in matches:
data.append((int(match[0]), match[1], int(match[2]), match[4], start))
count += 1
self.logger.info(f"Batch {letter} done!")
with self.db.pool.get_connection() as conn:
with conn.cursor() as cur:
cur.executemany(
"INSERT INTO all_orgs(org_id, org_name, member_count, faction, last_seen) "
"VALUES(?, ?, ?, ?, ?) "
"ON DUPLICATE KEY UPDATE "
"org_name=VALUE(org_name), "
"member_count=VALUE(member_count), "
"last_seen=VALUE(last_seen)",
data)
self.db.exec("DELETE FROM all_orgs where last_seen < ?", [time.time() - 2 * 24 * 60 * 60])
self.logger.info(f"Successfully fetched {count} orgs in {time.time() - start:.2f} seconds.")
self.threads.pop('orgdiscover', None)
if "orgdiscover" not in self.threads.keys():
thread = Thread(name="orgdiscover", target=discover, daemon=True)
self.threads["orgdiscover"] = thread
thread.start()
@timerevent(budatime="24h", description="Pull data for our own orgs")
def fetch_orgs(self, _, _1):
def discover():
start = time.time()
self.logger.info("Fetching orgdata..")
output = []
data = []
accounts = []
ours = self.db.query("SELECT o.org_id, a.org_name from orgs o "
"left join all_orgs a on o.org_id = a.org_id order by lower(a.org_name)")
for org in ours:
result = requests.get(self.single_org_uri % org.org_id).json()
if result:
self.cache.store('org_roster', f"{org.org_id}.5.json", json.dumps(result))
else:
result = json.loads(self.cache.retrieve('org_roster', f"{org.org_id}.5.json").data)
for char_info in result[1]:
data.append((char_info["CHAR_INSTANCE"], char_info["NAME"], char_info["FIRSTNAME"],
char_info["LASTNAME"], char_info["LEVELX"], char_info["BREED"],
char_info["SEX"], result[0]["SIDE_NAME"], char_info["PROF"],
char_info["PROF_TITLE"], char_info["DEFENDER_RANK_TITLE"], char_info["ALIENLEVEL"],
result[0]["ORG_INSTANCE"], result[0]["NAME"], char_info["RANK_TITLE"],
char_info["RANK"], char_info["CHAR_DIMENSION"], char_info["HEADID"],
0, char_info["PVPTITLE"], "roster", int(time.time())))
accounts.append((char_info["CHAR_INSTANCE"],
char_info["CHAR_INSTANCE"],
result[0]["ORG_INSTANCE"],
start, start))
if not self.buddy_service.get_buddy(char_info["CHAR_INSTANCE"]):
self.buddy_service.add_buddy(char_info["CHAR_INSTANCE"], "member")
output.append(DictObject({"action": "JOIN",
"name": char_info['NAME'],
"org_name": result[0]["NAME"],
"org_id": result[0]["ORG_INSTANCE"],
"level": char_info["LEVELX"],
"ai_level": char_info["ALIENLEVEL"],
"ranks": 0}))
self.logger.info(f"Organisation {org.org_name} has been updated.")
self.account_service.create_users(accounts)
if len(data) > 1:
with self.db.lock:
with self.db.pool.get_connection() as conn:
with conn.cursor() as cur:
cur.executemany("INSERT INTO player(char_id, name, first_name, last_name, "
"level, breed, gender, faction, profession, profession_title, "
"ai_rank, ai_level, org_id, org_name, org_rank_name, "
"org_rank_id, dimension, head_id, pvp_rating, pvp_title, "
"source, last_updated) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "
"ON DUPLICATE KEY UPDATE first_name=VALUE(first_name), "
"last_name=VALUE(last_name), level=VALUE(level), "
"breed=VALUE(breed), gender=VALUE(gender), "
"faction=VALUE(faction), profession=VALUE(profession), "
"profession_title=VALUE(profession_title),ai_rank=VALUE(ai_rank), "
"ai_level=VALUE(ai_level), org_name=VALUE(org_name), "
"org_id=VALUE(org_id), org_rank_name=VALUE(org_rank_name), "
"org_rank_id=VALUE(org_rank_id), source=VALUE(source), "
"last_updated=VALUE(last_updated)", data)
conn.commit()
self.db.exec("UPDATE account set member = -1 "
"where char_id NOT IN "
"(select char_id from player where org_id in (select org_id from orgs)) and member > 0")
self.db.exec("DELETE FROM ranks where main not in (select main from account where member > -1)")
players = self.db.query("SELECT a.char_id, p.* FROM account a "
"left join player p on a.char_id=p.char_id "
"where (a.last_updated < ? and member >1) or "
"(p.org_id NOT IN (select org_id from orgs) and a.member>1) ",
[time.time() - 24 * 60 * 60])
accounts = []
for player in players:
bonus = None
player = DictObject(player)
count = 0
if self.buddy_service.remove_buddy(player.char_id, "member"):
count = self.db.exec("DELETE FROM ranks where main=?", [player.char_id])
accounts.append((player.char_id, 1))
bonus = "LEAVE"
new_data = self.pork.request_char_info(player.name, player.dimension)
if new_data and new_data.char_id == player.char_id:
self.pork.save_character_info(new_data)
else:
bonus = "DEL"
accounts.append((player.char_id, 1))
if bonus:
output.append(DictObject({"action": bonus,
"name": player.name,
"org_name": player.org_name,
"org_id": player.org_id,
"level": player.level,
"ai_level": player.ai_level,
"ranks": count}))
self.account_service.remove_members(accounts)
self.log(output, time.time() - start)
self.logger.info(
f"Successfully fetched {len(data)} players from {len(ours)} orgs "
f"in {time.time() - start:.2f} seconds. - ")
del self.threads['roster']
if "roster" not in self.threads.keys():
thread = Thread(name="roster", target=discover, daemon=True)
self.threads["roster"] = thread
thread.start()
def fetch_single(self, org_id, org_name, sender: object):
start = time.time()
data = []
accounts = []
self.logger.info("Fetching orgdata..")
count = 0
result = requests.get(self.single_org_uri % org_id).json()
for char_info in result[1]:
data.append((char_info["CHAR_INSTANCE"], char_info["NAME"], char_info["FIRSTNAME"],
char_info["LASTNAME"],
char_info["LEVELX"], char_info["BREED"],
char_info["SEX"], result[0]["SIDE_NAME"], char_info["PROF"],
char_info["PROF_TITLE"], char_info["DEFENDER_RANK_TITLE"], char_info["ALIENLEVEL"],
result[0]["ORG_INSTANCE"], result[0]["NAME"], char_info["RANK_TITLE"],
char_info["RANK"], char_info["CHAR_DIMENSION"], char_info["HEADID"],
0, char_info["PVPTITLE"], "roster", int(time.time())))
accounts.append((char_info["CHAR_INSTANCE"], char_info["CHAR_INSTANCE"], result[0]["ORG_INSTANCE"],
start, start))
self.buddy_service.add_buddy(char_info['CHAR_INSTANCE'], "member")
count += 1
with self.db.pool.get_connection() as conn:
with conn.cursor() as cur:
cur.executemany("REPLACE INTO player(char_id, name, first_name, last_name, level, breed, "
"gender, faction, profession, profession_title, ai_rank, ai_level, "
"org_id, org_name, org_rank_name, org_rank_id, dimension, head_id, "
"pvp_rating, pvp_title, source, last_updated) VALUES "
"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", data)
self.account_service.create_users(accounts)
self.logger.info(f"Organisation {org_name} added!")
sender.reply(f"<highlight>{org_name}</highlight> has been added to the roster. "
f"Runtime: {time.time() - start:.2f} seconds.")
self.logger.info(f"Successfully fetched {count} players in {time.time() - start} seconds.")
del self.threads[org_id]
def remove_single(self, org_id, org_name):
members = self.db.query("SELECT * from player where org_id=?", [org_id])
for member in members:
self.buddy_service.remove_buddy(member.char_id, "member")
self.db.exec("UPDATE account set member=-1 where char_id in"
" (SELECT char_id from player where org_id=?)", [org_id])
self.db.exec("DELETE FROM online where char_id in (SELECT char_id from player where org_id=?)", [org_id])
self.logger.info(f"Organisation {org_name} removed!")
del self.threads[org_id]
def log(self, blob, duration):
out = []
s = []
current = ""
for entry in blob:
s.append(f"[{self.alias_controller.get_alias(entry.org_id)}] [{entry.action}] {entry.name} "
f"({entry.level}/{entry.ai_level}) {'[R-P]' if entry.ranks > 0 else ''}\n")
s = sorted(s)
if len(s) > 0:
s.append(f"\nRuntime: {duration:.2f} seconds.")
for entry in s:
if len(current) > 1500:
out.append(current)
current = ""
current += entry
if len(current) > 10:
out.append(current)
if len(out) > 0:
self.relay_hub_service.send_message("member_logger", None, out, out)