diff --git a/bootstrap.py b/bootstrap.py index 2d8e8cc..690994a 100644 --- a/bootstrap.py +++ b/bootstrap.py @@ -50,7 +50,7 @@ try: mod = __import__(f'conf.{bot}', fromlist=['BotConfig']) config: BotConfig = getattr(mod, 'BotConfig') if sys.version_info < (3, 9): - logger.warning("Versions lower then Python3.10 are not supported.") + logger.warning("Versions lower then Python3.9 are not supported.") exit(-1) # load config values from env vars diff --git a/conf/config.py b/conf/config.py index 63e9eca..87c274b 100644 --- a/conf/config.py +++ b/conf/config.py @@ -25,6 +25,7 @@ class BotConfig: "username": "", "password": "", "host": "", + "port": 3306, "name": ""}) # IGNCore supports splitting of the DB; If you're hosting multiple bots, its generally a good idea to fill @@ -39,14 +40,11 @@ class BotConfig: "username": "", "password": "", "host": "", + "port": 3306, "name": "" }) # END # - # If you have got access to any sort of tower API (there are multiple) - # you can enter the URI for accessing it here. - tower_url = "" - # In this section you can add some slaves to the bot. # They serve as a buddylist expander, and tells may get sent through them. (Tells which are known to be spammy) slaves = [ @@ -75,4 +73,4 @@ class BotConfig: ] # DO NOT TOUCH THIS LINE - slaves = [DictObject(x) for x in slaves if password != ""] + slaves = [DictObject(x) for x in slaves] \ No newline at end of file diff --git a/conf/template.config.py b/conf/template.config.py index ba0f50b..76318cb 100644 --- a/conf/template.config.py +++ b/conf/template.config.py @@ -12,6 +12,7 @@ class BotConfig: "username": "", "password": "", "host": "", + "port": 3306, "name": ""}) shared_db = DictObject({ @@ -19,9 +20,9 @@ class BotConfig: "username": "", "password": "", "host": "", + "port": 3306, "name": "" }) - tower_url = "" slaves = [ # {"username": "account_name", "password": "password", "character": "character_name"}, @@ -39,4 +40,5 @@ class BotConfig: "modules/raidbot" ] - slaves = [DictObject(x) for x in slaves if password != ""] + # DO NOT TOUCH THIS LINE + slaves = [DictObject(x) for x in slaves] diff --git a/core/igncore.py b/core/igncore.py index 029c624..aa07789 100644 --- a/core/igncore.py +++ b/core/igncore.py @@ -42,7 +42,7 @@ class IgnCore: self.last_timer_event = 0 self.start_time = int(time.time()) self.major_version = "IGNCore v2.7" - self.minor_version = "1" + self.minor_version = "2" self.incoming_queue = FifoQueue() self.mass_message_queue = None self.conns = DictObject() diff --git a/dummy.start.sh b/dummy.start.sh index 1b5dd94..ce57408 100644 --- a/dummy.start.sh +++ b/dummy.start.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash -PYTHON_BINARY=python3 -if ! [ -x "$(command -v $PYTHON_BINARY)" ]; then - PYTHON_BINARY=python -fi +#PYTHON_BINARY=python3 +#if ! [ -x "$(command -v $PYTHON_BINARY)" ]; then +# PYTHON_BINARY=python +#fi -$PYTHON_BINARY --version source venv/bin/activate +python --version set -o pipefail -o errexit # The bot uses non-zero exit codes to signal state. diff --git a/modules/core/accounting/services/account_service.py b/modules/core/accounting/services/account_service.py index 561c338..b98a2c6 100644 --- a/modules/core/accounting/services/account_service.py +++ b/modules/core/accounting/services/account_service.py @@ -122,18 +122,18 @@ class AccountService: self.event_service.register_event_type(self.MEMBER_LOGON) self.event_service.register_event_type(self.MEMBER_LOGOFF) self.setting_service.register(self.module_name, "is_alliance_bot", False, BooleanSettingType(), - "Is this bot used as an alliancebot") + "Is this bot used as an alliancebot") self.setting_service.register(self.module_name, "alt_verification", False, BooleanSettingType(), - "alts require admin verification") + "alts require admin verification") # Default preferences self.setting_service.register(self.module_name, "pref_autoinvite", False, BooleanSettingType(), - "Default Value for the auto invite preference") + "Default Value for the auto invite preference") self.setting_service.register(self.module_name, "pref_raidinvite", True, BooleanSettingType(), - "Default Value for the raid invite (Massinvite) preference") + "Default Value for the raid invite (Massinvite) preference") self.setting_service.register(self.module_name, "pref_raidspam", True, BooleanSettingType(), - "Default Value for the raid spam (Mass Message) preference") + "Default Value for the raid spam (Mass Message) preference") self.setting_service.register(self.module_name, "pref_newsspam", True, BooleanSettingType(), - "Default Value for the news spam (News on logon) preference") + "Default Value for the news spam (News on logon) preference") v = self.setting_service.get_value("pref_raidspam") # Ranks @@ -316,6 +316,7 @@ class AccountService: try: def del_orgs(_): del self.orgs + self.orgs = [x["org_id"] for x in self.db.query("SELECT * from orgs", [])] self.bot.job_scheduler.delayed_job(del_orgs, 60) return self.orgs @@ -331,6 +332,7 @@ class AccountService: try: def del_orgs(_): del self.org_names + self.orgs = [x["org_name"] for x in self.db.query("SELECT * from orgs o " "LEFT JOIN all_orgs a on o.org_id = ao.org_id", [])] self.bot.job_scheduler.delayed_job(del_orgs, 60) diff --git a/modules/core/discord/discord_controller.py b/modules/core/discord/discord_controller.py index 5f05c32..e5c7b6c 100644 --- a/modules/core/discord/discord_controller.py +++ b/modules/core/discord/discord_controller.py @@ -108,7 +108,7 @@ class DiscordController: def pre_start(self): self.setting_service.register(self.module_name, "discord_token", "", HiddenSettingType(allow_empty=True), - "Enter your Discord token here") + "Enter your Discord token here") def get_name(self, discord_id): data = self.db.query_single( @@ -584,6 +584,7 @@ class DiscordController: return channel: TextChannel = msg.channel if channel.id == int(self.setting_service.get_value("dc_relay_public")): + if self.setting_service.get_value('is_alliance_bot') == "0": response = f"[{html.escape(msg.author.nick if msg.author.nick else msg.author.name, False)}" \ f"]: " \ @@ -592,7 +593,8 @@ class DiscordController: response = f"{html.escape(msg.author.nick if msg.author.nick else msg.author.name, False)}: " \ f"{html.escape(emojis.decode(msg.clean_content), False)}" await msg.delete(delay=3600) - self.relay_hub_service.send_message("public_relay", [msg.author.nick, msg.author], response, response) + sender = self.db.query_single("SELECT * from account a left join player p on a.main = p.char_id where a.discord_id = ?", [msg.author.id]) + self.relay_hub_service.send_message("public_relay", sender, html.escape(emojis.decode(msg.clean_content), False), response) if self.is_command(msg.content): admin = self.get_role("Administrator", self.guild.roles) diff --git a/modules/core/private_channel/private_channel_controller.py b/modules/core/private_channel/private_channel_controller.py index 382c313..e2d4b55 100644 --- a/modules/core/private_channel/private_channel_controller.py +++ b/modules/core/private_channel/private_channel_controller.py @@ -42,6 +42,7 @@ class PrivateChannelController: self.account_service: AccountService = registry.get_instance("account_service") self.db: DB = registry.get_instance("db") self.priv: PrivateChannelService = registry.get_instance("private_channel_service") + self.online_controller = registry.get_instance("online_controller", is_optional=True) def pre_start(self): self.db.create_view("online") @@ -205,6 +206,10 @@ class PrivateChannelController: msg = f"{self.text.format_char_info(self.pork.get_character_info(event_data.char_id))} joined us {info}" self.bot.send_private_channel_message(msg, fire_outgoing_event=False) self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, msg, self.PRIVATE_CHANNEL_PREFIX + msg) + afk_list = {} + if self.online_controller: + afk_list = self.online_controller.afk_list + od = OnlineDisplay(self.text, self.util, self.db, afk_list) od = OnlineDisplay(self.text, self.util, self.db) params = [self.bot.name, self.bot.get_char_id()] self.bot.send_mass_message(event_data.char_id, diff --git a/modules/onlinebot/online/org_controller.py b/modules/onlinebot/online/org_controller.py index 2d28b9a..cd49b82 100644 --- a/modules/onlinebot/online/org_controller.py +++ b/modules/onlinebot/online/org_controller.py @@ -4,7 +4,6 @@ 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 @@ -173,7 +172,6 @@ class OrgController: 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(?, ?, ?, ?, ?) " @@ -235,7 +233,6 @@ class OrgController: 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, " diff --git a/modules/orgbot/alliance/alliance_relay.py b/modules/orgbot/alliance/alliance_relay.py index f695a10..41b7f33 100644 --- a/modules/orgbot/alliance/alliance_relay.py +++ b/modules/orgbot/alliance/alliance_relay.py @@ -6,6 +6,7 @@ from core.chat_blob import ChatBlob from core.command_param_types import Const, Character, Options, Any from core.conn import Conn from core.decorators import instance, command, setting +from core.dict_object import DictObject from core.logger import Logger from core.lookup.character_service import CharacterService from core.setting_service import SettingService @@ -44,8 +45,8 @@ class AllianceRelay: 100) self.bot.register_packet_handler(server_packets.PrivateChannelMessage.id, self.handle_private_channel_message) - @command(command="mrelay", params=[], access_level="admin", - description="View the relay settings") + @command(command="mrelay", params=[], access_level="member", + description="View the relay settings", sub_command="info") def mrelay(self, request): def display_color(color, msg=None): if not msg: @@ -347,7 +348,8 @@ class AllianceRelay: name = self.format_text(self.relay_color_sender().get_value().get(priv, self.relay_color_sender().get_value().get("default")), name) text = self.format_text(self.relay_color_msg().get_value().get(priv, self.relay_color_msg().get_value().get("default")), text) formatted = self.format_text(self.relay_color_base().get_value().get(priv, self.relay_color_base().get_value().get("default")), f"[{org}] {name}: {text}") - self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, plain, formatted) + sender = DictObject({"char_id": packet.char_id, "name": self.character_service.get_char_name(packet.char_id)}) + self.message_hub_service.send_message(self.MESSAGE_SOURCE, sender, plain, formatted) def handle_relay_hub_message(self, ctx): enabled = lambda x: self.relay_enabled().get_value().get(x, self.relay_enabled().get_value().get('default', False)) @@ -366,7 +368,12 @@ class AllianceRelay: if len(message) < 1: continue sender = ctx.sender.name - abbrv = self.relay_guild_abbreviations().get_value().get(key, self.relay_guild_abbreviations().get_value().get("default")) + + abbrv = self.relay_guild_abbreviations().get_value().get(key, self.setting_service.get_value("org_name")) + if ctx.source == "private_channel": + abbrv += " - Guest" + elif ctx.source == "public_relay": + abbrv += " - DC" cmd = self.relay_command().get_value().get(key, self.relay_command().get_value().get("default")) msg = f"{cmd} [{abbrv}] {sender}: {message}" self.send_message_to_alliance(key, msg) diff --git a/modules/orgbot/org/org_controller.py b/modules/orgbot/org/org_controller.py index 6e8fe04..7806ad9 100644 --- a/modules/orgbot/org/org_controller.py +++ b/modules/orgbot/org/org_controller.py @@ -88,7 +88,10 @@ class OrgChannelController: self.bot.send_org_message(msg, fire_outgoing_event=False) self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, None, "[Org] " + msg) - od = OnlineDisplay(self.text, self.util, self.db) + afk_list = [] + if self.online_controller: + afk_list = self.online_controller.afk_list + od = OnlineDisplay(self.text, self.util, self.db, afk_list) params = [self.bot.name, self.bot.get_char_id()] self.bot.send_mass_message(event_data.packet.char_id, od.format_blob(od.format_by_channel_main("and channel_id IN (1, 2) ", params))) diff --git a/modules/standard/online/online_controller.py b/modules/standard/online/online_controller.py index 65965a7..bc59b5d 100644 --- a/modules/standard/online/online_controller.py +++ b/modules/standard/online/online_controller.py @@ -1,3 +1,4 @@ +import re import time from threading import Thread @@ -7,13 +8,17 @@ from core.command_alias_service import CommandAliasService from core.command_param_types import Int, Any, Const, Options from core.db import DB from core.decorators import instance, event, command +from core.dict_object import DictObject from core.fifo_queue import FifoQueue from core.logger import Logger +from core.lookup.character_service import CharacterService from core.private_channel_service import PrivateChannelService +from core.public_channel_service import PublicChannelService from core.setting_service import SettingService from core.text import Text from core.igncore import IgnCore from core.util import Util +from modules.core.accounting.services.account_service import AccountService from modules.standard.online.online_display import OnlineDisplay @@ -26,7 +31,8 @@ class User: @instance() class OnlineController: def __init__(self): - self.assist = [] + self.afk_list = {} + self.afk_regex = re.compile("^(afk|brb) ?(.*)$", re.IGNORECASE) self.awaiting_data = FifoQueue() def inject(self, registry): @@ -39,7 +45,9 @@ class OnlineController: self.buddy_service: BuddyService = registry.get_instance("buddy_service") self.setting_service: SettingService = registry.get_instance("setting_service") self.priv: PrivateChannelService = registry.get_instance("private_channel_service") - self.online_display: OnlineDisplay = OnlineDisplay(self.text, self.util, self.db) + self.online_display: OnlineDisplay = OnlineDisplay(self.text, self.util, self.db, self.afk_list) + self.account_service: AccountService = registry.get_instance("account_service") + self.character_service: CharacterService = registry.get_instance("character_service") def pre_start(self): self.db.exec("DROP TABLE IF EXISTS online") @@ -67,6 +75,8 @@ class OnlineController: def priv_leave(self, _, event_data): self.db.exec("delete from online where char_id = ? and bot = ? and channel=?", [event_data.char_id, self.bot.get_char_id(), self.bot.name]) + for alt in self.account_service.get_alts(event_data.char_id): + self.afk_list.pop(alt.char_id, None) @event(event_type="member_logon", description="declare players as online") def logon(self, _, event_data): @@ -90,6 +100,8 @@ class OnlineController: self.db.exec("DELETE FROM online where char_id=? and bot=?", [data.packet.char_id, self.bot.get_char_id()]) self.db.exec("UPDATE account set last_seen=? where char_id=?", [time.time(), data.packet.char_id]) + for alt in self.account_service.get_alts(data.packet.char_id): + self.afk_list.pop(alt.char_id, None) @command(command="online", params=[Const('all', is_optional=True), Int("min_level", is_optional=True), @@ -134,3 +146,34 @@ class OnlineController: return f"Invalid Title level: {filters}" output = self.online_display.count_tl(query, params, filters) return output if output != "" else "Nobody is in my private channel, sorry..." + + @event(PrivateChannelService.PRIVATE_CHANNEL_MESSAGE_EVENT, "Check for afk messages in private channel") + def afk_check_private_channel_event(self, event_type, event_data): + self.afk_check(event_data.char_id, event_data.message, lambda msg: self.bot.send_private_channel_message(msg)) + + @event(PublicChannelService.ORG_CHANNEL_MESSAGE_EVENT, "Check for afk messages in org channel") + def afk_check_org_channel_event(self, event_type, event_data): + self.afk_check(event_data.char_id, event_data.message, lambda msg: self.bot.send_org_message(msg)) + + def afk_check(self, char_id, message, channel_reply): + matches = self.afk_regex.search(message) + if matches: + char_name = self.character_service.resolve_char_to_name(char_id) + channel_reply(f"{char_name} is now afk.") + alts = self.account_service.get_alts(char_id) + for alt in alts: + self.afk_list[alt.char_id] = DictObject({"message": message, "time": time.time()}) + elif char_id in self.afk_list.keys(): + # TODO handle multiple rows + + alts = self.account_service.get_alts(char_id) + data = None + for alt in alts: + out = self.afk_list.pop(alt.char_id) + if data: + continue + data = out + char_name = self.character_service.resolve_char_to_name(char_id) + time_string = self.util.time_to_readable(int(time.time()) - data.time) + channel_reply(f"{char_name} is back after {time_string}.") + diff --git a/modules/standard/online/online_display.py b/modules/standard/online/online_display.py index 3ad99b6..75cd6f9 100644 --- a/modules/standard/online/online_display.py +++ b/modules/standard/online/online_display.py @@ -1,3 +1,4 @@ +import time from typing import List from core.chat_blob import ChatBlob @@ -8,10 +9,11 @@ from core.util import Util class OnlineDisplay: - def __init__(self, text, util, db): + def __init__(self, text, util, db, afk_list={}): self.text: Text = text self.util: Util = util self.db: DB = db + self.afk_list = afk_list def get_online_players(self, order, group="", params=None): return self.db.query(f"SELECT r.*, " @@ -68,10 +70,11 @@ class OnlineDisplay: blob += f"\n\n.::
{self.get_channel_name(channel_id)}
::.
" main_id = 0 in_org_priv.append(player.char_id) - + afk = self.afk_list.get(player.char_id, None) if main_id != player.main_id: main_id = player.main_id - blob += f"\n{player.main_name}{rank}:\n" + afk = "" if not afk else f" [{afk.message} - since {self.util.time_to_readable(int(time.time() - afk.time))}]" + blob += f"\n{player.main_name}{rank}:{afk}\n" if channel_id == 1: org += 1 elif channel_id == 2: @@ -109,18 +112,20 @@ class OnlineDisplay: blob += f"\n\n.::
{self.get_channel_name(channel_id)}
::.
" profession = 0 in_org_priv.append(player.char_id) + afk = self.afk_list.get(player.char_id, None) + afk = "" if not afk else f" [{afk.message} - since {self.util.time_to_readable(int(time.time() - afk.time))}]" if profession != player.profession: profession = player.profession blob += f"\n{player.profession}:\n" if channel_id == 1: - blob += self.format_org(player, rank, main_order=True) + blob += self.format_org(player, rank, afk, main_order=True) org += 1 elif channel_id == 2: - blob += self.format_priv(player, rank, main_order=True) + blob += self.format_priv(player, rank, afk, main_order=True) priv += 1 elif channel_id == 3: - blob += self.format_notify(player, rank, main_order=True) + blob += self.format_notify(player, rank, afk, main_order=True) notify += 1 return blob, org, priv, notify @@ -161,25 +166,25 @@ class OnlineDisplay: elif c_id == 3: return "Buddylist" - def format_org(self, player, rank="", main_order=False): + def format_org(self, player, rank="", afk="", main_order=False): main = f"[{self.text.make_tellcmd(player.main_name, f'alts {player.main_name}')}]" if main_order else "" return f" {self.util.get_prof_icon(player.profession)} {rank}" \ f"{self.text.zfill(player.level, 220)}/{self.text.zfill(player.ai_level, 30)} " \ - f"<{player.faction.lower()}>{player.name} ({player.org_rank_name}) {main}\n" + f"<{player.faction.lower()}>{player.name} ({player.org_rank_name}){afk} {main}\n" - def format_priv(self, player, rank="", main_order=False): + def format_priv(self, player, rank="", afk="", main_order=False): main = f"[{self.text.make_tellcmd(player.main_name, f'alts {player.main_name}')}]" if main_order else "" return f" {self.util.get_prof_icon(player.profession)} {rank}" \ f"{self.text.zfill(player.level, 220)}/{self.text.zfill(player.ai_level, 30)} " \ f"<{player.faction.lower()}>{player.name} " \ - f"({player.org_name}|{player.org_rank_name}) {main}\n" + f"({player.org_name}|{player.org_rank_name}){afk} {main}\n" - def format_notify(self, player, rank="", main_order=False): + def format_notify(self, player, rank="", afk="", main_order=False): main = f"[{self.text.make_tellcmd(player.main_name, f'alts {player.main_name}')}]" if main_order else "" return f" {self.util.get_prof_icon(player.profession)} {rank}" \ f"{self.text.zfill(player.level, 220)}/{self.text.zfill(player.ai_level, 30)} " \ f"<{player.faction.lower()}>{player.name} " \ - f"({player.org_name}|{player.org_rank_name}) {main}\n" + f"({player.org_name}|{player.org_rank_name}){afk} {main}\n" def count_prof(self, query, params, filters): if filters: diff --git a/modules/standard/timers/timer_controller.py b/modules/standard/timers/timer_controller.py index 9d3c8bd..c6782c9 100644 --- a/modules/standard/timers/timer_controller.py +++ b/modules/standard/timers/timer_controller.py @@ -2,7 +2,8 @@ import time from core.chat_blob import ChatBlob from core.command_param_types import Any, Const, Time, Options -from core.decorators import instance, command +from core.decorators import instance, command, event +from core.igncore import IgnCore from core.registry import Registry from modules.standard.news.worldboss_controller import WorldBossController @@ -35,7 +36,7 @@ class TimerController: self.alerts = [60 * 60, 60 * 15, 60 * 1] def inject(self, registry): - self.bot = registry.get_instance("bot") + self.bot: IgnCore = registry.get_instance("bot") self.db = registry.get_instance("db") self.util = registry.get_instance("util") self.job_scheduler = registry.get_instance("job_scheduler") @@ -129,6 +130,12 @@ class TimerController: else: return f"Error! Insufficient access level to remove timer {timer.name}." + @event("connect", description="reload timers on restart") + def reload_timers(self, _, _1): + timers = self.db.query("SELECT * from timer") + for timer in timers: + self.timer_alert(time.time(), timer.name) + @command(command="rtimer", params=[Const("add", is_optional=True), TimerTime("start_time"), @@ -195,7 +202,6 @@ class TimerController: if timer.finished_at > t: msg = f"Timer {timer.name} has " \ f"{self.util.time_to_readable(timer.finished_at - t)} left." - alert_duration = self.get_next_alert(timer.finished_at - t) job_id = self.job_scheduler.scheduled_job(self.timer_alert, t + alert_duration, timer.name) @@ -213,7 +219,8 @@ class TimerController: new_t += timer.repeating_every self.add_timer(timer.name, timer.char_id, timer.channel, new_t, timer.repeating_every, timer.repeating_every) - + if not self.bot.is_ready(): + return if timer.channel == "org": self.bot.send_org_message(msg) elif timer.channel == "priv":