Fixed:
-> !wants -> !orgs info -> special cmd's -> !assist -> "afk" for players without active account -> !loot add <item_ref> <count> => nolonger breaks !account Changes: -> grouped !tara, !gaunt, .. into !wb -> Display the most recent news entry on logon (default: enabled) -> improved grouping of !items -> Added the option to authentificate WS connections (Datanet module). This is used in special cases, where the Websocket Server requires the clien tto authentificate itself. (Server sends "#auth", client responds with the auth string) -> Add main name to relaying (priv <-> org) [default: disabled] -> Added logon/logoff messages back -> restricted default access to "dangerous" commands to moderator -> Added optional logging (Private Channel, Org Channel, Tells, ... disabled by default) Rewrite of the Tower Module. -> More verbosity, if enabled in config. by default, GAS and Hot timer only. -> !hot displays currently hot (and in penalty) sites, and these which go hot in < 60 minutes -> !attacks filterable by PF and Site -> display current contract QL's grouped by org: !contracts (requires managed cache)
This commit is contained in:
@@ -8,14 +8,15 @@ from core.buddy_service import BuddyService
|
||||
from core.chat_blob import ChatBlob
|
||||
from core.command_param_types import Options, Int, Const, Any
|
||||
from core.db import DB
|
||||
from core.decorators import instance, event, command
|
||||
from core.decorators import instance, event, command, setting
|
||||
from core.dict_object import DictObject
|
||||
from core.igncore import IgnCore
|
||||
from core.job_scheduler import JobScheduler
|
||||
from core.logger import Logger
|
||||
from core.lookup.pork_service import PorkService
|
||||
from core.setting_service import SettingService
|
||||
from core.setting_types import BooleanSettingType
|
||||
from core.text import Text
|
||||
from core.igncore import IgnCore
|
||||
from core.util import Util
|
||||
from modules.core.accounting.preference_controller import PreferenceController
|
||||
from modules.core.accounting.services.account_service import AccountService
|
||||
@@ -76,6 +77,7 @@ hh:mm - DD.MM.YYYY
|
||||
"added_by int not null, "
|
||||
"added_at timestamp, "
|
||||
"headline int not null default 0)")
|
||||
self.db.exec("CREATE TABLE IF NOT EXISTS news_read(main INT NOT NULL PRIMARY KEY, post_id INT NOT NULL)")
|
||||
|
||||
def start(self):
|
||||
self.commands = f" [{self.text.make_chatcmd('raids', '/tell <myname> raids')}] " \
|
||||
@@ -89,7 +91,8 @@ hh:mm - DD.MM.YYYY
|
||||
def logon_event(self, _, data):
|
||||
if not self.bot.is_ready():
|
||||
return
|
||||
if "member" in self.buddy_service.get_buddy(data.packet.char_id)["types"]:
|
||||
if "member" in self.buddy_service.get_buddy(data.packet.char_id)["types"] \
|
||||
or "org_member" in self.buddy_service.get_buddy(data.packet.char_id)["types"]:
|
||||
account = data.account
|
||||
# Apply standard checks. (User Banned, Account disabled, ...)
|
||||
if not self.account_service.simple_checks(data.account):
|
||||
@@ -101,18 +104,21 @@ hh:mm - DD.MM.YYYY
|
||||
discord = f"Your Account is <highlight>not</highlight> connected to Discord " \
|
||||
f"[{self.text.make_chatcmd('Join', '/tell <myname> discord invite')}]"
|
||||
else:
|
||||
prefix = ""
|
||||
if self.setting_service.get_value('is_alliance_bot') == '1':
|
||||
prefix = f"[{self.alias_controller.get_alias(account.org_id)}] "
|
||||
discord = f"Your Account <highlight>is</highlight> connected to Discord as " \
|
||||
f"<highlight>[{self.alias_controller.get_alias(account.org_id)}] " \
|
||||
f"{account.name}</highlight>"
|
||||
f"<highlight>{prefix}{account.name}</highlight>"
|
||||
|
||||
user = self.pork.get_character_info(data.packet.char_id)
|
||||
self.job_schedule.delayed_job(self.send_news, 15, user, self.account_service.get_alts(account.main),
|
||||
discord,
|
||||
self.preferences.get_pref_view_small(account))
|
||||
|
||||
@command(command="raids", params=[], description="Show the Raids", access_level="member")
|
||||
def show_raids(self, _):
|
||||
with open("data/latest_raids.txt", "r") as f:
|
||||
return self.text.format_page("Raidschedule", f.read())
|
||||
# @command(command="raids", params=[], description="Show the Raids", access_level="member")
|
||||
# def show_raids(self, _):
|
||||
# with open("data/latest_raids.txt", "r") as f:
|
||||
# return self.text.format_page("Raidschedule", f.read())
|
||||
|
||||
def get_timers(self):
|
||||
events = [self.weekly.get_next_bs(),
|
||||
@@ -140,12 +146,21 @@ hh:mm - DD.MM.YYYY
|
||||
self.weekly.get_next_dio()]
|
||||
return sorted(events, key=lambda timer: timer.start)[0]
|
||||
|
||||
@setting(name="preview_recent", value="true",
|
||||
description="Sends the newest News entry to the user directly, without wrapping it into a blob")
|
||||
def preview_recent(self):
|
||||
return BooleanSettingType()
|
||||
|
||||
def send_news(self, _, sender, alts, discord, prefs, auto=True):
|
||||
if auto:
|
||||
if self.buddy_service.get_buddy(sender.char_id)["online"] == 0:
|
||||
return
|
||||
timers = self.get_timers()
|
||||
next_event = self.get_next_event()
|
||||
last_updated: datetime = (self.db.query_single("SELECT added_at FROM news order by ID desc") or {}).get(
|
||||
"added_at", 0)
|
||||
if type(last_updated) != int:
|
||||
last_updated = last_updated.astimezone().timestamp()
|
||||
news = self.layout.format(time=datetime.now(timezone.utc).strftime("%H:%M - %d.%m.%Y") + " [UTC-0]",
|
||||
main=alts[0].name,
|
||||
alts_show=self.text.make_chatcmd(len(alts), "/tell <myname> alts"),
|
||||
@@ -155,15 +170,38 @@ hh:mm - DD.MM.YYYY
|
||||
prefs=prefs,
|
||||
discord=discord)
|
||||
name = self.weekly.get_long_name(next_event.type)
|
||||
if last_updated > 0:
|
||||
last_updated = f"[{self.util.time_to_readable(datetime.utcfromtimestamp(time.time()).timestamp() - last_updated)} ago]"
|
||||
else:
|
||||
last_updated = ""
|
||||
blob = f"<yellow>{random.choice(self.greetings)} {sender.name} :: " \
|
||||
f"{self.text.format_page('Your News', textwrap.dedent(news))} </yellow> " \
|
||||
f"{self.text.format_page('Your News', textwrap.dedent(news))} </yellow>{last_updated} " \
|
||||
f"{f'<yellow>::</yellow> {name} running' if next_event.is_running() else ''}"
|
||||
self.bot.send_mass_message(sender.char_id, blob)
|
||||
if self.preview_recent().get_value() and auto:
|
||||
entry = self.get_newest_entry()
|
||||
if not entry:
|
||||
return
|
||||
if entry.id > self.get_read_id(sender.char_id):
|
||||
data = f":: <yellow>Your most recent unread news</yellow> :: \n" \
|
||||
f"{entry.text}\n" \
|
||||
f"{self.text.format_page('Mark as read', self.text.make_tellcmd('Mark as read', f'news read {entry.id}'))}"
|
||||
self.bot.send_mass_message(sender.char_id, data)
|
||||
|
||||
#####################
|
||||
# News Management #
|
||||
#####################
|
||||
|
||||
@command(command="news", params=[Const("read"), Int("ID", is_optional=True)],
|
||||
description="Marks All news until [ID] as read, or all of none specified.",
|
||||
access_level="member",
|
||||
extended_description="It is also possible to move the read marker back, i.e. from ID 5 -> 1.\nThis has no effect if the preview_recent setting is off.")
|
||||
def cmd_news_read(self, sender, _, entry_id):
|
||||
if not entry_id:
|
||||
entry_id = (self.db.query_single("SELECT id from news ORDER BY id desc limit 1") or {}).get("id", 0)
|
||||
self.mark_as_read(sender.sender.char_id, entry_id)
|
||||
return f"Your News Read marker has been moved to ID <highlight>{entry_id}</highlight>."
|
||||
|
||||
@command(command="news", params=[], description="Show the news", access_level="member")
|
||||
def show_news(self, sender):
|
||||
user = self.db.query_single("SELECT * FROM player where char_id=?", [sender.sender.char_id])
|
||||
@@ -243,6 +281,8 @@ hh:mm - DD.MM.YYYY
|
||||
normal = ""
|
||||
headline = ""
|
||||
for entry in data:
|
||||
if len(self.text.strip_html_tags(headline + normal) or []) > limit:
|
||||
break
|
||||
if entry.headline == 1:
|
||||
if len(self.text.strip_html_tags(headline) or []) > limit / 2:
|
||||
continue
|
||||
@@ -268,3 +308,16 @@ hh:mm - DD.MM.YYYY
|
||||
def add_entry(self, text, sender):
|
||||
self.db.exec("INSERT INTO news(text, added_by, added_at) VALUES(?, ?, ?)",
|
||||
[text, sender.char_id, datetime.utcfromtimestamp(time.time())])
|
||||
|
||||
def get_newest_entry(self):
|
||||
return self.db.query_single("SELECT * from news order by id desc")
|
||||
|
||||
def get_read_id(self, char_id):
|
||||
return (self.db.query_single(
|
||||
"SELECT post_id from news_read where main = (SELECT main from account where char_id=?)",
|
||||
[char_id]) or {}).get('post_id', 0)
|
||||
|
||||
def mark_as_read(self, char_id, post_id):
|
||||
self.db.exec(
|
||||
"INSERT INTO news_read(main, post_id) VALUES((SELECT main from account where char_id=? LIMIT 1), ?) ON DUPLICATE KEY UPDATE post_id=?",
|
||||
[char_id, post_id, post_id])
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
import time
|
||||
|
||||
from core.chat_blob import ChatBlob
|
||||
from core.command_alias_service import CommandAliasService
|
||||
from core.command_param_types import Any
|
||||
from core.decorators import instance, command, event
|
||||
from core.dict_object import DictObject
|
||||
from core.igncore import IgnCore
|
||||
from core.job_scheduler import JobScheduler
|
||||
from core.logger import Logger
|
||||
from core.setting_service import SettingService
|
||||
from core.setting_types import BooleanSettingType
|
||||
from core.text import Text
|
||||
from core.igncore import IgnCore
|
||||
from core.util import Util
|
||||
from modules.standard.datanet.ws_controller import WebsocketRelayController
|
||||
|
||||
|
||||
@instance()
|
||||
class WorldBossController:
|
||||
# Timers are provided through an local websocket relay, which gets fed by an external API;
|
||||
# If you intend to take advantage of this module,
|
||||
# you **will** need to contact the API host of your choice to whitelist
|
||||
# The IP Addresses with which you intend to access the API.
|
||||
# Timers are provided through a local websocket relay, which gets fed by an external API.
|
||||
# example timer data:
|
||||
# [{"name":"Tarasque","time": <mortal time>},
|
||||
# {"name":"Vizaresh","time": <mortal time>}]
|
||||
timer_data = []
|
||||
|
||||
alerts = [480 * 60, 360 * 60, 240 * 60, 120 * 60, 60 * 60, 60 * 15,
|
||||
60 * 5, 60 * 3, 60 * 2, 60, 30, 15, 10, 5, 4, 3, 2, 1, 0]
|
||||
60 * 5, 60 * 3, 60 * 2, 60, 30, 15, 5, 0]
|
||||
jobs = []
|
||||
|
||||
def inject(self, registry):
|
||||
@@ -38,25 +37,46 @@ class WorldBossController:
|
||||
self.setting_service: SettingService = registry.get_instance("setting_service")
|
||||
|
||||
def pre_start(self):
|
||||
self.setting_service.register(self.module_name, 'timer_spam', True, BooleanSettingType(),
|
||||
"should timers be spammed")
|
||||
self.setting_service.register(self.module_name, 'timer_spam', False, BooleanSettingType(),
|
||||
"should timers be spammed")
|
||||
self.command_alias_service.add_alias("tara", "wb tara")
|
||||
self.command_alias_service.add_alias("gaunt", "wb gaunt")
|
||||
self.command_alias_service.add_alias("loren", "wb loren")
|
||||
self.command_alias_service.add_alias("reaper", "wb reaper")
|
||||
|
||||
@event(WebsocketRelayController.WS_RELAY, "save most current timers")
|
||||
@event(WebsocketRelayController.WS_RELAY, "save most current local_timers")
|
||||
def get_timer(self, _, data):
|
||||
if data.type == "timer":
|
||||
self.timer_data = data.payload
|
||||
|
||||
def test(test_data):
|
||||
if test_data:
|
||||
if data.type != "timer":
|
||||
return
|
||||
local_timers = {}
|
||||
spam = True if self.setting_service.get_value("timer_spam") == "1" else False
|
||||
for x in self.timer_data:
|
||||
local_timers[x['name']] = x['time']
|
||||
for row in data.payload:
|
||||
spawn = self.get_spawn(row)
|
||||
if not spawn:
|
||||
continue
|
||||
if row['name'] not in local_timers:
|
||||
self.timer_data.append(row)
|
||||
if spam:
|
||||
for x in self.jobs:
|
||||
if x['name'] == row['name']:
|
||||
self.job_scheduler.cancel_job(x['id'])
|
||||
self.jobs.append(
|
||||
{'name': row['name'], 'id': self.job_scheduler.delayed_job(self.timer_alert, 2, spawn)})
|
||||
continue
|
||||
elif (local_timers[row['name']] + 1) < row['time']:
|
||||
for timer in self.timer_data:
|
||||
if timer['name'] == row['name']:
|
||||
timer['time'] = row['time']
|
||||
if spam:
|
||||
for job in self.jobs:
|
||||
if job["name"] == test_data.name:
|
||||
return
|
||||
self.job_scheduler.delayed_job(self.timer_alert, 2, test_data)
|
||||
|
||||
if self.setting_service.get_value("timer_spam") == "1":
|
||||
for row in self.timer_data:
|
||||
data = self.get_spawn(row)
|
||||
test(data)
|
||||
if job['name'] == row['name']:
|
||||
self.job_scheduler.cancel_job(job['id'])
|
||||
alert_duration = self.get_next_alert(spawn.at - time.time())
|
||||
job_id = self.job_scheduler.scheduled_job(self.timer_alert, 2,
|
||||
spawn)
|
||||
job['id'] = job_id
|
||||
|
||||
def get_spawn(self, timer):
|
||||
timer = DictObject(timer)
|
||||
@@ -108,26 +128,26 @@ class WorldBossController:
|
||||
return duration - alert - 1
|
||||
return duration
|
||||
|
||||
@command(command="gaunt", params=[], description="Displays the next Vizaresh pop time", access_level="member")
|
||||
def show_gaunt(self, _):
|
||||
for timer in self.timer_data:
|
||||
if timer['name'] == "Vizaresh":
|
||||
return self.show_user(timer)
|
||||
return "Timer not found"
|
||||
|
||||
@command(command="loren", params=[], description="Displays the next Loren Warr pop time", access_level="member")
|
||||
def show_loren(self, _):
|
||||
for timer in self.timer_data:
|
||||
if timer['name'] == "Loren Warr":
|
||||
return self.show_user(timer)
|
||||
return "Timer not found"
|
||||
|
||||
@command(command="tara", params=[], description="Displays the next Tara pop time", access_level="member")
|
||||
def show_tara(self, _):
|
||||
for timer in self.timer_data:
|
||||
if timer['name'] == "Tarasque":
|
||||
return self.show_user(timer)
|
||||
return "Timer not found"
|
||||
@command(command="wb", params=[Any("worldboss", is_optional=True)],
|
||||
description="Displays the next worldboss spawns", access_level="member")
|
||||
def show_worldboss(self, request, boss: str):
|
||||
if boss:
|
||||
boss = boss.lower()
|
||||
if boss in ["tara", "tarasque"]:
|
||||
boss = "Tarasque"
|
||||
elif boss in ["viza", "vizaresh", "gaunt", "gauntlet"]:
|
||||
boss = "Vizaresh"
|
||||
elif boss in ["loren", "loren warr", "loren war", "lw"]:
|
||||
boss = "Loren Warr"
|
||||
elif boss in ["thr", "the hollow", "the hollow reaper", "reaper"]:
|
||||
boss = "The Hollow Reaper"
|
||||
for x in self.timer_data:
|
||||
if x['name'] == boss:
|
||||
return self.show_user(x)
|
||||
else:
|
||||
blob = "\n".join([self.show_user(x) for x in self.timer_data if
|
||||
self.show_user(x) != "No timers cached; please try again later."])
|
||||
return ChatBlob("Next Worldboss spawns", blob)
|
||||
|
||||
def show_user(self, timer):
|
||||
timer = self.get_spawn(timer)
|
||||
@@ -144,7 +164,9 @@ class WorldBossController:
|
||||
for row in self.timer_data:
|
||||
if row["name"] == timer.name:
|
||||
timer = self.get_spawn(row)
|
||||
alert_duration = self.get_next_alert(timer.at - t)
|
||||
if not timer.at:
|
||||
return
|
||||
alert_duration = self.get_next_alert(timer.at - time.time())
|
||||
if timer.at - time.time() < 1:
|
||||
if timer.type == "mortal":
|
||||
self.send_warn(f"<highlight>{timer.name}</highlight> :: is now mortal")
|
||||
@@ -152,7 +174,7 @@ class WorldBossController:
|
||||
elif timer.type == "spawn":
|
||||
self.send_warn(f"<highlight>{timer.name}</highlight> :: has just spawned")
|
||||
self.jobs = [x for x in self.jobs if x['name'] != timer.name]
|
||||
else: # timer.at > time.time():
|
||||
else:
|
||||
if timer.type == "mortal":
|
||||
self.send_warn(f"<highlight>{timer.name}</highlight> :: mortal in {self.util.format_time(timer.time)}")
|
||||
elif timer.type == "spawn":
|
||||
@@ -162,7 +184,7 @@ class WorldBossController:
|
||||
for row in self.timer_data:
|
||||
if row["name"] == timer.name:
|
||||
timer = self.get_spawn(row)
|
||||
job_id = self.job_scheduler.scheduled_job(self.timer_alert, t + alert_duration, timer)
|
||||
job_id = self.job_scheduler.scheduled_job(self.timer_alert, time.time() + alert_duration, timer)
|
||||
for job in self.jobs:
|
||||
if job['name'] == timer.name:
|
||||
job['id'] = job_id
|
||||
|
||||
Reference in New Issue
Block a user