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 {orgs.org_name} 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 {orgs.org_name} is in the roster already." elif len(orgs) == 0: return f"No org with the name {search} was found on PoRK." else: blob = "Your search had multiple results; please pick an org:
" for org in orgs: blob += f'[{self.text.make_chatcmd("Add", f"/tell orgs add {org.org_id}")}]' \ f'[{self.text.make_chatcmd("More", f"/tell org info {org.org_id}")}]' \ f' {org.org_name} ({org.org_id}) ' \ f'<{org.faction.lower()}>{org.faction} [{org.member_count} members]' \ f'
' 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 {orgs.org_name} from the roster." else: return f"The organisation {orgs.org_name} is not on the roster list." elif len(orgs) == 0: return f"The organisation {search} is not on the roster list." else: blob = "Your search had multiple results; please pick an org:
" for org in orgs: blob += f'[{self.text.make_chatcmd("Remove", f"/tell orgs remove {org.org_id}")}]' \ f'[{self.text.make_chatcmd("More", f"/tell org info {org.org_id}")}]' \ f' {org.org_name} ({org.org_id}) ' \ f'<{org.faction.lower()}>{org.faction} [{org.member_count} members]' \ f'
' 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 = "
Organisations registered as Members
" 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%s (%d) with %d members\n" % ( "[" + self.text.make_chatcmd("Info", f"/tell orgs info {org.org_id}", style="style='text-decoration:none'") + "] ", "[" + self.text.make_chatcmd("Remove", f"/tell orgs rem {org.org_id}", style="style='text-decoration:none'") + "] " if sender.sender.access_level["level"] <= 10 else "", f"[{self.org_alias.get_alias(org.org_id)}] ", 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(""" (.+) (\d+) (\d+) (\w+) (\w+) RK5 """, 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"{org_name} 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)