c04f76c0db
Fixed command & event threading Events are now threaded by event_type (i.e. all buddy_logon events get ran in the same one) Added default preferences Fixed recipe loading for multiple installs (i.e. on different machines)
371 lines
20 KiB
Python
371 lines
20 KiB
Python
import json
|
|
import re
|
|
import time
|
|
from threading import Thread
|
|
|
|
import requests
|
|
from mysql.connector.cursor import CursorBase
|
|
|
|
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.tyrbot import Tyrbot
|
|
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: Tyrbot = 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")
|
|
|
|
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")
|
|
def orgs_add_any(self, sender, _, org):
|
|
return self.orgs_add(org, sender)
|
|
|
|
def orgs_add(self, search, sender):
|
|
orgs = self.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")
|
|
def orgs_rem_any(self, _, _1, org):
|
|
return self.orgs_rem(org)
|
|
|
|
def orgs_rem(self, search):
|
|
orgs = self.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="list")
|
|
def orgs_list(self, sender: CommandRequest, _):
|
|
head = "<header>Organisations in our Alliance<end>"
|
|
blob = ""
|
|
for org in self.db.query("SELECT * from orgs o "
|
|
"left join all_orgs a on o.org_id = a.org_id order by a.org_name"):
|
|
org = DictObject(org)
|
|
blob += "- %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 "",
|
|
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:
|
|
if int(match[2]) < 6:
|
|
continue
|
|
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: CursorBase
|
|
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: CursorBase
|
|
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 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 ?", ["%" + search + "%"])
|
|
|
|
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)
|