Fix for mrelay - some bots are sending malformed messages, caused by wrong usage of their modules.

Accessing an external service through the bot for gathering tower data is nolonger supported; and should be done via external scripts.
Fix for callers, and missing alias'es for loot tables.
!accounts will only show mains now.
the character order of all alt lists has been reversed: [main] high => low instead of [main] low => high
!account add <name> also marks accounts as type 0, if an account gets re-enabled. might cause strange behaviour with member-logs, if used in onlinebots.
Member type is being displayed in !account now. [Member (X)]
This commit is contained in:
2021-09-19 14:09:44 +02:00
parent 5acfb6866a
commit 810c2c8c4d
9 changed files with 69 additions and 111 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ class IgnCore:
self.last_timer_event = 0 self.last_timer_event = 0
self.start_time = int(time.time()) self.start_time = int(time.time())
self.major_version = "IGNCore v2.6" self.major_version = "IGNCore v2.6"
self.minor_version = "4" self.minor_version = "5"
self.incoming_queue = FifoQueue() self.incoming_queue = FifoQueue()
self.mass_message_queue = None self.mass_message_queue = None
self.conns = DictObject() self.conns = DictObject()
+15 -7
View File
@@ -51,12 +51,12 @@ class AccountController:
if main != entry.main: if main != entry.main:
main = entry.main main = entry.main
out += f"\n<{entry.faction.lower()}>{entry.name}</{entry.faction.lower()}>: " \ out += f"\n<{entry.faction.lower()}>{entry.name}</{entry.faction.lower()}>: " \
f"[{self.text.make_tellcmd('D', f'account {entry.name}')}]\n" f"[{self.text.make_tellcmd('D', f'account {entry.name}')}]"
main_name = entry.name main_name = entry.name
n = f"<red>N</red>" if entry.last_seen == 0 else "" # n = f"<red>N</red>" if entry.last_seen == 0 else ""
out += f"<tab> {self.util.get_prof_icon(entry.profession)}" \ # 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" {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" # f"<{entry.faction.lower()}>{entry.name}</{entry.faction.lower()}> {n}\n"
entries.append([main_name, out]) entries.append([main_name, out])
out = "" out = ""
msg = sorted(entries, key=lambda k: k[0]) msg = sorted(entries, key=lambda k: k[0])
@@ -81,12 +81,20 @@ class AccountController:
@command(command="account", params=[Const("add"), Character("char")], @command(command="account", params=[Const("add"), Character("char")],
access_level="moderator", access_level="moderator",
sub_command="moderate", description="Create a new account for given character") sub_command="moderate", description="Create a new account for given character",
extended_description="Potentially breaks in bots using a database generated by an onlinebot. "
"Use with extreme caution.")
def add_account(self, request: command_request, _, user): def add_account(self, request: command_request, _, user):
if not user.char_id: if not user.char_id:
return f"Character <highlight>{user.name}</highlight> not found." return f"Character <highlight>{user.name}</highlight> not found."
if self.account_service.get_account(user.char_id): if self.account_service.get_account(user.char_id):
# Unban the account, if banned
self.account_service.account_enable(user.char_id) self.account_service.account_enable(user.char_id)
# set the memberstatus to 0
# (-1 is no member,
# 0 is member,
# any number above 0 indicates that its an org_member of the same ID)
self.account_service.account_add_member(user.char_id)
self.buddy_service.add_buddy(user.char_id, "member") self.buddy_service.add_buddy(user.char_id, "member")
self.account_service.add_log(request.sender.char_id, "system", self.account_service.add_log(request.sender.char_id, "system",
f"Opened Account for <highlight>{user.name}</highlight>.", f"Opened Account for <highlight>{user.name}</highlight>.",
@@ -230,7 +238,7 @@ class AccountController:
if not mod: if not mod:
response += f" Options: {prefs}\n" response += f" Options: {prefs}\n"
response += f" Points: <notice>{alts[0].points}</notice>\n" response += f" Points: <notice>{alts[0].points}</notice>\n"
access_levels = {"Member": self.account_service.check_member(alts[0].char_id), access_levels = {f"Member ({alts[0].member})": self.account_service.check_member(alts[0].char_id),
"Officer": self.account_service.check_officer(alts[0].char_id), "Officer": self.account_service.check_officer(alts[0].char_id),
"General": self.account_service.check_general(alts[0].char_id), "General": self.account_service.check_general(alts[0].char_id),
"President": self.account_service.check_president(alts[0].char_id), "President": self.account_service.check_president(alts[0].char_id),
@@ -155,7 +155,7 @@ class AccountService:
def get_alts(self, char_id) -> List[DictObject]: def get_alts(self, char_id) -> List[DictObject]:
return self.db.query( return self.db.query(
"SELECT p.*, a.* from account a left join player p on a.char_id = p.char_id where " "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", "main=(SELECT main from account where char_id=?) ORDER BY a.main = a.char_id desc, p.level desc, p.name DESC",
[char_id]) [char_id])
acc_cache = {} acc_cache = {}
@@ -377,6 +377,11 @@ class AccountService:
"UPDATE account set disabled=0 where main in (SELECT main from account where char_id=?)", "UPDATE account set disabled=0 where main in (SELECT main from account where char_id=?)",
[char_id]) else False [char_id]) else False
def account_add_member(self, char_id) -> bool:
return True if self.db.exec(
"UPDATE account set member=0 where main in (SELECT main from account where char_id=?)",
[char_id]) else False
def remove_members(self, users) -> None: def remove_members(self, users) -> None:
if type(users) == list and len(users) > 0: if type(users) == list and len(users) > 0:
with self.db.pool.get_connection() as conn: with self.db.pool.get_connection() as conn:
@@ -337,6 +337,10 @@ class AllianceRelay:
if not message: if not message:
return return
org, name, text = message.groups() org, name, text = message.groups()
org = org.strip()
name = name.strip()
text = text.strip()
plain = f"[{org}] {name}: {text}" plain = f"[{org}] {name}: {text}"
org = self.format_text(self.relay_color_org().get_value().get(priv, self.relay_color_org().get_value().get("default")), org) org = self.format_text(self.relay_color_org().get_value().get(priv, self.relay_color_org().get_value().get("default")), org)
name = self.format_text(self.relay_color_sender().get_value().get(priv, self.relay_color_sender().get_value().get("default")), name) name = self.format_text(self.relay_color_sender().get_value().get(priv, self.relay_color_sender().get_value().get("default")), name)
@@ -458,7 +458,6 @@ class RaidbotController(BaseModule):
self.raid = None self.raid = None
self.leader.set_raid_leader(self.leader.leader, None) self.leader.set_raid_leader(self.leader.leader, None)
self.event_service.fire_event("RAID_ENDED")
return "Raid has ended, logs got saved. <notice>Raidleader cleared.<end>" return "Raid has ended, logs got saved. <notice>Raidleader cleared.<end>"
else: else:
@@ -346,17 +346,17 @@ class TowerAttackController:
def format_battle_info(self, row, t): def format_battle_info(self, row, t):
blob = "" blob = ""
defeated = " - <notice>Defeated!</notice>" if row.is_finished else "" defeated = " - <notice>Defeated!</notice>" if row.is_finished else ""
blob += "Site: <highlight>%s %s</highlight>\n" % (row.short_name, row.site_number or "?") blob += f"Site: <highlight>{row.short_name} {row.site_number or '?'}</highlight>\n"
blob += "Defender: <highlight>%s</highlight> (%s)%s\n" % (row.def_org_name, row.def_faction, defeated) blob += f"Defender: <highlight>{row.def_org_name}</highlight> ({row.def_faction}){defeated}\n"
blob += "Last Activity: %s\n" % self.format_timestamp(row.last_updated, t) blob += f"Last Activity: {self.format_timestamp(row.last_updated, t)}\n"
return blob return blob
def format_timestamp(self, t, current_t): def format_timestamp(self, t, current_t):
return "<highlight>%s</highlight> (%s ago)" % ( return f"<highlight>{self.util.format_datetime(t)}</highlight> " \
self.util.format_datetime(t), self.util.time_to_readable(current_t - t)) f"({self.util.time_to_readable(current_t - t)} ago)"
def get_chat_command(self, page): def get_chat_command(self, page):
return "/tell <myname> attacks --page=%d" % page return f"/tell <myname> attacks --page={page}"
def check_for_all_towers_channel(self): def check_for_all_towers_channel(self):
if not self.public_channel_service.get_channel_name(TowerController.ALL_TOWERS_ID): if not self.public_channel_service.get_channel_name(TowerController.ALL_TOWERS_ID):
+5 -65
View File
@@ -1,17 +1,12 @@
import sys
import time import time
from mysql.connector.cursor import CursorBase
from requests import Session
from conf.config import BotConfig
from core.aochat.BaseModule import BaseModule from core.aochat.BaseModule import BaseModule
from core.db import DB from core.db import DB
from core.decorators import instance, timerevent, event, setting from core.decorators import instance, event, setting
from core.igncore import IgnCore
from core.job_scheduler import JobScheduler from core.job_scheduler import JobScheduler
from core.setting_types import BooleanSettingType from core.setting_types import BooleanSettingType
from core.text import Text from core.text import Text
from core.igncore import IgnCore
from core.util import Util from core.util import Util
from modules.core.accounting.services.account_service import AccountService from modules.core.accounting.services.account_service import AccountService
from modules.raidbot.tower.tower_controller import TowerController from modules.raidbot.tower.tower_controller import TowerController
@@ -34,13 +29,6 @@ class TowerService(BaseModule):
self.job_scheduler: JobScheduler = registry.get_instance("job_scheduler") self.job_scheduler: JobScheduler = registry.get_instance("job_scheduler")
self.account_service: AccountService = registry.get_instance("account_service") self.account_service: AccountService = registry.get_instance("account_service")
mod = __import__(f'conf.{sys.argv[1]}', fromlist=['BotConfig'])
config: BotConfig = getattr(mod, 'BotConfig')
if hasattr(config, "tower_url"):
self.tower_url = config.tower_url
else:
self.tower_url = None
def pre_start(self): def pre_start(self):
self.db.shared.exec("CREATE TABLE IF NOT EXISTS towers(" self.db.shared.exec("CREATE TABLE IF NOT EXISTS towers("
"pf_id int not null, " "pf_id int not null, "
@@ -90,6 +78,9 @@ class TowerService(BaseModule):
self.prepare_nw_warn(row.pf_id, row.site) self.prepare_nw_warn(row.pf_id, row.site)
elif event_data.type == "terminated": elif event_data.type == "terminated":
# DEBUG: terminated sites.. behave strange.
# for that reason, we'll just output these events to the console, but not the log.
print(event_data)
field = self.db.query("SELECT * FROM towers t where t.org_name=? and t.pf_id=?", field = self.db.query("SELECT * FROM towers t where t.org_name=? and t.pf_id=?",
[event_data.loser.org_name, event_data.playfield.id]) [event_data.loser.org_name, event_data.playfield.id])
if len(field) == 1: if len(field) == 1:
@@ -115,57 +106,6 @@ class TowerService(BaseModule):
self.prepare_nw_warn(event_data.playfield.id, "(<red>UKN</red>)", self.prepare_nw_warn(event_data.playfield.id, "(<red>UKN</red>)",
f"(<red>UKN</red>) PO: {event_data.loser.org_name}|{event_data.loser.faction}") f"(<red>UKN</red>) PO: {event_data.loser.org_name}|{event_data.loser.faction}")
@timerevent(budatime="4h", description="fetch the towerAPI cache", is_enabled=False)
def fetch_tower_update(self, _1, _2):
if self.tower_url:
#
# ONLY Access the API's via Tor... Tyrence is shadow-banning every IP Subnet
# accessing it multiple times/hour whenever the limit parameter is being used....
# ?limit is a parameter not documented anywhere, but allows pulling the whole list
# On other API implementations this parameter has no effect,
# as the server always responds with the full list.
# In the Future, this Event will get moved to an external process,
# which maintains the tower Cache for all connected bots,
# like it has been done with the Worldboss timers.
from torpy.http.requests import TorRequests
with TorRequests() as tor_request:
with tor_request.get_session(1) as session:
session: Session
r = session.get(self.tower_url)
if not r:
return
if data := r.json()["results"]:
blob = []
for row in data:
blob.append((row["playfield_id"], row["site_number"], row["ql"],
row["x_coord"], row["y_coord"],
row["org_id"], row['org_name'], row['faction'],
row["close_time"], row["created_at"],
row["enabled"]))
with self.db.lock:
with self.db.pool.get_connection() as conn:
with conn.cursor(dictionary=True) as cur:
cur: CursorBase
cur.executemany(
"INSERT INTO towers (pf_id, site_number, "
"ql, x_coord, y_coord, org_id, org_name, "
"faction, close_time, planted, enabled) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE "
"org_id=VALUE(org_id), "
"org_name=VALUE(org_name), "
"faction=VALUE(faction), "
"close_time=VALUE(close_time),"
"ql=VALUE(ql),"
"planted=VALUE(planted) ", blob)
rem = []
for key, value in enumerate(sorted(self.attack_hot, key=lambda k: k['hot'])):
if value['hot'] < time.time():
rem.append(key)
for key in reversed(rem):
self.attack_hot.pop(key)
def day_time(self, day_t): def day_time(self, day_t):
if day_t > 86400: if day_t > 86400:
day_t -= 86400 day_t -= 86400
+27 -25
View File
@@ -99,6 +99,9 @@ class LootListsController:
self.command_alias_service.add_alias("taurus", "pande taurus") self.command_alias_service.add_alias("taurus", "pande taurus")
self.command_alias_service.add_alias("sagittarius", "pande sagittarius") self.command_alias_service.add_alias("sagittarius", "pande sagittarius")
self.command_alias_service.add_alias("tnh", "pande tnh") self.command_alias_service.add_alias("tnh", "pande tnh")
self.command_alias_service.add_alias("barmor", "pande barmor")
self.command_alias_service.add_alias("bstars", "pande bstars")
self.command_alias_service.add_alias("bweapons", "pande bweapons")
self.command_alias_service.add_alias("s7", "apf s7") self.command_alias_service.add_alias("s7", "apf s7")
self.command_alias_service.add_alias("s13", "apf s13") self.command_alias_service.add_alias("s13", "apf s13")
@@ -138,9 +141,9 @@ class LootListsController:
items = self.get_items("APF", category) items = self.get_items("APF", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "APF", category, add_all)) return ChatBlob(f"{category} loot table", self.build_list(items, "APF", category, add_all))
else: else:
return "No loot registered for <highlight>%s<end>." % category return f"No loot registered for <highlight>{category}<end>."
@command(command="apf", params=[], description="Get list of items from APF", access_level="member") @command(command="apf", params=[], description="Get list of items from APF", access_level="member")
def apf_tables_cmd(self, _): def apf_tables_cmd(self, _):
@@ -162,9 +165,9 @@ class LootListsController:
items = self.get_items("Albtraum", category) items = self.get_items("Albtraum", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "Albtraum", category)) return ChatBlob(f"{category} loot table", self.build_list(items, "Albtraum", category))
else: else:
return "No loot registered for <highlight>%s<end>." % category return f"No loot registered for <highlight>{category}<end>."
# # # #
# Pandemonium # # Pandemonium #
@@ -180,9 +183,9 @@ class LootListsController:
items = self.get_items("Pande", category) items = self.get_items("Pande", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "Pande", category)) return ChatBlob(f"{category} loot table", self.build_list(items, "Pande", category))
else: else:
return "No loot registered for <highlight>%s<end>." % category_name return f"No loot registered for <highlight>{category_name}<end>."
@command(command="pande", params=[], description="Get list of items from Pandemonium", access_level="member") @command(command="pande", params=[], description="Get list of items from Pandemonium", access_level="member")
def pande_tables_cmd(self, _): def pande_tables_cmd(self, _):
@@ -197,9 +200,9 @@ class LootListsController:
category = self.get_real_category_name(category) category = self.get_real_category_name(category)
items = self.get_items("DustBrigade", category) items = self.get_items("DustBrigade", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "DustBrigade", category)) return ChatBlob(f"{category} loot table", self.build_list(items, "DustBrigade", category))
else: else:
return "No loot registered for <highlight>%s<end>." % category return f"No loot registered for <highlight>{category}<end>."
@command(command="db", params=[], description="Get list of items from DustBrigade", access_level="member") @command(command="db", params=[], description="Get list of items from DustBrigade", access_level="member")
def db_tables_cmd(self, _): def db_tables_cmd(self, _):
@@ -222,7 +225,7 @@ class LootListsController:
else: else:
blob += self.build_list(self.get_items(category, sub), category, sub) blob += self.build_list(self.get_items(category, sub), category, sub)
return ChatBlob("%s loot table" % category, blob) return ChatBlob(f"{category} loot table", blob)
@command(command="xan", params=[], description="Get list of items from Xan", access_level="member") @command(command="xan", params=[], description="Get list of items from Xan", access_level="member")
def xan_tables_cmd(self, _): def xan_tables_cmd(self, _):
@@ -230,14 +233,14 @@ class LootListsController:
raids = ["Mitaar", "Vortexx", "12Man"] raids = ["Mitaar", "Vortexx", "12Man"]
for raid in raids: for raid in raids:
show_loot = self.text.make_chatcmd( show_loot = self.text.make_chatcmd("Loot table",
"Loot table", "/tell <myname> xan %s" % self.get_real_category_name(raid, True)) f"/tell <myname> xan {self.get_real_category_name(raid, True)}")
sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE raid = ?" sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE raid = ?"
count = self.db.query_single(sql, [raid]).count count = self.db.query_single(sql, [raid]).count
blob += "%s - %s items\n" % (raid, count) blob += f"{raid} - {count} items\n"
blob += " └ [%s]\n\n" % show_loot blob += f" └ [{show_loot}]\n\n"
return ChatBlob("Xan loot tables", blob) return ChatBlob("Xan loot tables", blob)
@@ -250,9 +253,9 @@ class LootListsController:
category = self.get_real_category_name(category_name) category = self.get_real_category_name(category_name)
items = self.get_items("Pyramid of Home", category) items = self.get_items("Pyramid of Home", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "poh", category)) return ChatBlob(f"{category} loot table", self.build_list(items, "poh", category))
else: else:
return "No loot registered for <highlight>%s<end>." % category_name return f"No loot registered for <highlight>{category_name}<end>."
@command(command="poh", params=[], description="Get list of items from Pyramid of Home", access_level="member") @command(command="poh", params=[], description="Get list of items from Pyramid of Home", access_level="member")
def poh_tables_cmd(self, _): def poh_tables_cmd(self, _):
@@ -270,9 +273,9 @@ class LootListsController:
category = self.get_real_category_name(category_name) category = self.get_real_category_name(category_name)
items = self.get_items("Temple of Three Winds (HL)", category) items = self.get_items("Temple of Three Winds (HL)", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "totwh", category)) return ChatBlob(f"{category} loot table", self.build_list(items, "totwh", category))
else: else:
return "No loot registered for <highlight>%s<end>." % category_name return f"No loot registered for <highlight>{category_name}<end>."
@command(command="totwh", params=[], description="Get list of items from Temple of Three Winds", access_level="member") @command(command="totwh", params=[], description="Get list of items from Temple of Three Winds", access_level="member")
def totwh_tables_cmd(self, _): def totwh_tables_cmd(self, _):
@@ -289,9 +292,9 @@ class LootListsController:
category = self.get_real_category_name(category_name) category = self.get_real_category_name(category_name)
items = self.get_items("Condemned Subway (HL)", category) items = self.get_items("Condemned Subway (HL)", category)
if items: if items:
return ChatBlob("%s loot table" % category, self.build_list(items, "subh", category)) return ChatBlob(f"{category} loot table", self.build_list(items, "subh", category))
else: else:
return "No loot registered for <highlight>%s<end>." % category_name return f"No loot registered for <highlight>{category_name}<end>."
@command(command="subh", params=[], description="Get list of items from Condemned Subway (HL)", @command(command="subh", params=[], description="Get list of items from Condemned Subway (HL)",
access_level="member") access_level="member")
@@ -305,13 +308,13 @@ class LootListsController:
raids = self.db.query(sql, [name]) raids = self.db.query(sql, [name])
for raid in raids: for raid in raids:
show_loot = self.text.make_chatcmd( show_loot = self.text.make_chatcmd(
"Loot table", "/tell <myname> %s %s" % (cmd, self.get_real_category_name(raid.category, True))) "Loot table", f"/tell <myname> {cmd} {self.get_real_category_name(raid.category, True)}")
sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE category = ? and raid=?" sql = "SELECT COUNT(*) AS count FROM raid_loot WHERE category = ? and raid=?"
count = self.db.query_single(sql, [raid.category, name]).count count = self.db.query_single(sql, [raid.category, name]).count
blob += "<highlight>%s<end> - %s items\n" % (raid.category, count) blob += f"<highlight>{raid.category}<end> - {count} items\n"
blob += " └ [%s]\n\n" % show_loot blob += f" └ [{show_loot}]\n\n"
blob += "<pagebreak>" blob += "<pagebreak>"
if not name and not cmd: if not name and not cmd:
raids = [ raids = [
@@ -347,10 +350,9 @@ class LootListsController:
blob = "" blob = ""
if add_all: if add_all:
blob += "%s items to loot list\n\n" % self.text.make_chatcmd( blob += f"{self.text.make_chatcmd('Add all', f'/tell <myname> loot addraid {raid} {category}')} items to loot list\n\n"
"Add all", f"/tell <myname> loot addraid {raid} {category}")
blob += "<header2>%s<end>\n" % category if category is not None else "" blob += f"<header2>{category}<end>\n" if category is not None else ""
for item in items: for item in items:
comment = f" ({item.comment})" if item.comment != "" else "" comment = f" ({item.comment})" if item.comment != "" else ""
+5 -5
View File
@@ -31,9 +31,9 @@ class AssistController:
def assist_command(self, _): def assist_command(self, _):
blob = "" blob = ""
for caller in self.assist: for caller in self.assist:
blob += caller blob += caller.capitalize()
blob += f" - [{self.text.make_chatcmd('assist', f'assist {caller}')}]" blob += f" - [{self.text.make_chatcmd('assist', f'assist {caller}')}]"
blob += f"[{self.text.make_tellcmd('REM', f'assist del {caller}')}]<br>" blob += f" [{self.text.make_tellcmd('REM', f'assist del {caller}')}]<br>"
blob += self.get_assist_output() blob += self.get_assist_output()
self.last_mod = time.time() self.last_mod = time.time()
return ChatBlob(f"Callers ({len(self.assist)})", blob) return ChatBlob(f"Callers ({len(self.assist)})", blob)
@@ -57,7 +57,7 @@ class AssistController:
return "No assist targets set." return "No assist targets set."
self.last_mod = time.time() self.last_mod = time.time()
try: try:
self.assist.remove(char.name) self.assist.remove(char.name.lower())
return f"<highlight>{char.name}</highlight> is no longer a caller. " \ return f"<highlight>{char.name}</highlight> is no longer a caller. " \
f"Use: <highlight>{self.get_assist_output()}</highlight>" f"Use: <highlight>{self.get_assist_output()}</highlight>"
except ValueError: except ValueError:
@@ -73,8 +73,8 @@ class AssistController:
if not self.leader_controller.can_use_command(request.sender.char_id): if not self.leader_controller.can_use_command(request.sender.char_id):
return LeaderController.NOT_LEADER_MSG return LeaderController.NOT_LEADER_MSG
for caller in targets: for caller in targets:
if caller not in self.assist: if caller.lower() not in self.assist:
self.assist.append(caller) self.assist.append(caller.lower())
return self.assist_command(request) return self.assist_command(request)
def get_assist_output(self): def get_assist_output(self):