d4d08b3054
Orgbots will run the roster 24 hours and 15 minutes after the last one funcom published on PORK; Onlinebots 24 hours and 10 minutes of the slowest roster maintained.
379 lines
21 KiB
Python
379 lines
21 KiB
Python
import json
|
|
import re
|
|
import time
|
|
from threading import Thread
|
|
|
|
import datetime
|
|
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}</highlight> 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}</highlight> is in the roster already."
|
|
elif len(orgs) == 0:
|
|
return f"No org with the name <highlight>{search}</highlight> 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}</highlight> (<highlight>{org.org_id}</highlight>) ' \
|
|
f'<{org.faction.lower()}>{org.faction}</{org.faction.lower()}> [<highlight>{org.member_count}</highlight> 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}</highlight> from the roster."
|
|
else:
|
|
return f"The organisation <highlight>{orgs.org_name}</highlight> is not on the roster list."
|
|
elif len(orgs) == 0:
|
|
return f"The organisation <highlight>{search}</highlight> 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}</highlight> (<highlight>{org.org_id}</highlight>) ' \
|
|
f'<{org.faction.lower()}>{org.faction}</{org.faction.lower()}> [<highlight>{org.member_count}</highlight> 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</header>"
|
|
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</highlight> (%d) with <highlight>%d</highlight> members\n" % (
|
|
"[<highlight>" + self.text.make_chatcmd("Info", f"/tell <myname> orgs info {org.org_id}",
|
|
style="style='text-decoration:none'") + "</highlight>] ",
|
|
"[<red>" + self.text.make_chatcmd("Remove", f"/tell <myname> orgs rem {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 = []
|
|
timestamp = 0
|
|
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 and len(result[1]) > 0:
|
|
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)
|
|
d = datetime.datetime.strptime(result[2] + " +0000", '%Y/%m/%d %H:%M:%S %z').timestamp()
|
|
if d > timestamp:
|
|
timestamp = d
|
|
|
|
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. - ")
|
|
while (timestamp + 24 * 60 * 60) < datetime.datetime.now().timestamp():
|
|
timestamp += 24 * 60 * 60
|
|
self.db.exec("UPDATE timer_event SET next_run=? WHERE handler=? AND event_sub_type=?", [
|
|
int(timestamp +24*60*60 + 10*60),
|
|
"modules.onlinebot.online.org_controller.OrgController.fetch_orgs",
|
|
86400
|
|
])
|
|
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:
|
|
y = 0
|
|
for x in out:
|
|
y += 1
|
|
blob = ChatBlob(f"Recent changes - {datetime.date.today()} - ({y}/{len(out)})", x, embed=True)
|
|
self.relay_hub_service.send_message("member_logger", None, blob, blob)
|