321 lines
17 KiB
Python
321 lines
17 KiB
Python
import random
|
|
import textwrap
|
|
import time
|
|
from datetime import datetime, timezone
|
|
from queue import Queue
|
|
|
|
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, 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.util import Util
|
|
from modules.core.accounting.preference_controller import PreferenceController
|
|
from modules.core.accounting.services.account_service import AccountService
|
|
from modules.onlinebot.online.org_alias_controller import OrgAliasController
|
|
from modules.standard.news.weekly_controller import WeeklyController
|
|
from modules.standard.news.worldboss_controller import WorldBossController
|
|
|
|
|
|
@instance()
|
|
class NewsController:
|
|
queue = Queue()
|
|
# taken from https://app2brain.com/de/sprachen-lernen/woerter-saetze/hallo/
|
|
greetings = ["Welcome", "Greetings", "Hey", "Namaste", "Ahoy", "Salutations", "Hola", "Ahoj", "Hallo", "Hello",
|
|
"Hei", "Salut", "Helló", "Halló", "Ciao", "Olá", "Ahoj", "¡Hola"]
|
|
layout = \
|
|
"""
|
|
<notice>CURRENT TIME</notice>
|
|
hh:mm - DD.MM.YYYY
|
|
{time}
|
|
|
|
<notice>ACCOUNT</notice>
|
|
Your main is: <highlight>{main}</highlight>
|
|
You have {alts_show} <highlight>alts</highlight> registered
|
|
{discord}
|
|
Preferences: {prefs}
|
|
|
|
<notice>NEWS</notice>
|
|
{news}
|
|
<notice>TIMERS</notice>
|
|
{timers}
|
|
<notice>POPULAR COMMANDS</notice>
|
|
{commands}
|
|
"""
|
|
|
|
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.account_service: AccountService = registry.get_instance("account_service")
|
|
self.pork: PorkService = registry.get_instance("pork_service")
|
|
self.util: Util = registry.get_instance("util")
|
|
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
|
|
self.setting_service: SettingService = registry.get_instance("setting_service")
|
|
self.job_schedule: JobScheduler = registry.get_instance("job_scheduler")
|
|
self.preferences: PreferenceController = registry.get_instance("preference_controller")
|
|
self.alias_controller: OrgAliasController = registry.get_instance("org_alias_controller")
|
|
self.weekly: WeeklyController = registry.get_instance("weekly_controller")
|
|
self.worldboss: WorldBossController = registry.get_instance("world_boss_controller")
|
|
|
|
def pre_start(self):
|
|
self.db.exec(
|
|
"CREATE TABLE IF NOT EXISTS news("
|
|
"id int primary key auto_increment, "
|
|
"text text, "
|
|
"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')}] " \
|
|
f"[{self.text.make_chatcmd('online', '/tell <myname> online')}]\n" \
|
|
f" [{self.text.make_chatcmd('rules', '/tell <myname> rules')}] " \
|
|
f"[{self.text.make_chatcmd('about', '/tell <myname> about')}]\n" \
|
|
f" [{self.text.make_chatcmd('orgs', '/tell <myname> orgs')}] " \
|
|
f"[{self.text.make_chatcmd('admins', '/tell <myname> admins')}]\n"
|
|
|
|
@event(event_type="member_logon", description="Send news to players logging in")
|
|
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"] \
|
|
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):
|
|
return
|
|
if self.db.query_single("SELECT * from org_bots where char_id=?", [data.packet.char_id]):
|
|
return
|
|
if account.news_spam == 1:
|
|
if account.discord_joined == 0:
|
|
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>{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())
|
|
|
|
def get_timers(self):
|
|
events = [self.weekly.get_next_bs(),
|
|
self.weekly.get_next_ai(),
|
|
self.weekly.get_next_dio()]
|
|
next_event = sorted(events, key=lambda timer_event: timer_event.start)[0]
|
|
name = self.bot.get_char_name().upper()
|
|
out = f" <highlight>{self.weekly.get_long_name(next_event.type)}</highlight> - "
|
|
if next_event.is_running():
|
|
out += f"[Ends in {self.util.time_to_readable(next_event.end - time.time())}] " \
|
|
f"[by <highlight>{name}</highlight>]\n"
|
|
else:
|
|
out += f"[Starts in {self.util.time_to_readable(next_event.start - time.time())}] " \
|
|
f"[by <highlight>{name}</highlight>]\n"
|
|
|
|
out += self.worldboss.getWBTimer()
|
|
return out
|
|
|
|
def get_next_event(self):
|
|
events = [self.weekly.get_next_bs(),
|
|
self.weekly.get_next_ai(),
|
|
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 not sender:
|
|
return
|
|
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"),
|
|
news=self.get_news(),
|
|
timers=timers,
|
|
commands=self.commands,
|
|
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 = ChatBlob('Your News', textwrap.dedent(news),
|
|
prefix=f"<yellow>{random.choice(self.greetings)} {sender.name} :: ",
|
|
suffix=f"</yellow> {last_updated} {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])
|
|
alts = self.account_service.get_alts(sender.sender.char_id)
|
|
if not alts:
|
|
discord = f"Your Account is <highlight>not</highlight> connected to Discord " \
|
|
f"[{self.text.make_chatcmd('Join', '/tell <myname> discord invite')}]"
|
|
alts = [self.account_service.get_main(sender.sender.char_id)]
|
|
elif alts[0].discord_joined == 0:
|
|
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(alts[0].org_id)}] "
|
|
discord = f"Your Account <highlight>is</highlight> connected to Discord as " \
|
|
f"<highlight>{prefix}{alts[0].name}</highlight>"
|
|
prefs = self.preferences.get_pref_view_small(alts[0]) if alts else ""
|
|
self.send_news(0, user, alts, discord, prefs, False)
|
|
|
|
@command(command="news", params=[Options(["pin", "unpin"]), Int("ID")], description="Set news as headlines",
|
|
access_level="moderator", sub_command="moderate")
|
|
def news_pin(self, _, option, news_id):
|
|
if option == "pin":
|
|
row = self.db.exec("UPDATE news set headline=1 where id =?", [news_id])
|
|
if row == 0:
|
|
return f"There's no entry with the ID <highlight>{news_id}</highlight>, or it is already pinned."
|
|
elif row == 1:
|
|
return f"The entry with the ID <highlight>{news_id}</highlight> has been pinned."
|
|
else:
|
|
return "Multiple entries have been pinned... SHIT!"
|
|
elif option == "unpin":
|
|
row = self.db.exec("UPDATE news set headline=0 where id =?", [news_id])
|
|
if row == 0:
|
|
return f"There's no entry with the ID <highlight>{news_id}</highlight>, or it is not pinned."
|
|
elif row == 1:
|
|
return f"The entry with the ID <highlight>{news_id}</highlight> has been unpinned."
|
|
else:
|
|
return "Multiple entries have been unpinned... SHIT!"
|
|
|
|
@command(command="news", params=[Options(["remove", "delete", "rem", "del"]), Int("ID")],
|
|
description="Set news as headlines", access_level="moderator", sub_command="moderate")
|
|
def news_del(self, _, _1, news_id):
|
|
row = self.db.exec("DELETE FROM news where id =?", [news_id])
|
|
if row == 0:
|
|
return f"There's no entry with the ID <highlight>{news_id}</highlight>."
|
|
elif row == 1:
|
|
return f"The entry with the ID <highlight>{news_id}</highlight> has been removed."
|
|
else:
|
|
return "Multiple entries have been deleted... SHIT!"
|
|
|
|
@command(command="news", params=[Const("add"), Any("text")], description="Moderate the news",
|
|
access_level="moderator", sub_command="moderate")
|
|
def news_add(self, sender, _, text):
|
|
self.add_entry(text, sender.sender)
|
|
row = DictObject(
|
|
{"added_at": datetime.utcfromtimestamp(time.time()), "added_by": sender.sender.char_id, "headline": 0,
|
|
"id": self.db.query_single("SELECT id from news order by id desc").id, "text": text})
|
|
return ChatBlob(f"News entry with ID {row.id}",
|
|
self.format_entry(row, sender.sender, sender.sender.access_level['level'] <= 30))
|
|
|
|
@command(command="news", params=[Options(["detail", "info", "more"]), Int("ID", is_optional=True)],
|
|
description="Moderate the news", access_level="member")
|
|
def news_detail(self, sender, _, news_id):
|
|
if news_id:
|
|
row = self.db.query_single("SELECT * from news where id=?", [news_id])
|
|
if row:
|
|
return ChatBlob(f"News entry with ID {news_id}",
|
|
self.format_entry(row, sender.sender, sender.sender.access_level['level'] <= 30))
|
|
else:
|
|
return f"There's no entry with the ID <highlight>{news_id}</highlight>."
|
|
else:
|
|
return ChatBlob("The latest news", self.get_news(10000, sender.sender))
|
|
|
|
def get_news(self, limit=500, receiver=None):
|
|
data = self.db.query("SELECT * from news order by ID desc")
|
|
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
|
|
headline += self.format_entry(entry, receiver)
|
|
else:
|
|
if len(self.text.strip_html_tags(normal) or []) > limit / 2:
|
|
continue
|
|
normal += self.format_entry(entry, receiver)
|
|
if len(self.text.strip_html_tags(headline + normal) or []) > limit:
|
|
normal += f" There seem to be more news,<br> but they're too long to get shown here. " \
|
|
f"[{self.text.make_tellcmd('Display', 'news detail')}]<br>"
|
|
return headline + "____________________________<br><br>" + normal if headline != "" else normal
|
|
|
|
# noinspection LongLine
|
|
def format_entry(self, entry, receiver=None, mod=False):
|
|
name = self.bot.get_char_name().upper()
|
|
|
|
base = f"""<font color=#8CB5FF>On {entry.added_at.strftime("%d.%m.%Y - %H:%M:%S")} UTC by <white>{self.bot.character_service.resolve_char_to_name(entry.added_by) if entry.added_by != 0 else f"{name}"}</white> (ID {entry.id}):</font><br><white>{entry.text}</white><br><br>"""
|
|
if mod:
|
|
base += f"""<br> - <green>Moderate this entry: [{self.text.make_tellcmd('PIN' if entry.headline == 0 else 'UNPIN', f'news {"pin" if entry.headline == 0 else "unpin"} {entry.id}')}] [{self.text.make_tellcmd('DELETE', f'news delete {entry.id}')}]</green>"""
|
|
return base
|
|
|
|
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])
|