Initial Release of IGNCore version 2.5

This commit is contained in:
2021-08-09 13:18:56 +02:00
commit a83d98c47e
910 changed files with 224171 additions and 0 deletions
@@ -0,0 +1,263 @@
import time
from core import command_request
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Character, Options
from core.db import DB
from core.decorators import instance, command
from core.event_service import EventService
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.text import Text
from core.translation_service import TranslationService
from core.util import Util
from modules.core.accounting.preference_controller import PreferenceController
from modules.core.accounting.services.access_service import AccessService
from modules.core.accounting.services.account_service import AccountService
@instance()
class AccountController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.buddy_service = registry.get_instance("buddy_service")
self.util: Util = registry.get_instance("util")
self.ts: TranslationService = registry.get_instance("translation_service")
self.db: DB = registry.get_instance("db")
self.character_service: CharacterService = registry.get_instance("character_service")
self.account_service: AccountService = registry.get_instance("account_service")
self.event_service: EventService = registry.get_instance("event_service")
self.getresp = self.ts.get_response
self.pork_service: PorkService = registry.get_instance("pork_service")
self.text: Text = registry.get_instance("text")
self.preferences: PreferenceController = registry.get_instance("preference_controller")
self.access_service: AccessService = registry.get_instance("access_service")
@command(command="account", params=[], access_level="member",
description="View your account")
def account(self, request: command_request):
out = self.show_account(request.sender.char_id)
self.bot.send_mass_message(request.sender.char_id, out)
@command(command="accounts", params=[Const('online', is_optional=True)], access_level="moderator",
description="View all accounts")
def accounts(self, request: command_request, online):
accs = self.account_service.get_all_members(True if online else False)
main = 0
main_name = ""
entries = []
for entry in accs:
out = ""
if main != entry.main:
main = entry.main
out += f"\n<{entry.faction.lower()}>{entry.name}</{entry.faction.lower()}>: " \
f"[{self.text.make_tellcmd('D', f'account {entry.name}')}]\n"
main_name = entry.name
n = f"<red>N</red>" if entry.last_seen == 0 else ""
out += f"<tab> {self.util.get_prof_icon(entry.profession)}" \
f" {self.text.zfill(entry.level, 220)}/<green>{self.text.zfill(entry.ai_level, 30)}</green> " \
f"<{entry.faction.lower()}>{entry.name}</{entry.faction.lower()}> {n}\n"
entries.append([main_name, out])
out = ""
msg = sorted(entries, key=lambda k: k[0])
for _, mess in msg:
out += mess
self.bot.send_mass_message(request.sender.char_id, ChatBlob("All accounts", out))
@command(command="points", params=[], access_level="member",
description="View your points")
def points(self, request: command_request):
alts = self.account_service.get_account(request.sender.char_id)
self.bot.send_mass_message(request.sender.char_id,
f"So far, you've piled <notice>{alts.points}</notice> points up.")
@command(command="account", params=[Character("user")],
access_level="moderator",
sub_command="moderate", description="Lookup account of another char")
def display_account(self, _, user):
if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found."
return self.show_account(user.char_id, True)
@command(command="account", params=[Const("add"), Character("char")],
access_level="moderator",
sub_command="moderate", description="Create a new account for given character")
def add_account(self, request: command_request, _, user):
if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found."
if self.account_service.get_account(user.char_id):
self.account_service.account_enable(user.char_id)
self.buddy_service.add_buddy(user.char_id, "member")
self.account_service.add_log(request.sender.char_id, "system",
f"Opened Account for <highlight>{user.name}</highlight>.",
request.sender.char_id)
self.account_service.add_log(user.char_id, "system",
f"Account has been reopened by <highlight>{request.sender.name}</highlight>.",
request.sender.char_id)
return f"There's already an account for character <highlight>{user.name}</highlight>; It has been enabled."
count = self.account_service.create_users([(user.char_id, user.char_id, 0, time.time(), time.time())])
if count > 0:
self.buddy_service.add_buddy(user.char_id, "member")
self.account_service.add_log(request.sender.char_id, "system",
f"Created Account for <highlight>{user.name}</highlight>.",
request.sender.char_id)
self.account_service.add_log(user.char_id, "system",
f"Account has been created by <highlight>{request.sender.name}</highlight>.",
request.sender.char_id)
self.bot.send_mass_message(user.char_id,
f"Your Account has been <green>opened</green> by "
f"<highlight>{request.sender.name}</highlight>.")
return f"Character <highlight>{user.name}'s</highlight> Account has been created!"
else:
return f"There's already an account for character <highlight>{user.name}</highlight>."
@command(command="account", params=[Const("rem"), Character("char")],
access_level="moderator",
sub_command="moderate", description="Disable a new account for given character")
def rem_account(self, request: command_request, _, user):
if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found."
if not self.access_service.has_sufficient_access_level(request.sender.char_id, user.char_id):
return f"The user <highlight>{user.name}</highlight> has a higher rank then you, you cant do that."
disabled = self.account_service.account_disable(user.char_id)
if disabled:
self.buddy_service.remove_buddy(user.char_id, "member")
self.account_service.add_log(request.sender.char_id, "system",
f"Disabled Account of <highlight>{user.name}</highlight>.",
request.sender.char_id)
self.account_service.add_log(user.char_id, "system",
f"Account disabled by <highlight>{request.sender.name}</highlight>.",
request.sender.char_id)
return f"Character <highlight>{user.name}'s</highlight> Account has been disabled!"
else:
return f"There's no active account for character <highlight>{user.name}</highlight>."
@command(command="account", params=[Const("purge"), Character("char")],
access_level="admin",
sub_command="modify", description="Purge a user from the database")
def purge_account(self, request: command_request, _, user):
if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found."
if not self.access_service.has_sufficient_access_level(request.sender.char_id, user.char_id):
return f"The user <highlight>{user.name}</highlight> has a higher rank then you, you cant do that."
account = self.account_service.get_alts(user.char_id)
if account:
for alt in account:
self.buddy_service.remove_buddy(alt.char_id, "member")
self.db.exec("DELETE FROM notes where char_id=?", [alt.char_id])
self.db.exec("DELETE FROM account where main=?", [account[0].main])
self.db.exec("DELETE FROM account_log where char_id=?", [account[0].main])
self.db.exec("DELETE FROM command_usage where char_id=?", [account[0].main])
self.db.exec("DELETE FROM pending_accounts where main=? or alt=?", [account[0].main, account[0].main])
self.db.exec("DELETE FROM mail where recipient=? or sender=?", [account[0].main, account[0].main])
self.account_service.add_log(request.sender.char_id, "system",
f"Disabled Account of <highlight>{user.name}</highlight>.",
request.sender.char_id)
return f"Character <highlight>{user.name}'s</highlight> Account has been purged!"
else:
return f"There's no active account for character <highlight>{user.name}</highlight>."
@command(command="account",
params=[Const("rank"), Options(["admin", "moderator", "council", "leader"]), Character("user")],
access_level="admin", sub_command="modify", description="Add a rank to an Account")
def set_rank(self, request: command_request, _, rank: str, user):
if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found."
user = self.account_service.get_main(user.char_id)
if not user:
return f"No account for <highlight>{user.name}</highlight> found."
# is Superadmin
if rank == "admin" and \
self.account_service.check_superadmin(self.account_service.get_main(request.sender.char_id).char_id):
if "admin" in self.account_service.get_ranks(user.char_id):
return f"<highlight>{user.name}</highlight> already has the rank " \
f"<highlight>{rank.capitalize()}</highlight>."
else:
self.account_service.add_rank(user.char_id, "admin")
return f"<highlight>{user.name}</highlight> got promoted to the rank " \
f"<highlight>{rank.capitalize()}</highlight>."
# is Admin
if self.account_service.check_admin(self.account_service.get_main(request.sender.char_id).char_id) \
or self.account_service.check_superadmin(self.account_service.get_main(request.sender.char_id).char_id):
if rank.lower() in self.account_service.get_ranks(user.char_id):
return f"<highlight>{user.name}</highlight> already has the rank " \
f"<highlight>{rank.capitalize()}</highlight>."
else:
self.account_service.add_rank(user.char_id, rank.lower())
return f"<highlight>{user.name}</highlight> got promoted to the rank " \
f"<highlight>{rank.capitalize()}</highlight>."
return "You can't do that."
@command(command="account",
params=[Options(["delrank", "remrank"]), Options(["admin", "moderator", "council", "leader"]),
Character("user")],
access_level="admin", sub_command="modify", description="Remove a rank from an Account")
def rem_rank(self, request: command_request, _, rank: str, user):
if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found."
user = self.account_service.get_main(user.char_id)
# is Superadmin
if rank == "admin" and self.account_service.check_superadmin(
self.account_service.get_main(request.sender.char_id).char_id):
if "admin" not in self.account_service.get_ranks(user.char_id):
return f"<highlight>{user.name}</highlight> is not in the " \
f"<highlight>{rank.capitalize()}</highlight> group."
else:
self.account_service.del_rank(user.char_id, "admin")
return f"<highlight>{user.name}</highlight> is nolonger in the " \
f"<highlight>{rank.capitalize()}</highlight> group."
# is Admin
elif self.account_service.check_admin(self.account_service.get_main(request.sender.char_id).char_id):
if rank.lower() not in self.account_service.get_ranks(user.char_id):
return f"<highlight>{user.name}</highlight> is not in the " \
f"<highlight>{rank.capitalize()}</highlight> group."
else:
self.account_service.del_rank(user.char_id, rank.lower())
return f"<highlight>{user.name}</highlight> is nolonger in the " \
f"<highlight>{rank.capitalize()}</highlight> group."
def show_account(self, char_id, mod=False) -> str:
alts = self.account_service.get_alts(char_id)
if len(alts) < 1:
return "No Account registered for this user."
prefs = self.preferences.get_pref_view_small(alts[0])
response = f"<header>{alts[0].name}'s Account</header>\n\n" \
f" Owner: <notice>{alts[0].name}</notice> ({alts[0].char_id}) " \
f"[{self.text.make_tellcmd('W', f'whois {alts[0].name}')}] " \
f"[{self.text.make_tellcmd('Alts', f'alts {alts[0].name}')}] " \
f"({len(alts)})\n"
if not mod:
response += f" Options: {prefs}\n"
response += f" Points: <notice>{alts[0].points}</notice>\n"
access_levels = {"Member": self.account_service.check_member(alts[0].char_id),
"Officer": self.account_service.check_officer(alts[0].char_id),
"General": self.account_service.check_general(alts[0].char_id),
"President": self.account_service.check_president(alts[0].char_id),
"Leader": self.account_service.check_leader(alts[0].char_id),
"Council": self.account_service.check_council(alts[0].char_id),
"Moderator": self.account_service.check_moderator(alts[0].char_id),
"Admin": self.account_service.check_admin(alts[0].char_id)}
perms = []
for key, value in access_levels.items():
if value:
perms.append(key)
response += f" Status: {'<green>Open</green>' if alts[0].disabled == 0 else '<red>Closed</red>'}\n"
response += f" Created at: <notice>{self.util.format_datetime(alts[0].created)}</notice>\n"
response += f" Permissions: <notice>{', '.join(perms)}</notice>\n"
if alts[0].discord_joined == 1:
joined = '(Joined server)'
else:
joined = '(Left server)' if alts[0].discord_invite != '' else 'Never joined'
response += f" Discord: <notice>{alts[0].discord_handle}</notice> {joined}\n\n"
log_types = ['Points', 'Loot', 'Raid', 'Public', 'Admin', 'System']
response += " Logs: ["
for i in log_types:
response += f" {self.text.make_tellcmd(i, f'account log {i.lower()} {alts[0].name} 100')}"
response += " ]\n\n"
response += "<header>Last 20 Logs</header>\n"
rows = self.account_service.get_logs(alts[0].char_id, limit=20)
for i in rows:
response += self.account_service.format_entry(i)
return self.text.format_page(f"{alts[0].name}'s Account", response)
@@ -0,0 +1,82 @@
from core import command_request
from core.aochat.BaseModule import BaseModule
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Options, Int, Character, Any
from core.db import DB
from core.decorators import instance, command
from core.event_service import EventService
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
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
@instance()
class AccountLogController(BaseModule):
# noinspection DuplicatedCode
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.buddy_service = registry.get_instance("buddy_service")
self.util: Util = registry.get_instance("util")
self.db: DB = registry.get_instance("db")
self.character_service: CharacterService = registry.get_instance("character_service")
self.account_service: AccountService = registry.get_instance("account_service")
self.event_service: EventService = registry.get_instance("event_service")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.text: Text = registry.get_instance("text")
self.preferences: PreferenceController = registry.get_instance("preference_controller")
@command(command="account", params=[Const("log"), Options(["public", "raid", "loot"]),
Character("char"), Int("count", is_optional=True)], access_level="member",
description="Lookup logentries of a Account",
sub_command="public")
def account_log_1(self, _, _1, log_type, user, count):
main = self.account_service.get_main(user.char_id).char_id
entries = self.account_service.get_logs(main, log_type=log_type, limit=count or 25)
return ChatBlob(f"Logentries for {user.name} in the category {log_type}", self.display_logs(entries))
@command(command="account",
params=[Const("log"), Options(["admin", "system", "points"]),
Character("char"), Int("count", is_optional=True)],
access_level="moderator",
description="Lookup logentries of a Account",
sub_command="moderate")
def account_log_2(self, _, _1, log_type, user, count):
main = self.account_service.get_main(user.char_id).char_id
entries = self.account_service.get_logs(main, log_type=log_type, limit=count or 25)
return ChatBlob(f"Logentries for {user.name} in the category {log_type}", self.display_logs(entries))
@command(command="account", params=[Const("addlog"), Options(["admin", "public"]),
Character("char"), Any("text")], access_level="moderator",
description="Add a log entry to an Account",
sub_command="moderate")
def account_add_log(self, request: command_request, _, log_type, user, text):
main = self.account_service.get_main(user.char_id).char_id
self.account_service.add_log(main, log_type, text, request.sender.char_id)
# noinspection LongLine
@command(command="account", params=[Const("log"), Const("id"), Int("id")], access_level="moderator",
description="View a logentry of an user",
sub_command="moderate")
def account_view_log(self, _, _1, _2, log_id):
entry = self.account_service.get_log_by_id(log_id)
if entry:
return ChatBlob(f"Information about Logentry {log_id}",
f"ID: <highlight>{entry.log_id}</highlight>\n"
f"Type: <highlight>{entry.type.capitalize()}</highlight>\n"
f"Affected Main: <highlight>{self.character_service.resolve_char_to_name(entry.char_id)}</highlight>\n"
f"Responsible Leader: <highlight>{self.character_service.resolve_char_to_name(entry.leader_id)}</highlight>\n"
f"Points: <highlight>{f'<green>+{entry.delta}</green>' if entry.delta > 0 else f'<red>{entry.delta}</red>'}</highlight>\n"
f"Message: <grey>{entry.reason}</grey>\n"
f"Logging Time: <highlight>{self.util.format_datetime(entry.created_at)}</highlight>")
else:
return f"No Logentry with ID {id} found."
def display_logs(self, entries) -> str:
blob = ""
for entry in entries:
blob += self.account_service.format_entry(entry)
return blob
+66
View File
@@ -0,0 +1,66 @@
{
"list": {
"en_US": "Alts of {char} ({amount})",
"de_DE": "Alts von {char} ({amount})"
},
"new_main": {
"en_US": "<highlight>{char}</highlight> character has been set as your main. Your preferences have been reset.",
"de_DE": "Dein neuer Main ist <highlight>{char}</highlight>."
},
"not_an_alt": {
"en_US": "Error! This character cannot be set as your main since you do not have any alts",
"de_DE": "Error! Da du keine Alts hast, kannst du auch keinen Main Charakter setzen."
},
"already_main": {
"en_US": "Error! This character is already set as your main.",
"de_DE": "Error! Dieser Charakter ist bereits dein Main."
},
"add_fail_self": {
"en_US": "Error! You cannot register yourself as an alt.",
"de_DE": "Error! Du kannst dich nicht als dein eigener Alt registrieren."
},
"add_success_target": {
"en_US": "<highlight>{char}</highlight> has added you as an alt.",
"de_DE": "<highlight>{char}</highlight> hat dich als seinen Alt hinzugefügt."
},
"add_success_self": {
"en_US": "<highlight>{char}</highlight> has been added as your alt.",
"de_DE": "<highlight>{char}</highlight> wurde zu deinen Alts hinzugefügt."
},
"add_fail_already": {
"en_US": "Error! <highlight>{char}</highlight> already has alts.",
"de_DE": "Error! <highlight>{char}</highlight> hat bereits alts."
},
"rem_success": {
"en_US": "<highlight>{char}</highlight> has been removed as your alt.",
"de_DE": "<highlight>{char}</highlight> wurde von deinen Alts entfernt."
},
"rem_fail_not": {
"en_US": "Error! <highlight>{char}</highlight> is not your alt.",
"de_DE": "Error! <highlight>{char}</highlight> ist kein Alt von dir."
},
"rem_fail_main": {
"en_US": "Error! You cannot remove your main.",
"de_DE": "Error! Du kannst deinen Main nicht entfernen."
},
"altadmin_add_same": {
"en_US": "Error! alt and main are identical.",
"de_DE": "Error! Der Alt und Main Charakter sind identisch."
},
"altadmin_add_success": {
"en_US": "The Toon <highlight>{alt}</highlight> got added as an alt of <highlight>{main}</highlight> successfully.",
"de_DE": "Der Charakter <highlight>{alt}</highlight> ist nun ein Alt von <highlight>{main}</highlight>."
},
"altadmin_rem_success": {
"en_US": "<highlight>{alt}</highlight> is nolonger an alt of <highlight>{main}</highlight>.",
"de_DE": "<highlight>{alt}</highlight> ist nun kein Alt von <highlight>{main}</highlight> mehr."
},
"altadmin_rem_fail_not": {
"en_US": "Error! <highlight>{alt}</highlight> is not an alt of <highlight>{main}</highlight>.",
"de_DE": "Error! <highlight>{alt}</highlight> ist kein Alt von <highlight>{main}</highlight>."
},
"altadmin_rem_fail_main": {
"en_US": "Error! Main Characters may not get removed from their alt list.",
"de_DE": "Error! Du kannst keinen Main von seiner Altliste entfernen."
}
}
+188
View File
@@ -0,0 +1,188 @@
import re
import hjson
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Options, Character, Multiple, Any
from core.decorators import instance, command
from core.dict_object import DictObject
from core.setting_service import SettingService
from core.text import Text
from core.translation_service import TranslationService
from core.tyrbot import Tyrbot
from modules.core.accounting.register_controller import RegisterController
from modules.core.accounting.services.account_service import AccountService
@instance()
class AltsController:
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.account_service: AccountService = registry.get_instance("account_service")
self.buddy_service = registry.get_instance("buddy_service")
self.util = registry.get_instance("util")
self.text: Text = registry.get_instance("text")
self.ts: TranslationService = registry.get_instance("translation_service")
self.character_service = registry.get_instance("character_service")
self.getresp = self.ts.get_response
self.setting_service: SettingService = registry.get_instance("setting_service")
self.register_controller: RegisterController = registry.get_instance("register_controller")
def start(self):
self.ts.register_translation("module/alts", self.load_alts_msg)
def load_alts_msg(self) -> dict:
with open("modules/core/accounting/alts.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@command(command="alts", params=[], access_level="member",
description="Show your alts")
def alts_list_cmd(self, request):
alts = self.account_service.get_alts(request.sender.char_id)
blob = self.format_alt_list(alts)
return ChatBlob(self.getresp("module/alts", "list", {"char": alts[0].name, "amount": len(alts)}), blob)
@command(command="alts", params=[Const("setmain")], access_level="member", description="Set a new main",
extended_description="You must run this from the character you want to be your new main")
def alts_setmain_cmd(self, request, _):
msg, result = self.account_service.set_as_main(request.sender.char_id)
if result:
return self.getresp("module/alts", "new_main", {"char": request.sender.name})
elif msg == "not_an_alt":
return self.getresp("module/alts", "not_an_alt")
elif msg == "already_main":
return self.getresp("module/alts", "already_main")
else:
raise Exception("Unknown msg: " + msg)
@command(command="alts", params=[Const("add"), Multiple(Character("character"))], access_level="member",
description="Add an alt")
def alts_add_cmd(self, request, _, alt_chars):
responses = []
for alt_char in alt_chars:
if not alt_char.char_id:
responses.append(self.getresp("global", "char_not_found", {"char": alt_char.name}))
continue
elif alt_char.char_id == request.sender.char_id:
responses.append(self.getresp("module/alts", "add_fail_self"))
continue
manual = self.setting_service.get_value("alt_verification") == "1"
if manual:
responses.append(self.register_controller.register_alt(request, _, alt_char))
continue
msg, result = self.account_service.add_alt(request.sender.char_id, alt_char.char_id)
if result:
self.bot.send_mass_message(alt_char.char_id, self.getresp("module/alts", "add_success_target",
{"char": request.sender.name}))
responses.append(self.getresp("module/alts", "add_success_self", {"char": alt_char.name}))
elif msg == "another_main":
responses.append(self.getresp("module/alts", "add_fail_already", {"char": alt_char.name}))
else:
raise Exception("Unknown msg: " + msg)
return "\n".join(responses)
@command(command="alts", params=[Options(["rem", "remove"]), Character("character")], access_level="member",
description="Remove an alt")
def alts_remove_cmd(self, request, _, alt_char):
manual = self.setting_service.get_value("alt_verification") == "1"
if manual:
return "This command is disabled, you cannot remove alts."
if not alt_char.char_id:
return self.getresp("global", "char_not_found", {"char": alt_char.name})
msg, result = self.account_service.remove_alt(request.sender.char_id, alt_char.char_id)
if result:
return self.getresp("module/alts", "rem_success", {"char": alt_char.name})
elif msg == "not_alt":
return self.getresp("module/alts", "rem_fail_not", {"char": alt_char.name})
elif msg == "remove_main":
return self.getresp("module/alts", "rem_fail_main")
else:
raise Exception("Unknown msg: " + msg)
@command(command="alts", params=[Character("character")], access_level="member",
description="Show alts of another character", sub_command="show")
def alts_list_other_cmd(self, _, char):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
alts = self.account_service.get_alts(char.char_id)
blob = self.format_alt_list(alts)
if len(alts) < 1:
return "No alts found"
return ChatBlob(self.getresp("module/alts", "list", {"char": alts[0].name, "amount": len(alts)}), blob)
@command(command="altadmin", params=[Const("add"), Character("main"), Any("alts")],
access_level="admin", description="Add alts to Main")
def altadmin_add_cmd(self, _, _1, main, altlist):
alts = re.findall(r"([^ ]+)", altlist)
notfound = []
done = []
failed = []
gotalts = []
if alts:
for alt in alts:
char_id = self.character_service.resolve_char_to_id(alt)
if not char_id:
notfound.append(alt)
continue
alt = DictObject({"char_id": char_id, "name": alt})
if main.char_id == alt.char_id:
failed.append(self.getresp("module/alts", "altadmin_add_same"))
msg, result = self.account_service.add_alt(main.char_id, alt.char_id)
if result:
done.append(alt.name)
elif msg == "another_main":
gotalts.append(alt.name)
reply = ""
if len(done) > 0:
reply += self.getresp("module/alts", "altadmin_add_success",
{"alt": ", ".join(done),
"main": main.name})
if len(notfound) > 0:
reply += "\n" + self.getresp("global", "char_not_found", {"char": ", ".join(notfound)})
if len(gotalts) > 0:
reply += "\n" + self.getresp("module/alts", "add_fail_already", {"char": ", ".join(gotalts)})
reply += "\n".join(failed)
return reply
@command(command="altadmin", params=[Options(["rem", "remove"]), Character("main"), Character("alt")],
access_level="admin",
description="Remove alts of main")
def altadmin_remove_cmd(self, _, _1, main, alt):
if not main.char_id:
return self.getresp("global", "char_not_found", {"char": main.name})
if not alt.char_id:
return self.getresp("global", "char_not_found", {"char": alt.name})
msg, result = self.account_service.remove_alt(main.char_id, alt.char_id)
if result:
return self.getresp("module/alts", "altadmin_rem_success", {"alt": alt.name, "main": main.name})
elif msg == "not_alt":
return self.getresp("module/alts", "altadmin_rem_fail_not", {"alt": alt.name, "main": main.name})
elif msg == "remove_main":
return self.getresp("module/alts", "altadmin_rem_fail_main")
else:
raise Exception("Unknown msg: " + msg)
def format_alt_list(self, alts) -> str:
blob = ""
for alt in alts:
name = f"{alt.name}"
blob += f"{self.util.get_prof_icon(alt.profession)} " \
f"<yellow>{self.text.zfill(alt.level, 220)}</yellow>:" \
f"<green>{self.text.zfill(alt.ai_level, 30)}</green> " \
f":: <{alt.faction.lower()}>{name}</{alt.faction.lower()}> " \
f"[<{alt.faction.lower()}>{alt.org_name}</{alt.faction.lower()}> - " \
f"<highlight>{alt.org_rank_id + 1}</highlight>]"
if self.buddy_service.is_online(alt.char_id):
blob += " [<green>Online</green>]"
blob += "\n"
return blob
@@ -0,0 +1,41 @@
from core import command_request
from core.command_param_types import Options, Int, Character, Any
from core.db import DB
from core.decorators import instance, command
from core.event_service import EventService
from core.lookup.pork_service import PorkService
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
@instance()
class PointsController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.buddy_service = registry.get_instance("buddy_service")
self.util: Util = registry.get_instance("util")
self.db: DB = registry.get_instance("db")
self.character_service = registry.get_instance("character_service")
self.account_service: AccountService = registry.get_instance("account_service")
self.event_service: EventService = registry.get_instance("event_service")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.text: Text = registry.get_instance("text")
self.preferences: PreferenceController = registry.get_instance("preference_controller")
@command(command="account",
params=[Options(["give", "take"]), Int("points"), Character("char"), Any("reason")],
access_level="admin",
description="Give or take points from an Account",
sub_command="modify")
def account_add_mod_pts(self, request: command_request, option, amount, user, reason):
if option == "take":
amount = -amount
self.account_service.add_pts(user.char_id, amount, reason, request.sender.char_id)
def display_logs(self, entries):
blob = ""
for entry in entries:
blob += self.account_service.format_entry(entry)
return blob
@@ -0,0 +1,107 @@
from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_alias_service import CommandAliasService
from core.command_param_types import Const, Options, Character
from core.db import DB
from core.decorators import instance, command
from core.logger import Logger
from core.lookup.pork_service import PorkService
from core.setting_service import SettingService
from core.text import Text
from core.tyrbot import Tyrbot
from core.util import Util
from modules.core.accounting.services.account_service import AccountService
from modules.core.discord.discord_controller import DiscordController
@instance()
class PreferenceController:
def inject(self, registry):
self.logger = Logger(__name__)
self.bot: Tyrbot = 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.account_service: AccountService = registry.get_instance("account_service")
self.pork: PorkService = registry.get_instance("pork_service")
self.org_pork: PorkService = 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.setting_service: SettingService = registry.get_instance("setting_service")
self.discord: DiscordController = registry.get_instance("discord_controller")
self.job_scheduler = registry.get_instance("job_scheduler")
def start(self):
self.command_alias_service.add_alias("prefs", "preferences")
self.command_alias_service.add_alias("autoinvite", "preferences set autoinvite")
@command(command="preferences", params=[], description="View your Preferences", access_level="member")
def show_prefs(self, request):
account = self.account_service.get_account(request.sender.char_id)
if not account:
return "You dont have any preferences you could manage...."
return ChatBlob("Your Preferences", self.get_pref_view_full(account))
@command(command="preferences",
params=[Const("set"),
Options(["news", "raidinvite", "subtilespam", "autoinvite", "raidspam"]),
Options(["on", "off", "yes", "no"])],
description="Change your Preferences", access_level="member")
def set_prefs(self, request, _, pref: str, value):
account = self.account_service.get_account(request.sender.char_id)
if not account:
return "You dont have any preferences you could manage...."
self.set_pref(request.sender.char_id, pref, 0 if value in ["off", "no"] else 1)
return f"Your <highlight>{pref.capitalize()}</highlight> preference has been set to {value}."
@command(command="prefadmin",
params=[Const("set"),
Character("main"),
Options(["news", "raidinvite", "subtilespam", "autoinvite", "raidspam"]),
Options(["on", "off", "yes", "no"])],
description="Change your Preferences",
sub_command='mdf',
access_level="admin")
def set_pref_admin(self, _, _1, main, pref: str, value):
self.set_pref(main.char_id, pref, 0 if value in ["off", "no"] else 1)
return f"<highlight>{main.name}</highlight>'s <highlight>{pref.capitalize()}</highlight> " \
f"preference has been set to {value}."
def get_prefs(self, char_id):
return self.account_service.get_account(char_id)
def set_pref(self, char_id, pref, value):
pref = "subtile_spam" if pref == "subtilespam" \
else "raid_invite" if pref == "raidinvite" \
else "news_spam" if pref == "news" \
else "auto_invite" if pref == "autoinvite" \
else 'raid_spam' if pref == "raidspam" \
else ""
self.db.exec(f"UPDATE account set {pref}={value} where char_id=(SELECT main from account where char_id=?)",
[char_id])
def get_pref_view_small(self, prefs):
return f"\n" \
f" └ [{self._make_cmd('news', prefs.news_spam)}] News - " \
f"Autoinvite [{self._make_cmd('autoinvite', prefs.auto_invite)}], \n" \
f" └ [{self._make_cmd('raidinvite', prefs.raid_invite)}] Raidinvite - " \
f"Massmessage [{self._make_cmd('raidspam', prefs.raid_invite)}], \n" \
f" └ [{self._make_cmd('subtilespam', prefs.subtile_spam)}] Subtilespam"
def get_pref_view_full(self, prefs):
return f"\n" \
f"<tab>└ [ {self._make_cmd('news', prefs.news_spam)} ] Do you want your News on Logon? \n" \
f"<tab>└ [ {self._make_cmd('autoinvite', prefs.auto_invite)} ] Do you want to receive autoinvites? \n" \
f"<tab>└ [ {self._make_cmd('raidinvite', prefs.raid_invite)} ] Do you want to receive raidinvites? \n" \
f"<tab>└ [ {self._make_cmd('raidspam', prefs.raid_invite)} ] Do you want to receive massmessages? \n" \
f"<tab>└ [ {self._make_cmd('subtilespam', prefs.subtile_spam)} ] Do you want a subtile invite spam? \n"
def _make_cmd(self, pref, value):
# ---
# [ ON | OFF ]
if value == 1:
return f"<green>YES</green> | {self.text.make_chatcmd('NO', f'/tell <myname> preferences set {pref} off')}"
else:
return f"{self.text.make_chatcmd('YES', f'/tell <myname> preferences set {pref} on')} | <red>NO</red>"
@@ -0,0 +1,254 @@
import time
from core.chat_blob import ChatBlob
from core.command_alias_service import CommandAliasService
from core.command_param_types import Const, Character, Any, Int
from core.db import DB
from core.decorators import instance, command
from core.event_service import EventService
from core.lookup.character_history_service import CharacterHistoryService
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.message_hub_service import MessageHubService
from core.setting_service import SettingService
from core.text import Text
from core.tyrbot import Tyrbot
from core.util import Util
from modules.core.accounting.services.account_service import AccountService
@instance()
class RegisterController:
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.buddy_service = registry.get_instance("buddy_service")
self.util: Util = registry.get_instance("util")
self.db: DB = registry.get_instance("db")
self.character_service: CharacterService = registry.get_instance("character_service")
self.account_service: AccountService = registry.get_instance("account_service")
self.event_service: EventService = registry.get_instance("event_service")
self.pork_service: PorkService = registry.get_instance("pork_service")
self.text: Text = registry.get_instance("text")
self.setting_service: SettingService = registry.get_instance("setting_service")
self.character_history_service: CharacterHistoryService = registry.get_instance("character_history_service")
self.command_alias_service: CommandAliasService = registry.get_instance("command_alias_service")
self.messagehub: MessageHubService = registry.get_instance("message_hub_service")
def start(self):
self.command_alias_service.add_alias("pending", "register pending")
self.command_alias_service.add_alias("recommend", "register recommend")
self.messagehub.register_message_source("registration")
@command(command="register", params=[Const("pending"), Int('count', is_optional=True)],
description="Show the latest Recommendations", sub_command="mng", access_level="admin")
def register_pending(self, _, _1, limit):
limit = limit or 25
latest = self.db.query("SELECT "
"main.char_id as main_id, "
"alt.char_id as alt_id, "
"recommender.char_id as recommender_id, "
"main.name as main_name, "
"alt.name as alt_name, "
"recommender.name as recommender_name, "
"main.faction as main_faction, "
"alt.faction as alt_faction, "
"recommender.faction as recommender_faction, "
"main.level as main_level, "
"alt.level as alt_level, "
"recommender.level as main_level, "
"alt.ai_level as alt_ai_level, "
"main.ai_level as main_ai_level, "
"recommender.ai_level as recommender_ai_level, "
"reason "
"from pending_accounts p "
"left join player alt on alt.char_id = p.alt "
"left join player main on main.char_id = p.main "
"left join player recommender on recommender.char_id = p.recommender where answered=0 "
"order by time desc limit ?", [limit])
blob = ""
for entry in latest:
blob += self.format_pending(entry, buttons=True)
return ChatBlob("Latest recommendations", blob)
@command(command="register", params=[Const("pending"), Const('latest'), Int('count', is_optional=True)],
description="Show the latest Recommendations", sub_command="mng", access_level="admin")
def register_pending_latest(self, _, _1, _2, limit):
limit = limit or 25
latest = self.db.query("SELECT "
"main.char_id as main_id, "
"alt.char_id as alt_id, "
"recommender.char_id as recommender_id, "
"main.name as main_name, "
"alt.name as alt_name, "
"recommender.name as recommender_name, "
"main.faction as main_faction, "
"alt.faction as alt_faction, "
"recommender.faction as recommender_faction, "
"main.level as main_level, "
"alt.level as alt_level, "
"recommender.level as main_level, "
"alt.ai_level as alt_ai_level, "
"main.ai_level as main_ai_level, "
"recommender.ai_level as recommender_ai_level, "
"reason "
"from pending_accounts p "
"left join player alt on alt.char_id = p.alt "
"left join player main on main.char_id = p.main "
"left join player recommender on recommender.char_id = p.recommender "
"order by time desc limit ?", [limit])
blob = ""
for entry in latest:
blob += self.format_pending(entry)
return ChatBlob("Latest recommendations", blob)
# noinspection LongLine
def format_pending(self, entry, buttons=False):
button = "[<green>" + self.text.make_tellcmd(name="APPROVE", msg=f"pending approve {entry.alt_name}",
style="style='text-decoration:none'") + "</green>] - [<red>" + self.text.make_tellcmd(
name="DENY", msg=f"pending deny {entry.alt_name}",
style="style='text-decoration:none'") + "</red>]" if buttons else ""
if entry.main_id == entry.alt_id:
text = f"[<notice>ACC</notice>] <{entry.main_faction.lower()}>{entry.main_name}</{entry.main_faction.lower()}> ({entry.main_level}/<green>{entry.main_ai_level}</green>) " \
f" [{self.text.make_tellcmd('W', f'whois {entry.main_name}')}] " \
f"[{self.text.make_tellcmd('H', f'history {entry.main_name}')}]\n" \
f"Recommender: <{entry.recommender_faction.lower()}>{entry.recommender_name}</{entry.recommender_faction.lower()}> Why: <notice>{entry.reason}</notice>\n" \
f"{button}"
else:
account = self.account_service.get_account(entry.main_id)
text = f"[<notice>ALT</notice>] <{entry.alt_faction.lower()}>{entry.alt_name}</{entry.alt_faction.lower()}> ({entry.alt_level}/<green>{entry.alt_ai_level}</green>) " \
f" [{self.text.make_tellcmd('W', f'whois {entry.alt_name}')}] " \
f"[{self.text.make_tellcmd('H', f'history {entry.alt_name}')}]\n" \
f"Main: <{entry.main_faction.lower()}>{entry.main_name}</{entry.main_faction.lower()}> ({entry.main_level}/<green>{entry.main_ai_level}</green>) [Join: {self.util.format_datetime(account.created)}]" \
f" - [{self.text.make_tellcmd('W', f'whois {entry.main_name}')}] " \
f"[{self.text.make_tellcmd('H', f'history {entry.main_name}')}]\n" \
f"{button}"
return text + "\n\n"
@command(command="register", params=[Const("pending"), Const('deny'), Character('character')],
description="Deny (and disable) a pending account", sub_command="mng", access_level="admin")
def register_pending_deny(self, request, _1, _2, user):
pending = self.account_service.is_pending(user.char_id)
if not pending:
return f"There's no pending registration for <highlight>{user.name}</highlight>."
self.account_service.create_users([(pending.alt, pending.alt, -1, time.time(), time.time())])
self.account_service.add_log(request.sender.char_id, "system",
f"Created <highlight>{user.name}</highlight>'s account.",
request.sender.char_id)
self.account_service.add_log(user.char_id, "system",
f"Account closed by <highlight>{request.sender.name}</highlight>.",
request.sender.char_id)
self.db.exec("UPDATE pending_accounts set answered = 1 where alt=?", [pending.alt])
self.account_service.account_disable(user.char_id)
self.send_notify(self.Notify.DENIED, user.name, user.name)
# noinspection LongLine
@command(command="register", params=[Const("pending"), Const('approve'), Character('character')],
description="Approve (and activate) a pending account", sub_command="mng", access_level="admin")
def register_pending_approve(self, request, _1, _2, user):
pending = self.account_service.is_pending(user.char_id)
if not pending:
return f"There's no pending registration for <highlight>{user.name}</highlight>."
self.account_service.create_users([(pending.alt, pending.alt, 0, time.time(), time.time())])
if pending.alt == pending.main:
self.db.exec("UPDATE pending_accounts set answered = 1 where alt=?", [pending.alt])
self.account_service.add_log(request.sender.char_id, "system",
f"Opened <highlight>{user.name}</highlight>'s account.",
request.sender.char_id)
self.account_service.add_log(user.char_id, "system",
f"Account opened by <highlight>{request.sender.name}</highlight>.",
request.sender.char_id)
recommender: str = self.character_service.get_char_name(pending.recommender)
self.buddy_service.add_buddy(user.char_id, "member")
self.bot.send_mass_message(user.char_id,
f"Your Account has been <green>opened</green> by "
f"<highlight>{request.sender.name}</highlight>. "
f"<highlight>{recommender.capitalize()}</highlight> has recommended you.")
self.send_notify(self.Notify.APPROVAL_MAIN, user.name, user.name)
else:
msg, typ = self.account_service.add_alt(pending.main, pending.alt, approve=True)
if msg == "success":
self.db.exec("UPDATE pending_accounts set answered = 1 where alt=?", [pending.alt])
self.account_service.add_log(request.sender.char_id, "system",
f"Added <highlight>{user.name}</highlight> as an alt of <highlight>{self.character_service.resolve_char_to_name(pending.main)}</highlight>.",
request.sender.char_id)
self.account_service.add_log(user.char_id, "system",
f"Request to add <highlight>{user.name}</highlight> as an alt "
f"has been approved by <highlight>{request.sender.name}</highlight>.",
request.sender.char_id)
self.send_notify(self.Notify.APPROVAL_ALT,
self.character_service.resolve_char_to_name(pending.main),
user.name)
else:
return "Something went wrong.."
@command(command="register", params=[Const('alt'), Character('character')],
description="Add an alt", access_level="member")
def register_alt(self, request, _, alt):
manual = self.setting_service.get_value("alt_verification") == "1"
if not alt.char_id:
return "Character does not exist"
if not manual:
return "This command is currently disabled, please use <highlight>!alts add</highlight>"
msg, _ = self.account_service.add_pending_alt(request.sender.char_id, alt.char_id)
if msg in ["another_main", "already_main"]:
return f"You cannot add <highlight>{alt.name}</highlight> as an alt, because it already has a main."
elif msg == "pending_alt":
return f"There's already a pending registration for the alt <highlight>{alt.name}</highlight>, " \
f"please wait for an admin to confirm it."
elif msg == "success":
self.send_notify(self.Notify.REQUEST_ALT, request.sender.name, alt.name)
return f"You requested to add <highlight>{alt.name}</highlight> as an alt."
@command(command="register", params=[Const('recommend'), Character('character'), Any('reason')],
description="Recommend someone as a raider", access_level="member")
def register_account(self, request, _, user, reason):
if not user.char_id:
return "Character does not exist"
if self.account_service.get_account(user.char_id):
return f"There's already an account for <highlight>{user.name}</highlight> registered."
msg, _ = self.account_service.add_pending_account(request.sender.char_id, user.char_id, reason)
if msg in ["another_main", "already_main"]:
return f"You cannot recommend <highlight>{user.name}</highlight>, " \
f"because the character is already registered."
elif msg == "pending_alt":
return f"There's already a pending registration for the character <highlight>{user.name}</highlight>, " \
f"please wait for an admin to confirm it."
elif msg == "success":
self.send_notify(self.Notify.REQUEST_MAIN, user.name, user.name)
def send_notify(self, notify_type, main, alt):
message = "[<cyan>PG</cyan>] "
if notify_type == self.Notify.APPROVAL_MAIN:
message += f"[<cyan>Main</cyan>] Approved: <highlight>{main}</highlight>'s Account has been opened."
elif notify_type == self.Notify.APPROVAL_ALT:
message += f"[<cyan>Alt</cyan>] Approved: <highlight>{alt}</highlight> " \
f"has been assigned to <highlight>{main}</highlight>."
elif notify_type == self.Notify.DENIED:
message += f"Denied: <highlight>{main}</highlight>'s Account has been closed."
elif notify_type == self.Notify.REQUEST_ALT:
message += f"[<cyan>Alt</cyan>] Request: <highlight>{alt}</highlight> => <highlight>{main}</highlight>"
elif notify_type == self.Notify.REQUEST_MAIN:
message += f"[<cyan>Main</cyan>] Request: <highlight>{main}</highlight> as a new " \
f"<highlight>raider</highlight>"
self.messagehub.send_message("registration", None, f"{message}", f"{message}")
class Notify:
APPROVAL_MAIN = "approval-main"
APPROVAL_ALT = "approval-alt"
DENIED = "denied"
REQUEST_ALT = "req-alt"
REQUEST_MAIN = "req-main"
@@ -0,0 +1,137 @@
import inspect
from typing import List
from core.decorators import instance
from core.logger import Logger
# noinspection PyUnusedLocal
@instance()
class AccessService:
def __init__(self):
self.access_levels = [
{"label": "none", "level": 0, "handler": self.no_access},
{"label": "all", "level": 100, "handler": self.all_access}]
self.logger = Logger(__name__)
def inject(self, registry):
self.character_service = registry.get_instance("character_service")
self.account_service = registry.get_instance("account_service")
def register_access_level(self, label, level, handler):
"""
Call during pre_start
Args:
label: str
level: int
handler: (char_id: Int) -> bool
"""
if len(inspect.signature(handler).parameters) != 1:
raise Exception(
"Incorrect number of arguments for handler '%s.%s()'" % (handler.__module__, handler.__name__))
self.logger.debug("Registering access level %d with label '%s'" % (level, label))
self.access_levels.append({"label": label.lower(), "level": level, "handler": handler})
self.access_levels = sorted(self.access_levels, key=lambda k: k["level"])
def get_access_levels(self) -> List[dict]:
return self.access_levels
def get_access_level(self, char_id) -> dict:
account = self.account_service.get_main(char_id)
if account:
al = self.get_single_access_level(account.char_id)
if al["label"] == "all":
al = self.get_single_access_level(char_id)
return al
else:
return self.get_single_access_level(char_id)
# access_level1 = self.get_single_access_level(char_id)
#
# alts = self.account_service.get_alts(char_id)
# if not alts:
# return access_level1
#
# main = alts[0]
# if main.char_id == char_id:
# return access_level1
# else:
# access_level2 = self.get_single_access_level(main.char_id)
# if access_level1["level"] < access_level2["level"]:
# return access_level1
# else:
# return access_level2
def compare_access_levels(self, access_level1, access_level2) -> int:
"""
Returns a positive number if the access_level1 is greater than access_level2,
a negative number if access_level1 is less than access_level2,
and 0 if the access levels are equal.
:param access_level1:
:param access_level2:
:return: int
"""
a1 = self.get_access_level_by_label(access_level1)
a2 = self.get_access_level_by_label(access_level2)
return a2["level"] - a1["level"]
def has_sufficient_access_level(self, char_id1, char_id2) -> bool:
"""
Returns True if char1 has a higher access level than char2
or if char1 is a verified alt of char2, and False otherwise.
:param char_id1:
:param char_id2:
:return:
"""
# return True if char_ids are the same
if char_id1 == char_id2:
return True
# return True if both chars have the same main
if self.account_service.get_main(char_id1).char_id == (self.account_service.get_main(char_id2) or {}).get(
"char_id", 0):
return True
a1 = self.get_access_level(char_id1)
a2 = self.get_access_level(char_id2)
return a2["level"] - a1["level"] > 0
def get_single_access_level(self, char) -> dict:
char_id = self.character_service.resolve_char_to_id(char)
for access_level in self.access_levels:
if access_level["handler"](char_id):
return access_level
def get_access_level_by_level(self, level) -> dict or bool:
for access_level in self.access_levels:
if access_level["level"] == level:
return access_level
return False
def get_access_level_by_label(self, label) -> dict or bool:
label = label.lower()
for access_level in self.access_levels:
if access_level["label"] == label:
return access_level
return False
def check_access(self, char, access_level_label) -> bool:
char_id = self.character_service.resolve_char_to_id(char)
if not char_id:
return False
# noinspection LongLine
return (self.get_access_level(char) or {}).get("level", 100) <= \
self.get_access_level_by_label(access_level_label)["level"]
def no_access(self, char_id) -> bool:
return False
def all_access(self, char_id) -> bool:
return True
@@ -0,0 +1,568 @@
import time
from typing import List
from core.buddy_service import BuddyService
from core.db import DB, SqlException
from core.decorators import instance, timerevent, event
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.character_service import CharacterService
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.tyrbot import Tyrbot
from modules.core.accounting.services.access_service import AccessService
# noinspection SqlCaseVsIf,SqlResolve,PyMethodMayBeStatic
@instance()
class AccountService:
MAIN_CHANGED_EVENT_TYPE = "main_changed"
MEMBER_LOGON = "member_logon"
MEMBER_LOGOFF = "member_logoff"
def __init__(self):
self.logger = Logger("Accounting")
# noinspection PyAttributeOutsideInit
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
self.util = registry.get_instance("util")
self.character_service: CharacterService = registry.get_instance("character_service")
self.access_service: AccessService = registry.get_instance("access_service")
self.db: DB = registry.get_instance("db")
self.event_service = registry.get_instance("event_service")
self.pork: PorkService = registry.get_instance("pork_service")
self.setting_service: SettingService = registry.get_instance("setting_service")
self.text: Text = registry.get_instance("text")
def pre_start(self):
self.db.exec("CREATE TABLE IF NOT EXISTS account ("
"char_id int(11) NOT NULL,"
"points int(11) NOT NULL default 0,"
"member int(2) NOT NULL DEFAULT 0,"
"created int(255) NOT NULL DEFAULT 0,"
"disabled int(2) NOT NULL DEFAULT 0,"
"main int(11) NOT NULL DEFAULT char_id,"
"subtile_spam int(2) NOT NULL DEFAULT 0,"
"auto_invite int(2) NOT NULL DEFAULT 0,"
"raid_invite int(2) NOT NULL DEFAULT 1,"
"raid_spam int(2) NOT NULL DEFAULT 1,"
"news_spam int(2) NOT NULL DEFAULT 1,"
"discord_id bigint(30) UNSIGNED NULL DEFAULT 0,"
"discord_handle varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
"discord_invite text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
"discord_joined int(2) NOT NULL DEFAULT 0,"
"logon text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
"logoff text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '',"
"last_seen int(11) NOT NULL DEFAULT 0,"
"last_updated int(11) NOT NULL DEFAULT 0,"
"PRIMARY KEY (char_id) USING BTREE,"
"INDEX char_id(char_id) USING BTREE,"
"INDEX main(main) USING BTREE,"
"INDEX disabled(disabled) USING BTREE,"
"INDEX char_id_2(char_id, disabled) USING BTREE,"
"INDEX discord_id(discord_id) USING BTREE)")
self.db.exec(
"CREATE TABLE IF NOT EXISTS raid_log ("
"raid_id INT PRIMARY KEY AUTO_INCREMENT, "
"raid_name VARCHAR(255) NOT NULL, "
"started_by BIGINT NOT NULL, "
"raid_start INT NOT NULL, "
"raid_end INT NOT NULL)")
self.db.exec(
"CREATE TABLE IF NOT EXISTS raid_log_participants ("
"raid_id INT NOT NULL, "
"raider_id BIGINT NOT NULL, "
"accumulated_points INT DEFAULT 0, "
"left_raid INT, "
"was_kicked INT, "
"was_kicked_reason VARCHAR(500))")
self.db.exec("CREATE TABLE IF NOT EXISTS points_presets ("
"preset_id INT PRIMARY KEY AUTO_INCREMENT, "
"name VARCHAR(50) NOT NULL, "
"points INT DEFAULT 1, "
"UNIQUE(name));")
self.db.exec("CREATE TABLE IF NOT EXISTS pending_accounts ("
"main INT(11) NOT NULL, "
"alt INT(11) NOT NULL, "
"reason TEXT DEFAULT '', "
"recommender INT(11) NOT NULL , "
"time int(255) NOT NULL,"
"answered int(2) default 0 not null);")
self.db.exec("CREATE TABLE IF NOT EXISTS account_log ("
"log_id INTEGER PRIMARY KEY AUTO_INCREMENT, "
"char_id BIGINT NOT NULL, "
"type VARCHAR(32), "
"delta INT NOT NULL DEFAULT 0, "
"leader_id BIGINT NOT NULL, "
"reason VARCHAR(255), "
"created_at INTEGER NOT NULL, "
"INDEX char_id (char_id), "
"INDEX leader (leader_id), "
"INDEX created(created_at));")
self.db.exec("CREATE TABLE IF NOT EXISTS ranks ("
"main int(11) NOT NULL, "
"`rank` varchar(32) not null, "
"INDEX main(main, `rank`) USING BTREE)")
self.db.exec("CREATE TABLE IF NOT EXISTS org_bots(char_id int primary key not null, org_id int not null)")
self.event_service.register_event_type(self.MAIN_CHANGED_EVENT_TYPE)
self.event_service.register_event_type(self.MEMBER_LOGON)
self.event_service.register_event_type(self.MEMBER_LOGOFF)
self.setting_service.register_new(self.module_name, "is_alliance_bot", False, BooleanSettingType(),
"Is this bot used as an alliancebot")
self.setting_service.register_new(self.module_name, "alt_verification", False, BooleanSettingType(),
"alts require admin verification")
if self.setting_service.get_value("is_alliance_bot") == "1":
self.access_service.register_access_level("officer", 80, self.check_officer)
self.access_service.register_access_level("general", 70, self.check_general)
self.access_service.register_access_level("president", 60, self.check_president)
self.access_service.register_access_level("council", 40, self.check_council)
self.access_service.register_access_level("moderator", 30, self.check_moderator)
self.access_service.register_access_level("member", 90, self.check_member)
self.access_service.register_access_level("admin", 20, self.check_admin)
self.access_service.register_access_level("leader", 50, self.check_leader)
self.access_service.register_access_level("superadmin", 10, self.check_superadmin)
def get_main(self, char_id) -> DictObject:
alts = self.get_alts(char_id)
return alts[0] if alts else self.db.query_single("select * from player where char_id=?", [char_id])
def get_alts(self, char_id) -> List[DictObject]:
return self.db.query(
"SELECT p.*, a.* from account a left join player p on a.char_id = p.char_id where "
"main=(SELECT main from account where char_id=?) ORDER BY a.main = a.char_id desc, p.level, p.name DESC",
[char_id])
acc_cache = {}
def get_account(self, char_id) -> DictObject:
if char_id not in self.acc_cache.keys():
out = self.db.query_single(
"SELECT a.*, p.* from account a left join player p on a.char_id = p.char_id where "
"a.char_id=(SELECT main from account where char_id=?) "
"and a.char_id not in (SELECT char_id from org_bots)",
[char_id]) or DictObject({})
self.acc_cache[char_id] = out
self.bot.job_scheduler.delayed_job(lambda x: self.acc_cache.pop(char_id), 5)
else:
out = self.acc_cache.get(char_id)
return out
def get_entry(self, char_id) -> DictObject:
return self.db.query_single("SELECT a.*, p.* from account a left join player p on a.char_id = p.char_id "
"where a.char_id=? and a.char_id not in (SELECT char_id from org_bots)",
[char_id]) or DictObject({})
def add_pending_alt(self, main, alt) -> [str, bool]:
data = self.check_alt(alt)
if data:
return data
acc = self.get_account(main)
if acc:
main = acc.main
self.pork.load_character_info(alt, skeleton_only=True)
self.db.exec("INSERT INTO pending_accounts(main, alt, recommender, time) VALUES(?, ?, ?, ?)",
[acc.main, alt, acc.main, time.time()])
self.add_log(main, 'system',
f'Requested to add <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight>'
f' as an alt',
acc.main)
return ["success", True]
def add_pending_account(self, main, user, reason) -> [str, bool]:
data = self.check_alt(user)
if data:
return data
acc = self.get_account(main)
if acc:
main = acc.main
self.pork.load_character_info(user, skeleton_only=True)
self.db.exec("INSERT INTO pending_accounts(main, alt, recommender, reason, time) VALUES(?, ?, ?, ?, ?)",
[user, user, acc.main, reason, time.time()])
self.add_log(main, 'system',
f'Recommended <highlight>{self.character_service.resolve_char_to_name(user)}</highlight>'
f' as raider, reason: {reason}',
acc.main)
return ["success", True]
def check_alt(self, alt):
alt_alts = self.get_alts(alt) or []
if len(alt_alts) > 1:
for alts in alt_alts:
if alts.char_id == alt:
if alts.main != alts.char_id:
return ["another_main", False]
alt_main = self.get_account(alt)
if alt_main:
if alt_main.main != alt:
return ["already_main", False]
if self.is_pending(alt):
return ['pending_alt', False]
def is_pending(self, char_id) -> DictObject or bool:
return self.db.query_single("SELECT * from pending_accounts where alt=? and answered = 0", [char_id]) or False
def add_alt(self, sender, alt, approve=False) -> [str, bool]:
alt_alts = self.get_alts(alt) or []
if alt_alts:
if len(alt_alts) > 1:
return ["another_main", False]
if alt_main := alt_alts[0]:
if alt_main.main != alt:
return ["already_main", False]
acc = self.get_account(sender)
if acc:
main = acc.main
self.pork.load_character_info(alt, skeleton_only=True)
member = -1 if acc.disabled == 1 or acc.member == -1 else 0
if self.db.exec("INSERT IGNORE INTO account(char_id, main, created, member) VALUES(?, ?, ?, ?)",
[alt, main, time.time(), member]) == 0:
self.db.exec("UPDATE account set main=?, created=? where char_id=?", [main, time.time(), alt])
self.event_service.fire_event(self.MAIN_CHANGED_EVENT_TYPE,
DictObject({"old_main_id": alt, "new_main_id": main}))
if not approve:
self.add_log(sender, 'system',
f'Added <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight>'
f' as an alt',
acc.main)
# Only add as member if he's neither banned, nor unregistered
if member != -1:
self.buddy_service.add_buddy(alt, "member")
return ["success", True]
self.db.exec("INSERT IGNORE INTO account(char_id, main, created, member) VALUES(?, ?, ?, ?)",
[sender, sender, time.time(), -1])
self.db.exec("INSERT INTO account(char_id, main, created, member) VALUES(?, ?, ?, ?) ON DUPLICATE KEY UPDATE "
"main=VALUE(main), discord_id=0, discord_handle='', discord_invite='', discord_joined=0",
[alt, sender, time.time(), -1])
if not approve:
self.add_log(sender, 'system',
f'Added <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight> as an alt',
sender)
# Main does not exist, do not add as member
# self.buddy_service.add_buddy(alt, "member")
return ["success", True]
def remove_alt(self, sender, alt) -> [str, bool]:
if not self.db.query(
"SELECT * FROM account where char_id=? and main=(SELECT main from account where char_id=?)",
[alt, sender]):
return ["not_alt", False]
alts = self.get_alts(sender)
for row in alts:
if row.char_id == alt and row.main == alt:
return ["remove_main", False]
if row.char_id == alt and row.main == alts[0].char_id:
self.db.exec("UPDATE account set main=? where char_id=?", [alt, alt])
self.add_log(sender, 'system',
f'Removed <highlight>{self.character_service.resolve_char_to_name(alt)}</highlight>'
f' from his alts.',
sender)
return ["success", True]
else:
return [""]
def set_as_main(self, sender) -> [str, bool]:
alts = self.get_alts(sender)
if len(alts) < 2:
return ["not_an_alt", False]
elif alts[0].char_id == sender:
return ["already_main", False]
else:
self.db.exec("UPDATE account set main=? where main=(SELECT main from account where char_id=?)",
[sender, sender])
self.event_service.fire_event(self.MAIN_CHANGED_EVENT_TYPE,
DictObject({"old_main_id": alts[0].char_id,
"new_main_id": sender}))
return ["success", True]
def get_orgs(self) -> list:
try:
return [x["org_id"] for x in self.db.query("SELECT * from orgs", [])]
except SqlException:
return [self.bot.public_channel_service.org_id]
def create_users(self, users, disable=False) -> int:
if type(users) == list and len(users) > 0:
with self.db.pool.get_connection() as conn:
with conn.cursor() as cur:
if disable:
cur.executemany(
"INSERT IGNORE INTO account(char_id, main, member, disabled, last_updated, created) "
"VALUES(?, ?, ?, 1, ?, ?) ON DUPLICATE KEY UPDATE "
"member=VALUE(member), last_updated=VALUE(last_updated), disabled=1",
users)
return cur.rowcount
cur.executemany(
"INSERT IGNORE INTO account(char_id, main, member, last_updated, created) "
"VALUES(?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE "
"member=VALUE(member), last_updated=VALUE(last_updated)",
users)
return cur.rowcount
def account_disable(self, char_id) -> bool:
return True if self.db.exec(
"UPDATE account set disabled=1 where main in (SELECT main from account where char_id=?)",
[char_id]) else False
def account_enable(self, char_id) -> bool:
return True if self.db.exec(
"UPDATE account set disabled=0 where main in (SELECT main from account where char_id=?)",
[char_id]) else False
def remove_members(self, users) -> None:
if type(users) == list and len(users) > 0:
with self.db.pool.get_connection() as conn:
with conn.cursor() as cur:
cur.executemany("UPDATE account set member=-1 where char_id=? and ?", users)
def check_member(self, char_id) -> bool:
account = self.get_account(char_id) or {}
if account.get('disabled', 1) == 1:
return False
if account.get('member', -1) == -1:
return False
if self.setting_service.get_value("is_alliance_bot") == "0":
return True
if account.get('org_id', 0) not in self.get_orgs():
return False
return True
def check_president(self, char_id) -> bool:
if self.setting_service.get_value("is_alliance_bot") == "0":
return False
account = self.get_account(char_id) or {}
if not self.simple_checks(account):
return False
if account.get('org_rank_id', -1) == 0:
return True
return False
def check_general(self, char_id) -> bool:
if self.setting_service.get_value("is_alliance_bot") == "0":
return False
account = self.get_account(char_id) or {}
if not self.simple_checks(account):
return False
if self.get_rank_count(char_id) != 7:
return False
if account.get('org_rank_id', -1) <= 1:
return True
return False
def check_officer(self, char_id) -> bool:
if self.setting_service.get_value("is_alliance_bot") == "0":
return False
account = self.get_account(char_id) or {}
if not self.simple_checks(account):
return False
if self.get_rank_count(char_id) != 7:
return False
if account.get('org_rank_id', -1) <= 2:
return True
return False
def check_council(self, char_id) -> bool:
if self.setting_service.get_value("is_alliance_bot") == "0":
return False
if self.simple_checks(self.get_account(char_id)):
return "council" in self.get_ranks(char_id)
def check_leader(self, char_id) -> bool:
if self.simple_checks(self.get_account(char_id)):
return "leader" in self.get_ranks(char_id)
def check_moderator(self, char_id) -> bool:
if self.simple_checks(self.get_account(char_id)):
return "moderator" in self.get_ranks(char_id)
def check_admin(self, char_id) -> bool:
if self.simple_checks(self.get_account(char_id)):
return "admin" in self.get_ranks(char_id)
def check_superadmin(self, char_id) -> int:
return char_id in self.bot.superadmin
def get_ranks(self, char_id) -> List[DictObject]:
return [x["rank"] for x in
self.db.query(" SELECT rank FROM ranks where main = (SELECT main from account where char_id=? limit 1)",
[char_id])] or []
def get_all_admins(self) -> List[DictObject]:
return self.get_by_group("admin")
def get_all_moderators(self) -> List[DictObject]:
return self.get_by_group("moderator")
def get_all_councils(self) -> List[DictObject]:
return self.get_by_group("council")
def get_all_leaders(self) -> List[DictObject]:
return self.get_by_group("leader")
def get_all_members(self, online_only=False) -> List[DictObject]:
return self.db.query(
f"SELECT p.*, a.*, CASE when o.char_id IS NOT NULL then 1 ELSE 0 end as online from account a "
f"LEFT JOIN player p ON a.char_id=p.char_id "
f"LEFT JOIN (SELECT * FROM online WHERE bot=?) o ON a.char_id=o.char_id "
f"WHERE a.char_id NOT IN (SELECT char_id from org_bots) "
f"and a.disabled = 0 {'and o.char_id is not null' if online_only else ''} "
f"order by a.main, a.main=a.char_id desc, p.level desc, p.ai_level desc",
[self.bot.get_char_id()])
def get_by_group(self, group) -> List[DictObject]:
return self.db.query("""SELECT CASE when rank = 'admin' then 0
when rank = 'moderator' then 1
when rank = 'council' then 2
when rank = 'leader' then 3
ELSE 99 END AS rank_id,
CASE when o.char_id IS NOT NULL
then 1
ELSE 0 end as online,
p.*, a.* FROM ranks r
LEFT JOIN account a ON r.main=a.main
LEFT JOIN player p ON a.char_id=p.char_id
LEFT JOIN online o ON a.char_id=o.char_id
WHERE r.rank=?
ORDER BY rank_id, a.main desc, a.main=a.char_id DESC, p.name """, [group])
def get_group_tag(self, string) -> str or bool:
string = string.lower()
if string in ["adm", "admins", "admin", "administrator", "administrators"]:
return "admin"
elif string in ["mod", "mods", "moderator", "moderators"]:
return "moderator"
elif string in ["cnc", "council", "councilors"]:
return "council"
elif string in ["rl", "leader", "leaders", "raidleader", "raidleaders", "rls"]:
return "leader"
elif string in ["all", "full", "everyone"]:
return "all"
else:
return False
def add_log(self, char_id, log_type, message, leader, delta=0) -> None:
main = self.get_main(char_id).char_id
self.db.exec("INSERT INTO account_log(char_id, type, delta, leader_id, reason, created_at) "
"VALUES (?, ?, ?, ?, ?, ?)",
[main, log_type, delta, leader, message, time.time()])
def get_logs(self, user, log_type=None, limit=25) -> List[DictObject]:
if not log_type:
return self.db.query("SELECT * FROM account_log "
"where char_id=? and type != 'admin' order by log_id desc LIMIT ? ", [user, limit])
else:
return self.db.query("SELECT * FROM account_log "
"where char_id=? and type=? order by log_id desc LIMIT ?", [user, log_type, limit])
def get_log_by_id(self, log_id) -> DictObject:
return self.db.query_single("SELECT * FROM account_log where log_id=? ", [log_id])
def format_entry(self, entry) -> str:
msg = f"<grey>[{self.util.format_datetime(entry.created_at)}] " \
f"[{self.text.make_tellcmd('D', f'account log id {entry.log_id}')}]</grey> "
entry.type = entry.type.lower()
if entry.type == "points":
entry.reason = entry.reason.replace('"', "'")
msg += f"<white>{f'<green>+{entry.delta}P</green>' if entry.delta >= 0 else f'<red>{entry.delta}P</red>'}" \
f" by <notice>{self.character_service.resolve_char_to_name(entry.leader_id)}</notice>" \
f" for <notice>{entry.reason}</notice></white>"
elif entry.type == "loot":
msg += f"<white>Won Item: {entry.reason}</white>"
elif entry.type == "raid":
msg += f"<white>{entry.reason}</white>"
elif entry.type == "public":
msg += f"<white>{entry.reason}</white>"
elif entry.type == "admin":
msg += f"<white>Notice from " \
f"<notice>{self.character_service.resolve_char_to_name(entry.leader_id)}</notice>: " \
f"{entry.reason}</white>"
elif entry.type == "system":
msg += f"<yellow>{entry.reason}</yellow>"
return msg + "\n"
def add_pts(self, char_id, points, reason, leader) -> None:
self.db.exec("UPDATE account set points = points+? where char_id=(select main from account where char_id=?)",
[points, char_id])
self.add_log(char_id, 'points', reason, leader, delta=points)
def rem_pts(self, char_id, points, reason, leader) -> None:
if points < 0:
points = -points
self.db.exec("UPDATE account set points = points-? where char_id=(select main from account where char_id=?)",
[points, char_id])
self.add_log(char_id, 'points', reason, leader, delta=-points)
def add_rank(self, char_id, rank) -> int:
return self.db.exec("INSERT INTO ranks(main, `rank`) VALUES(?, ?)", [char_id, rank])
def del_rank(self, char_id, rank) -> int:
return self.db.exec("DELETE FROM ranks where main=? and rank =?", [char_id, rank])
@timerevent(budatime="12h", description="Delete dead ranks")
def clear_raid(self, _, _1):
count = self.db.exec("DELETE FROM ranks where main not in (SELECT main from account)")
if count > 0:
self.logger.info(f"Purged {count} dead ranks")
count = self.db.exec(
"delete from ranks where main in(select r.main from ranks r "
"left join account a on r.main=a.char_id "
"where a.member=-1)")
if count > 0:
self.logger.info(f"Purged {count} ranks; caused by: main no longer in the alliance")
@event(event_type="main_changed", description="Fix ranks")
def fix_ranks(self, _, data):
self.db.exec("UPDATE ranks set main=? where main=?", [data.new_main_id, data.old_main_id])
self.db.exec("UPDATE account_log set char_id=? where char_id=?", [data.new_main_id, data.old_main_id])
self.db.exec("UPDATE account_log set leader_id=? where leader_id=?", [data.new_main_id, data.old_main_id])
@event(event_type="buddy_logon", description="Member logon manager")
def member_login(self, _, data):
buddy = self.buddy_service.get_buddy(data.char_id)
if buddy:
if self.bot.is_ready():
if "member" in buddy['types'] or "org_member" in buddy['types']:
if main := self.get_account(data.char_id):
self.event_service.fire_event(self.MEMBER_LOGON, DictObject({'account': main, 'packet': data}))
else:
if main := self.get_account(data.char_id):
self.event_service.fire_event(self.MEMBER_LOGON, DictObject({'account': main, 'packet': data}))
@event(event_type="buddy_logoff", description="Member logoff manager")
def member_logout(self, _, data):
if self.bot.is_ready():
buddy = self.buddy_service.get_buddy(data.char_id)
if buddy:
if "member" in buddy['types'] or "org_member" in buddy['types']:
if main := self.get_account(data.char_id):
self.event_service.fire_event(self.MEMBER_LOGOFF, DictObject({'account': main, 'packet': data}))
def get_rank_count(self, char_id):
return self.db.query_single(
"SELECT MAX(org_rank_id)+1 AS count FROM player WHERE org_id=(SELECT org_id FROM player where char_id=?)",
[char_id]).count
def simple_checks(self, account):
if account.get('disabled', 1) == 1:
return False
if account.get('member', -1) == -1:
return False
if self.setting_service.get_value("is_alliance_bot") == "1":
if account.get('org_id', 0) not in self.get_orgs():
return False
return True
+18
View File
@@ -0,0 +1,18 @@
{
"add_success": {
"en_US": "Character <highlight>{char}</highlight> added as <highlight>{rank}</highlight> successfully.",
"de_DE": "Der Charakter <highlight>{char}</highlight> wurde erfolgreich zum <highlight>{rank}</highlight> ernannt."
},
"add_fail": {
"en_US": "Could not add character <highlight>{char}</highlight> as <highlight>{rank}</highlight>.",
"de_DE": "Der Charakter <highlight>{char}</highlight> konnte nicht zum <highlight>{rank}</highlight> ernannt werden."
},
"rem_success": {
"en_US": "Character <highlight>{char}</highlight> removed as <highlight>{rank}</highlight> successfully.",
"de_DE": "Der Charakter <highlight>{char}</highlight> ist nun kein <highlight>{rank}</highlight> mehr."
},
"rem_fail": {
"en_US": "Could not remove character <highlight>{char}</highlight> as <highlight>{rank}</highlight>.",
"de_DE": "Dem Charakter <highlight>{char}</highlight> konnte der Rang <highlight>{rank}</highlight> nicht entfernt werden."
}
}
+165
View File
@@ -0,0 +1,165 @@
from typing import List
import hjson
from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_param_types import Const
from core.db import DB
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.setting_service import SettingService
from core.text import Text
from core.translation_service import TranslationService
from modules.onlinebot.online.org_alias_controller import OrgAliasController
@instance()
class AdminController:
def __init__(self):
pass
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.pork_service = registry.get_instance("pork_service")
self.command_alias_service = registry.get_instance("command_alias_service")
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
self.db: DB = registry.get_instance("db")
self.alias_controller: OrgAliasController = registry.get_instance("org_alias_controller")
self.text: Text = registry.get_instance("text")
self.settings_service: SettingService = registry.get_instance("setting_service")
def start(self):
self.command_alias_service.add_alias("adminlist", "admin")
self.command_alias_service.add_alias("admins", "admin")
self.ts.register_translation("module/admin", self.load_admin_msg)
def load_admin_msg(self):
with open("modules/core/admin/admin.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@event(event_type="connect", description="Add admins as buddies")
def connect_event(self, _, _1):
for row in self.get_all():
self.buddy_service.add_buddy(row.char_id, "admin")
def get_all(self) -> List[DictObject]:
return self.db.query("SELECT p.*, COALESCE(p.name, t.char_id) AS name, t.rank, t.sort FROM "
"("
"SELECT a.main as char_id, a.rank, "
"CASE WHEN rank = 'admin' THEN 0 "
"WHEN rank = 'moderator' THEN 1 "
"WHEN rank = 'council' THEN 2 "
"WHEN rank = 'leader' THEN 3 END AS sort FROM ranks a) t "
"LEFT JOIN player p ON t.char_id = p.char_id "
"where rank in ('admin', 'moderator', 'council', 'leader') "
"ORDER BY sort, p.name")
@command(command="admin", params=[Const("all")], access_level="member",
description="Shows the administrators of the Bot", sub_command="list")
def show_all_admins(self, _, _1):
count = []
# noinspection SqlAggregates
users = self.db.query("""
SELECT r.rank, r.rank_id, a.main, p.*, IF(o.char_id IS NULL, 0, 1) AS online
FROM (SELECT * FROM (SELECT
CASE when rank = 'admin' then 0
when rank = 'moderator' then 1
when rank = 'council' then 2
when rank = 'leader' then 3
ELSE 99 END AS rank_id, RANK,
main FROM ranks ORDER BY rank_id, main LIMIT 100000) a
GROUP BY main ORDER BY rank_id) r
LEFT JOIN account a ON r.main = a.main
LEFT JOIN player p ON a.char_id=p.char_id
LEFT JOIN online o ON a.char_id=o.char_id
WHERE a.disabled=0
GROUP BY char_id
ORDER BY r.rank_id, a.main desc, a.char_id = a.main DESC, p.name;""")
blob = ""
main = 0
rank = ""
ranks = {'admin': "Administrator's",
"moderator": "Moderator's",
"council": "Council Member's",
"leader": "Raidleader's"}
for user in users:
if user.main not in count:
count.append(user.main)
if user.rank != rank:
if rank != "":
blob += "<br>"
rank = user.rank
blob += f"<notice><tab><tab>:::<tab><header>{ranks[user.rank]}</header><tab>:::</notice><br>"
if user.online == 0:
user.online = 1 if self.buddy_service.is_online(user.char_id) else 0
if main != user.main:
main = user.main
blob += self.format_user(user)
else:
blob += self.format_user(user, False)
return ChatBlob("Administrators (%d)" % len(count), blob)
@command(command="admin", params=[], access_level="member",
description="Shows the online Administrators of the bot", sub_command="list")
def show_admin(self, _):
count = []
# noinspection SqlAggregates
users = self.db.query("""
SELECT r.rank, r.rank_id, a.main, p.*, IF(o.char_id IS NULL, 0, 1) AS online
FROM (SELECT * FROM (SELECT
CASE when rank = 'admin' then 0
when rank = 'moderator' then 1
when rank = 'council' then 2
when rank = 'leader' then 3
ELSE 99 END AS rank_id, RANK,
main FROM ranks ORDER BY rank_id, main LIMIT 100000) a
GROUP BY main ORDER BY rank_id) r
LEFT JOIN account a ON r.main = a.main
LEFT JOIN player p ON a.char_id=p.char_id
LEFT JOIN online o ON a.char_id=o.char_id
WHERE a.disabled=0 and (o.char_id IS NOT NULL or
(a.main = a.char_id and (SELECT count(*) from online
where char_id in
(SELECT char_id from account where main = r.main)) > 0))
GROUP BY char_id
ORDER BY r.rank_id, a.main desc, a.char_id = a.main DESC, p.name;""")
blob = ""
main = 0
rank = ""
ranks = {'admin': "Administrator's",
"moderator": "Moderator's",
"council": "Council Member's",
"leader": "Raidleader's"}
for user in users:
if user.main not in count:
count.append(user.main)
if user.rank != rank:
if rank != "":
blob += "<br>"
rank = user.rank
blob += f"<notice><tab><tab>:::<tab><header>{ranks[user.rank]}</header><tab>:::</notice><br>"
main = ""
if main != user.main:
main = user.main
blob += self.format_user(user)
else:
blob += self.format_user(user, False)
return ChatBlob("Online Administrators (%d)" % len(count), blob)
# noinspection LongLine
def format_user(self, user, main=True):
alias = self.alias_controller.get_alias(user.org_id) if self.settings_service.get_value(
"is_alliance_bot") == "1" else user.org_name
if main:
return f"<br><pagebreak>[{'<red>O</red>' if user.online == 0 else '<green>O</green>'}] <highlight>{user.name}</highlight> ({user.level}/<green>{user.ai_level}</green> - {alias}) {'[' + self.text.make_chatcmd('Tell', '/tell ' + user.name) + ']' if user.online == 1 else ''}<br>"
return f" └ [{'<red>O</red>' if user.online == 0 else '<green>O</green>'}] <highlight>{user.name}</highlight> ({user.level}/<green>{user.ai_level}</green> - {alias}) {'[' + self.text.make_chatcmd('Tell', '/tell ' + user.name) + ']' if user.online == 1 else ''}<br>"
+49
View File
@@ -0,0 +1,49 @@
{
"list_blob": {
"en_US": [
"<pagebreak>Name: <highlight>{char}</highlight>\n",
"Added: <highlight>{added_time}</highlight>\n",
"By: <highlight>{banner}</highlight>\n",
"Ends: <highlight>{end_time}</highlight>{left}\n",
"Reason: <highlight>{reason}</highlight>\n\n"
],
"de_DE": [
"<pagebreak>Name: <highlight>{char}</highlight>\n",
"Hinzugefuegt: <highlight>{added_time}</highlight>\n",
"Von: <highlight>{banner}</highlight>\n",
"Endet: <highlight>{end_time}</highlight>{left}\n",
"Grund: <highlight>{reason}</highlight>\n\n"
]
},
"list": {
"en_US": "Ban List ({amount})"
},
"not_banned": {
"en_US": "<highlight>{char}</highlight> is not banned.",
"de_DE": "<highlight>{char}</highlight> ist nicht gebannt."
},
"unbanned_target": {
"en_US": "You have been unbanned by <highlight>{char}</highlight>.",
"de_DE": "Du wurdest von <highlight>{char}</highlight> entbannt."
},
"unbanned_self": {
"en_US": "<highlight>{char}</highlight> has been removed from the ban list.",
"de_DE": "Du hast <highlight>{char}</highlight> entbannt."
},
"already_banned": {
"en_US": "<highlight>{char}</highlight> is already banned.",
"de_DE": "<highlight>{char}</highlight> ist bereits gebannt."
},
"banned_target_1": {
"en_US": "You have been banned by <highlight>{banner}</highlight> for reason: <highlight>{reason}</highlight>. Duration: <highlight>{duration}</highlight>.",
"de_DE": "Du wurdest von <highlight>{banner}</highlight> gebannt. Dauer: <highlight>{duration}</highlight>. Der Grund war: <highlight>{reason}</highlight>"
},
"banned_target_2": {
"en_US": "You have been banned by <highlight>{banner}</highlight>. Duration: <highlight>{duration}</highlight>.",
"de_DE": "Du wurdest von <highlight>{banner}</highlight> gebannt - Dauer: <highlight>{duration}</highlight> gebannt."
},
"banned_self": {
"en_US": "<highlight>{char}</highlight> has been added to the ban list.",
"de_DE": "<highlight>{char}</highlight> wurde gebannt."
}
}
+79
View File
@@ -0,0 +1,79 @@
import hjson
from core.aochat.BaseModule import BaseModule
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Const, Options, Time, Character
from core.db import DB
from core.decorators import instance, command
from core.lookup.character_service import CharacterService
from core.translation_service import TranslationService
from core.tyrbot import Tyrbot
from modules.core.ban.ban_service import BanService
@instance()
class BanController(BaseModule):
# noinspection DuplicatedCode
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text = registry.get_instance("text")
self.util = registry.get_instance("util")
self.ban_service: BanService = registry.get_instance("ban_service")
self.command_alias_service = registry.get_instance("command_alias_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.character_service: CharacterService = registry.get_instance("character_service")
self.getresp = self.ts.get_response
def start(self):
self.command_alias_service.add_alias("unban", "ban rem")
self.ts.register_translation("module/ban", self.load_ban_msg)
def load_ban_msg(self):
with open("modules/core/ban/ban.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@command(command="ban", params=[Const("list", is_optional=True)], access_level="moderator",
description="Show the ban list")
def ban_list_cmd(self, _, _1):
query = self.db.query("SELECT a.*, p.*, b.reason, b.finished_at, b.created_at, b.sender_char_id from account a "
"left join player p on a.char_id = p.char_id "
"left join ban_list b on a.char_id = b.char_id "
"where main=a.char_id "
"and disabled=1 "
"and p.org_id NOT IN (SELECT org_id from ban_org_list)")
blob = ""
for row in query:
ends = "never" if (row.finished_at or -1) == -1 else self.util.format_datetime(row.finished_at)
blob += f"<highlight>{row.name}</highlight> by " \
f"{self.character_service.resolve_char_to_name(row.sender_char_id or self.bot.get_char_id())}\n"
if row.sender_char_id != 0 and row.sender_char_id is not None:
blob += f"<tab>Added at: <highlight>{self.util.format_datetime(row.created_at)}</highlight> " \
f"Ends: <highlight>{ends}</highlight>\n"
blob += f"<tab>Reason: {row.reason or 'None given'}\n"
blob += "\n"
return ChatBlob("Banned Accounts", blob)
@command(command="ban", params=[Options(["rem", "remove"]), Character("character")], access_level="moderator",
description="Remove a character from the ban list")
def ban_remove_cmd(self, _, _1, char):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
elif not self.ban_service.get_ban(char.char_id):
return self.getresp("module/ban", "not_banned", {"char": char.name})
else:
self.ban_service.remove_ban(char.char_id)
return self.getresp("module/ban", "unbanned_self", {"char": char.name})
@command(command="ban",
params=[Const("add", is_optional=True), Character("character"), Time("duration", is_optional=True),
Any("reason", is_optional=True)], access_level="moderator",
description="Add a character to the ban list")
def ban_add_cmd(self, request, _, char, duration, reason):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
elif self.ban_service.get_ban(char.char_id):
return f"<highlight>{char.name}</highlight> is already banned."
else:
self.ban_service.add_ban(char.char_id, request.sender.char_id, duration, reason)
request.reply(f"<highlight>{char.name}</highlight> has been added to the ban list.")
+101
View File
@@ -0,0 +1,101 @@
import time
from core.decorators import instance
from core.dict_object import DictObject
from core.logger import Logger
from modules.core.accounting.services.account_service import AccountService
@instance()
class BanService:
BAN_ADDED_EVENT = "ban_added"
BAN_REMOVED_EVENT = "ban_removed"
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.db = registry.get_instance("db")
self.event_service = registry.get_instance("event_service")
self.command_service = registry.get_instance("command_service")
self.account_service: AccountService = registry.get_instance("account_service")
def pre_start(self):
self.db.exec("CREATE TABLE IF NOT EXISTS ban_list (char_id INT NOT NULL, "
"sender_char_id INT NOT NULL, "
"created_at INT NOT NULL, "
"finished_at INT NOT NULL, "
"reason VARCHAR(255) NOT NULL, "
"ended_early SMALLINT NOT NULL, "
"INDEX `char_id` (`char_id`) USING BTREE, "
"INDEX `sender_char_id` (`sender_char_id`) USING BTREE)")
self.db.exec("CREATE TABLE IF NOT EXISTS ban_org_list (org_id INT NOT NULL, "
"sender_char_id INT NOT NULL, "
"created_at INT NOT NULL, "
"finished_at INT NOT NULL, "
"reason VARCHAR(255) NOT NULL, "
"ended_early SMALLINT NOT NULL, "
"INDEX `org_id` (`org_id`) USING BTREE, "
"INDEX `sender_char_id` (`sender_char_id`) USING BTREE)")
self.event_service.register_event_type(self.BAN_ADDED_EVENT)
self.event_service.register_event_type(self.BAN_REMOVED_EVENT)
def start(self):
self.command_service.register_command_pre_processor(self.check_for_banned)
def add_ban(self, char_id, sender_char_id, duration=None, reason=None):
reason = reason or ""
t = int(time.time())
if duration:
finished_at = t + duration
else:
finished_at = -1
num_rows = self.db.exec("INSERT INTO ban_list (char_id, sender_char_id, created_at, "
"finished_at, reason, ended_early) VALUES (?, ?, ?, ?, ?, 0)",
[char_id, sender_char_id, t, finished_at, reason])
if num_rows:
self.account_service.create_users([(char_id, char_id, 0, time.time(), time.time())])
self.db.exec("UPDATE account set disabled=1 where main=(SELECT main from account where char_id=?)",
[char_id])
self.event_service.fire_event(self.BAN_ADDED_EVENT, DictObject({"char_id": char_id,
"sender_char_id": sender_char_id,
"duration": duration,
"reason": reason}))
return num_rows
def remove_ban(self, char_id):
t = int(time.time())
num_rows = self.db.exec("UPDATE ban_list SET ended_early = 1 "
"WHERE char_id = ? AND (finished_at > ? OR finished_at = -1)", [char_id, t])
if num_rows:
self.db.exec("UPDATE account SET disabled = 0 "
"WHERE main = (SELECT main from account where char_id=?)", [char_id])
self.event_service.fire_event(self.BAN_REMOVED_EVENT, DictObject({"char_id": char_id}))
return num_rows
def get_ban(self, char_id):
return self.db.query_single("SELECT * FROM account "
"WHERE char_id = (SELECT main from account where char_id=?) and disabled=1",
[char_id])
def get_ban_list(self):
t = int(time.time())
return self.db.query("SELECT b.*, COALESCE(p1.name, b.char_id) AS name, p2.name AS sender_name FROM ban_list b "
"LEFT JOIN player p1 ON b.char_id = p1.char_id "
"LEFT JOIN player p2 ON b.sender_char_id = p2.char_id "
"WHERE ended_early != 1 AND (finished_at > ? OR finished_at = -1) "
"ORDER BY b.created_at DESC", [t])
def check_for_banned(self, context):
char_id = context.char_id
if self.get_ban(char_id):
# do nothing if character is banned
self.logger.info("ignoring banned character %d for command '%s'" % (char_id, context.message))
return False
else:
return True
+172
View File
@@ -0,0 +1,172 @@
import time
import hjson
import requests
from core.aochat.BaseModule import BaseModule
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Const, Options, Int
from core.db import DB
from core.decorators import instance, command, timerevent
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.lookup.org_pork_service import OrgPorkService
from core.private_channel_service import PrivateChannelService
from core.translation_service import TranslationService
from core.tyrbot import Tyrbot
from modules.core.accounting.services.account_service import AccountService
from modules.core.ban.ban_service import BanService
@instance()
class OrgBanController(BaseModule):
single_org_uri = "https://people.anarchy-online.com/org/stats/d/5/name/%d/basicstats.xml?data_type=json"
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text = registry.get_instance("text")
self.util = registry.get_instance("util")
self.ban_service: BanService = registry.get_instance("ban_service")
self.command_alias_service = registry.get_instance("command_alias_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.character_service: CharacterService = registry.get_instance("character_service")
self.pork: OrgPorkService = registry.get_instance("character_service")
self.getresp = self.ts.get_response
self.account_service: AccountService = registry.get_instance("account_service")
self.priv: PrivateChannelService = registry.get_instance("private_channel_service")
def start(self):
self.ts.register_translation("module/ban", self.load_ban_msg)
def load_ban_msg(self):
with open("modules/core/ban/ban.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@command(command="orgban", params=[Const("list", is_optional=True)], access_level="admin",
description="Show the ban list")
def ban_list_cmd(self, _, _1):
query = self.db.query("SELECT * from ban_org_list b "
"left join all_orgs a on a.org_id = b.org_id")
blob = ""
for row in query:
blob += f"<highlight>{row.org_name}</highlight> (<highlight>{row.org_id}</highlight> " \
f"with <highlight>{row.member_count}</highlight> members) by " \
f"{self.character_service.resolve_char_to_name(row.sender_char_id)}\n"
if row.sender_char_id != 0 and row.sender_char_id is not None:
blob += f"<tab>Added at: <highlight>{self.util.format_datetime(row.created_at)}</highlight>\n"
blob += f"<tab>Reason: {row.reason or 'None given'}\n"
blob += "\n"
return ChatBlob("Banned Organisations", blob)
@command(command="orgban", params=[Options(["rem", "remove"]), Int("Organisation")], access_level="admin",
description="Remove an org from the ban list")
def ban_remove_cmd(self, _, _1, org):
if self.db.query("SELECT * from ban_org_list where org_id=?", [org]):
self.db.exec("DELETE FROM ban_org_list where org_id = ?", [org])
return f"Successfully unbanned org <highlight>{org}</highlight> - " \
f"Beware, all players are still banned using an account ban; " \
f"It can be lifted using !account add <name>"
@command(command="orgban", params=[Options(["rem", "remove"]), Any("Organisation")], access_level="admin",
description="Remove an org from the ban list")
def ban_remove_cmd_name(self, request, _, org: str):
orgs = self.db.query(
"SELECT * from all_orgs where org_name LIKE ? and org_id in (SELECT org_id from ban_org_list)",
["%" + org.replace(" ", "%") + "%"])
if len(orgs) == 1:
self.ban_remove_cmd(request, _, orgs[0].org_id)
elif len(orgs) == 0:
return "No orgs matching your search found."
elif len(orgs) > 1:
blob = "Your search had multiple results; please pick an org:<br>"
for org in orgs:
blob += "[%s] <highlight>%s<end> (<highlight>%s<end>) <%s>%s<end> [<highlight>%s<end> " \
"members]<br><pagebreak>" \
% (self.text.make_chatcmd("Unban", "/tell <myname> orgban remove %s" % org.org_id),
# self.text.make_chatcmd("More", "/tell <myname> org info %s" % org.org_id),
org.org_name, org.org_id, org.faction.lower(), org.faction, org.member_count)
return ChatBlob("Pick an Org", blob)
@command(command="orgban", params=[Const("add", is_optional=True), Int("Organisation"),
Any("reason", is_optional=True)], access_level="admin",
description="Add an org to the ban list")
def ban_add_cmd(self, request, _, org, reason):
if self.db.query("SELECT * from ban_org_list where org_id=?", [org]):
return f"Organisation with the ID <highlight>{org}</highlight> is already banned."
if self.db.exec(
"INSERT INTO ban_org_list(org_id, sender_char_id, created_at, finished_at, reason, ended_early) "
"VALUES (?, ?, ?, 0, ?, 0)",
[org, request.sender.char_id, time.time(), reason or ""]):
self.fetch_single(org, request)
# return f"Organisation with the ID <highlight>{org}</highlight> has been banned."
@command(command="orgban",
params=[Options(["add"], is_optional=True), Any("Organisation")],
access_level="admin",
description="Add an org from the ban list")
def ban_add_cmd_name(self, request, _, org: str):
orgs = self.db.query("SELECT * from all_orgs where org_name LIKE ? order by org_name",
["%" + org.replace(" ", "%") + "%"])
if len(orgs) == 1:
self.ban_add_cmd(request, _, orgs[0].org_id, "")
elif len(orgs) == 0:
return " No orgs matching your search found."
elif len(orgs) > 1:
blob = "Your search had multiple results; please pick an org:<br>"
for org in orgs:
blob += "[%s] <highlight>%s<end> (<highlight>%s<end>) <%s>%s<end> [<highlight>%s<end> " \
"members]<br><pagebreak>" \
% (self.text.make_chatcmd("Add Ban", "/tell <myname> orgban add %s" % org.org_id),
org.org_name, org.org_id, org.faction.lower(), org.faction, org.member_count)
return ChatBlob("Pick an Org", blob)
def fetch_single(self, org_id, sender: object):
start = time.time()
data = []
accounts = []
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"], -1, start, start))
self.priv.kick(char_info['CHAR_INSTANCE'])
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, disable=True)
if sender:
sender.reply(
f"Org with ID <highlight>{org_id}</highlight> has been banned. "
f"Runtime: {time.time() - start:.2f} seconds.")
@timerevent(budatime="24h", description="Refresh orgbans")
def refresh_orgban(self, _, _1):
banned_orgs = self.db.query("SELECT * from ban_org_list")
# for org in [1456129, 391173,1136642, 1011713]:
# if org not in banned_orgs:
# self.messagehub.send_message("system_logger", None, f"**WARN** Rebanning {org}",
# f"**WARN** Rebanning {org}")
# self.ban_add_cmd(None, None, org, 'AUTO-BAN')
# banned_orgs.append(org)
for org in banned_orgs:
self.fetch_single(org.org_id, None)
self.logger.info(f"Refreshed bans for org {org.org_id}")
+26
View File
@@ -0,0 +1,26 @@
{
"blob_title": {
"en_US": "Buddy list ({amount})",
"de_DE": "Freundesliste ({amount})"
},
"add_success": {
"en_US": "Character <highlight>{char}</highlight> has been added to the buddy list for type <highlight>{type}</highlight>.",
"de_DE": "Der Charakter <highlight>{char}</highlight> wurde der Freundesliste als <highlight>{type}</highlight> hinzugefügt."
},
"rem_all": {
"en_US": "Removed all <highlight>{count}</highlight> buddies from the buddy list.",
"de_DE": "Es wurden <highlight>{count}</highlight> Freunde aus der Freundesliste entfernt."
},
"rem_single": {
"en_US": "Character <highlight>{char}</highlight> has been removed from the buddy list for type <highlight>{type}</highlight>.",
"de_DE": "Der Charakter <highlight>{char}</highlight> ist nun nicht mehr als <highlight>{type}</highlight> in der Freundesliste."
},
"rem_orphaned": {
"en_US": "Removed <highlight>{count}</highlight> orphaned buddies from the buddy list.",
"de_DE": "Es wurden <highlight>{count}</highlight> verwaiste Freunde entfernt."
},
"search_title": {
"en_US": "Buddy List Search Results ({amount})",
"de_DE": "Ergebnis der Suche in der Freundesliste ({amount})"
}
}
+115
View File
@@ -0,0 +1,115 @@
import hjson
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Const, Options, Character
from core.decorators import instance, command, timerevent
from core.logger import Logger
from core.translation_service import TranslationService
@instance()
class BuddyController:
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.character_service = registry.get_instance("character_service")
self.buddy_service = registry.get_instance("buddy_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
def start(self):
self.ts.register_translation("module/buddy", self.load_buddy_msg)
def load_buddy_msg(self):
with open("modules/core/buddy/buddy.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@command(command="buddylist", params=[], access_level="admin",
description="Show characters on the buddy list")
def buddylist_cmd(self, _):
buddy_list = []
for char_id, buddy in self.buddy_service.get_all_buddies().items():
char_name = self.character_service.resolve_char_to_name(char_id, "Unknown(%d)" % char_id)
buddy_list.append([char_name, buddy])
blob = self.format_buddies(buddy_list)
return ChatBlob(self.getresp("module/buddy", "blob_title", {"amount": len(buddy_list)}), blob)
@command(command="buddylist",
params=[Const("add"), Character("character"), Any("type")],
access_level="admin",
description="Add a character to the buddy list")
def buddylist_add_cmd(self, _, _1, char, buddy_type):
buddy_type = buddy_type.lower()
if char.char_id:
self.buddy_service.add_buddy(char.char_id, buddy_type)
return self.getresp("module/buddy", "add_success", {"char": char.name, "type": buddy_type})
else:
return self.getresp("global", "char_not_found", {"char": char.name})
@command(command="buddylist", params=[Options(["rem", "remove"]), Const("all")], access_level="admin",
description="Remove all characters from the buddy list")
def buddylist_remove_all_cmd(self, _, _1, _2):
count = 0
for char_id, buddy in self.buddy_service.get_all_buddies().items():
self.buddy_service.remove_buddy(char_id, None, True)
count += 1
return self.getresp("module/buddy", "rem_all", {"count": count})
@command(command="buddylist",
params=[Options(["rem", "remove"]), Character("character"), Any("type")],
access_level="admin",
description="Remove a character from the buddy list")
def buddylist_remove_cmd(self, _, _1, char, buddy_type):
buddy_type = buddy_type.lower()
if char.char_id:
self.buddy_service.remove_buddy(char.char_id, buddy_type)
return self.getresp("module/buddy", "rem_single", {"char": char.name, "type": buddy_type})
else:
return self.getresp("global", "char_not_found", {"char": char.name})
@command(command="buddylist", params=[Const("clean")], access_level="admin",
description="Remove all orphaned buddies from the buddy list")
def buddylist_clean_cmd(self, _, _1):
return self.getresp("module/buddy", "rem_orphaned", {"count": self.remove_orphaned_buddies()})
@command(command="buddylist", params=[Const("search"), Any("character")], access_level="admin",
description="Search for characters on the buddy list")
def buddylist_search_cmd(self, _, _1, search):
search = search.lower()
buddy_list = []
for char_id, buddy in self.buddy_service.get_all_buddies().items():
char_name = self.character_service.resolve_char_to_name(char_id, "Unknown(%d)" % char_id)
if search in char_name.lower():
buddy_list.append([char_name, buddy])
blob = self.format_buddies(buddy_list)
return ChatBlob(self.getresp("module/buddy", "search_title", {"amount": len(buddy_list)}), blob)
@timerevent(budatime="24h", description="Remove orphaned buddies", is_hidden=True)
def remove_orphaned_buddies_event(self, _, _1):
self.logger.debug("removing %d orphaned buddies" % self.remove_orphaned_buddies())
def remove_orphaned_buddies(self):
count = 0
for char_id, buddy in self.buddy_service.get_all_buddies().items():
if len(buddy["types"]) == 0:
self.buddy_service.remove_buddy(char_id, None, True)
count += 1
return count
def format_buddies(self, buddy_list):
buddy_list = sorted(buddy_list, key=lambda x: x[0])
blob = ""
for name, buddy in buddy_list:
blob += "%s(%s) - %s\n" % (name, buddy["conn_id"], ",".join(buddy["types"]))
return blob
+48
View File
@@ -0,0 +1,48 @@
from core.aochat.BaseModule import BaseModule
from core.decorators import instance
from core.setting_service import SettingService
from core.setting_types import ColorSettingType
from core.tyrbot import Tyrbot
@instance()
class ColorController(BaseModule):
def inject(self, registry):
self.setting_service: SettingService = registry.get_instance("setting_service")
self.bot: Tyrbot = registry.get_instance("bot")
# noinspection LongLine
def start(self):
self.setting_service.register_new(self.module_name, "header_color", "#FFFF00", ColorSettingType(),
"Color for headers")
self.setting_service.register_new(self.module_name, "header2_color", "#FCA712", ColorSettingType(),
"Color for sub-headers")
self.setting_service.register_new(self.module_name, "highlight_color", "#00FF00", ColorSettingType(),
"Color for highlight")
self.setting_service.register_new(self.module_name, "notice_color", "#FF8C00", ColorSettingType(),
"Color for important notices")
self.setting_service.register_new(self.module_name, "neutral_color", "#E6E1A6", ColorSettingType(),
"Color for neutral faction")
self.setting_service.register_new(self.module_name, "omni_color", "#FA8484", ColorSettingType(),
"Color for omni faction")
self.setting_service.register_new(self.module_name, "clan_color", "#F79410", ColorSettingType(),
"Color for clan faction")
self.setting_service.register_new(self.module_name, "unknown_color", "#FF0000", ColorSettingType(),
"Color for unknown faction")
self.setting_service.register_new(self.module_name, "org_channel_color", "#89D2E8", ColorSettingType(),
"Default org channel color")
self.setting_service.register_new(self.module_name, "private_channel_color", "#89D2E8", ColorSettingType(),
"Default private channel color")
self.setting_service.register_new(self.module_name, "private_message_color", "#89D2E8", ColorSettingType(),
"Default private message color")
self.setting_service.register_new(self.module_name, "blob_color", "#FFFFFF", ColorSettingType(),
"Default blob content color")
if 'orgbot' in self.bot.modules:
self.setting_service.register_new(self.module_name, "alliance_base", "#00FF00", ColorSettingType(),
"Base color for alliance relay")
self.setting_service.register_new(self.module_name, "alliance_org", "#FFFF00", ColorSettingType(),
"Org color for alliance relay")
self.setting_service.register_new(self.module_name, "alliance_sender", "#FF8C00", ColorSettingType(),
"Name color for alliance relay")
self.setting_service.register_new(self.module_name, "alliance_msg", "#FF8C00", ColorSettingType(),
"Message color for alliance relay")
+39
View File
@@ -0,0 +1,39 @@
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Any, Options
from core.decorators import instance, command
from core.translation_service import TranslationService
@instance()
class AliasController:
def inject(self, registry):
self.command_alias_service = registry.get_instance("command_alias_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
@command(command="alias", params=[Const("list")], access_level="member",
description="List command aliases")
def alias_list_cmd(self, _, _1):
blob = ""
data = self.command_alias_service.get_enabled_aliases()
count = len(data)
for row in data:
blob += row.alias + " - " + row.command + "\n"
return ChatBlob(self.getresp("module/config", "alias_blob_title", {"amount": count}), blob)
@command(command="alias", params=[Const("add"), Any("alias"), Any("command")], access_level="admin",
description="Add a command alias", sub_command="modify")
def alias_add_cmd(self, _, _1, alias, command_str):
if self.command_alias_service.add_alias(alias, command_str, force_enable=True):
return self.getresp("module/config", "alias_add_success", {"alias": alias, "cmd": command_str})
else:
return self.getresp("module/config", "alias_add_fail", {"alias": alias})
@command(command="alias", params=[Options(["rem", "remove"]), Any("alias")], access_level="admin",
description="Remove a command alias", sub_command="modify")
def alias_remove_cmd(self, _, _1, alias):
if self.command_alias_service.remove_alias(alias):
return self.getresp("module/config", "alias_rem_success", {"alias": alias})
else:
return self.getresp("module/config", "alias_rem_fail", {"alias": alias})
+181
View File
@@ -0,0 +1,181 @@
{
"alias_blob_title": {
"en_US": "Aliases ({amount})",
"de_DE": "Alias Liste ({amount})"
},
"alias_add_success": {
"en_US": "Alias <highlight>{alias}</highlight> for command <highlight>{cmd}</highlight> added successfully.",
"de_DE": "Der Alias <highlight>{alias}</highlight> für den Befehl <highlight>{cmd}</highlight> wurde erfolgreich hinzugefügt."
},
"alias_add_fail": {
"en_US": "Cannot add alias <highlight>{alias}</highlight> since there is already an active alias with that name.",
"de_DE": "Der Alias <highlight>{alias}</highlight> konnte nicht hinzugefügt werden, da bereits einer mit selbigem Alias existiert."
},
"alias_rem_success": {
"en_US": "Alias <highlight>{alias}</highlight> has been removed successfully.",
"de_DE": "Der Alias <highlight>{alias}</highlight> wurde erfolgreich entfernt."
},
"alias_rem_fail": {
"en_US": "Could not find alias <highlight>{alias}</highlight>",
"de_DE": "Der Alias <highlight>{alias}</highlight> konnte nicht gefunden werden."
},
"cmdlist_commands": {
"en_US": "Commands ({amount})",
"de_DE": "Befehlsliste ({amount})"
},
"cmd_unknown_channel": {
"en_US": "Unknown command channel <highlight>{channel}</highlight>.",
"de_DE": "Der Befehlschannel <highlight>{channel}</highlight> existiert nicht."
},
"cmd_unknown_for_channel": {
"en_US": "Could not find command <highlight>{cmd}</highlight> for channel <highlight>{channel}</highlight>.",
"de_DE": "Der Befehl <highlight>{cmd}</highlight> für den Channel <highlight>{channel}</highlight> wurde nicht gefunden."
},
"enabled_low": {
"en_US": "enabled",
"de_DE": "aktiviert"
},
"disabled_low": {
"en_US": "disabled",
"de_DE": "deaktiviert"
},
"run": {
"en_US": "run",
"de_DE": "ausgeführt"
},
"enabled_high": {
"en_US": "<green>Enabled</green>",
"de_DE": "<green>Aktiviert</green>"
},
"disabled_high": {
"en_US": "<red>Disabled</red>",
"de_DE": "<red>Deaktiviert</red>"
},
"enable": {
"en_US": "Enable",
"de_DE": "Aktivieren"
},
"disable": {
"en_US": "Disable",
"de_DE": "Deaktivieren"
},
"partial": {
"en_US": "Partial",
"de_DE": "Teilweise aktiviert"
},
"config": {
"en_US": "Config ({count})",
"de_DE": "Einstellungen ({count})"
},
"cmd_toggle_success": {
"en_US": "Command <highlight>{cmd}</highlight> has been <highlight>{changedto}</highlight> successfully.",
"de_DE": "Der Befehl <highlight>{cmd}</highlight> ist nun <highlight>{changedto}</highlight>"
},
"cmd_toggle_channel_success": {
"en_US": "Command <highlight>{cmd}</highlight> for channel <highlight>{channel}</highlight> has been <highlight>{changedto}</highlight> successfully.",
"de_DE": "Der Befehl <highlight>{cmd}</highlight> ist im channel <highlight>{channel}</highlight> nun <highlight>{changedto}</highlight>."
},
"unknown_accesslevel": {
"en_US": "Unknown access level <highlight>{al}</highlight>.",
"de_DE": "Unbekanntes Rechtelevel: <highlight>{al}</highlight>"
},
"set_accesslevel_success": {
"en_US": "Access level <highlight>{al}</highlight> for command <highlight>{cmd}</highlight> has been set successfully.",
"de_DE": "Für den Befehl <highlight>{cmd}</highlight> wurden die Zugriffsreche erfolgreich auf <highlight>{al}</highlight> geändert."
},
"set_accesslevel_fail": {
"en_US": "Access level <highlight>{al}</highlight> for command <highlight>{cmd}</highlight> on channel <highlight>{channel}</highlight> has been set successfully.",
"de_DE": "Der Befehl <highlight>{cmd}</highlight> benötigt im channel <highlight>{channel}</highlight> nun den Rang <highlight>{al}</highlight>."
},
"access_level": {
"en_US": "Access Level",
"de_DE": "Zugriffslevel"
},
"no_cmd": {
"en_US": "Could not find command <highlight>{cmd}</highlight>.",
"de_DE": "Der Befehl <highlight>{cmd}</highlight> ist mir unbekannt."
},
"settings": {
"en_US": "<header2>Settings</header2>\n",
"de_DE": "<header2>Einstellungen</header2>\n"
},
"commands": {
"en_US": "\n<header2>Commands</header2>\n",
"de_DE": "\n<header2>Befehle</header2>\n"
},
"events": {
"en_US": "Events",
"de_DE": "Events"
},
"hidden_events": {
"en_US": "Hidden Events",
"de_DE": "Verstecke Events"
},
"mod_title": {
"en_US": "Module ({mod})",
"de_DE": "Modul ({mod})"
},
"mod_not_found": {
"en_US": "Could not find module <highlight>{mod}</highlight>",
"de_DE": "Das Modul <highlight>{mod}</highlight> wurde nicht gefunden."
},
"no_new_value": {
"en_US": "Error! New value required to update setting.",
"de_DE": "Error! Es muss eine neue Option eingegeben werden, um die Einstellung zu ändern."
},
"set_clr": {
"en_US": "Setting <highlight>{setting}</highlight> has been cleared.",
"de_DE": "Die Einstellung <highlight>{setting}</highlight> wurde geleert."
},
"set_new": {
"en_US": "Setting <highlight>{setting}</highlight> has been set to {value}.",
"de_DE": "Die Einstellung <highlight>{setting}</highlight> hat nun den Wert: {value}"
},
"setting_not_found": {
"en_US": "Could not find setting <highlight>{setting}</highlight>.",
"de_DE": "Die Einstellung <highlight>{setting}</highlight> wurde nicht gefunden."
},
"current_value": {
"en_US": "Current Value: <highlight>{value}</highlight>\n",
"de_DE": "Aktueller Wert: <highlight>{value}</highlight>\n"
},
"description": {
"en_US": "Description: <highlight>{desc}</highlight>\n\n",
"de_DE": "Beschreibung: <highlight>{desc}</highlight>\n\n"
},
"setting": {
"en_US": "Setting ({setting})",
"de_DE": "Einstellung ({setting})"
},
"settinglist_title": {
"en_US": "Settings ({count})",
"de_DE": "Einstellungen ({count})"
},
"unknown_event": {
"en_US": "Unknown event type <highlight>{type}</highlight>.",
"de_DE": "Unbekannter Eventtyp: <highlight>{type}</highlight>"
},
"event_enable_fail": {
"en_US": "Handler <highlight>{handler}</highlight> type <highlight>{type}</highlight> already has the desired type."
},
"event_enable_success": {
"en_US": "Event type <highlight>{type}</highlight> for handler <highlight>{handler}</highlight> has been <highlight>{changedto}</highlight> successfully.",
"de_DE": "Für den Eventtyp <highlight>{type}</highlight> wurde der handler <highlight>{handler}</highlight> erfolgreich <highlight>{changedto}</highlight>."
},
"event_manual": {
"en_US": "Only <highlight>timer</highlight> events can be run manually.",
"de_DE": "Es können nur <highlight>timer</highlight> Events manuell angestoßen werden."
},
"blob_events": {
"en_US": "Events ({amount})",
"de_DE": "Events ({amount})"
},
"hidden": {
"en_US": "hidden",
"de_DE": "versteckt"
},
"include_hidden_events": {
"en_US": "Include hidden events",
"de_DE": "Versteckte Events anzeigen"
}
}
@@ -0,0 +1,82 @@
from core.chat_blob import ChatBlob
from core.command_param_types import NamedParameters, Const
from core.db import DB
from core.decorators import instance, command
from core.text import Text
from core.translation_service import TranslationService
@instance()
class CommandListController:
def inject(self, registry):
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.command_service = registry.get_instance("command_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
@command(command="config", params=[Const("cmdlist"), NamedParameters(["access_level"])], access_level="admin",
description="List all commands")
def config_cmdlist_cmd(self, _, _1, named_params):
sql = "SELECT access_level, channel, enabled, command, module, sub_command FROM command_config"
params = []
if named_params.access_level:
sql += " WHERE access_level = ?"
params.append(named_params.access_level)
sql += " ORDER BY module, command, sub_command, channel"
data = self.db.query(sql, params)
blob = ""
current_module = ""
current_command_key = ""
count = 0
temp_rows = []
for row in data:
if current_module != row.module:
if temp_rows:
blob += self.display_row_data(temp_rows)
temp_rows = []
blob += "\n<pagebreak><header2>%s</header2>\n" % row.module
current_module = row.module
current_command_key = ""
command_key = self.command_service.get_command_key(row.command, row.sub_command)
if current_command_key != command_key:
if temp_rows:
blob += self.display_row_data(temp_rows)
temp_rows = []
count += 1
blob += "%s - " % (self.text.make_tellcmd(command_key, "config cmd " + command_key))
current_command_key = command_key
temp_rows.append(row)
if temp_rows:
blob += self.display_row_data(temp_rows)
return ChatBlob(self.getresp("module/config", "cmdlist_commands", {"amount": count}), blob)
def display_row_data(self, rows):
return "[%s %s]\n" % (self.get_enabled_str(rows), self.get_access_levels_str(rows))
def get_access_levels_str(self, rows):
access_levels = list(map(lambda x: x.access_level, rows))
if all(x == access_levels[0] for x in access_levels):
return access_levels[0]
else:
return ",".join(access_levels)
def get_enabled_str(self, rows):
enabled = list(map(lambda x: x.enabled, rows))
blob = ""
if all(x == enabled[0] for x in enabled):
blob += self.format_enabled(enabled[0])
else:
for x in enabled:
blob += self.format_enabled(x)
return blob
def format_enabled(self, enabled):
return "<green>E</green>" if enabled else "<red>D</red>"
@@ -0,0 +1,143 @@
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Any, Options
from core.db import DB
from core.decorators import instance, command
from core.text import Text
from core.translation_service import TranslationService
@instance()
class ConfigCommandController:
def inject(self, registry):
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.access_service = registry.get_instance("access_service")
self.command_service = registry.get_instance("command_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
@command(command="config",
params=[Const("cmd"), Any("cmd_name"), Options(["enable", "disable"]), Any("channel")],
access_level="admin",
description="Enable or disable a command")
def config_cmd_status_cmd(self, _, _1, cmd_name, action, cmd_channel):
cmd_name = cmd_name.lower()
action = action.lower()
cmd_channel = cmd_channel.lower()
command_str, sub_command_str = self.command_service.get_command_key_parts(cmd_name)
enabled = 1 if action == "enable" else 0
if cmd_channel != "all" and not self.command_service.is_command_channel(cmd_channel):
return self.getresp("module/config", "cmd_unknown_channel", {"channel": cmd_channel})
sql = "UPDATE command_config SET enabled = ? WHERE command = ? AND sub_command = ?"
params = [enabled, command_str, sub_command_str]
if cmd_channel != "all":
sql += " AND channel = ?"
params.append(cmd_channel)
count = self.db.exec(sql, params)
if count == 0:
return self.getresp("module/config", "cmd_unknown_for_channel", {"channel": cmd_channel, "cmd": cmd_name})
else:
action = self.getresp("module/config", "enabled_low" if action == "enable" else "disabled_low")
if cmd_channel == "all":
return self.getresp("module/config", "cmd_toggle_success", {"cmd": cmd_name, "changedto": action})
else:
return self.getresp("module/config", "cmd_toggle_channel_success",
{"channel": cmd_channel, "cmd": cmd_name, "changedto": action})
@command(command="config",
params=[Const("cmd"), Any("cmd_name"), Const("access_level"), Any("channel"), Any("access_level")],
access_level="admin",
description="Change access_level for a command")
def config_cmd_access_level_cmd(self, _, _1, cmd_name, _2, cmd_channel, access_level):
cmd_name = cmd_name.lower()
cmd_channel = cmd_channel.lower()
access_level = access_level.lower()
command_str, sub_command_str = self.command_service.get_command_key_parts(cmd_name)
if cmd_channel != "all" and not self.command_service.is_command_channel(cmd_channel):
return self.getresp("module/config", "cmd_unknown_channel", {"channel": cmd_channel})
if self.access_service.get_access_level_by_label(access_level) is None:
return self.getresp("module/config", "unknown_accesslevel", {"al": access_level})
sql = "UPDATE command_config SET access_level = ? WHERE command = ? AND sub_command = ?"
params = [access_level, command_str, sub_command_str]
if cmd_channel != "all":
sql += " AND channel = ?"
params.append(cmd_channel)
count = self.db.exec(sql, params)
if count == 0:
return self.getresp("module/config", "cmd_unknown_for_channel", {"channel": cmd_channel, "cmd": cmd_name})
else:
if cmd_channel == "all":
return self.getresp("module/config", "set_accesslevel_success", {"cmd": cmd_name, "al": access_level})
else:
return self.getresp("module/config", "set_accesslevel_fail",
{"channel": cmd_channel, "cmd": cmd_name, "al": access_level})
@command(command="config", params=[Const("cmd"), Any("cmd_name")], access_level="admin",
description="Show command configuration")
def config_cmd_show_cmd(self, _, _1, cmd_name):
cmd_name = cmd_name.lower()
command_str, sub_command_str = self.command_service.get_command_key_parts(cmd_name)
blob = ""
for command_channel, channel_label in self.command_service.channels.items():
cmd_configs = self.command_service.get_command_configs(command=command_str,
sub_command=sub_command_str,
channel=command_channel,
enabled=None)
if len(cmd_configs) > 0:
cmd_config = cmd_configs[0]
status = self.getresp("module/config", "enabled_high" if cmd_config.enabled == 1 else "disabled_high")
blob += "<header2>%s</header2> %s (%s: %s)\n" % (channel_label, status,
self.getresp("module/config", "access_level"),
cmd_config.access_level.capitalize())
# show status config
blob += "Status:"
enable_link = self.text.make_tellcmd(self.getresp("module/config", "enable"),
"config cmd %s enable %s"
% (cmd_name, command_channel))
disable_link = self.text.make_tellcmd(self.getresp("module/config", "disable"),
"config cmd %s disable %s"
% (cmd_name, command_channel))
blob += " " + enable_link + " " + disable_link
# show access level config
blob += "\n" + self.getresp("module/config", "access_level")
for access_level in self.access_service.access_levels:
# skip "None" access level
if access_level["level"] == 0:
continue
label = access_level["label"]
link = self.text.make_tellcmd(label.capitalize(),
f"config cmd {cmd_name} access_level {command_channel} {label}")
blob += " " + link
blob += "\n\n"
if blob:
sub_commands = self.get_sub_commands(command_str, sub_command_str)
if sub_commands:
blob += "<header2>Subcommands</header2>\n"
for row in sub_commands:
command_name = self.command_service.get_command_key(row.command, row.sub_command)
blob += self.text.make_tellcmd(command_name, f"config cmd {command_name}") + "\n\n"
# include help text
blob += "\n\n".join(map(lambda handler: handler["help"], self.command_service.get_handlers(cmd_name)))
return ChatBlob("Command (%s)" % cmd_name, blob)
else:
return self.getresp("module/config", "no_cmd", {"cmd": cmd_name})
def get_sub_commands(self, command_str, sub_command_str):
return self.db.query("SELECT DISTINCT command, sub_command FROM command_config "
"WHERE command = ? AND sub_command != ?",
[command_str, sub_command_str])
+193
View File
@@ -0,0 +1,193 @@
import hjson
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Any, Options, NamedFlagParameters
from core.db import DB
from core.decorators import instance, command
from core.text import Text
from core.translation_service import TranslationService
# noinspection SqlCaseVsIf
@instance()
class ConfigController:
def inject(self, registry):
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.command_service = registry.get_instance("command_service")
self.event_service = registry.get_instance("event_service")
self.setting_service = registry.get_instance("setting_service")
self.config_events_controller = registry.get_instance("config_events_controller")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
def start(self):
self.ts.register_translation("module/config", self.load_config_msg)
def load_config_msg(self):
with open("modules/core/config/config.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@command(command="config", params=[], access_level="admin",
description="Show configuration options for the bot")
def config_list_cmd(self, _):
sql = """SELECT
module,
SUM(CASE WHEN enabled = 1 THEN 1 ELSE 0 END) count_enabled,
SUM(CASE WHEN enabled = 0 THEN 1 ELSE 0 END) count_disabled
FROM
(SELECT module, enabled FROM command_config
UNION
SELECT module, enabled FROM event_config WHERE is_hidden = 0
UNION
SELECT module, 2 FROM setting) t
GROUP BY
module
ORDER BY
module"""
data = self.db.query(sql)
count = len(data)
blob = ""
current_group = ""
for row in data:
parts = row.module.split(".")
group = parts[0]
module = parts[1]
if group != current_group:
current_group = group
blob += "\n<header2>" + current_group + "</header2>\n"
blob += self.text.make_tellcmd(module, "config mod " + row.module) + " "
if row.count_enabled > 0 and row.count_disabled > 0:
blob += self.getresp("module/config", "partial")
else:
blob += f"[{'<green>Enabled</green>' if row.count_disabled == 0 else '<red>Disabled</red>'}]"
blob += "\n"
return ChatBlob(self.getresp("module/config", "config", {"count": count}), blob)
@command(command="config",
params=[Options(["mod", "module"]), Any("module_name"), NamedFlagParameters(["include_hidden_events"])],
access_level="admin",
description="Show configuration options for a specific module")
def config_module_list_cmd(self, _, _1, module, named_params):
module = module.lower()
blob = ""
data = self.db.query("SELECT name FROM setting WHERE module = ? ORDER BY name", [module])
if data:
blob += self.getresp("module/config", "settings")
for row in data:
setting = self.setting_service.get(row.name)
blob += "%s: %s (%s)\n" % (setting.get_description(), setting.get_display_value(),
self.text.make_tellcmd("change", "config setting " + row.name))
data = self.db.query(
"SELECT DISTINCT command, sub_command FROM command_config WHERE module = ? ORDER BY command", [module])
if data:
blob += self.getresp("module/config", "commands")
for row in data:
command_key = self.command_service.get_command_key(row.command, row.sub_command)
blob += self.text.make_tellcmd(command_key, "config cmd " + command_key) + "\n"
blob += self.format_events(self.get_events(module, False), self.getresp("module/config", "events"))
if named_params.include_hidden_events:
blob += self.format_events(self.get_events(module, True), self.getresp("module/config", "hidden_events"))
if blob:
if not named_params.include_hidden_events:
blob += "\n" + self.text.make_tellcmd(self.getresp("module/config", "include_hidden_events"),
f"config mod {module} --include_hidden_events")
return ChatBlob(self.getresp("module/config", "mod_title", {"mod": module}), blob)
else:
return self.getresp("module/config", "mod_not_found", {"mod": module})
@command(command="config", params=[Const("settinglist")], access_level="admin",
description="List all settings")
def config_settinglist_cmd(self, _, _1):
blob = ""
data = self.db.query("SELECT * FROM setting ORDER BY module, name")
count = len(data)
if data:
blob += self.getresp("module/config", "settings")
current_module = ""
for row in data:
if row.module != current_module:
current_module = row.module
blob += "\n<pagebreak><header2>%s</header2>\n" % row.module
setting = self.setting_service.get(row.name)
blob += "%s: %s (%s)\n" % (setting.get_description(),
setting.get_display_value(),
self.text.make_tellcmd("change", "config setting " + row.name))
return ChatBlob(self.getresp("module/config", "settinglist_title", {"count": count}), blob)
@command(command="config", params=[Const("setting"), Any("setting_name"), Options(["set", "clear"]),
Any("new_value", is_optional=True)], access_level="admin",
description="Change a setting value")
def config_setting_update_cmd(self, _, _1, setting_name, op, new_value):
setting_name = setting_name.lower()
if op == "clear":
new_value = ""
elif not new_value:
return self.getresp("module/config", "no_new_value")
setting = self.setting_service.get(setting_name)
if setting:
setting.set_value(new_value)
if op == "clear":
return self.getresp("module/config", "set_clr", {"setting": setting_name})
else:
return self.getresp("module/config", "set_new", {"setting": setting_name,
"value": setting.get_display_value()})
else:
return self.getresp("module/config", "setting_not_found", {"setting": setting_name})
@command(command="config", params=[Const("setting"), Any("setting_name")], access_level="admin",
description="Show configuration options for a setting")
def config_setting_show_cmd(self, _, _1, setting_name):
setting_name = setting_name.lower()
blob = ""
setting = self.setting_service.get(setting_name)
if setting:
blob += self.getresp("module/config", "current_value", {"value": str(setting.get_display_value())})
blob += self.getresp("module/config", "description", {"desc": setting.get_description()})
if setting.get_extended_description():
blob += setting.get_extended_description() + "\n\n"
blob += setting.get_display()
return ChatBlob(self.getresp("module/config", "setting", {"setting": setting_name}), blob)
else:
return self.getresp("module/config", "setting_not_found", {"setting": setting_name})
def get_events(self, module, is_hidden):
return self.db.query("SELECT event_type, event_sub_type, handler, description, enabled, is_hidden "
f"FROM event_config WHERE module = ? AND is_hidden = ? "
"ORDER BY is_hidden, event_type, handler",
[module, 1 if is_hidden else 0])
def format_events(self, data, title):
blob = ""
if data:
blob += f"\n<header2>{title}</header2>\n"
for row in data:
event_type_key = self.event_service.get_event_type_key(row.event_type, row.event_sub_type)
enabled = self.getresp("module/config", "enabled_high" if row.enabled == 1 else "disabled_high")
blob += f"{self.config_events_controller.format_event_type(row)} - {row.description} [{enabled}]"
blob += " " + self.text.make_tellcmd("On", "config event %s %s enable" % (event_type_key, row.handler))
blob += " " + self.text.make_tellcmd("Off",
"config event %s %s disable" % (event_type_key, row.handler))
if row.event_type == "timer":
blob += " " + self.text.make_tellcmd("Run Now",
"config event %s %s run" % (event_type_key, row.handler))
blob += "\n"
return blob
@@ -0,0 +1,110 @@
import time
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Any, Options, NamedParameters
from core.db import DB
from core.decorators import instance, command
from core.text import Text
from core.translation_service import TranslationService
@instance()
class ConfigEventsController:
def inject(self, registry):
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.command_service = registry.get_instance("command_service")
self.event_service = registry.get_instance("event_service")
self.setting_service = registry.get_instance("setting_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
@command(command="config",
params=[Const("event"), Any("event_type"), Any("event_handler"), Options(["enable", "disable"])],
access_level="admin",
description="Enable or disable an event")
def config_event_status_cmd(self, _, _1, event_type, event_handler, action):
event_type = event_type.lower()
event_handler = event_handler.lower()
action = action.lower()
event_base_type, event_sub_type = self.event_service.get_event_type_parts(event_type)
enabled = 1 if action == "enable" else 0
if not self.event_service.is_event_type(event_base_type):
return self.getresp("module/config", "unknown event", {"type", event_type})
count = self.event_service.update_event_status(event_base_type, event_sub_type, event_handler, enabled)
if count == 0:
return self.getresp("module/config", "event_enable_fail", {"type": event_type, "handler": event_handler})
else:
action = self.getresp("module/config", "enabled_high" if action == "enable" else "disabled_high")
return self.getresp("module/config", "event_enable_success", {"type": event_type,
"handler": event_handler,
"changedto": action})
@command(command="config",
params=[Const("event"), Any("event_type"), Any("event_handler"), Const("run")],
access_level="admin",
description="Execute a timed event immediately")
def config_event_run_cmd(self, _, _1, event_type, event_handler, _2):
event_type = event_type.lower()
event_handler = event_handler.lower()
event_base_type, event_sub_type = self.event_service.get_event_type_parts(event_type)
if not self.event_service.is_event_type(event_base_type):
return self.getresp("module/config", "unknown event", {"type", event_type})
row = self.db.query_single("SELECT e.event_type, e.event_sub_type, e.handler, t.next_run FROM timer_event t "
"JOIN event_config e ON t.event_type = e.event_type AND t.handler = e.handler "
"WHERE e.event_type = ? AND e.event_sub_type = ? AND e.handler LIKE ?",
[event_base_type, event_sub_type, event_handler])
if not row:
return self.getresp("module/config", "event_enable_fail", {"type": event_type, "handler": event_handler})
elif row.event_type != "timer":
return self.getresp("module/config", "event_manual")
else:
self.event_service.execute_timed_event(row, int(time.time()))
action = self.getresp("module/config", "run")
return self.getresp("module/config", "event_enable_success", {"type": event_type,
"handler": event_handler,
"changedto": action})
@command(command="config", params=[Const("eventlist"), NamedParameters(["event_type"])], access_level="admin",
description="List all events")
def config_eventlist_cmd(self, _, _1, named_params):
params = []
sql = "SELECT module, event_type, event_sub_type, handler, description, enabled, is_hidden FROM event_config"
if named_params.event_type:
sql += " WHERE event_type = ?"
params.append(named_params.event_type)
sql += " ORDER BY module, is_hidden, event_type, event_sub_type, handler"
data = self.db.query(sql, params)
blob = "Asterisk (*) denotes a hidden event. Only change these events if you understand the implications.\n"
current_module = ""
for row in data:
if current_module != row.module:
blob += "\n<pagebreak><header2>%s</header2>\n" % row.module
current_module = row.module
event_type_key = self.format_event_type(row)
on_link = self.text.make_tellcmd("On", "config event %s %s enable" % (event_type_key, row.handler))
off_link = self.text.make_tellcmd("Off", "config event %s %s disable" % (event_type_key, row.handler))
if row.is_hidden == 1:
blob += "*"
blob += f"{event_type_key} [{self.format_enabled(row.enabled)}] {on_link} {off_link} - {row.description}\n"
return ChatBlob(self.getresp("module/config", "blob_events", {"amount": len(data)}), blob)
def format_enabled(self, enabled):
return "<green>E</green>" if enabled else "<red>D</red>"
def format_event_type(self, row):
if row.event_sub_type:
return row.event_type + ":" + row.event_sub_type
else:
return row.event_type
+704
View File
@@ -0,0 +1,704 @@
import asyncio
import html
import logging
import re
import threading
import time
from asyncio import BaseEventLoop
from html.parser import HTMLParser
# noinspection PyPackageRequirements
import discord
import emojis as emojis
# noinspection PyPackageRequirements
from discord import Message, TextChannel, Guild, Embed, Role
from core.chat_blob import ChatBlob
from core.command_param_types import Const
from core.db import DB
from core.decorators import instance, command, event, timerevent
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.message_hub_service import MessageHubService
from core.setting_service import SettingService
from core.setting_types import HiddenSettingType
from core.text import Text
from core.tyrbot import Tyrbot
from core.util import Util
from modules.core.accounting.services.access_service import AccessService
from modules.core.accounting.services.account_service import AccountService
from modules.core.ban.ban_service import BanService
from modules.onlinebot.online.org_alias_controller import OrgAliasController
class MLStripper(HTMLParser):
def error(self, message):
pass
def __init__(self):
super().__init__()
self.reset()
self.strict = False
self.convert_charrefs = True
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return "".join(self.fed)
# noinspection PyUnusedLocal
@instance()
class DiscordController:
MESSAGE_SOURCE = "discord"
def __init__(self):
self.logger = Logger(__name__)
logging.getLogger("discord.gateway").setLevel(logging.WARN)
logging.getLogger("discord.client").setLevel(logging.WARN)
intents = discord.Intents.all()
self.client = discord.Client(intents=intents, chunk_guilds_at_startup=True)
self.client.event(self.on_ready)
self.client.event(self.on_member_join)
self.client.event(self.on_member_remove)
self.client.event(self.on_member_update)
self.client.event(self.on_guild_role_delete)
self.client.event(self.on_guild_role_create)
self.client.event(self.on_guild_role_update)
self.client.event(self.on_invite_create)
self.client.event(self.on_invite_delete)
self.client.event(self.on_message)
self.guild = None
self.channel = None
self.thread = None
self.invites = None
self.discord_roles = DictObject(
{"override": "Override",
"admin": "Administrator",
"council": "Council",
"leader": "Raid Leader",
"president": "President",
"general": "General",
"officer": "Officer",
"member": "Member",
"failed": "failed"})
# logging.getLogger("discord").setLevel(logging.INFO)
self.discord_queue = []
self.ao_queue = []
# noinspection PyAttributeOutsideInit
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.character_service: CharacterService = registry.get_instance("character_service")
self.util: Util = registry.get_instance("util")
self.access_service: AccessService = registry.get_instance("access_service")
self.pork: PorkService = registry.get_instance("pork_service")
self.org: PorkService = registry.get_instance("pork_service")
self.relay_hub_service: MessageHubService = registry.get_instance("message_hub_service")
self.setting_service: SettingService = registry.get_instance("setting_service")
self.alias_controller: OrgAliasController = registry.get_instance("org_alias_controller")
self.account_service: AccountService = registry.get_instance("account_service")
self.setting_service: SettingService = registry.get_instance("setting_service")
def pre_start(self):
self.setting_service.register_new(self.module_name, "discord_token", "", HiddenSettingType(allow_empty=True),
"Enter your Discord token her")
def get_name(self, discord_id):
data = self.db.query_single(
"SELECT d.*, p.* FROM account d LEFT JOIN player p on d.main=p.char_id where discord_id=?", [discord_id])
return data.name if data else None
def setting_discord_token(self):
return self.setting_service.get("discord_token")
@command(command="discord", params=[Const("invite")], access_level="member",
description="Get a personal Discord invite", sub_command="invite")
def discord_invite_cmd(self, request, _):
if not self.client:
return "Discord module has not been initiated yet. Please try again later."
account = self.account_service.get_account(request.sender.char_id)
if account is None:
return "You do not have an account"
if account.disabled == 1:
return "Your account is disabled"
if account.discord_joined == 1:
return "You have already joined the Discord server"
if account.discord_invite != "":
for ginvite in self.invites:
if ginvite.code == account.discord_invite:
asyncio.run_coroutine_threadsafe(self.discord_delete_invite(ginvite), self.loop)
invite = asyncio.run_coroutine_threadsafe(self.discord_create_invite(account.name), self.loop)
invite = invite.result()
self.set_discord_invite(account.char_id, invite.code)
self.bot.send_mass_message(request.sender.char_id,
f"Your personal Discord invite is: {invite} \n"
f"This invite is only valid for 5 minutes.")
@command(command="discord", params=[Const("update")], access_level="member",
description="Update your discord information", sub_command="update")
def discord_update_cmd(self, request, _):
if not self.client:
return "Discord module has not been initiated yet. Please try again later."
return self.discord_update_account(request.sender.char_id)
@command(command="discord", params=[Const("disconnect")], access_level="admin",
description="Disconnect from Discord", sub_command="admin")
def discord_disconnect_cmd(self, _, _1):
if not self.client:
return "Discord module has not been initiated yet. Please try again later."
if self.client.is_closed():
return "Discord is already disconnected"
else:
asyncio.run_coroutine_threadsafe(self.discord_disconnect(), self.loop)
return "Disconnecting Discord"
@command(command="discord",
params=[Const("members")],
access_level="admin",
description="Get all discord members",
sub_command="members")
def discord_members_cmd(self, _, _1):
if not self.client:
return "Discord module has not been initiated yet. Please try again later."
blob = ""
for member in self.guild.members:
member_roles = []
for role in member.roles:
if role.name == "@everyone": # Skip @everyone as everyone has it.
continue
member_roles.append("%s" % role.name)
member_roles.sort(key=str.lower)
blob += "%s (%s)\n" % (member.name + "#" + member.discriminator, ", ".join(member_roles))
return ChatBlob("Discord Members (%d)" % (len(self.guild.members)), blob)
@event(event_type="connect", description="Connects the Discord client automatically on startup, if a token exists")
def handle_connect_event(self, _, _1):
token = self.setting_discord_token().get_value()
if token == "None":
return
# noinspection PyTypeChecker
self.loop: BaseEventLoop = asyncio.get_event_loop()
self.loop.create_task(self.discord_connect(token))
self.thread = threading.Thread(target=self.run_it_forever, daemon=True)
self.thread.start()
@event(event_type=BanService.BAN_ADDED_EVENT, description="Ban user from Discord")
def ban_added_event(self, _, event_data):
token = self.setting_discord_token().get_value()
if token == "None":
return
account = self.account_service.get_account(event_data.char_id)
if account.discord_joined == 1 and account.discord_id != "":
member = self.guild.get_member(int(account.discord_id))
if member is not None:
ban = asyncio.run_coroutine_threadsafe(self.discord_ban_user(member, event_data.reason), self.loop)
@event(event_type=BanService.BAN_REMOVED_EVENT, description="Remove Discord ban")
def ban_removed_event(self, _, event_data):
token = self.setting_discord_token().get_value()
if token == "None":
return
account = self.account_service.get_account(event_data.char_id)
if account.discord_id != "":
banlist = asyncio.run_coroutine_threadsafe(self.discord_banlist(), self.loop)
banlist = banlist.result()
for ban in banlist:
if ban.user.id == int(account.discord_id):
unban = asyncio.run_coroutine_threadsafe(self.discord_unban_user(ban.user), self.loop)
@timerevent(budatime="1s", description="Handle Discord queue")
def timer_check_discord_queue(self, _, _1):
while self.discord_queue:
t = int(time.time())
obj = self.discord_queue.pop(0)
if obj.type == "on_member_remove":
member = obj.member
handle = member.name + "#" + member.discriminator
account = self.get_account_discord_id(member.id)
if not account:
log = '**%s** has left discord (**%s**)' % (member.nick or member.name, handle)
self.relay_hub_service.send_message("system_logger", 0, log, log)
self.logger.info(log)
continue
main = self.account_service.get_main(account.char_id)
if account is None:
continue
log = '**%s** has left discord (**%s**)' % (main.name, handle)
self.relay_hub_service.send_message("system_logger", 0, log, log)
self.logger.info(log)
# self.bot.send_private_channel_message("%s has left Discord (%s)" % (main.name, handle))
self.set_discord_left(account.main)
if account.discord_handle != handle:
self.set_discord_handle(member.id, handle)
if obj.type == "on_member_join":
member = obj.member
invite_used = obj.invite
handle = member.name + "#" + member.discriminator
if invite_used is None:
log = '**%s** joined discord with unknown invite' % handle
self.relay_hub_service. \
send_message("system_logger", 0,
log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
f"check that!",
log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
f"check that!")
self.logger.info(log)
continue
self.guild: Guild = self.client.get_guild(self.client.guilds[0].id)
account = self.get_discord_invite(invite_used.code)
if account is None:
log = '**%s** joined discord with invite **%s** but couldnt find account!' % (
handle, invite_used.code)
self.relay_hub_service. \
send_message("system_logger", 0,
log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
f"check that!",
log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
f"check that!")
self.logger.info(log)
asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, None, None), self.loop)
continue
main = self.account_service.get_main(account.main)
self.set_discord_joined(account.main, handle, member.id)
if self.setting_service.get_value('is_alliance_bot') == '1':
nick = f"{f'[{self.alias_controller.get_alias(main.org_id)}] '}{main.name}"
else:
nick = f"{main.name}"
nick = asyncio.run_coroutine_threadsafe(self.discord_member_nick(member, nick), self.loop)
access_level = access_level = self.access_service.get_access_level(account.main)
roles = asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, account, access_level),
self.loop)
# noinspection LongLine
log = '**%s** joined discord with invite **%s** and matches account **%s**' \
% (handle, invite_used.code,
f"{f'[{self.alias_controller.get_alias(main.org_id)}] ' if self.setting_service.get_value('is_alliance_bot') == '1' else ''}{main.name}")
self.relay_hub_service.send_message("system_logger", account.main, log, log)
self.logger.info(log)
@timerevent(budatime="1h", description="Verify Discord members", run_at_startup=True)
def timer_check_discord_members(self, event_type, event_data):
token = self.setting_discord_token().get_value()
if token in ["None", "", "NULL", None]:
return
if not self.bot.is_ready():
return
update = asyncio.run_coroutine_threadsafe(self.discord_update_bot_basic(), self.loop)
# Update accounts that have left/joined discord without the bot being online
accounts = self.db.query("SELECT * FROM account WHERE discord_id !=''")
for account in accounts:
match = False
for member in self.guild.members:
handle = member.name + "#" + member.discriminator
if member.id == int(account.discord_id):
match = True
if account.discord_joined == 0:
self.set_discord_joined(account.main, handle, member.id)
break
if match is False:
if account.discord_joined == 1:
self.set_discord_left(account.main)
# Update current discord Members
accounts = self.db.query("SELECT * FROM account WHERE discord_joined = 1 and char_id = main")
for member in self.guild.members:
if member.id == self.client.user.id:
continue
member_account = None
for account in list(accounts):
if int(account.discord_id) == member.id:
member_account = account
accounts.remove(account)
break
access_level = 0
if member_account is not None:
access_level = self.access_service.get_access_level(member_account.main)
roles = asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, member_account, access_level),
self.loop)
if member_account is not None:
main = self.pork.get_character_info(member_account.main)
# noinspection LongLine
nick = f"{f'[{self.alias_controller.get_alias(main.org_id)}] ' if self.setting_service.get_value('is_alliance_bot') == '1' else ''}{main.name}"
nick = asyncio.run_coroutine_threadsafe(self.discord_member_nick(member, nick), self.loop)
def run_it_forever(self):
self.loop.run_forever()
def set_discord_invite(self, char_id, invite):
return self.db.exec("UPDATE account SET discord_invite = ? WHERE main = ?", [invite, char_id])
def set_discord_joined(self, char_id, handle, discord_id):
return self.db.exec("UPDATE account SET discord_joined = 1, discord_handle = ?, discord_id = ? WHERE main = ?",
[handle, discord_id, char_id])
def set_discord_left(self, char_id):
return self.db.exec("UPDATE account SET discord_joined = 0 WHERE main = ?", [char_id])
def set_discord_handle(self, discord_id, handle):
return self.db.exec("UPDATE account SET discord_handle = ? WHERE discord_id = ?", [handle, discord_id])
def get_discord_invite(self, invite):
return self.db.query_single("SELECT * FROM account WHERE discord_invite = ?", [invite])
def get_account_discord_id(self, discord_id):
return self.db.query_single(
"SELECT * FROM account a left join player p on a.char_id=p.char_id WHERE discord_id = ?", [discord_id])
def discord_update_account(self, char_id):
account = self.account_service.get_account(char_id)
if account is None:
return
if account.discord_id != "" and account.discord_joined == 1:
member = self.guild.get_member(int(account.discord_id))
if member is not None:
access_level = self.access_service.get_access_level(account.main)
roles = asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, account, access_level),
self.loop)
# noinspection LongLine
nick = f"{f'[{self.alias_controller.get_alias(account.org_id)}] ' if self.setting_service.get_value('is_alliance_bot') == '1' else ''}{account.name}"
nick = asyncio.run_coroutine_threadsafe(self.discord_member_nick(member, nick), self.loop)
return "Processing..."
return "No Discord account found"
def discord_invite_used(self, temp_invites):
for temp_invite in temp_invites:
# if temp_invite.inviter.id == self.client.user.id:
# ## Using this line limits it to invite created by the account the Discord bot runs as..
for invite in self.invites:
if int(temp_invite.uses) > int(invite.uses) and temp_invite.code == invite.code:
return temp_invite
return None
def get_role(self, name, roles) -> Role or None:
for role in roles:
if role.name == name:
return role
return None
async def on_member_join(self, member):
temp_invites = await self.guild.invites()
invite_used = self.discord_invite_used(temp_invites)
self.discord_queue.append(DictObject({"type": "on_member_join", "member": member, "invite": invite_used}))
await invite_used.delete()
async def on_member_remove(self, member):
self.discord_queue.append(DictObject({"type": "on_member_remove", "member": member}))
await self.discord_update_bot_full()
async def on_ready(self):
await self.discord_update_bot_full()
count = self.db.query_single('SELECT count(*) as count from online '
'where char_id NOT IN (select char_id from org_bots) and bot=?',
[self.bot.get_char_id()]).count
act = discord.Activity(type=discord.ActivityType.listening,
name=f"{count} Players")
asyncio.run_coroutine_threadsafe(self.client.change_presence(activity=act), self.loop)
await self.clean_channel()
if self.guild.large:
self.logger.error(
f"Guild {self.guild.name} is classified as large, "
f"you need to request offline members to manage roles properly, this is not yet implemented")
async def on_member_update(self, member_before, member_after):
await self.discord_update_bot_basic()
async def on_guild_role_create(self, role):
await self.discord_update_bot_full()
async def on_guild_role_delete(self, role):
await self.discord_update_bot_full()
async def on_guild_role_update(self, role_before, role_after):
await self.discord_update_bot_full()
async def on_invite_create(self, invite):
await self.discord_update_bot_full()
async def on_invite_delete(self, invite):
await self.discord_update_bot_full()
async def discord_update_bot_basic(self):
self.guild = self.client.get_guild(self.client.guilds[0].id)
self.channel = self.client.get_channel(self.guild.text_channels[0].id)
async def discord_update_bot_full(self):
await self.discord_update_bot_basic()
self.invites = await self.guild.invites()
async def discord_disconnect(self):
await self.client.close()
async def discord_connect(self, token):
try:
if not self.client.is_closed():
self.logger.info("Logging into Discord...")
await self.client.start(token)
except discord.ClientException as exc:
self.logger.error("Something broke, I'm out!: %s" % str(exc))
async def discord_create_invite(self, reason=""):
created_invite = await self.channel.create_invite(max_age=300, max_uses=2, reason=reason)
self.guild = self.client.get_guild(self.client.guilds[0].id)
return created_invite
async def discord_delete_invite(self, invite):
await invite.delete()
async def discord_banlist(self):
return await self.guild.bans()
async def discord_ban_user(self, user, reason=""):
await self.guild.ban(user, reason=reason)
async def discord_unban_user(self, user):
await self.guild.unban(user)
async def discord_member_nick(self, account, nick):
if self.get_role(self.discord_roles.override, account.roles):
return # We do not process accounts that have `override` role
if account.nick != nick:
result = await account.edit(nick=nick)
async def discord_member_roles(self, discord_user: discord.Member, account, access_level):
if self.get_role(self.discord_roles.override, discord_user.roles):
return # We do not process accounts that have `override` role
addroles = []
remroles = []
if account is None: # We assign failed role
discrole = self.get_role(self.discord_roles.failed, discord_user.roles)
self.logger.info("%s role result is %s" % (discord_user.nick, discrole))
if discrole is None:
failed = self.get_role(self.discord_roles.failed, self.guild.roles)
if failed is None:
return
addroles.append(failed)
remroles = discord_user.roles
else:
# Superadmin
if access_level["level"] in [10, 20]:
if self.get_role(self.discord_roles.admin, discord_user.roles) is None:
rank = self.get_role(self.discord_roles.admin, self.guild.roles)
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.admin, discord_user.roles)
if rank is not None:
remroles.append(rank)
# Member Role
if 100 > access_level["level"]:
rank = self.get_role(self.discord_roles.member, self.guild.roles) or None
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.member, self.guild.roles) or None
if rank is not None:
remroles.append(rank)
# Leader
if self.account_service.check_leader(account.main):
rank = self.get_role(self.discord_roles.leader, self.guild.roles) or None
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.leader, self.guild.roles) or None
if rank is not None:
remroles.append(rank)
if self.setting_service.get_value("is_alliance_bot") == "1":
if self.account_service.check_council(account.main):
rank = self.get_role(self.discord_roles.council, self.guild.roles) or None
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.council, self.guild.roles) or None
if rank is not None:
remroles.append(rank)
# President
if self.account_service.check_president(account.main):
rank = self.get_role(self.discord_roles.president, self.guild.roles) or None
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.president, self.guild.roles) or None
if rank is not None:
remroles.append(rank)
# General
if self.account_service.check_general(account.main):
rank = self.get_role(self.discord_roles.general, self.guild.roles) or None
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.general, self.guild.roles) or None
if rank is not None:
remroles.append(rank)
# Officer
if self.account_service.check_officer(account.main):
rank = self.get_role(self.discord_roles.officer, self.guild.roles) or None
if rank is not None:
addroles.append(rank)
else:
rank = self.get_role(self.discord_roles.officer, self.guild.roles) or None
if rank is not None:
remroles.append(rank)
if account.disabled == 1 or access_level == 0 or access_level == 100:
addroles = [self.get_role(self.discord_roles.failed, self.guild.roles)]
remroles = discord_user.roles
else:
remroles.append(self.get_role(self.discord_roles.failed, self.guild.roles))
# Execute
if len(addroles) > 0:
addroles = list(dict.fromkeys(addroles)) # Remove Duplicates
for role in addroles:
await discord_user.add_roles(role)
if len(remroles) > 0:
remroles = list(dict.fromkeys(remroles)) # Remove Duplicates
for role in remroles:
if role.name == "@everyone": # Skip @everyone as we cant remove that
continue
await discord_user.remove_roles(role)
async def on_message(self, msg: Message):
if msg.author.id == self.client.user.id:
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"[<notice>{html.escape(msg.author.nick if msg.author.nick else msg.author.name, False)}" \
f"</notice>]: " \
f"{html.escape(emojis.decode(msg.clean_content), False)}"
else:
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)
if self.is_command(msg.content):
admin = self.get_role("Administrator", self.guild.roles)
council = self.get_role("Council", self.guild.roles)
if msg.content[:4] == "!pin":
if admin in msg.author.roles:
matches = re.findall(pattern="<head>(.+?)<\/head>(.+)", string=msg.content[4:].strip(),
flags=re.DOTALL)
if matches:
mess = await channel.send(
embed=Embed(color=3066993, title=matches[0][0], description=matches[0][1]))
else:
mess = await channel.send(embed=Embed(color=3066993, description=msg.content[4:]))
await mess.pin()
if msg.content[:5] == "!note":
if admin in msg.author.roles:
matches = re.findall(pattern="<head>(.+?)<\/head>(.+)", string=msg.content[5:].strip(),
flags=re.DOTALL)
if matches:
mess = await channel.send(
embed=Embed(color=3066993, title=matches[0][0], description=matches[0][1]))
else:
mess = await channel.send(embed=Embed(color=3066993, description=msg.content[5:]))
if msg.content[:6] == "!purge":
if admin in msg.author.roles:
count: str = msg.content[6:].strip()
if type(count) == str:
if count.isdigit():
# noinspection PyTypeChecker
count = int(count)
if type(count) == int:
# noinspection PyTypeChecker
while count > 100:
await msg.channel.purge(check=self.check)
count -= 100
else:
await msg.channel.purge(check=self.check, limit=count)
await msg.delete(delay=0)
if msg.content[:10] == "!subscribe":
if admin in msg.author.roles:
out = msg.content[10:].strip()
if self.setting_service.get(out) is None:
await msg.reply(f"The Channel {out} does not exist.")
else:
self.setting_service.set_value(out, msg.channel.id)
self.send_message(msg.channel.id, Embed(color=3066993, title="Channel Guard",
description=f"This channel has been "
f"subscriped to the source {out}."))
def send_message(self, channel: int, message, delete=0):
if self.client.is_ready():
channel: TextChannel = self.client.get_channel(int(channel)) if type(channel) in [int, str] else channel
if type(message) == Embed:
if delete != 0:
asyncio.run_coroutine_threadsafe(channel.send(embed=message, delete_after=delete), self.loop)
else:
asyncio.run_coroutine_threadsafe(channel.send(embed=message), self.loop)
else:
if delete != 0:
asyncio.run_coroutine_threadsafe(channel.send(message, delete_after=delete), self.loop)
else:
asyncio.run_coroutine_threadsafe(channel.send(message), self.loop)
def is_command(self, message: str):
for x in ["subscribe", "pin", "purge", "note"]:
if message.startswith("!" + x):
return True
async def clean_channel(self):
if self.setting_service.get_value("dc_relay_public") not in ["0", None]:
channel: TextChannel = self.client.get_channel(int(self.setting_service.get_value("dc_relay_public")))
for i in range(5):
await channel.purge(check=self.check)
def check(self, msg: Message):
if msg.pinned or len(msg.embeds) > 0:
return False
else:
return True
@event(event_type="main_changed", description="Fix discord names & ranks")
def fix_ranks(self, _, data):
row = self.db.query_single("SELECT * from account where char_id=?", [data.old_main_id])
if row.discord_joined == 0:
self.logger.debug(f"{data.old_main_id} was not in discord, ignoring main fixing")
return
elif row.discord_joined == 1:
self.db.exec(
"UPDATE account set discord_handle=?, discord_id=?, discord_invite=?, discord_joined=? where char_id=?",
[row.discord_handle, row.discord_id, row.discord_invite, row.discord_joined, data.new_main_id])
self.db.exec(
"UPDATE account set discord_handle='', discord_id=0, discord_invite='', discord_joined=0 "
"where char_id=?",
[data.old_main_id])
self.logger.info(f"{data.old_main_id} was in discord, overwriting {data.new_main_id} with {data}")
self.discord_update_account(data.new_main_id)
@timerevent(budatime="5m", description="update activity")
def change_count(self, _, _1):
if hasattr(self, "loop"):
count = self.db.query_single('SELECT count(*) as count from online '
'where char_id NOT IN (select char_id from org_bots) and bot=?',
[self.bot.get_char_id()]).count
act = discord.Activity(type=discord.ActivityType.listening,
name=f"{count} Players")
asyncio.run_coroutine_threadsafe(self.client.change_presence(activity=act), self.loop)
+30
View File
@@ -0,0 +1,30 @@
Our Home: <a href='chatcmd:///start https://www.aoalliance.org/'>https://www.aoalliance.org/</a>
Gitlab Repository: <a href='chatcmd:///start https://gitlab.com/CynderGames/igncore'>https://gitlab.com/CynderGames/igncore</a>
IGNCore is a performance oriented fork of Tyrbot based on version 0.5,
and is being maintained by a group <highlight>Volunteers</highlight>.
At this point, we'd like to send a special "thank you" to the following people, which in one way or another, contributed to IGNCore/IGNCom:
» <highlight>Zetabyte & its Network</highlight>, for many feature ideas,
and the heavy assistance with knowledge.
» <highlight>Chrisax</highlight> for hosting the !history mirror.
» Everyone who participated in test runs,
» sent us ideas for bot improvements,
» helped us by sending bug reports our way,
» or just by keeping us busy during a long night of development.
aswell as the initial developers of Tyrbot, on which IGNCore is based:
» <highlight>Tyrence</highlight>
» <highlight>Teeko</highlight> for all the time he spent testing new features,
reporting bugs, and making suggestions
» <highlight>nepherius/wafflespower</highlight> who was critical during the
early parts of development in testing and suggesting changes
» <highlight>minidodo</highlight> for his significant contributions and fixes
to Tyrbot, and his willingness to assist other users in the Discord channel
» <highlight>hughp135</highlight>
» <highlight>jroovers</highlight>
» <highlight>dustify</highlight>
» <highlight>equinitry</highlight>
» <highlight>Ilon Sjögren</highlight>
If you'd like to know more, feel free to contact us on the toons listed in <highlight>!admins</highlight> - Administrators.
+10
View File
@@ -0,0 +1,10 @@
{
"no_help": {
"en_US": "Could not find help on <highlight>{topic}</highlight>",
"de_DE": "Zu dem Thema <highlight>{topic}</highlight> konnte keine Hilfe gefunden werden."
},
"about_title": {
"en_US": "About {version}",
"de_DE": "Über <highlight>{version}</highlight>"
}
}
+85
View File
@@ -0,0 +1,85 @@
import os
import hjson
from core.chat_blob import ChatBlob
from core.command_param_types import Any, NamedFlagParameters
from core.decorators import instance, command
from core.translation_service import TranslationService
@instance()
class HelpController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.text = registry.get_instance("text")
self.db = registry.get_instance("db")
self.access_service = registry.get_instance("access_service")
self.command_service = registry.get_instance("command_service")
self.command_alias_service = registry.get_instance("command_alias_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
def start(self):
self.ts.register_translation("module/help", self.load_help_msg)
self.command_alias_service.add_alias("version", "about")
def load_help_msg(self):
with open("modules/core/help/help.msg", mode="r", encoding="UTF-8") as f:
return hjson.load(f)
@command(command="about", params=[], access_level="member",
description="Show information about the development of this bot")
def about_cmd(self, _):
with open(os.path.dirname(os.path.realpath(__file__)) + os.sep + "about.txt", mode="r", encoding="UTF-8") as f:
return ChatBlob(self.getresp("module/help", "about_title",
{"version": f"{self.bot.major_version}.{self.bot.minor_version}"}), f.read())
@command(command="help", params=[], access_level="member",
description="Show a list of commands to get help with")
def help_list_cmd(self, request):
data = self.db.query("SELECT command, module, access_level FROM command_config "
"WHERE enabled = 1 "
"ORDER BY module, command")
blob = ""
current_group = ""
current_module = ""
current_command = ""
access_level = self.access_service.get_access_level(request.sender.char_id)
for row in data:
if access_level["level"] > self.access_service.get_access_level_by_label(row.access_level)["level"]:
continue
parts = row.module.split(".")
group = parts[0]
module = parts[1]
if group != current_group:
current_group = group
blob += "\n\n<header2>" + current_group + "</header2>"
if module != current_module:
current_module = module
blob += "\n" + module + ":"
if row.command != current_command:
current_command = row.command
blob += " " + self.text.make_tellcmd(row.command, "help " + row.command)
return ChatBlob("Help (main)", blob)
@command(command="help", params=[Any("command"), NamedFlagParameters(["show_regex"])], access_level="member",
description="Show help for a specific command")
def help_detail_cmd(self, request, help_topic, named_params):
help_topic = help_topic.lower()
# check for alias
alias = self.command_alias_service.check_for_alias(help_topic)
if alias:
help_topic = alias
help_text = self.command_service.get_help_text(request.sender.char_id, help_topic,
request.channel, named_params.show_regex)
if help_text:
return self.command_service.format_help_text(help_topic, help_text)
else:
return self.getresp("module/help", "no_help", {"topic": help_topic})
@@ -0,0 +1,82 @@
{
"invite_fail": {
"en_US": "<highlight>{target}</highlight> is already in the private channel.",
"de_DE": "Der Spieler <highlight>{target}</highlight> ist bereits ein MItglied des privaten Channels."
},
"invite_success_target": {
"en_US": "You have been invited to the private channel by <highlight>{inviter}</highlight>.",
"de_DE": "Du wurdest von <highlight>{inviter}</highlight> zu meinem privaten Channel eingeladen."
},
"invite_success_self": {
"en_US": "You have invited <highlight>{target}</highlight> to the private channel.",
"de_DE": "Du hast <highlight>{target}</highlight> erfolgreich in den privaten Channel eingeladen."
},
"kick_success_target": {
"en_US": "You have been kicked from the private channel by <highlight>{kicker}</highlight>.",
"de_DE": "Du wurdest von <highlight>{kicker}</highlight> aus dem privaten Channel gekickt."
},
"kick_success_self": {
"en_US": "You have kicked <highlight>{target}</highlight> from the private channel.",
"de_DE": "Du hast <highlight>{target}</highlight> aus dem privaten Channel gekickt."
},
"kick_fail": {
"en_US": "You do not have the required access level to kick <highlight>{target}</highlight>.",
"de_DE": "Du hast nicht das benötigte Zugriffslevel, um <highlight>{target}</highlight> zu kicken."
},
"kick_fail_not_in_priv": {
"en_US": "<highlight>{target}</highlight> is not an member of the private channel.",
"de_DE": "<highlight>{target}</highlight> ist kein Mitglied des privaten Channels."
},
"join": {
"en_US": "{char} has joined the private channel. {logon}",
"de_DE": "<orange>Online<end>: {char}. {logon}"
},
"leave": {
"en_US": "<highlight>{char}</highlight> has left the private channel. {logoff}",
"de_DE": "<highlight>{char}</highlight> hat <yellow>sich nach</yellow> <red>Terra</red> <blue>gebeamt.</blue> {logoff}"
},
"kick_all": {
"en_US": "Everyone will be kicked from this channel in 10 seconds. [by <highlight>{char}</highlight>]",
"de_DE": "In 10 Sekunden wird jeder aus dem Channel gekickt. [von <highlight>{char}</highlight>]"
},
"mem_add_fail": {
"en_US": "<highlight>{char}</highlight> is already a member.",
"de_DE": "<highlight>{char}</highlight> ist bereits ein Mitglied."
},
"mem_add_success": {
"en_US": "<highlight>{char}</highlight> has been added as a member.",
"de_DE": "<highlight>{char}</highlight> wurde als Mitglied hinzugefügt."
},
"mem_rem_success": {
"en_US": "<highlight>{char}</highlight> has been removed as a member.",
"de_DE": "<highlight>{char}</highlight> wurde als Mitglied entfernt."
},
"mem_rem_fail": {
"en_US": "<highlight>{char}</highlight> is not a member.",
"de_DE": "<highlight>{char}</highlight> ist kein Mitglied."
},
"blob_mem_list": {
"en_US": "Members ({amount})",
"de_DE": "Mitglieder ({amount})"
},
"autoinvite_changed": {
"en_US": "Your auto invite preference has been set to <highlight>{changedto}</highlight>.",
"de_DE": "Deine Autoinvite Einstellung ist nun <highlight>{changedto}</highlight>."
},
"not_an_member": {
"en_US": "You must be a member of this bot to set your auto invite preference.",
"de_DE": "Du musst ein Mitglied des Bots sein, um deine Autoinvite Einstellung zu setzen."
},
"auto_invited": {
"en_US": "You have been auto-invited to the private channel.",
"de_DE": "Du wurdest automatsich in meinen privaten Channel eingeladen."
},
"on": {
"en_US": "on",
"de_DE": "an"
},
"off": {
"en_US": "off",
"de_DE": "aus"
}
}
@@ -0,0 +1,246 @@
import hjson
from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_param_types import Character, Multiple
from core.db import DB, SqlException
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.private_channel_service import PrivateChannelService
from core.setting_service import SettingService
from core.text import Text
from core.translation_service import TranslationService
from core.tyrbot import Tyrbot
from core.util import Util
from modules.core.accounting.services.account_service import AccountService
from modules.core.ban.ban_service import BanService
from modules.standard.online.online_display import OnlineDisplay
@instance()
class PrivateChannelController:
MESSAGE_SOURCE = "private_channel"
PRIVATE_CHANNEL_PREFIX = "[<cyan>Priv</cyan>] "
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.util: Util = registry.get_instance("util")
self.private_channel_service = registry.get_instance("private_channel_service")
self.character_service: CharacterService = registry.get_instance("character_service")
self.pork: PorkService = registry.get_instance("pork_service")
self.job_scheduler = registry.get_instance("job_scheduler")
self.access_service = registry.get_instance("access_service")
self.message_hub_service = registry.get_instance("message_hub_service")
self.ban_service = registry.get_instance("ban_service")
self.text: Text = registry.get_instance("text")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
self.setting_service: SettingService = registry.get_instance("setting_service")
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
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")
def pre_start(self):
self.db.create_view("online")
self.message_hub_service.register_message_source(self.MESSAGE_SOURCE)
try:
self.reinvite = [x.char_id for x in
self.db.query("SELECT char_id from online where channel = ?", [self.bot.name])]
except SqlException:
self.reinvite = []
def start(self):
self.message_hub_service.register_message_destination(self.MESSAGE_SOURCE,
self.handle_incoming_relay_message,
["registration"],
[self.MESSAGE_SOURCE])
self.ts.register_translation("module/private_channel", self.load_private_channel_msg)
@event("connect", "Reinvite previous raiders")
def reinvite_all(self, _, _1):
for user in self.reinvite:
self.bot.send_mass_message(user, "You have been <green>reinvited</green> into my private channel "
"after a bot restart or crash; Sorry for the inconvenience.")
self.priv.invite(user)
del self.reinvite
@staticmethod
def load_private_channel_msg():
with open("modules/core/private_channel/private_channel.msg", mode="r", encoding="utf-8") as f:
return hjson.load(f)
def handle_incoming_relay_message(self, ctx):
self.bot.send_private_channel_message(ctx.formatted_message, fire_outgoing_event=False)
@event(event_type="member_logon", description="Send autoinvites to players logging in")
def logon_event(self, _, data):
if not self.bot.is_ready():
if data.packet.char_id not in self.reinvite:
account = data.account
if account.disabled == 1:
pass
elif account.auto_invite == 1:
self.reinvite.append(data.packet.char_id)
return
if self.private_channel_service.in_private_channel(data.packet.char_id):
return
account = data.account
if account.disabled == 1:
return
if self.db.query_single("SELECT * from org_bots where char_id=?", [data.packet.char_id]):
return
if account.auto_invite == 1:
if self.pork.get_character_info(data.packet.char_id).org_id != self.bot.public_channel_service.org_id:
self.private_channel_service.invite(data.packet.char_id)
self.bot.send_mass_message(data.packet.char_id, "You have been "
"<highlight>auto invited</highlight> "
"into my private channel.")
@command(command="join", params=[], access_level="member",
description="Join the private channel")
def join_cmd(self, request):
self.private_channel_service.invite(request.sender.char_id)
@command(command="leave", params=[], access_level="member",
description="Leave the private channel")
def leave_cmd(self, request):
self.private_channel_service.kick(request.sender.char_id)
@command(command="invite", params=[Multiple(Character("character"))], access_level="member",
description="Invite a character to the private channel")
def invite_cmd(self, request, chars):
success, in_channel, none, banned = [], [], [], []
for char in chars:
if char.char_id:
if self.private_channel_service.in_private_channel(char.char_id):
in_channel.append(char.name)
elif self.ban_service.get_ban(char.char_id):
banned.append(char.name)
else:
self.bot.send_private_message(char.char_id, self.getresp("module/private_channel",
"invite_success_target",
{"inviter": request.sender.name}))
self.private_channel_service.invite(char.char_id)
success.append(char.name)
else:
none.append(char.name)
out = ""
if len(in_channel) > 0:
out += self.getresp("module/private_channel", "invite_fail", {"target": ", ".join(in_channel)}) + "\n"
if len(success) > 0:
out += self.getresp("module/private_channel", "invite_success_self", {"target": ", ".join(success)}) + "\n"
if len(none) > 0:
out += self.getresp("global", "char_not_found", {"char": ", ".join(none)}) + "\n"
if len(banned) > 0:
out += f'The Character <highlight>{", ".join(banned)}</highlight> is <red>banned</red>, ' \
f'and cannot be invited.'
if request.channel == "priv":
return out.strip("\n")
else:
self.bot.send_mass_message(request.sender.char_id, out.strip("\n"))
@command(command="kick", params=[Character("character")], access_level="moderator",
description="Kick a character from the private channel")
def kick_cmd(self, request, char):
if char.char_id:
if not self.private_channel_service.in_private_channel(char.char_id):
return self.getresp("module/private_channel", "kick_fail_not_in_priv", {"target": char.name})
else:
if self.access_service.has_sufficient_access_level(request.sender.char_id, char.char_id):
self.bot.send_private_message(char.char_id, self.getresp("module/private_channel",
"kick_success_target",
{"kicker": request.sender.name}))
self.private_channel_service.kick(char.char_id)
return self.getresp("module/private_channel", "kick_success_self", {"target": char.name})
else:
return self.getresp("module/private_channel", "kick_fail", {"target": char.name})
else:
return self.getresp("global", "char_not_found", {"char": char.name})
@command(command="kickall", params=[], access_level="moderator",
description="Kick all characters from the private channel")
def kickall_cmd(self, request):
self.bot.send_private_channel_message(self.getresp("module/private_channel", "kick_all",
{"char": request.sender.name}))
self.job_scheduler.delayed_job(lambda t: self.private_channel_service.kickall(), 10)
@event(event_type=BanService.BAN_ADDED_EVENT, description="Kick characters from the private channel who are banned",
is_hidden=True)
def ban_added_event(self, _, event_data):
self.private_channel_service.kick(event_data.char_id)
@event(event_type=PrivateChannelService.PRIVATE_CHANNEL_MESSAGE_EVENT,
description="Relay messages from the private channel to the relay hub", is_hidden=True)
def handle_private_channel_message_event(self, _, event_data):
if event_data.char_id == self.bot.get_char_id() or self.ban_service.get_ban(event_data.char_id):
return
char_name = self.character_service.resolve_char_to_name(event_data.char_id)
sender = DictObject({"char_id": event_data.char_id, "name": char_name})
char = self.text.make_charlink(char_name)
formatted_message = f"{self.PRIVATE_CHANNEL_PREFIX} {char}: {event_data.message}"
self.message_hub_service.send_message(self.MESSAGE_SOURCE, sender, event_data.message, formatted_message)
@event(event_type=PrivateChannelService.JOINED_PRIVATE_CHANNEL_EVENT,
description="Notify when a character joins the private channel")
def handle_private_channel_joined_event(self, _, event_data):
main = self.account_service.get_account(event_data.char_id)
if main:
info = "" if self.account_service.check_superadmin(main.char_id) else \
":: <red>Admin</red> " if self.account_service.check_admin(main.char_id) else \
":: <red>Moderator</red> " if self.account_service.check_moderator(main.char_id) else \
":: <orange>Raidleader</orange> " if self.account_service.check_leader(main.char_id) else ""
if main.char_id != event_data.char_id:
info += f":: Alt of <{main.faction.lower()}>{main.name}</{main.faction.lower()}>"
else:
if self.setting_service.get_value('is_alliance_bot') == "0":
info = ":: <red>WARN</red> » <notice>NO ACC</notice>"
else:
info = ""
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)
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,
od.format_blob(od.format_by_channel_prof("and channel_id IN (1, 2) ", params)))
@event(event_type=PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT,
description="Notify when a character leaves the private channel")
def handle_private_channel_left_event(self, _, event_data):
char_info = self.pork.get_character_info(event_data.char_id)
msg = f"<{char_info.faction.lower()}>{char_info.name}</{char_info.faction.lower()}> left us"
self.bot.send_private_channel_message(msg, fire_outgoing_event=False)
self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, msg, msg)
@event(event_type=Tyrbot.OUTGOING_PRIVATE_CHANNEL_MESSAGE_EVENT,
description="Relay commands from the private channel to the relay hub", is_hidden=True)
def outgoing_private_channel_message_event(self, _, event_data):
if isinstance(event_data.message, ChatBlob):
pages = self.text.paginate(ChatBlob(event_data.message.title, event_data.message.msg),
self.setting_service.get("org_channel_max_page_length").get_value())
if len(pages) < 4:
for page in pages:
message = "{priv} {message}".format(priv=self.PRIVATE_CHANNEL_PREFIX, message=page)
self.message_hub_service.send_message(self.MESSAGE_SOURCE,
None,
page,
message)
else:
message = "{priv} {message}".format(priv=self.PRIVATE_CHANNEL_PREFIX, message=event_data.message.title)
self.message_hub_service.send_message(self.MESSAGE_SOURCE,
None,
event_data.message.title,
message)
else:
message = "{priv} {message}".format(priv=self.PRIVATE_CHANNEL_PREFIX, message=event_data.message)
self.message_hub_service.send_message(self.MESSAGE_SOURCE,
None,
message,
message)
@@ -0,0 +1,75 @@
import time
from core.command_param_types import Const, Any, Options
from core.db import DB
from core.decorators import instance, command, setting, event
from core.dict_object import DictObject
from core.private_channel_service import PrivateChannelService
from core.setting_types import DictionarySettingType
from core.text import Text
@instance()
class TopicController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.db: DB = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.util = registry.get_instance("util")
self.command_alias_service = registry.get_instance("command_alias_service")
self.private_channel_service: PrivateChannelService = registry.get_instance("private_channel_service")
def start(self):
self.command_alias_service.add_alias("motd", "topic")
@setting(name="topic", value="", description="The bot topic")
def topic(self):
return DictionarySettingType()
@command(command="topic", params=[], access_level="member",
description="Show the current topic")
def topic_show_command(self, request):
topic = self.topic().get_value()
if topic:
return self.format_topic_message(topic)
else:
return "There is no current topic."
@command(command="topic", params=[Options(["clear", "unset"])], access_level="leader",
description="Clears the current topic", sub_command="modify")
def topic_clear_command(self, _, _1):
self.topic().set_value("")
return "The topic has been cleared."
@command(command="topic",
params=[Const("set", is_optional=True), Any("topic_message")],
access_level="leader",
description="Set the current topic", sub_command="modify")
def topic_set_command(self, request, _, topic_message):
sender = DictObject({"name": request.sender.name, "char_id": request.sender.char_id})
topic = {"topic_message": topic_message,
"created_by": sender,
"created_at": int(time.time())}
self.topic().set_value(topic)
return "The topic has been set."
def format_topic_message(self, topic):
time_string = self.util.time_to_readable(int(time.time()) - topic["created_at"])
return f"Topic: <highlight>{topic['topic_message']}<end> " \
f"[set by <highlight>{topic['created_by']['name']}<end>][{time_string} ago]"
@event(PrivateChannelService.JOINED_PRIVATE_CHANNEL_EVENT, "Show topic to characters joining the private channel")
def show_topic(self, _, event_data):
topic = self.topic().get_value()
if topic:
self.bot.send_private_message(event_data.char_id, self.format_topic_message(topic))
@event(PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT,
"Clear topic when there are no characters in the private channel")
def clear_topic(self, _, event_data):
if self.topic().get_value() and len(self.private_channel_service.get_all_in_private_channel()) == 0:
self.topic().set_value("")
+395
View File
@@ -0,0 +1,395 @@
import math
from core.buddy_service import BuddyService
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Character, Any, NamedParameters
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.private_channel_service import PrivateChannelService
from core.setting_service import SettingService
from core.setting_types import TextSettingType
from core.text import Text
from core.translation_service import TranslationService
from core.tyrbot import Tyrbot
# noinspection SqlCaseVsIf,SqlCaseVsCoalesce
@instance()
class RIAdminController:
PAGE_SIZE = 20
UNASSIGNED_RAID_INSTANCE_ID = 0
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot: Tyrbot = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text: Text = registry.get_instance("text")
self.setting_service: SettingService = registry.get_instance("setting_service")
self.util = registry.get_instance("util")
self.character_service: CharacterService = registry.get_instance("character_service")
self.private_channel_service: PrivateChannelService = registry.get_instance("private_channel_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.getresp = self.ts.get_response
self.buddy_service: BuddyService = registry.get_instance("buddy_service")
self.pork: PorkService = registry.get_instance("pork_service")
def start(self):
self.db.exec(
"CREATE TABLE IF NOT EXISTS raid_instance (id INT PRIMARY KEY AUTO_INCREMENT, "
"name VARCHAR(255) NOT NULL, "
"bot varchar(32)) ENGINE MEMORY")
self.db.exec(
"CREATE TABLE IF NOT EXISTS raid_instance_char ("
"raid_instance_id INT NOT NULL, "
"char_id INT PRIMARY KEY, "
"is_leader TINYINT NOT NULL) ENGINE MEMORY")
self.setting_service.register_new(self.module_name, "riadmin_network",
"[905848882, 272234, 1923370, 313107, 1217210821, 1134420908]",
TextSettingType(), "Allowed bots (charID's)",
extended_description="This setting is *NOT* synchronized across the network;"
" this needs to be done manually!")
@event(event_type="buddy_logoff", description="Track raiders")
def raider_logoff(self, _, event_data):
if self.bot.is_ready():
buddy = self.buddy_service.get_buddy(event_data.char_id)
if not buddy:
return
if "raider" in buddy.get('types', []):
user = self.db.query_single("SELECT * from player where char_id=?", [event_data.char_id])
raid = self.get_raid_instance_by_char(event_data.char_id)
if user and raid:
if raid.bot != 0:
self.bot.send_private_channel_message(f'[<red>RI</red>] Raider logged off: '
f'{user.name} - {user.profession} [{raid.name}]')
self.db.exec("DELETE FROM raid_instance_char WHERE char_id = ?", [event_data.char_id])
self.buddy_service.remove_buddy(event_data.char_id, 'raider')
@event(event_type=PrivateChannelService.LEFT_PRIVATE_CHANNEL_EVENT, description="Track Raiders")
def raider_leave(self, _, event_data):
if self.bot.is_ready():
buddy = self.buddy_service.get_buddy(event_data.char_id)
if not buddy:
return
if "raider" in buddy.get('types', []):
user = self.pork.get_character_info(event_data.char_id)
raid = self.get_raid_instance_by_char(event_data.char_id)
part = int(raid.bot) if raid else 0
if part in [self.bot.get_char_id(), 0]:
if part != 0:
self.bot.send_private_channel_message(
f'[<red>RI</red>] Raider left: {user.name} - {user.profession} '
f'[{raid.name if raid else "UNASSIGNED"}]')
self.db.exec("DELETE FROM raid_instance_char WHERE char_id = ?", [event_data.char_id])
self.buddy_service.remove_buddy(event_data.char_id, 'raider')
@event(event_type="connect", description="Init raider tracking on startup")
def connect(self, _, _1):
for user in self.get_raid_instance_chars():
self.buddy_service.add_buddy(user.char_id, 'raider')
@command(command="riadmin", params=[NamedParameters(['page'])], access_level="leader",
description="Show the current RIs")
def riadmin(self, _, named_params):
refresh = self.text.make_tellcmd("Refresh", "riadmin")
send = self.text.make_tellcmd("Apply", "riadmin send")
clear = self.text.make_tellcmd("Clear All", "riadmin clear")
blob = f"[{refresh}] - Update the RI List\n" \
f"[{send}] - Send all RI's to their bots\n" \
f"[{clear}] - Clear all RI's, and delete them"
blob += "\n\n"
page = int(named_params.page or "1")
offset = (page - 1) * self.PAGE_SIZE
data = self.get_raid_instance_chars()
return self.format_pagination(data, offset, page, self.compact_char_display, 'Raid Instances',
'No Raidinstances found.', 'riadmin ', self.PAGE_SIZE, blob)
def format_pagination(self, data, offset, page, formatter, title, nullmsg, cmd, page_size=10, headline=""):
raid_instances = self.get_raid_instances()
blob = ""
if page == 1:
blob = headline
selected = data[offset:offset + page_size]
if len(data) == 0:
return nullmsg
num_assigned = 0
num_unassigned = 0
pages = ""
if page > 1:
pages += "Pages: " + self.text.make_tellcmd("«« Page %d" % (page - 1), f'{cmd} --page={page - 1}')
if offset + page_size < len(data):
pages += f" Page {page}/{math.ceil(len(data) / page_size)}"
pages += " " + self.text.make_tellcmd("Page %d »»" % (page + 1), f'{cmd} --page={page + 1}')
pages += "\n"
current_raid_instance_id = None
for row in data:
if row.raid_instance_id == self.UNASSIGNED_RAID_INSTANCE_ID:
num_unassigned += 1
else:
if row.name:
num_assigned += 1
blob += "" + pages
for row in selected:
if row.raid_instance_id != current_raid_instance_id:
name = ""
if row.bot:
bot = self.pork.get_character_info(row.bot)
name = f"[{bot.name}]" if bot else ""
blob += f"\n<header2>{row.raid_instance_name} {name}</header2>\n"
current_raid_instance_id = row.raid_instance_id
if not row.char_id:
continue
blob += formatter(row)
add_leader_link = (row.raid_instance_id != self.UNASSIGNED_RAID_INSTANCE_ID and not row.is_leader)
blob += " " + self.get_assignment_links(raid_instances, row.name, add_leader_link)
blob += "\n"
blob += "\n" + pages
out = ChatBlob(title, blob)
out.page_postfix = f" (<highlight>{num_assigned}</highlight> Players)"
if num_unassigned > 0:
out.page_postfix = f" (Assigned: <highlight>{num_assigned}</highlight>, " \
f"Unassigned: <highlight>{num_unassigned}</highlight>)"
return out
@command(command="ritake", params=[Any("raiders")],
access_level="all",
description="take the RI from another bot")
def ritake(self, request, raiders: str):
users = raiders.split(",")
if request.sender.char_id not in eval(self.setting_service.get_value('riadmin_network')):
return
for user in users:
char = self.character_service.resolve_char_to_id(user.strip())
if not char:
continue
if self.private_channel_service.in_private_channel(char):
self.buddy_service.add_buddy(char, 'raider')
continue
self.bot.send_mass_message(char, 'You have been assigned to my RI :: <red>accept the invite!</red>')
self.private_channel_service.invite(char)
@command(command="riadmin",
params=[Const("add"), Any("raid_instance"), Character("char")],
access_level="leader",
description="Add a character to a RI", sub_command="leader")
def riadmin_add(self, _, _1, raid_instance_name, char):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
raid_instance = self.get_raid_instance(raid_instance_name)
if not raid_instance:
return f"Raid instance <highlight>{raid_instance_name}</highlight> does not exist."
self.refresh_raid_instance_chars()
row = self.db.query_single("SELECT raid_instance_id FROM raid_instance_char WHERE char_id = ?", [char.char_id])
if row:
if raid_instance.id == row.raid_instance_id:
if 'raider' not in (
self.buddy_service.get_buddy(char.char_id) or DictObject({'types': []}).get('types', [])):
self.buddy_service.add_buddy(char.char_id, 'raider')
return f"Character <highlight>{char.name}</highlight> is already assigned to " \
f"raid instance <highlight>{raid_instance.name}</highlight>."
else:
if 'raider' not in (
self.buddy_service.get_buddy(char.char_id) or DictObject({'types': []}).get('types', [])):
self.buddy_service.add_buddy(char.char_id, 'raider')
self.update_char_raid_instance(char.char_id, raid_instance.id)
return f"Character <highlight>{char.name}</highlight> has been assigned to " \
f"raid instance <highlight>{raid_instance.name}</highlight>."
else:
return f"Character <highlight>{char.name}</highlight> is not in the private channel."
@command(command="riadmin", params=[Const("clear")], access_level="leader",
description="Remove all raids and their players", sub_command="leader")
def riadmin_clear(self, _, _1):
query = self.db.query("SELECT * FROM raid_instance_char")
for user in query:
self.buddy_service.remove_buddy(user.char_id, 'raider')
self.db.exec("DELETE FROM raid_instance_char where 1")
self.db.exec("DELETE FROM raid_instance where 1")
return f"All characters have been removed from raid instances."
@command(command="riadmin", params=[Const("rem"), Character("char")], access_level="leader",
description="remove a character from the RI", sub_command="leader")
def riadmin_rem(self, _, _2, char):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
self.refresh_raid_instance_chars()
row = self.db.query_single(
"SELECT r2.name FROM raid_instance_char r1 "
"JOIN raid_instance r2 ON r1.raid_instance_id = r2.id WHERE r1.char_id = ?",
[char.char_id])
if row:
self.update_char_raid_instance(char.char_id, self.UNASSIGNED_RAID_INSTANCE_ID)
return f"Character <highlight>{char.name}</highlight> has been removed from " \
f"raid instance <highlight>{row.name}</highlight>."
else:
return f"Character <highlight>{char.name}</highlight> is not assigned to any raid instances."
@command(command="riadmin", params=[Const("leader"), Character("char")], access_level="leader",
description="Set the leader for a RI", sub_command="leader")
def riadmin_leader(self, _, _2, char):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
raid_instance = self.get_raid_instance_by_char(char.char_id)
if not raid_instance:
return f"Character <highlight>{char.name}</highlight> does not belong to a raid instance."
self.set_leader(char.char_id, raid_instance.id)
return f"Character <highlight>{char.name}</highlight> has been set as the leader for " \
f"raid instance <highlight>{raid_instance.name}</highlight>."
@command(command="riadmin", params=[Const("send")], access_level="leader",
description="Send the RIs to their preset bots", sub_command="leader")
def riadmin_send(self, _, _2):
self.bot.send_private_channel_message("Exporting raids..")
for raid_instance in self.get_raid_instances():
data = self.db.query(
"SELECT r.char_id, p.name, r.is_leader FROM raid_instance_char r "
"left join player p on r.char_id=p.char_id WHERE raid_instance_id = ?",
[raid_instance.id])
if int(raid_instance.bot) == self.bot.get_char_id() or raid_instance.bot == "0" or not raid_instance.bot:
continue
self.bot.send_private_message(int(raid_instance.bot), "ritake " + ", ".join([k['name'] for k in data]),
add_color=False)
for char in data:
if char.is_leader == 0:
self.private_channel_service.kick(char.char_id)
return "Raid instance configuration has been applied."
@command(command="riadmin", params=[Const("create"), Any("Raid_name"), Character("bot")],
access_level="leader",
description="Create or change a raid instance", sub_command="leader")
def riadmin_create(self, _, _2, raid_instance_name, bot):
if not bot.char_id:
return self.getresp("global", "char_not_found", {"char": bot.name})
if bot.char_id not in eval(self.setting_service.get_value('riadmin_network')):
return "Bot not valid: please ask an Administrator to verify it first."
raid_instance = self.get_raid_instance(raid_instance_name)
if raid_instance:
if raid_instance.name == raid_instance_name and raid_instance.bot == bot.char_id:
return f"Raid instance <highlight>{raid_instance_name}</highlight> already exists."
else:
self.db.exec("UPDATE raid_instance SET name = ?, bot = ? WHERE id = ?",
[raid_instance_name, bot.char_id, raid_instance.id])
return f"Raid instance <highlight>{raid_instance_name}</highlight> has been updated."
else:
self.db.exec("INSERT INTO raid_instance (name, bot) VALUES (?, ?)", [raid_instance_name, bot.char_id])
return f"Raid instance <highlight>{raid_instance_name}</highlight> [<highlight>{bot.name}</highlight>] " \
f"has been created."
@command(command="riadmin", params=[Const("delete"), Any("raid_instance_name")], access_level="leader",
description="Remove a RI", sub_command="leader")
def raid_instance_delete_cmd(self, _, _1, raid_instance_name):
raid_instance = self.get_raid_instance(raid_instance_name)
if not raid_instance:
return f"Raid instance <highlight>{raid_instance_name}</highlight> does not exist."
query = self.db.query("SELECT char_id from raid_instance_char where raid_instance_id=?", [raid_instance.id])
for user in query:
self.buddy_service.remove_buddy(user.char_id, 'raider')
self.db.exec("DELETE FROM raid_instance_char WHERE raid_instance_id = ?", [raid_instance.id])
self.db.exec("DELETE FROM raid_instance WHERE id = ?", [raid_instance.id])
return f"Raid instance <highlight>{raid_instance_name}</highlight> has been deleted."
def get_raid_instance_chars(self):
self.refresh_raid_instance_chars()
data = self.db.query("SELECT * FROM ("
"SELECT p.*, r2.id AS raid_instance_id, "
"r1.is_leader, r2.name AS raid_instance_name, r2.bot "
"FROM raid_instance r2 "
"LEFT JOIN raid_instance_char r1 ON r1.raid_instance_id = r2.id "
"LEFT JOIN player p ON r1.char_id = p.char_id "
"UNION "
"SELECT p.*, r3.raid_instance_id, "
"r3.is_leader, 'Unassigned' AS raid_instance_name, '' AS bot "
"FROM raid_instance_char r3 "
"LEFT JOIN player p ON r3.char_id = p.char_id "
"WHERE r3.raid_instance_id = ?) as r2r1pr3p "
"ORDER BY raid_instance_id != ? DESC, "
"raid_instance_name, "
"is_leader desc, "
"profession, level desc, "
"name",
[self.UNASSIGNED_RAID_INSTANCE_ID, self.UNASSIGNED_RAID_INSTANCE_ID])
return data
def refresh_raid_instance_chars(self):
users = self.db.query('SELECT * from online o '
'left join player p on o.char_id=p.char_id '
'where o.channel = ?', [self.bot.name])
for user in users:
self.db.exec(
"INSERT IGNORE INTO raid_instance_char (char_id, raid_instance_id, is_leader) VALUES (?, ?, 0)",
[user.char_id, self.UNASSIGNED_RAID_INSTANCE_ID])
self.buddy_service.add_buddy(user.char_id, 'raider')
def update_char_raid_instance(self, char_id, raid_instance_id):
return self.db.exec("UPDATE raid_instance_char SET raid_instance_id = ?, is_leader = 0 WHERE char_id = ?",
[raid_instance_id, char_id])
def set_leader(self, char_id, raid_instance_id):
self.db.exec("UPDATE raid_instance_char SET is_leader = 0 WHERE raid_instance_id = ?", [raid_instance_id])
self.db.exec("UPDATE raid_instance_char SET is_leader = 1 WHERE raid_instance_id = ? AND char_id = ?",
[raid_instance_id, char_id])
def compact_char_display(self, char_info):
if char_info.level:
msg = f" {self.util.get_prof_icon(char_info.profession)} " \
f"{self.text.zfill(char_info.level, 220)}/<green>{self.text.zfill(char_info.ai_level, 30)}</green> " \
f"{char_info.name: <13}"
elif char_info.name:
msg = " <highlight>%s</highlight>" % char_info.name
else:
msg = " <highlight>Unknown(%d)</highlight>" % char_info.char_id
if char_info.is_leader:
msg += " [<highlight>Leader</highlight>]"
return msg
def get_assignment_links(self, raid_instances, char_name, add_leader_link):
links = list(map(lambda x: self.text.make_tellcmd(x.name, f"riadmin add {x.name} {char_name}"), raid_instances))
links.insert(0, self.text.make_tellcmd("Rem", f"riadmin rem {char_name}"))
if add_leader_link:
links.insert(0, self.text.make_tellcmd("L", f"riadmin leader {char_name}"))
return f'[{"|".join(links)}]'
def get_raid_instances(self):
data = self.db.query("SELECT id, name, bot FROM raid_instance ORDER BY name")
return data
def get_raid_instance(self, raid_instance_name):
return self.db.query_single("SELECT id, name, bot FROM raid_instance WHERE name LIKE ?", [raid_instance_name])
def get_raid_instance_by_char(self, char_id):
return self.db.query_single("SELECT id, name, CASE WHEN bot IS NOT NULL THEN bot else 0 END as bot "
"FROM raid_instance r1 JOIN raid_instance_char r2 ON r1.id = r2.raid_instance_id "
"WHERE r2.char_id = ?", [char_id])
def get_conn_by_id(self, bot):
conn = self.bot.conns.get(bot)
if conn:
return conn
conns = self.bot.get_conns(lambda x: x.char_name.lower() == bot.lower())
if conns:
return conns[0][1]
return None
@@ -0,0 +1,87 @@
from functools import partial
from core.chat_blob import ChatBlob
from core.command_param_types import Const, Any
from core.decorators import instance, command
@instance()
class MessageHubController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text = registry.get_instance("text")
self.message_hub_service = registry.get_instance("message_hub_service")
self.getresp = partial(registry.get_instance("translation_service").get_response, "module/system")
def start(self):
pass
@command(command="messagehub", params=[], access_level="admin",
description="Show the current message hub subscriptions")
def messagehub_cmd(self, _):
blob = self.getresp("messagehub_info") + "\n"
subscriptions = self.message_hub_service.hub
for destination, obj in subscriptions.items():
edit_subs_link = self.text.make_tellcmd(destination, "messagehub edit %s" % destination)
blob += "\n%s\n" % edit_subs_link
for source in obj.sources:
blob += "%s\n" % source
return ChatBlob(self.getresp("messagehub_title", {"count": len(subscriptions)}), blob)
@command(command="messagehub", params=[Const("edit"), Any("destination")], access_level="admin",
description="Edit subscriptions for a destination")
def messagehub_edit_cmd(self, _, _1, destination):
obj = self.message_hub_service.hub[destination]
if not obj:
return self.getresp("destination_not_exist", {"destination": destination})
blob = ""
count = 0
for source in self.message_hub_service.sources:
if source in obj.invalid_sources:
continue
sub_link = self.text.make_tellcmd("Subscribe", "messagehub subscribe %s %s" % (destination, source))
unsub_link = self.text.make_tellcmd("Unsubscribe", "messagehub unsubscribe %s %s" % (destination, source))
status = ""
if source in obj.sources:
count += 1
status = "<green>%s</green>" % self.getresp("subscribed")
blob += "%s [%s] [%s] %s\n\n" % (source, sub_link, unsub_link, status)
return ChatBlob(
self.getresp("messagehub_edit_title", {"destination": destination.capitalize(), "count": count}), blob)
@command(command="messagehub",
params=[Const("subscribe"), Any("destination"), Any("source")],
access_level="admin",
description="Subscribe a destination to a source")
def messagehub_subscribe_cmd(self, _, _1, destination, source):
obj = self.message_hub_service.hub[destination]
if not obj:
return self.getresp("module/system", "destination_not_exist", {"destination": destination})
if source in obj.sources:
return self.getresp("messagehub_already_subscribed", {"destination": destination, "source": source})
if source in obj.invalid_sources:
return self.getresp("messagehub_invalid_subscription", {"destination": destination, "source": source})
self.message_hub_service.subscribe_to_source(destination, source)
return self.getresp("messagehub_subscribe_success", {"destination": destination, "source": source})
@command(command="messagehub", params=[Const("unsubscribe"), Any("destination"), Any("source")],
access_level="admin",
description="Unsubscribe a destination to a source")
def messagehub_unsubscribe_cmd(self, _, _1, destination, source):
obj = self.message_hub_service.hub[destination]
if not obj:
return self.getresp("module/system", "destination_not_exist", {"destination": destination})
if source not in obj.sources:
return self.getresp("messagehub_not_subscribed", {"destination": destination, "source": source})
self.message_hub_service.unsubscribe_from_source(destination, source)
return self.getresp("messagehub_unsubscribe_success", {"destination": destination, "source": source})
+20
View File
@@ -0,0 +1,20 @@
from core.command_param_types import Const
from core.decorators import instance, command
@instance()
class QueueController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.command_alias_service = registry.get_instance("command_alias_service")
self.getresp = registry.get_instance("translation_service").get_response
def start(self):
self.command_alias_service.add_alias("clearqueue", "queue clear")
@command(command="queue", params=[Const("clear")], access_level="moderator",
description="Clear the outgoing message queue")
def queue_clear_cmd(self, _, _1):
num_messages = len(self.bot.conns["main"].packet_queue)
self.bot.conns["main"].packet_queue.clear()
return self.getresp("module/system", "clear_queue", {"count": num_messages})
+22
View File
@@ -0,0 +1,22 @@
from core.command_param_types import Any, Character
from core.decorators import instance, command
@instance()
class RunasController:
def inject(self, registry):
self.command_service = registry.get_instance("command_service")
self.access_service = registry.get_instance("access_service")
self.getresp = registry.get_instance("translation_service").get_response
@command(command="runas", params=[Character("character"), Any("command")], access_level="superadmin",
description="Run a command as another character")
def runas_cmd(self, request, char, command_str):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
elif not self.access_service.has_sufficient_access_level(request.sender.char_id, char.char_id):
return self.getresp("module/system", "runas_fail", {"char": char.name})
else:
command_str = self.command_service.trim_command_symbol(command_str)
self.command_service.process_command(command_str, request.channel,
char.char_id, request.reply, request.conn)
@@ -0,0 +1,19 @@
from core.command_param_types import Any, Character
from core.decorators import instance, command
@instance()
class SendMessageController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.command_service = registry.get_instance("command_service")
self.getresp = registry.get_instance("translation_service").get_response
@command(command="sendtell", params=[Character("character"), Any("message")], access_level="superadmin",
description="Send a tell to another character from the bot")
def sendtell_cmd(self, _, char, message):
if char.char_id:
self.bot.send_private_message(char.char_id, message, add_color=False)
return self.getresp("module/system", "msg_sent")
else:
return self.getresp("global", "char_not_found", {"char": char.name})
+57
View File
@@ -0,0 +1,57 @@
from core.decorators import instance
@instance()
class SqlController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.text = registry.get_instance("text")
self.command_alias_service = registry.get_instance("command_alias_service")
self.getresp = registry.get_instance("translation_service").get_response
# def start(self):
# self.command_alias_service.add_alias("querysql", "sql query")
# self.command_alias_service.add_alias("executesql", "sql exec")
# @command(command="sql", params=[Const("query"), Any("sql_statement")], access_level="superadmin",
# description="Execute a SQL query and return the results")
# def sql_query_cmd(self, request, _, sql):
# try:
# results = self.db.query(sql)
# return ChatBlob(self.getresp("module/system", "sql_blob_title", {"count": len(results)}),
# json.dumps(results, indent=4, sort_keys=True))
# except Exception as e:
# return self.getresp("module/system", "sql_fail", {"error": str(e)})
#
# @command(command="sql", params=[Const("exec"), Any("sql_statement")], access_level="superadmin",
# description="Execute a SQL query and return number of affected rows")
# def sql_exec_cmd(self, request, _, sql):
# try:
# row_count = self.db.exec(sql)
# return self.getresp("module/system", "sql_exec_success", {"count": row_count})
# except Exception as e:
# return self.getresp("module/system", "sql_fail", {"error": str(e)})
#
# @command(command="sql", params=[Const("files")], access_level="superadmin",
# description="Show SQL files that have been loaded")
# def sql_files_cmd(self, request, _):
# data = self.db.query("SELECT file, version FROM db_version ORDER BY file ASC")
#
# blob = ""
# for row in data:
# reload_link = ""
# if row.file != "db_version":
# reload_link = self.text.make_tellcmd("Reload", f"sql load {row.file}")
# blob += f"{row.file} - {row.version} {reload_link}\n"
#
# return ChatBlob("SQL Files (%d)" % len(data), blob)
#
# @command(command="sql", params=[Const("load"), Any("sql_file")], access_level="superadmin",
# description="Load or reload a SQL file")
# def sql_load_cmd(self, request, _, file):
# try:
# self.db.load_sql_file(file, force_update=True)
# return f"SQL file <highlight>{file}</highlight> has been loaded."
# except Exception as e:
# return self.getresp("module/system", "sql_fail", {"error": str(e)})
+164
View File
@@ -0,0 +1,164 @@
{
"reload_lang": {
"en_US": "Language changed to <highlight>{lang_code}</highlight>",
"de_DE": "Die Sprache wurde auf <highlight>{lang_code}</highlight> geändert."
},
"current_lang": {
"en_US": "My current language is <highlight>{lang_code}</highlight>",
"de_DE": "Meine aktuelle Sprache ist <highlight>{lang_code}</highlight>."
},
"clear_queue": {
"en_US": "Cleared <highlight>{count}</highlight> messages from the outgoing message queue.",
"de_DE": "Es wurden <highlight>{count}</highlight> Nachrichten aus der ausgehenden Nachrichten warteschlange entfernt."
},
"runas_fail": {
"en_US": "Error! You must have a higher access level than <highlight>{char}</highlight>.",
"de_DE": "Error! Du musst ein höheres Rechtelevel haben als <highlight>{char}</highlight>."
},
"msg_sent": {
"en_US": "Your message has been sent.",
"de_DE": "Deine Nachricht wurde erfolgreich gesendet."
},
"sql_exec_success": {
"en_US": "{count} row(s) affected.",
"de_DE": "{count} Zeile(n) betroffen."
},
"sql_fail": {
"en_US": "There was an error executing your query: {error}",
"de_DE": "Bei der Bearbeitung deiner Anfrage ist ein Fehler aufgetreten: {error}"
},
"sql_blob_title": {
"en_US": "Results ({count})",
"de_DE": "Ergebnisse ({count})"
},
"expected_online": {
"en_US": "<myname> is now <green>online</green>.",
"de_DE": "<myname> ist wieder <green>online</green>."
},
"unexpected_online": {
"en_US": "<myname> is now <green>online</green> but may have shut down or restarted unexpectedly.",
"de_DE": "<myname> ist wieder <green>online</green>, wurde aber möglicherweise unerwartet heruntergefahren, oder neugestartet."
},
"shutdown": {
"en_US": "The bot is shutting down.",
"de_DE": "Der Bot wird heruntergefahren."
},
"restart": {
"en_US": "The bot is restarting.",
"de_DE": "Der Bot startet nun neu."
},
"reason": {
"en_US": " <highlight>Reason: {reason}</highlight>",
"de_DE": " <highlight>Der Grund hierfür: {reason}</highlight>"
},
"check_access": {
"en_US": "Access level for <highlight>{char}</highlight> is <highlight>{rank_main}</highlight>.",
"de_DE": "Das Zugriffslevel für <highlight>{char}</highlight> ist <highlight>{rank_main}</highlight>."
},
"show_output_target": {
"en_US": "<highlight>{sender}</highlight> is showing you output for command <highlight>{cmd}</highlight>:",
"de_DE": "<highlight>{sender}</highlight> zeigt dir den output des Befehls <highlight>{cmd}</highlight>:"
},
"show_output_self": {
"en_US": "Command <highlight>{cmd}</highlight> output has been sent to <highlight>{target}</highlight>.",
"de_DE": "Die Ausgabe des Befehls <highlight>{cmd}</highlight> wurde an <highlight>{target}</highlight> weitergeleitet."
},
"status_blob": {
"en_US": [
"Version: <highlight>{bot_ver}</highlight>\n",
"Name: <highlight><myname></highlight>\n",
"\n",
"OS: <highlight>{os_ver}</highlight>\n",
"Python: <highlight>{python_ver}</highlight>\n",
"Database: <highlight>{db_type}</highlight>\n",
"Memory Usage: <highlight>{mem_usage} KB</highlight>\n",
"\n",
"Superadmin: <highlight>{superadmin}</highlight>\n",
"Buddy List: <highlight>{bl_used}/{bl_size}</highlight>\n",
"Uptime: <highlight>{uptime}</highlight>\n",
"Dimension: <highlight>{dim}</highlight>\n",
"\n",
"Org Id: <highlight>{org_id}</highlight>\n",
"Org Name: <highlight>{org_name}</highlight>\n",
"\n",
"<pagebreak><header2>Bots Connected</header2>\n",
"{bots_connected}",
"\n",
"<pagebreak><header2>Public Channels</header2>\n",
"{pub_channels}",
"\n",
"<pagebreak><header2>Event Types</header2>\n",
"{event_types}",
"\n",
"<pagebreak><header2>Access Levels</header2>\n",
"{access_levels}"
],
"de_DE": [
"Version: <highlight>Tyrbot {bot_ver}</highlight>\n",
"Name: <highlight><myname></highlight>\n",
"\n",
"Betriebssystem: <highlight>{os_ver}</highlight>\n",
"Python: <highlight>{python_ver}</highlight>\n",
"Datenbank: <highlight>{db_type}</highlight>\n",
"Arbeitsspeichernutzung: <highlight>{mem_usage} KB</highlight>\n",
"\n",
"Superadmin: <highlight>{superadmin}</highlight>\n",
"Freundesliste: <highlight>{bl_used}/{bl_size}</highlight>\n",
"Onlinezeit: <highlight>{uptime}</highlight>\n",
"Dimension: <highlight>{dim}</highlight>\n",
"\n",
"Org ID: <highlight>{org_id}</highlight>\n",
"Org Name: <highlight>{org_name}</highlight>\n",
"\n",
"<pagebreak><header2>Bots Connected</header2>\n",
"{bots_connected}",
"\n",
"<pagebreak><header2>Öffentliche Channel</header2>\n",
"{pub_channels}",
"\n",
"<pagebreak><header2>Event Typen</header2>\n",
"{event_types}",
"\n",
"<pagebreak><header2>Zugriffslevel</header2>\n",
"{access_levels}",
"\n"
]
},
"status_title": {
"en_US": "System Info",
"de_DE": "Systeminformationen"
},
"messagehub_title": {
"en_US": "Message Hub Subscriptions ({count})"
},
"messagehub_info": {
"en_US": "Destinations are listed below, along with the sources they are subscribed to."
},
"subscribed": {
"en_US": "Subscribed"
},
"unsubscribed": {
"en_US": "Unsubscribed"
},
"messagehub_edit_title": {
"en_US": "{destination} Subscriptions ({count})"
},
"messagehub_already_subscribed": {
"en_US": "Destination <highlight>{destination}</highlight> is already subscribed to source <highlight>{source}</highlight>."
},
"messagehub_not_subscribed": {
"en_US": "Destination <highlight>{destination}</highlight> is not subscribed to source <highlight>{source}</highlight>."
},
"messagehub_invalid_subscription": {
"en_US": "Destination <highlight>{destination}</highlight> cannot be subscribed to source <highlight>{source}</highlight>."
},
"messagehub_subscribe_success": {
"en_US": "Destination <highlight>{destination}</highlight> has been subscribed to source <highlight>{source}</highlight> successfully."
},
"messagehub_unsubscribe_success": {
"en_US": "Destination <highlight>{destination}</highlight> has been unsubscribed from source <highlight>{source}</highlight> successfully."
},
"destination_not_exist": {
"en_US": "Destination <highlight>{destination}</highlight> does not exist."
}
}
+110
View File
@@ -0,0 +1,110 @@
import hjson
from core.command_param_types import Any
from core.command_service import CommandService
from core.decorators import instance, command, event
from core.dict_object import DictObject
from core.logger import Logger
from core.setting_service import SettingService
from core.setting_types import BooleanSettingType, TextSettingType, NumberSettingType
from core.translation_service import TranslationService
@instance()
class SystemController:
SHUTDOWN_EVENT = "shutdown"
MESSAGE_SOURCE = "shutdown_notice"
def __init__(self):
self.logger = Logger(__name__)
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.setting_service: SettingService = registry.get_instance("setting_service")
self.event_service = registry.get_instance("event_service")
self.character_service = registry.get_instance("character_service")
self.ts: TranslationService = registry.get_instance("translation_service")
self.message_hub_service = registry.get_instance("message_hub_service")
self.getresp = self.ts.get_response
def pre_start(self):
self.event_service.register_event_type(self.SHUTDOWN_EVENT)
self.message_hub_service.register_message_source(self.MESSAGE_SOURCE)
def start(self):
self.ts.register_translation("module/system", self.load_system_msg)
self.setting_service.register_new(self.module_name, "expected_shutdown", True, BooleanSettingType(),
"Helps bot to determine if last shutdown was expected or due to a problem")
self.setting_service.register_new("core.system", "symbol", "!",
TextSettingType(["!", "#", "*", "@", "$", "+", "-"]),
"Symbol for executing bot commands")
self.setting_service.register_new("core.system", "org_channel_max_page_length", 7500,
NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
"Maximum size of blobs in org channel")
self.setting_service.register_new("core.system", "private_message_max_page_length", 7500,
NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
"Maximum size of blobs in private messages")
self.setting_service.register_new("core.system", "private_channel_max_page_length", 7500,
NumberSettingType([4500, 6000, 7500, 9000, 10500, 12000]),
"Maximum size of blobs in private channel")
self.setting_service.register_new("core.system", "accept_commands_from_slave_bots", False, BooleanSettingType(),
"Accept and respond to commands sent to slave bots (only applies if you have "
"added slave bots in the config)")
def load_system_msg(self):
with open("modules/core/system/system.msg", mode="r", encoding="utf-8") as f:
return hjson.load(f)
def expected_shutdown(self):
return self.setting_service.get("expected_shutdown")
@command(command="shutdown", params=[Any("reason", is_optional=True)], access_level="superadmin",
description="Shutdown the bot")
def shutdown_cmd(self, request, reason):
if request.channel not in [CommandService.ORG_CHANNEL, CommandService.PRIVATE_CHANNEL]:
request.reply(self._format_message(False, reason))
self.shutdown(False, reason)
@command(command="restart", params=[Any("reason", is_optional=True)], access_level="admin",
description="Restart the bot")
def restart_cmd(self, request, reason):
if request.channel not in [CommandService.ORG_CHANNEL, CommandService.PRIVATE_CHANNEL]:
request.reply(self._format_message(True, reason))
self.shutdown(True, reason)
@event(event_type="connect", description="Notify superadmin that bot has come online")
def connect_event(self, _, _1):
if self.expected_shutdown().get_value():
msg = self.getresp("module/system", "expected_online")
else:
self.logger.warning("The bot has recovered from an unexpected shutdown or restart")
msg = self.getresp("module/system", "unexpected_online")
self.bot.send_org_message(msg, fire_outgoing_event=False)
self.bot.send_private_channel_message(msg, fire_outgoing_event=False)
self.expected_shutdown().set_value(False)
def shutdown(self, should_restart, reason=None):
self.event_service.fire_event(self.SHUTDOWN_EVENT, DictObject({"restart": should_restart, "reason": reason}))
# set expected flag
self.expected_shutdown().set_value(True)
self.message_hub_service.send_message(self.MESSAGE_SOURCE, None, None,
self._format_message(should_restart, reason))
if should_restart:
self.bot.restart()
else:
self.bot.shutdown()
def _format_message(self, restart, reason):
if restart:
if reason:
return self.getresp("module/system", "restart") + self.getresp("module/system", "reason",
{"reason": reason})
return self.getresp("module/system", "restart") + ".."
if reason:
return self.getresp("module/system", "shutdown") + self.getresp("module/system", "reason",
{"reason": reason})
return self.getresp("module/system", "shutdown") + ".."
+134
View File
@@ -0,0 +1,134 @@
import html
import os
import platform
import sys
import time
import psutil
from core.chat_blob import ChatBlob
from core.command_param_types import Any, Character
from core.decorators import instance, command
from core.util import Util
@instance()
class UtilController:
def inject(self, registry):
self.bot = registry.get_instance("bot")
self.db = registry.get_instance("db")
self.util: Util = registry.get_instance("util")
self.command_service = registry.get_instance("command_service")
self.buddy_service = registry.get_instance("buddy_service")
self.access_service = registry.get_instance("access_service")
self.event_service = registry.get_instance("event_service")
self.public_channel_service = registry.get_instance("public_channel_service")
self.getresp = registry.get_instance("translation_service").get_response
@command(command="checkaccess", params=[Character("character")], access_level="moderator",
description="Check access level for a character", sub_command="other")
def checkaccess_other_cmd(self, _, char):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
return self.getresp("module/system", "check_access",
{"char": char.name,
"rank_main": char.access_level["label"]})
@command(command="checkaccess", params=[], access_level="member",
description="Check your access level")
def checkaccess_cmd(self, request):
char = request.sender
return self.getresp("module/system", "check_access",
{"char": char.name,
"rank_main": char.access_level["label"]})
@command(command="macro", params=[Any("command1|command2|command3...")], access_level="member",
description="Execute multiple commands at once")
def macro_cmd(self, request, commands):
commands = commands.split("|")
for command_str in commands:
self.command_service.process_command(
self.command_service.trim_command_symbol(command_str),
request.channel,
request.sender.char_id,
request.reply,
request.conn)
@command(command="echo", params=[Any("message")], access_level="member",
description="Echo back a message")
def echo_cmd(self, _, message):
return html.escape(message)
@command(command="showcommand", params=[Character("character"), Any("message")], access_level="admin",
description="Show command output to another character")
def showcommand_cmd(self, request, char, command_str):
if not char.char_id:
return self.getresp("global", "char_not_found", {"char": char.name})
self.bot.send_private_message(char.char_id, self.getresp("module/system", "show_output_target",
{"sender": request.sender.name,
"cmd": command_str}))
self.command_service.process_command(
self.command_service.trim_command_symbol(command_str),
request.channel,
request.sender.char_id,
lambda msg: self.bot.send_private_message(char.char_id, msg),
request.conn)
return self.getresp("module/system", "show_output_self",
{"target": char.name,
"cmd": command_str})
@command(command="system", params=[], access_level="admin",
description="Show system information")
def system_cmd(self, _):
pub_channels = ""
event_types = ""
access_levels = ""
bots_connected = ""
for _id, conn in self.bot.conns.items():
bots_connected += f"{_id} - {conn.char_name} ({conn.char_id})\n"
for channel_id, name in self.public_channel_service.get_all_public_channels().items():
pub_channels += "%s - <highlight>%d</highlight>\n" % (name, channel_id)
for event_type in self.event_service.get_event_types():
event_types += "%s\n" % event_type
for access_level in self.access_service.get_access_levels():
access_levels += "%s (%d)\n" % (access_level["label"], access_level["level"])
blob = self.getresp("module/system", "status_blob", {
"bot_ver": f"{self.bot.major_version}.{self.bot.minor_version}",
"os_ver": platform.system() + " " + platform.release(),
"python_ver": str(sys.version_info.major)
+ "." + str(sys.version_info.minor)
+ "." + str(sys.version_info.micro)
+ "." + sys.version_info.releaselevel,
"db_type": self.db.type if not self.db.MARIADB else f"{self.db.MARIADB} with "
f"{self.db.pool_size} active connections",
"mem_usage": self.util.format_number(psutil.Process(os.getpid()).memory_info().rss / 1024),
"superadmin": "Not Set",
"bl_used": self.buddy_service.get_buddy_list_size(),
"bl_size": self.buddy_service.buddy_list_size,
"uptime": self.util.time_to_readable(int(time.time()) - self.bot.start_time, max_levels=None),
"dim": self.bot.dimension,
"org_id": self.public_channel_service.org_id,
"org_name": self.public_channel_service.org_name,
"bots_connected": bots_connected,
"pub_channels": pub_channels,
"event_types": event_types,
"access_levels": access_levels
})
return ChatBlob(self.getresp("module/system", "status_title"), blob)
@command(command="htmldecode", params=[Any("command")], access_level="member",
description="Decode html entities from a command before passing to the bot for execution")
def htmldecode_cmd(self, request, command_str):
self.command_service.process_command(html.unescape(command_str), request.channel, request.sender.char_id,
request.reply, request.conn)