refactored trigger

This commit is contained in:
iou1name 2020-01-07 18:58:19 -05:00
parent 7159deb7f8
commit 1b71e73757
47 changed files with 297 additions and 441 deletions

22
bot.py
View File

@ -166,10 +166,10 @@ class Fulvia(irc.IRCClient):
self.commands.pop(command) self.commands.pop(command)
if func.hook: if func.hook:
self._hooks.remove(func.__name__) self._hooks.remove(func)
if func.rate or func.channel_rate or func.global_rate: if func.rate or func.channel_rate or func.global_rate:
self._times.pop(func.__name__) self._times.pop(func)
if hasattr(func, 'url_callback'): if hasattr(func, 'url_callback'):
for url in func.url_callback: for url in func.url_callback:
@ -253,26 +253,26 @@ class Fulvia(irc.IRCClient):
funcs += self._hooks funcs += self._hooks
for func in funcs: for func in funcs:
trigger = Trigger(user, channel, message, "PRIVMSG") trigger = Trigger(user, channel, message)
bot = FulviaWrapper(self, trigger) bot = FulviaWrapper(self, trigger)
if func.rate: if func.rate:
t = self._times[func_name].get(trigger.nick, 0) t = self._times[func.__name__].get(trigger.nick, 0)
if time.time() - t < func.rate and not trigger.admin: if time.time() - t < func.rate and not trigger.admin:
return return
self._times[func_name][trigger.nick] = time.time() self._times[func.__name__][trigger.nick] = time.time()
if func.channel_rate: if func.channel_rate:
t = self._times[func_name].get(trigger.channel, 0) t = self._times[func.__name__].get(trigger.channel, 0)
if time.time() - t < func.channel_rate and not trigger.admin: if time.time() - t < func.channel_rate and not trigger.admin:
return return
self._times[func_name][trigger.channel] = time.time() self._times[func.__name__][trigger.channel] = time.time()
if func.global_rate: if func.global_rate:
t = self._times[func_name].get("global", 0) t = self._times[func.__name__].get("global", 0)
if time.time() - t < func.channel_rate and not trigger.admin: if time.time() - t < func.channel_rate and not trigger.admin:
return return
self._times[func_name]["global"] = time.time() self._times[func.__name__]["global"] = time.time()
if func.thread == True: if func.thread == True:
t = threading.Thread(target=self.call,args=(func, bot, trigger)) t = threading.Thread(target=self.call,args=(func, bot, trigger))
@ -394,9 +394,9 @@ class Fulvia(irc.IRCClient):
self.channels[channel].users[nick] = new_user self.channels[channel].users[nick] = new_user
for func in self._user_joined: for func in self._user_joined:
trigger = Trigger(user, channel, f"{user} has joined", "PRIVMSG") trigger = Trigger(user, channel, f"{user} has joined")
bot = FulviaWrapper(self, trigger) bot = FulviaWrapper(self, trigger)
t = threading.Thread(target=self.call,args=(func, bot, trigger)) t = threading.Thread(target=self.call, args=(func, bot, trigger))
t.start() t.start()

View File

@ -10,7 +10,8 @@ import module
@module.example('.join #example or .join #example key') @module.example('.join #example or .join #example key')
def join(bot, trigger): def join(bot, trigger):
"""Join the specified channel. This is an admin-only command.""" """Join the specified channel. This is an admin-only command."""
channel, key = trigger.group(3), trigger.group(4) channel = trigger.args[1] if len(trigger.args) >= 2 else None
key = trigger.args[2] if len(trigger.args) >= 3 else None
if not channel: if not channel:
return return
elif not key: elif not key:
@ -24,7 +25,14 @@ def join(bot, trigger):
@module.example('.part #example') @module.example('.part #example')
def part(bot, trigger): def part(bot, trigger):
"""Part the specified channel. This is an admin-only command.""" """Part the specified channel. This is an admin-only command."""
channel, _, part_msg = trigger.group(2).partition(' ') if len(trigger.args) < 2:
return
channel = trigger.args[1]
if len(trigger.args) >= 3:
part_msg = ' '.join(trigger.args[2:])
else:
part_msg = None
if not channel.startswith("#"): if not channel.startswith("#"):
part_msg = channel part_msg = channel
channel = "" channel = ""
@ -40,8 +48,9 @@ def part(bot, trigger):
@module.commands('quit') @module.commands('quit')
def quit(bot, trigger): def quit(bot, trigger):
"""Quit from the server. This is an owner-only command.""" """Quit from the server. This is an owner-only command."""
quit_message = trigger.group(2) if len(trigger.args) >= 2:
if not quit_message: quit_message = ' '.join(trigger.args[1:])
else:
quit_message = f"Quitting on command from {trigger.nick}" quit_message = f"Quitting on command from {trigger.nick}"
bot.quit(quit_message) bot.quit(quit_message)
@ -54,13 +63,10 @@ def msg(bot, trigger):
""" """
Send a message to a given channel or nick. Can only be done by an admin. Send a message to a given channel or nick. Can only be done by an admin.
""" """
if trigger.group(2) is None: if len(trigger.args) < 3:
return
channel, _, message = trigger.group(2).partition(' ')
message = message.strip()
if not channel or not message:
return return
channel = trigger.args[1]
message = ' '.join(trigger.args[2:])
bot.msg(channel, message) bot.msg(channel, message)
@ -73,13 +79,10 @@ def me(bot, trigger):
Send an ACTION (/me) to a given channel or nick. Can only be done by an Send an ACTION (/me) to a given channel or nick. Can only be done by an
admin. admin.
""" """
if trigger.group(2) is None: if len(trigger.args) < 3:
return
channel, _, action = trigger.group(2).partition(' ')
action = action.strip()
if not channel or not action:
return return
channel = trigger.args[1]
action = ' '.join(trigger.args[2:])
# msg = '\x01ACTION %s\x01' % action # msg = '\x01ACTION %s\x01' % action
bot.describe(channel, action) bot.describe(channel, action)
@ -90,6 +93,8 @@ def me(bot, trigger):
@module.example(".mode +B") @module.example(".mode +B")
def self_mode(bot, trigger): def self_mode(bot, trigger):
"""Set a user mode on Fulvia. Can only be done in privmsg by an admin.""" """Set a user mode on Fulvia. Can only be done in privmsg by an admin."""
mode = trigger.group(3) if len(trigger.args) < 2:
return
mode = trigger.args[1]
add_mode = mode.startswith("+") add_mode = mode.startswith("+")
bot.mode(bot.nickname, add_mode, mode) bot.mode(bot.nickname, add_mode, mode)

View File

@ -21,11 +21,12 @@ def kick(bot, trigger):
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].users[bot.nick].op_level not in op:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Who do you want me to kick?") return bot.reply("Who do you want me to kick?")
target = trigger.args[1]
target, _, reason = trigger.group(2).partition(" ") if len(trigger.args) >= 3:
if not reason: reason = ' '.join(trigger.args[2:])
else:
reason = "Stop doing the bad." reason = "Stop doing the bad."
if target == bot.nick: if target == bot.nick:
@ -45,10 +46,10 @@ def ban(bot, trigger):
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].users[bot.nick].op_level not in op:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Who do you want me to ban?") return bot.reply("Who do you want me to ban?")
banmask = configureHostMask(trigger.group(2)) banmask = configureHostMask(trigger.args[1])
bot.mode(trigger.channel, True, "b", mask=banmask) bot.mode(trigger.channel, True, "b", mask=banmask)
@ -63,10 +64,10 @@ def unban(bot, trigger):
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].users[bot.nick].op_level not in op:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Who do you want me to ban?") return bot.reply("Who do you want me to ban?")
banmask = configureHostMask(trigger.group(2)) banmask = configureHostMask(trigger.args[1])
bot.mode(trigger.channel, False, "b", mask=banmask) bot.mode(trigger.channel, False, "b", mask=banmask)
@ -81,17 +82,18 @@ def kickban(bot, trigger):
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].users[bot.nick].op_level not in op:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Who do you want me to ban?") return bot.reply("Who do you want me to ban?")
target = trigger.args[1]
target, _, reason = trigger.group(2).partition(" ") if len(trigger.args) >= 3:
if not reason: reason = ' '.join(trigger.args[2:])
else:
reason = "Stop doing the bad." reason = "Stop doing the bad."
if target == bot.nick: if target == bot.nick:
return bot.reply("I can't let you do that.") return bot.reply("I can't let you do that.")
banmask = configureHostMask(trigger.group(2).strip()) banmask = configureHostMask(trigger.args[1])
bot.mode(trigger.channel, False, "b", mask=banmask) bot.mode(trigger.channel, False, "b", mask=banmask)
bot.kick(trigger.channel, target, reason) bot.kick(trigger.channel, target, reason)
@ -107,7 +109,7 @@ def settopic(bot, trigger):
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].users[bot.nick].op_level not in op:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("What do you want the topic set to?") return bot.reply("What do you want the topic set to?")
bot.topic(trigger.channel, trigger.group(2).strip()) bot.topic(trigger.channel, ' '.join(trigger.args[1:]))

View File

@ -2,18 +2,18 @@
""" """
Sends a message to all channels the bot is currently in. Sends a message to all channels the bot is currently in.
""" """
from module import commands, example from module import commands, example, require_admin
@require_admin
@commands('announce') @commands('announce')
@example('.announce Some important message here') @example('.announce Some important message here')
def announce(bot, trigger): def announce(bot, trigger):
""" """
Send an announcement to all channels the bot is in. Send an announcement to all channels the bot is in.
""" """
if not trigger.admin: if len(trigger.args) < 2:
bot.reply("Sorry, I can't let you do that") return bot.reply("What message?")
return
for channel in bot.channels.keys(): for channel in bot.channels.keys():
bot.msg(channel, f"[ANNOUNCEMENT] {trigger.group(2)}") bot.msg(channel, f"[ANNOUNCEMENT] {trigger.args[1]}")
bot.reply('Announce complete.') bot.reply('Announce complete.')

View File

@ -233,7 +233,7 @@ def ascii(bot, trigger):
""" """
Downloads an image and converts it to ascii. Downloads an image and converts it to ascii.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.msg() return bot.msg()
parser = argparse.ArgumentParser(add_help=False) parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("imagePath") parser.add_argument("imagePath")
@ -243,7 +243,7 @@ def ascii(bot, trigger):
parser.add_argument("-B", "--brail2", action="store_true") parser.add_argument("-B", "--brail2", action="store_true")
parser.add_argument("-a", "--animated", action="store_true") parser.add_argument("-a", "--animated", action="store_true")
parser.add_argument("-h", "--help", action="store_true") parser.add_argument("-h", "--help", action="store_true")
args = parser.parse_args(trigger.group(2).split()) args = parser.parse_args(trigger.args[1:])
if args.help: if args.help:
return bot.msg(parser.print_help()) return bot.msg(parser.print_help())

View File

@ -16,10 +16,10 @@ def away(bot, trigger):
""" """
Stores in the user's name and away message in memory. Stores in the user's name and away message in memory.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
bot.memory['away'][trigger.nick] = "" bot.memory['away'][trigger.nick] = ""
else: else:
bot.memory['away'][trigger.nick] = trigger.group(2) bot.memory['away'][trigger.nick] = ' '.join(trigger.args[1:])
@hook(True) @hook(True)
@ -27,7 +27,7 @@ def message(bot, trigger):
""" """
If an away users name is said, print their away message. If an away users name is said, print their away message.
""" """
name = trigger.group(1) name = trigger.args[0]
if name.endswith(":") or name.endswith(","): if name.endswith(":") or name.endswith(","):
name = name[:-1] name = name[:-1]
if name in bot.memory["away"]: if name in bot.memory["away"]:
@ -41,6 +41,6 @@ def notAway(bot, trigger):
""" """
If an away user says something, remove them from the away dict. If an away user says something, remove them from the away dict.
""" """
if not trigger.group(0).startswith(".away"): if not trigger.args[0] == ".away":
if trigger.nick in bot.memory["away"]: if trigger.nick in bot.memory["away"]:
bot.memory["away"].pop(trigger.nick) bot.memory["away"].pop(trigger.nick)

View File

@ -16,7 +16,8 @@ def banhe(bot, trigger):
Bans he for a set period of time. Admins may set the period of time, Bans he for a set period of time. Admins may set the period of time,
non-admins only get 20 second bans. non-admins only get 20 second bans.
""" """
banhee, period = trigger.group(3), trigger.group(4) banhee = trigger.args[1] if len(trigger.args) >= 2 else ''
period = trigger.args[2] if len(trigger.args) >= 3 else ''
if not trigger.admin: if not trigger.admin:
period = 20 period = 20
@ -24,7 +25,7 @@ def banhe(bot, trigger):
conv = {'s':1, 'm':60, 'h':3600, 'd':86400} conv = {'s':1, 'm':60, 'h':3600, 'd':86400}
try: try:
period = conv[period[-1]] * int(period[:-1]) period = conv[period[-1]] * int(period[:-1])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError, IndexError):
period = 0 period = 0
banmask = configureHostMask(banhee) banmask = configureHostMask(banhee)
@ -47,7 +48,7 @@ def banheall(bot, trigger):
""" """
Ban them all, Johnny. Ban them all, Johnny.
""" """
period = trigger.group(2) period = trigger.args[1] if len(trigger.args) >= 2 else '20s'
conv = {'s':1, 'm':60, 'h':3600, 'd':86400} conv = {'s':1, 'm':60, 'h':3600, 'd':86400}
try: try:
period = conv[period[-1]] * int(period[:-1]) period = conv[period[-1]] * int(period[:-1])

View File

@ -15,10 +15,9 @@ BASE_TUMBOLIA_URI = 'https://tumbolia-two.appspot.com/'
@example('.c 5 + 3', '8') @example('.c 5 + 3', '8')
def c(bot, trigger): def c(bot, trigger):
"""Evaluate some calculation.""" """Evaluate some calculation."""
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Nothing to calculate.") return bot.reply("Nothing to calculate.")
# Account for the silly non-Anglophones and their silly radix point. eqn = ' '.join(trigger.args[1:])
eqn = trigger.group(2).replace(',', '.')
try: try:
result = eval_equation(eqn) result = eval_equation(eqn)
result = "{:.10g}".format(result) result = "{:.10g}".format(result)
@ -33,10 +32,10 @@ def c(bot, trigger):
@example('.py len([1,2,3])', '3') @example('.py len([1,2,3])', '3')
def py(bot, trigger): def py(bot, trigger):
"""Evaluate a Python expression.""" """Evaluate a Python expression."""
if not trigger.group(2): if len(trigger.args) < 2:
return bot.msg("Need an expression to evaluate") return bot.msg("Need an expression to evaluate")
query = trigger.group(2) query = trigger.args[1]
uri = BASE_TUMBOLIA_URI + 'py/' uri = BASE_TUMBOLIA_URI + 'py/'
res = requests.get(uri + query) res = requests.get(uri + query)
res.raise_for_status() res.raise_for_status()

View File

@ -14,9 +14,9 @@ def generic_countdown(bot, trigger):
""" """
.countdown <year> <month> <day> - displays a countdown to a given date. .countdown <year> <month> <day> - displays a countdown to a given date.
""" """
text = trigger.group(2) if len(trigger.args) < 2:
if not text:
return bot.msg("Please use correct format: .countdown 2012 12 21") return bot.msg("Please use correct format: .countdown 2012 12 21")
text = trigger.args[1]
text = text.split() text = text.split()
if (len(text) != 3 or not text[0].isdigit() or not text[1].isdigit() if (len(text) != 3 or not text[0].isdigit() or not text[1].isdigit()
@ -28,5 +28,5 @@ def generic_countdown(bot, trigger):
return bot.msg("Please use correct format: .countdown 2012 12 21") return bot.msg("Please use correct format: .countdown 2012 12 21")
msg = relativeTime(datetime.now(), date) msg = relativeTime(datetime.now(), date)
msg += " until " + trigger.group(2) msg += " until " + trigger.args[1]
bot.msg(msg) bot.msg(msg)

View File

@ -23,7 +23,9 @@ def crypto(bot, trigger):
"key": config.coinlib_api_key, "key": config.coinlib_api_key,
"pref": "USD", "pref": "USD",
} }
symbol = trigger.group(3) if len(trigger.args) < 2:
return bot.reply("What coin?")
symbol = trigger.args[1]
if symbol: if symbol:
params["symbol"] = symbol params["symbol"] = symbol
res = requests.get(URI + "/coin", params=params) res = requests.get(URI + "/coin", params=params)

View File

@ -19,14 +19,12 @@ def exchange(bot, trigger):
Supported currencies: https://www.exchangerate-api.com/supported-currencies Supported currencies: https://www.exchangerate-api.com/supported-currencies
""" """
amount = trigger.group(3) if len(trigger.args) < 5:
cur_from = trigger.group(4) return bot.reply("Insuffcient arguments.")
cur_to = trigger.group(5) amount = trigger.args[1]
if cur_to == "to": cur_from = trigger.args[2]
cur_to = trigger.group(6) cur_to = trigger.args[4]
if not all((amount, cur_to, cur_from)):
return bot.reply("I didn't understand that. Try: .cur 20 EUR to USD")
try: try:
amount = float(amount) amount = float(amount)
except ValueError: except ValueError:
@ -58,10 +56,11 @@ def bitcoin(bot, trigger):
Show the current bitcoin value in USD. Optional parameter allows non-USD Show the current bitcoin value in USD. Optional parameter allows non-USD
conversion. conversion.
""" """
cur_to = trigger.group(3) if len(trigger.args) < 2:
if not cur_to:
cur_to = "USD" cur_to = "USD"
cur_to = cur_to.upper() else:
cur_to = trigger.args[1]
cur_to = cur_to.upper()
url = BTC_URI.format(**{"CUR_TO": cur_to}) url = BTC_URI.format(**{"CUR_TO": cur_to})
res = requests.get(url, verify=True) res = requests.get(url, verify=True)

View File

@ -178,9 +178,9 @@ def roll(bot, trigger):
# Get a list of all dice expressions, evaluate them and then replace the # Get a list of all dice expressions, evaluate them and then replace the
# expressions in the original string with the results. Replacing is done # expressions in the original string with the results. Replacing is done
# using string formatting, so %-characters must be escaped. # using string formatting, so %-characters must be escaped.
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("No dice to roll.") return bot.reply("No dice to roll.")
arg_str = trigger.group(2) arg_str = ' '.join(trigger.args[1:])
dice_expressions = re.findall(dice_regexp, arg_str) dice_expressions = re.findall(dice_regexp, arg_str)
arg_str = arg_str.replace("%", "%%") arg_str = arg_str.replace("%", "%%")
arg_str = re.sub(dice_regexp, "%s", arg_str) arg_str = re.sub(dice_regexp, "%s", arg_str)
@ -215,7 +215,7 @@ def roll(bot, trigger):
return return
bot.reply("You roll %s: %s = %d" % ( bot.reply("You roll %s: %s = %d" % (
trigger.group(2), pretty_str, result)) ' '.join(trigger.args[1:]), pretty_str, result))
@module.commands("choice", "choose") @module.commands("choice", "choose")
@ -224,16 +224,17 @@ def choose(bot, trigger):
""" """
.choice option1|option2|option3 - Makes a difficult choice easy. .choice option1|option2|option3 - Makes a difficult choice easy.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply('I\'d choose an option, but you didn\'t give me any.') return bot.reply("I'd choose an option, but you didn't give me any.")
choices = [trigger.group(2)] msg = ' '.join(trigger.args[1:])
choices = [msg]
for delim in '|\\/,': for delim in '|\\/,':
choices = trigger.group(2).split(delim) choices = msg.split(delim)
if len(choices) > 1: if len(choices) > 1:
break break
# Use a different delimiter in the output, to prevent ambiguity. # Use a different delimiter in the output, to prevent ambiguity.
for show_delim in ',|/\\': for show_delim in ',|/\\':
if show_delim not in trigger.group(2): if show_delim not in msg:
show_delim += ' ' show_delim += ' '
break break

View File

@ -8,5 +8,5 @@ from module import commands, example
@example('.echo balloons') @example('.echo balloons')
def echo(bot, trigger): def echo(bot, trigger):
"""Echos the given string.""" """Echos the given string."""
if trigger.group(2): if len(trigger.args) >= 2:
bot.msg(trigger.group(2)) bot.msg(' '.join(trigger.args[1:]))

View File

@ -18,8 +18,8 @@ def grog(bot, trigger):
data = file.read().splitlines() data = file.read().splitlines()
num = None num = None
try: try:
num = int(trigger.group(2)) - 1 num = int(trigger.args[1]) - 1
except: except (IndexError, ValueError):
pass pass
if num == None: if num == None:
num = random.randint(0, len(data)-1) num = random.randint(0, len(data)-1)
@ -38,11 +38,11 @@ def magic(bot, trigger):
data = file.read().splitlines() data = file.read().splitlines()
num = None num = None
try: try:
num = int(trigger.group(2)) - 1 num = int(trigger.args[1]) - 1
except: except (IndexError, ValueError):
pass pass
if num == None: if num == None:
num = random.randint(0, len(data)-1) num = random.randint(0, len(data)-1)
if num >= len(data)-1: if num >= len(data)-1:
num = len(data)-1 num = len(data)-1
bot.msg(data[num]) bot.msg(data[num])

View File

@ -40,10 +40,10 @@ def hangman(bot, trigger):
Plays hangman. --start [-s] to start a new game, otherwise words are Plays hangman. --start [-s] to start a new game, otherwise words are
taken as attempts to solve and single characters are taken as guesses. taken as attempts to solve and single characters are taken as guesses.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Hang what?") return bot.reply("Hang what?")
if trigger.group(3) == "--start" or trigger.group(3) == "-s": if trigger.args[1] == "--start" or trigger.args[1] == "-s":
if bot.memory["hangman"].get(trigger.channel): if bot.memory["hangman"].get(trigger.channel):
return bot.reply("There is already a game running in this channel.") return bot.reply("There is already a game running in this channel.")
@ -59,8 +59,8 @@ def hangman(bot, trigger):
+ "Use '.hangman --start' to start one" + "Use '.hangman --start' to start one"
return bot.reply(msg) return bot.reply(msg)
if len(trigger.group(2)) > 1: if len(trigger.args[1]) > 1:
if trigger.group(2) == bot.memory["hangman"][trigger.channel].word: if trigger.args[1] == bot.memory["hangman"][trigger.channel].word:
bot.msg(f"{trigger.nick} has won!") bot.msg(f"{trigger.nick} has won!")
bot.msg(bot.memory["hangman"][trigger.channel].word) bot.msg(bot.memory["hangman"][trigger.channel].word)
bot.memory["hangman"].pop(trigger.channel) bot.memory["hangman"].pop(trigger.channel)
@ -71,10 +71,10 @@ def hangman(bot, trigger):
+ f"{bot.memory['hangman'][trigger.channel].tries} tries left." + f"{bot.memory['hangman'][trigger.channel].tries} tries left."
bot.reply(msg) bot.reply(msg)
elif len(trigger.group(2)) == 1: elif len(trigger.args[1]) == 1:
if trigger.group(2) in bot.memory["hangman"][trigger.channel].word: if trigger.args[1] in bot.memory["hangman"][trigger.channel].word:
bot.reply("Correct!") bot.reply("Correct!")
bot.memory["hangman"][trigger.channel].update(trigger.group(2)) bot.memory["hangman"][trigger.channel].update(trigger.args[1])
else: else:
bot.memory["hangman"][trigger.channel].tries -= 1 bot.memory["hangman"][trigger.channel].tries -= 1

View File

@ -22,8 +22,8 @@ def clean_docstring(doc):
@example('.help tell') @example('.help tell')
def help(bot, trigger): def help(bot, trigger):
"""Shows a command's documentation, and possibly an example.""" """Shows a command's documentation, and possibly an example."""
if trigger.group(2): if len(trigger.args) >= 2:
command = trigger.group(2) command = trigger.args[1]
command = command.lower() command = command.lower()
if command not in bot.commands: if command not in bot.commands:
return bot.msg("Command not found.") return bot.msg("Command not found.")

View File

@ -10,10 +10,10 @@ from module import commands, require_chanmsg
@commands('isup') @commands('isup')
def isup(bot, trigger): def isup(bot, trigger):
"""Queries the given url to check if it's up or not.""" """Queries the given url to check if it's up or not."""
url = trigger.group(2) if len(trigger.args) < 2:
if not url:
return bot.reply("What URL do you want to check?") return bot.reply("What URL do you want to check?")
url = trigger.args[1]
if url.startswith("192") and not trigger.owner: if url.startswith("192") and not trigger.owner:
return bot.reply("Do not violate the LAN.") return bot.reply("Do not violate the LAN.")

View File

@ -7,6 +7,6 @@ from module import commands
@commands('lmgtfy') @commands('lmgtfy')
def googleit(bot, trigger): def googleit(bot, trigger):
"""Let me just... google that for you.""" """Let me just... google that for you."""
if not trigger.group(2): if len(trigger.args) < 2:
return bot.msg('http://google.com/') return bot.msg('http://google.com/')
bot.msg('http://lmgtfy.com/?q=' + trigger.group(2).replace(' ', '+')) bot.msg('http://lmgtfy.com/?q=' + trigger.args[1].replace(' ', '+'))

View File

@ -16,7 +16,7 @@ def minecraft_start_server(bot, trigger):
cmd = ["tmux", "send", "-t", "main:2", "./start.sh", "ENTER"] cmd = ["tmux", "send", "-t", "main:2", "./start.sh", "ENTER"]
cmd_re = ["tmux", "send", "-t", "main:2", "^C", "ENTER"] cmd_re = ["tmux", "send", "-t", "main:2", "^C", "ENTER"]
msg = "Start signal sent to the minecraft server." msg = "Start signal sent to the minecraft server."
if trigger.group(2) == "-r": if len(trigger.args) >= 2:
msg = "Re-" + msg msg = "Re-" + msg
subprocess.run(cmd_re, check=True) subprocess.run(cmd_re, check=True)
subprocess.run(cmd, check=True) subprocess.run(cmd, check=True)

View File

@ -46,10 +46,9 @@ def movieInfo(bot, trigger):
Returns some information about a movie, like Title, Year, Rating, Returns some information about a movie, like Title, Year, Rating,
Genre and IMDB Link. Genre and IMDB Link.
""" """
word = trigger.group(2) if len(trigger.args) < 2:
if not word:
return bot.reply("What movie?") return bot.reply("What movie?")
word = word.replace(" ", "+") word = '+'.join(trigger.args[1:])
api_key = config.tmdb_api_key api_key = config.tmdb_api_key
uri = "https://api.themoviedb.org/3/search/movie?" + \ uri = "https://api.themoviedb.org/3/search/movie?" + \
@ -151,8 +150,8 @@ def pickMovie(bot, trigger):
else: else:
bot.reply(movie[0]) bot.reply(movie[0])
if trigger.group(2) == "-m": if len(trigger.args) >= 2:
trigger.set_group(f".movie {movie}") trigger.args = f".movie {movie}".strip().split(' ')
movieInfo(bot, trigger) movieInfo(bot, trigger)
@ -163,8 +162,10 @@ def addMovie(bot, trigger):
""" """
Adds the specified movie to the movie database. Adds the specified movie to the movie database.
""" """
if len(trigger.args) < 2:
return bot.reply("What movie?")
movie = ' '.join(trigger.args[1:])
bot.memory['movie_lock'].acquire() bot.memory['movie_lock'].acquire()
movie = trigger.group(2)
try: try:
bot.db.execute("INSERT INTO movie (movie_title, added_by) VALUES(?,?)", bot.db.execute("INSERT INTO movie (movie_title, added_by) VALUES(?,?)",
(movie, trigger.nick)) (movie, trigger.nick))

View File

@ -12,8 +12,8 @@ def check_privilege(bot, trigger):
""" """
Checks the user's privilege. Checks the user's privilege.
""" """
if trigger.group(2): if len(trigger.args) < 2:
nick = trigger.group(2) nick = trigger.args[1]
else: else:
nick = trigger.nick nick = trigger.nick

View File

@ -16,19 +16,12 @@ from module import commands, example
@example(".rand 10 99", "random(10, 99) = 29") @example(".rand 10 99", "random(10, 99) = 29")
def rand(bot, trigger): def rand(bot, trigger):
"""Replies with a random number between first and second argument.""" """Replies with a random number between first and second argument."""
arg1 = trigger.group(3) arg1 = trigger.args[1] if len(trigger.args) >= 2 else 0
arg2 = trigger.group(4) arg2 = trigger.args[2] if len(trigger.args) >= 3 else sys.maxsize
try: try:
if arg2 is not None: low = int(arg1)
low = int(arg1) high = int(arg2)
high = int(arg2)
elif arg1 is not None:
low = 0
high = int(arg1)
else:
low = 0
high = sys.maxsize
except (ValueError, TypeError): except (ValueError, TypeError):
return bot.reply("Arguments must be of integer type") return bot.reply("Arguments must be of integer type")
@ -48,8 +41,14 @@ def rand_letters(bot, trigger):
Generates a series of string of random letters. Generates a series of string of random letters.
""" """
num_letters = int(trigger.group(3)) if trigger.group(3) else 8 arg1 = trigger.args[1] if len(trigger.args) >= 2 else 8
num_vowels = int(trigger.group(4)) if trigger.group(4) else 2 arg2 = trigger.args[2] if len(trigger.args) >= 3 else 2
try:
num_letters = int(arg1)
num_vowels = int(arg2)
except (ValueError, TypeError):
return bot.reply("Arguments must be of integer type")
msg = [] msg = []
for _ in range(num_letters): for _ in range(num_letters):

View File

@ -13,9 +13,11 @@ import module
@module.thread(False) @module.thread(False)
def f_reload(bot, trigger): def f_reload(bot, trigger):
"""Reloads a module, for use by admins only.""" """Reloads a module, for use by admins only."""
name = trigger.group(2) if len(trigger.args) < 2:
return boy.reply("Reload what?")
name = trigger.args[1]
if not name or name == "*" or name.upper() == "ALL THE THINGS": if name == "*" or name.upper() == "ALL THE THINGS":
bot.load_modules() bot.load_modules()
return bot.msg("done") return bot.msg("done")
@ -35,9 +37,9 @@ def f_reload(bot, trigger):
@module.thread(False) @module.thread(False)
def f_load(bot, trigger): def f_load(bot, trigger):
"""Loads a module, for use by admins only.""" """Loads a module, for use by admins only."""
name = trigger.group(2) if len(trigger.args) < 2:
if not name:
return bot.msg('Load what?') return bot.msg('Load what?')
name = trigger.args[1]
if name in sys.modules: if name in sys.modules:
return bot.msg('Module already loaded, use reload.') return bot.msg('Module already loaded, use reload.')
@ -61,9 +63,9 @@ def f_load(bot, trigger):
@module.thread(False) @module.thread(False)
def f_unload(bot, trigger): def f_unload(bot, trigger):
"""Unloads a module, for use by admins only.""" """Unloads a module, for use by admins only."""
name = trigger.group(2) if len(trigger.args) < 2:
if not name:
return bot.msg('Unload what?') return bot.msg('Unload what?')
name = trigger.args[1]
if name not in sys.modules: if name not in sys.modules:
name = "modules." + name name = "modules." + name

View File

@ -6,9 +6,8 @@ import os
import re import re
import time import time
import sqlite3 import sqlite3
import datetime
import threading import threading
import collections from datetime import datetime
import config import config
from module import commands, example from module import commands, example
@ -133,15 +132,15 @@ multiplier = [
@example('.remind 3h45m Go to class') @example('.remind 3h45m Go to class')
def remind(bot, trigger): def remind(bot, trigger):
"""Gives you a reminder in the given amount of time.""" """Gives you a reminder in the given amount of time."""
if not trigger.group(2): if len(trigger.args) == 1:
return bot.msg("Missing arguments for reminder command.") return bot.msg("Missing arguments for reminder command.")
if trigger.group(3) and not trigger.group(4): if len(trigger.args) == 2:
reminder = '' reminder = ''
else: else:
reminder = ' '.join(trigger.group[4:]) reminder = ' '.join(trigger.args[2:])
duration = 0 duration = 0
for n, group in enumerate(re.search(regex, trigger.group(3)).groups()): for n, group in enumerate(re.search(regex, trigger.args[1]).groups()):
if not group: if not group:
continue continue
duration += int(group) * multiplier[n] duration += int(group) * multiplier[n]
@ -154,48 +153,24 @@ def remind(bot, trigger):
@example('.at 14:45:45 Remove dick from oven') @example('.at 14:45:45 Remove dick from oven')
def at(bot, trigger): def at(bot, trigger):
""" """
Gives you a reminder at the given time. Takes hh:mm:ssUTC+/-## Gives you a reminder at the given time and date. Datetime must be in
message. Timezone, if provided, must be in UTC format. 24 hour YYYY-MM-DD HH:MM:SS format. Only the bot's timezone is used.
clock format only.
""" """
if not trigger.group(2): if len(trigger.args) == 1:
return bot.msg("No arguments given for reminder command.") return bot.msg("Missing arguments for reminder command.")
if trigger.group(3) and not trigger.group(4): if len(trigger.args) == 2:
return bot.msg("No message given for reminder.") reminder = ''
regex = re.compile(r"(\d+):(\d+)(?::(\d+))?(?:UTC([+-]\d+))? (.*)")
match = regex.match(trigger.group(2))
if not match:
return bot.reply("Sorry, but I didn't understand your input.")
hour, minute, second, tz, message = match.groups()
if not second:
second = '0'
if tz:
try:
tz = int(tz.replace("UTC", ""))
except ValueError:
bot.msg("Invalid timezone. Using the bot's current timezone.")
tz = None
if tz:
timezone = datetime.timezone(datetime.timedelta(hours=tz))
else: else:
timezone = datetime.datetime.now().astimezone().tzinfo reminder = ' '.join(trigger.args[2:])
# current timezone the bot is in
now = datetime.datetime.now(timezone) try:
at_time = datetime.datetime(now.year, now.month, now.day, at_time = datetime.strptime(trigger.args[1], '%Y-%m-%d %H:%M:%S')
int(hour), int(minute), int(second), except ValueError:
tzinfo=timezone) return bot.msg("Datetime improperly formatted.")
timediff = at_time - now diff = at_time - datetime.now()
duration = diff.seconds
duration = timediff.seconds create_reminder(bot, trigger, duration, reminder)
if duration < 0:
duration += 86400
create_reminder(bot, trigger, duration, message)
def create_reminder(bot, trigger, duration, message): def create_reminder(bot, trigger, duration, message):
@ -213,9 +188,9 @@ def create_reminder(bot, trigger, duration, message):
insert_reminder(bot, t, reminder) insert_reminder(bot, t, reminder)
if duration >= 60: if duration >= 60:
remind_at = datetime.datetime.fromtimestamp(t) remind_at = datetime.fromtimestamp(t)
t_format = config.default_time_format t_format = config.default_time_format
timef = datetime.datetime.strftime(remind_at, t_format) timef = datetime.strftime(remind_at, t_format)
bot.reply('Okay, will remind at %s' % timef) bot.reply('Okay, will remind at %s' % timef)
else: else:

View File

@ -60,13 +60,13 @@ def resist(bot, trigger):
""" """
Displays the color band code of a resistor for the given resistance. Displays the color band code of a resistor for the given resistance.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.msg("Please specify a value") return bot.msg("Please specify a value")
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("value", nargs="+") parser.add_argument("value", nargs="+")
parser.add_argument("-r", "--reverse", action="store_true") parser.add_argument("-r", "--reverse", action="store_true")
parser.add_argument("-n", "--num_bands", type=int, choices=[3,4,5,6], default=4) parser.add_argument("-n", "--num_bands", type=int, choices=[3,4,5,6], default=4)
args = parser.parse_args(trigger.group(2).split()) args = parser.parse_args(trigger.args[1:])
if args.reverse: # bands-to-value if args.reverse: # bands-to-value
bot.msg(bands_to_value(" ".join(args.value))) bot.msg(bands_to_value(" ".join(args.value)))

View File

@ -13,9 +13,10 @@ def rundown(bot, trigger):
""" """
Provides rundown on demand. -c, --cabal for the IRCabal version. Provides rundown on demand. -c, --cabal for the IRCabal version.
""" """
if trigger.group(2) in ["-c", "--cabal"]: if len(trigger.args) > 2:
with open(os.path.join(bot.static, "cabaldown.txt"), "r") as file: if trigger.args[1] in ["-c", "--cabal"]:
data = file.read() with open(os.path.join(bot.static, "cabaldown.txt"), "r") as file:
data = file.read()
else: else:
with open(os.path.join(bot.static, "rundown.txt"), "r") as file: with open(os.path.join(bot.static, "rundown.txt"), "r") as file:
data = file.read() data = file.read()

View File

@ -54,10 +54,10 @@ def scramble(bot, trigger):
Plays scramble. --start [-s] to start a new game, otherwise arguments Plays scramble. --start [-s] to start a new game, otherwise arguments
are taken as attempts to solve. are taken as attempts to solve.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Scramble what?") return bot.reply("Scramble what?")
if trigger.group(3) == "--start" or trigger.group(3) == "-s": if trigger.args[1] == "--start" or trigger.args[1] == "-s":
if bot.memory["scramble"].get(trigger.channel): if bot.memory["scramble"].get(trigger.channel):
return bot.reply("There is already a game running in this channel.") return bot.reply("There is already a game running in this channel.")
@ -74,7 +74,7 @@ def scramble(bot, trigger):
return bot.reply(msg) return bot.reply(msg)
word = bot.memory["scramble"][trigger.channel].word word = bot.memory["scramble"][trigger.channel].word
if isAnagram(bot, word, trigger.group(2)): if isAnagram(bot, word, trigger.args[1]):
bot.msg(f"{trigger.nick} has won!") bot.msg(f"{trigger.nick} has won!")
msg = "Original word: " \ msg = "Original word: " \
+ bot.memory["scramble"][trigger.channel].word + bot.memory["scramble"][trigger.channel].word

View File

@ -8,135 +8,96 @@ using the sed notation (s///) commonly found in vi/vim.
import re import re
from module import hook from module import hook
from tools import FulviaMemory
def setup(bot): def setup(bot):
bot.memory['find_lines'] = FulviaMemory() bot.memory['sed_lines'] = {}
@hook(True) @hook(True)
def collectlines(bot, trigger): def collectlines(bot, trigger):
"""Create a temporary log of what people say""" """Create a temporary log of what people say"""
# Don't log things in PM
if trigger.is_privmsg: if trigger.is_privmsg:
return return
# Add a log for the channel and nick, if there isn't already one if trigger.channel not in bot.memory['sed_lines']:
if trigger.channel not in bot.memory['find_lines']: bot.memory['sed_lines'][trigger.channel] = {}
bot.memory['find_lines'][trigger.channel] = FulviaMemory() if trigger.nick not in bot.memory['sed_lines'][trigger.channel]:
if trigger.nick not in bot.memory['find_lines'][trigger.channel]: bot.memory['sed_lines'][trigger.channel][trigger.nick] = []
bot.memory['find_lines'][trigger.channel][trigger.nick] = list()
# Create a temporary list of the user's lines in a channel templist = bot.memory['sed_lines'][trigger.channel][trigger.nick]
templist = bot.memory['find_lines'][trigger.channel][trigger.nick] line = ' '.join(trigger.args)
line = trigger.group()
if line.startswith("s/"): # Don't remember substitutions if line.startswith("s/"): # Don't remember substitutions
return return
elif line.startswith("\x01ACTION"): # For /me messages templist.append(line)
line = line[:-1]
templist.append(line)
else:
templist.append(line)
del templist[:-10] # Keep the log to 10 lines per person del templist[:-10] # Keep the log to 10 lines per person
bot.memory['find_lines'][trigger.channel][trigger.nick] = templist bot.memory['sed_lines'][trigger.channel][trigger.nick] = templist
#Match nick, s/find/replace/flags. Flags and nick are optional, nick can be
#followed by comma or colon, anything after the first space after the third
#slash is ignored, you can escape slashes with backslashes, and if you want to
#search for an actual backslash followed by an actual slash, you're shit out of
#luck because this is the fucking regex of death as it is.
# @rule(r"""(?:
# (\S+) # Catch a nick in group 1
# [:,]\s+)? # Followed by colon/comma and whitespace, if given
# s/ # The literal s/
# ( # Group 2 is the thing to find
# (?:\\/ | [^/])+ # One or more non-slashes or escaped slashes
# )/( # Group 3 is what to replace with
# (?:\\/ | [^/])* # One or more non-slashes or escaped slashes
# )
# (?:/(\S+))? # Optional slash, followed by group 4 (flags)
# """)
@hook(True) @hook(True)
def findandreplace(bot, trigger): def findandreplace(bot, trigger):
# Don't bother in PM
if trigger.is_privmsg: if trigger.is_privmsg:
return return
if not trigger.args[0].startswith('s') and trigger.args[0][-1] not in ':,':
rule = re.compile(r"(\S+)?(?:[:,]\s|^)s\/((?:\\\/|[^/])+)\/((?:\\\/|[^/])*"\
+ r")(?:\/(\S+))?")
group = rule.search(trigger.group(0))
if not group:
return return
g = (trigger.group(0),) + group.groups() line = ' '.join(trigger.args)
trigger.set_group(g) if 's/' not in line:
# Correcting other person vs self.
rnick = (trigger.group(1) or trigger.nick)
search_dict = bot.memory['find_lines']
# only do something if there is conversation to work with
if trigger.channel not in search_dict:
return
if rnick not in search_dict[trigger.channel]:
return return
#TODO rest[0] is find, rest[1] is replace. These should be made variables of items = []
#their own at some point. line = line[line.index('s/')+2:]
rest = [trigger.group(2), trigger.group(3)] i = 0
rest[0] = rest[0].replace(r'\/', '/') while True:
rest[1] = rest[1].replace(r'\/', '/') i = line.find('/', i)
me = False # /me command if i == -1:
flags = (trigger.group(4) or '') break
print(flags) if line[max(i-1, 0)] == '\\' and line[max(i-2, 0)] != '\\':
i += 1
pass
else:
items.append(line[:i])
line = line[i+1:]
i = 0
items.append(line)
if len(items) < 2:
return
find, replace = items[:2]
find = find.replace(r'\/', '/')
flags = items[2] if len(items) >= 3 else ''
# If g flag is given, replace all. Otherwise, replace once.
if 'g' in flags: if 'g' in flags:
count = 0 count = 0
else: else:
count = 1 count = 1
# repl is a lambda function which performs the substitution. i flag turns try:
# off case sensitivity. re.U turns on unicode replacement. if 'i' in flags:
if 'i' in flags: regex = re.compile(find, re.I)
regex = re.compile(re.escape(rest[0]), re.U | re.I)
repl = lambda s: re.sub(regex, rest[1], s, count == 1)
else:
repl = lambda s: re.sub(rest[0], rest[1], s, count)
# Look back through the user's lines in the channel until you find a line
# where the replacement works
new_phrase = None
for line in reversed(search_dict[trigger.channel][rnick]):
if line.startswith("\x01ACTION"):
me = True # /me command
line = line[8:]
else: else:
me = False regex = re.compile(find)
new_phrase = repl(line) except re.error:
if new_phrase != line: # we are done return
break
if not new_phrase or new_phrase == line: if not trigger.args[0].startswith('s'):
return # Didn't find anything nick = trigger.args[0][:-1]
if nick not in bot.memory['sed_lines'][trigger.channel]:
# Save the new "edited" message. return
action = (me and '\x01ACTION ') or '' # If /me message, prepend \x01ACTION
templist = search_dict[trigger.channel][rnick]
templist.append(action + new_phrase)
search_dict[trigger.channel][rnick] = templist
bot.memory['find_lines'] = search_dict
# output
if not me:
new_phrase = f"\x02meant\x0f to say: {new_phrase}"
if trigger.group(1):
phrase = f"{trigger.nick} thinks {rnick} {new_phrase}"
else: else:
phrase = f"{trigger.nick} {new_phrase}" nick = trigger.nick
bot.msg(phrase) for line in reversed(bot.memory['sed_lines'][trigger.channel][nick]):
new_line = re.sub(regex, replace, line, count)
if new_line != line:
break
else:
return
if trigger.args[0].startswith('s'):
msg = f"{trigger.nick} \x02meant\x0f to say: {new_line}"
else:
msg = f"{trigger.nick} thinks {nick} \x02meant\x0f to say: {new_line}"
bot.msg(msg)

View File

@ -65,7 +65,7 @@ def seen(bot, trigger):
-m, --message - includes the first/last message the user sent. -m, --message - includes the first/last message the user sent.
-c, --context - includes irc logs before and after their last message as context. Implies --message. -c, --context - includes irc logs before and after their last message as context. Implies --message.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Seen who?") return bot.reply("Seen who?")
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
@ -74,7 +74,7 @@ def seen(bot, trigger):
parser.add_argument("-f", "--first", action="store_true") parser.add_argument("-f", "--first", action="store_true")
parser.add_argument("-m", "--message", action="store_true") parser.add_argument("-m", "--message", action="store_true")
parser.add_argument("-c", "--context", action="store_true") parser.add_argument("-c", "--context", action="store_true")
args = parser.parse_args(trigger.group[3:]) args = parser.parse_args(trigger.args[1:])
if args.nick == bot.nick: if args.nick == bot.nick:
return bot.reply("I'm right here!") return bot.reply("I'm right here!")
@ -142,9 +142,9 @@ def dump_seen_db(bot):
@require_chanmsg @require_chanmsg
def seen_hook(bot, trigger): def seen_hook(bot, trigger):
bot.memory["seen_lock"].acquire() bot.memory["seen_lock"].acquire()
last = (time.time(), trigger.channel, trigger.group(0)) last = (time.time(), trigger.channel, ' '.join(trigger.args))
if not trigger.nick in bot.memory["seen"]: if not trigger.nick in bot.memory["seen"]:
first = (time.time(), trigger.channel, trigger.group(0)) first = (time.time(), trigger.channel, ' '.join(trigger.args))
else: else:
first = bot.memory["seen"][trigger.nick][:3] first = bot.memory["seen"][trigger.nick][:3]
seen = first + last seen = first + last

View File

@ -37,7 +37,7 @@ def slur(bot, trigger):
parser.add_argument("-r", "--race", type=str, nargs='+') parser.add_argument("-r", "--race", type=str, nargs='+')
parser.add_argument("-s", "--slur", type=str) parser.add_argument("-s", "--slur", type=str)
parser.add_argument("-l", "--list", action="store_true") parser.add_argument("-l", "--list", action="store_true")
args = parser.parse_args(trigger.group[3:]) args = parser.parse_args(trigger.args[1:])
if args.list: if args.list:
races = bot.db.execute("SELECT DISTINCT race FROM slur").fetchall() races = bot.db.execute("SELECT DISTINCT race FROM slur").fetchall()

View File

@ -13,9 +13,9 @@ def spellcheck(bot, trigger):
Says whether the given word is spelled correctly, and gives suggestions if Says whether the given word is spelled correctly, and gives suggestions if
it's not. it's not.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("What word?") return bot.reply("What word?")
word = trigger.group(2) word = trigger.args[1]
if " " in word: if " " in word:
return bot.msg("One word at a time, please") return bot.msg("One word at a time, please")
dictionary = enchant.Dict("en_US") dictionary = enchant.Dict("en_US")

View File

@ -67,12 +67,14 @@ def setup(bot):
@example('.tell iou1name you broke something again.') @example('.tell iou1name you broke something again.')
def tell(bot, trigger): def tell(bot, trigger):
"""Give someone a message the next time they're seen""" """Give someone a message the next time they're seen"""
if not trigger.group(3): if len(trigger.args) < 2:
return bot.reply("Tell whom?") return bot.reply("Tell whom?")
if len(trigger.args) < 3:
return bot.reply("Tell them what?")
teller = trigger.nick teller = trigger.nick
tellee = trigger.group(3).rstrip('.,:;') tellee = trigger.args[1].rstrip('.,:;')
message = trigger.group(2).replace(tellee, "", 1).strip() message = ' '.join(trigger.args[2:])
if not message: if not message:
return bot.reply(f"Tell {tellee} what?") return bot.reply(f"Tell {tellee} what?")

View File

@ -13,11 +13,9 @@ URI = 'https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains'
@example('.tld me') @example('.tld me')
def gettld(bot, trigger): def gettld(bot, trigger):
"""Show information about the given Top Level Domain.""" """Show information about the given Top Level Domain."""
word = trigger.group(2).strip() if len(trigger.args) < 2:
if not word:
return bot.reply("What TLD?") return bot.reply("What TLD?")
if " " in word: word = trigger.args[1]
return bot.reply("One TLD at a time.")
if not word.startswith("."): if not word.startswith("."):
word = "." + word word = "." + word

View File

@ -48,9 +48,9 @@ def addTopic(bot, trigger):
""" """
Adds the specified topic to the topic database. Adds the specified topic to the topic database.
""" """
topic = trigger.group(2) if len(trigger.args) < 2:
if not topic:
return bot.msg("Please be providing a topic sir.") return bot.msg("Please be providing a topic sir.")
topic = trigger.args[1]
bot.memory['topic_lock'].acquire() bot.memory['topic_lock'].acquire()
try: try:
bot.db.execute("INSERT INTO topic (topic, added_by) VALUES(?,?)", bot.db.execute("INSERT INTO topic (topic, added_by) VALUES(?,?)",

View File

@ -42,14 +42,14 @@ def tr2(bot, trigger):
-i LANG, --inlang LANG - specifies the assumed input language. -i LANG, --inlang LANG - specifies the assumed input language.
-o LANG, --outlang LANG - specifies the desired output language. -o LANG, --outlang LANG - specifies the desired output language.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Translate what?") return bot.reply("Translate what?")
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("text", nargs=argparse.REMAINDER) parser.add_argument("text", nargs=argparse.REMAINDER)
parser.add_argument("-i", "--inlang", default="auto") parser.add_argument("-i", "--inlang", default="auto")
parser.add_argument("-o", "--outlang", default="en") parser.add_argument("-o", "--outlang", default="en")
args = parser.parse_args(trigger.group(2).split()) args = parser.parse_args(trigger.args[1:])
args.text = " ".join(args.text) args.text = " ".join(args.text)
tr_text, in_lang = translate(args.text, in_lang=args.inlang, tr_text, in_lang = translate(args.text, in_lang=args.inlang,
@ -60,9 +60,9 @@ def tr2(bot, trigger):
@commands('mangle') @commands('mangle')
def mangle(bot, trigger): def mangle(bot, trigger):
"""Repeatedly translate the input until it makes absolutely no sense.""" """Repeatedly translate the input until it makes absolutely no sense."""
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Mangle what?") return bot.reply("Mangle what?")
tr_text = trigger.group(2) tr_text = ' '.join(trigger.args[1:])
long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy',
'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb',

View File

@ -11,9 +11,9 @@ from module import commands, example
@example('.u 203D', 'U+203D INTERROBANG (‽)') @example('.u 203D', 'U+203D INTERROBANG (‽)')
def codepoint(bot, trigger): def codepoint(bot, trigger):
"""Looks up unicode information.""" """Looks up unicode information."""
arg = trigger.group(2) if len(trigger.args) < 2:
if not arg:
return bot.reply('What code point do you want me to look up?') return bot.reply('What code point do you want me to look up?')
arg = ' '.join(trigger.args[1])
stripped = arg.strip() stripped = arg.strip()
if len(stripped) > 0: if len(stripped) > 0:
arg = stripped arg = stripped

View File

@ -43,7 +43,7 @@ def temperature(bot, trigger):
Convert temperatures Convert temperatures
""" """
try: try:
source = find_temp.match(trigger.group(2)).groups() source = find_temp.match(' '.join(trigger.args[1:])).groups()
except (AttributeError, TypeError): except (AttributeError, TypeError):
return bot.reply("That's not a valid temperature.") return bot.reply("That's not a valid temperature.")
unit = source[1].upper() unit = source[1].upper()
@ -77,7 +77,7 @@ def distance(bot, trigger):
Convert distances Convert distances
""" """
try: try:
source = find_length.match(trigger.group(2)).groups() source = find_length.match(' '.join(trigger.args[1:])).groups()
except (AttributeError, TypeError): except (AttributeError, TypeError):
return bot.reply("That's not a valid length unit.") return bot.reply("That's not a valid length unit.")
unit = source[1].lower() unit = source[1].lower()
@ -151,7 +151,7 @@ def mass(bot, trigger):
Convert mass Convert mass
""" """
try: try:
source = find_mass.match(trigger.group(2)).groups() source = find_mass.match(' '.join(trigger.args[1:])).groups()
except (AttributeError, TypeError): except (AttributeError, TypeError):
return bot.reply("That's not a valid mass unit.") return bot.reply("That's not a valid mass unit.")
unit = source[1].lower() unit = source[1].lower()

View File

@ -27,12 +27,12 @@ def uptime(bot, trigger):
@commands('updick') @commands('updick')
def updick(bot, trigger): def updick(bot, trigger):
""".updick - Returns the uptime of Fulvia, measured in dicks.""" """.updick - Returns the uptime of Fulvia, measured in dicks."""
if trigger.group(2): if len(trigger.args) < 2:
if trigger.group(2) in bot.users: if trigger.args[1] in bot.users:
d = defer.Deferred() d = defer.Deferred()
d.addCallback(idleTime, bot) d.addCallback(idleTime, bot)
bot.memory["idle_callbacks"][trigger.group(2)] = d bot.memory["idle_callbacks"][trigger.args[1]] = d
bot.whois(trigger.group(2)) bot.whois(trigger.args[1])
else: else:
delta = datetime.datetime.now() - bot.memory["uptime"] delta = datetime.datetime.now() - bot.memory["uptime"]
bot.msg("8" + "="*delta.days + "D") bot.msg("8" + "="*delta.days + "D")
@ -41,12 +41,12 @@ def updick(bot, trigger):
@commands('upwulf') @commands('upwulf')
def upwulf(bot, trigger): def upwulf(bot, trigger):
""".upwulf - Returns the uptime of Fulvia, measured in Adalwulfs.""" """.upwulf - Returns the uptime of Fulvia, measured in Adalwulfs."""
if trigger.group(2): if len(trigger.args) < 2:
if trigger.group(2) in bot.users: if trigger.args[1] in bot.users:
d = defer.Deferred() d = defer.Deferred()
d.addCallback(idleTimeWulf, bot) d.addCallback(idleTime, bot)
bot.memory["idle_callbacks"][trigger.group(2)] = d bot.memory["idle_callbacks"][trigger.args[1]] = d
bot.whois(trigger.group(2)) bot.whois(trigger.args[1])
else: else:
delta = datetime.datetime.now() - bot.memory["uptime"] delta = datetime.datetime.now() - bot.memory["uptime"]
bot.msg("Adalwulf" + "_"*delta.days) bot.msg("Adalwulf" + "_"*delta.days)
@ -75,7 +75,9 @@ def idleTimeWulf(result, bot):
@commands('unix') @commands('unix')
def unixtolocal(bot, trigger): def unixtolocal(bot, trigger):
"""Converts the given timestamp from unix time to local time.""" """Converts the given timestamp from unix time to local time."""
unix = int(trigger.group(2)) if len(trigger.args) < 2:
return bot.reply("Unix what?")
unix = int(trigger.args[1])
dt = datetime.datetime.utcfromtimestamp(unix) dt = datetime.datetime.utcfromtimestamp(unix)
dt = dt.replace(tzinfo=datetime.timezone.utc) dt = dt.replace(tzinfo=datetime.timezone.utc)
bot.msg(dt.astimezone(tz=None).strftime('%Y-%m-%d %H:%M:%S')) bot.msg(dt.astimezone(tz=None).strftime('%Y-%m-%d %H:%M:%S'))

View File

@ -36,11 +36,11 @@ def title_auto(bot, trigger):
Automatically show titles for URLs. For shortened URLs/redirects, find Automatically show titles for URLs. For shortened URLs/redirects, find
where the URL redirects to and show the title for that. where the URL redirects to and show the title for that.
""" """
if "http" not in trigger.group(0): if "http" not in ' '.join(trigger.args):
return return
url_finder = re.compile(r"((?:http|https)(?::\/\/\S+))", re.IGNORECASE) url_finder = re.compile(r"((?:http|https)(?::\/\/\S+))", re.IGNORECASE)
urls = re.findall(url_finder, trigger.group(0)) urls = re.findall(url_finder, ' '.join(trigger.args))
if len(urls) == 0: if len(urls) == 0:
return return

View File

@ -85,10 +85,10 @@ def watch(bot, trigger):
""" """
A thread watcher for 4chan. A thread watcher for 4chan.
""" """
url = trigger.group(3) if len(trigger.args) < 2:
name = trigger.group(4) boy.reply("What thread?")
if not name: url = trigger.args[1]
name = "Anonymous" name = trigger.args[2] if len(trigger.args) >= 3 else "Anonymous"
if url in bot.memory["watcher"].keys(): if url in bot.memory["watcher"].keys():
return bot.msg("Error: I'm already watching that thread.") return bot.msg("Error: I'm already watching that thread.")
@ -120,7 +120,9 @@ def unwatch(bot, trigger):
""" """
Stops the thread watcher thread for that thread. Stops the thread watcher thread for that thread.
""" """
url = trigger.group(2) if len(trigger.args) < 2:
boy.reply("What thread?")
url = trigger.args[1]
try: try:
bot.memory["watcher"][url].stop.set() bot.memory["watcher"][url].stop.set()
bot.memory["watcher"].pop(url) bot.memory["watcher"].pop(url)

View File

@ -17,11 +17,13 @@ def weather(bot, trigger):
-m, --metric - uses metric units instead of imperial. -m, --metric - uses metric units instead of imperial.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("Weather where?") return bot.reply("Weather where?")
location = trigger.group(3) location = trigger.args[1]
if location == '-m' or location == '--metric': if location == '-m' or location == '--metric':
location = trigger.group(4) if len(trigger.args) < 3:
return bot.reply("Weather where?")
location = trigger.args[2]
units = 'metric' units = 'metric'
else: else:
units = 'imperial' units = 'imperial'

View File

@ -74,10 +74,10 @@ def wiki_info(bot, url):
@example('.wiki San Francisco') @example('.wiki San Francisco')
def wikipedia(bot, trigger): def wikipedia(bot, trigger):
"""Search wikipedia and return a snippet of the results.""" """Search wikipedia and return a snippet of the results."""
if trigger.group(2) is None: if len(trigger.args) < 2:
return bot.reply("What do you want me to look up?") return bot.reply("What do you want me to look up?")
query = trigger.group(2) query = ' '.join(trigger.args[1:])
if not query: if not query:
return bot.reply("What do you want me to look up?") return bot.reply("What do you want me to look up?")

View File

@ -76,9 +76,9 @@ def format(result, definitions, number=2):
@example('.dict bailiwick') @example('.dict bailiwick')
def wiktionary(bot, trigger): def wiktionary(bot, trigger):
"""Look up a word on Wiktionary.""" """Look up a word on Wiktionary."""
word = trigger.group(2) if len(trigger.args) < 2:
if word is None:
return bot.reply('You must tell me what to look up!') return bot.reply('You must tell me what to look up!')
word = trigger.args[1]
_, definitions = wikt(word) _, definitions = wikt(word)
if not definitions: if not definitions:

View File

@ -8,7 +8,7 @@ from module import commands, example
@example('.willilike Banished Quest') @example('.willilike Banished Quest')
def willilike(bot, trigger): def willilike(bot, trigger):
"""An advanced AI that will determine if you like something.""" """An advanced AI that will determine if you like something."""
if trigger.group(2): if len(trigger.args) >= 2:
bot.reply("No.") bot.reply("No.")
@commands('upvote') @commands('upvote')

View File

@ -15,11 +15,11 @@ def wa_command(bot, trigger):
""" """
Queries WolframAlpha. Queries WolframAlpha.
""" """
if not trigger.group(2): if len(trigger.args) < 2:
return bot.reply("You must provide a query.") return bot.reply("You must provide a query.")
if not config.wolfram_app_id: if not config.wolfram_app_id:
bot.reply("Wolfram|Alpha API app ID not configured.") bot.reply("Wolfram|Alpha API app ID not configured.")
query = trigger.group(2).strip() query = ' '.join(trigger.args[1:])
app_id = config.wolfram_app_id app_id = config.wolfram_app_id
units = config.wolfram_units units = config.wolfram_units

View File

@ -54,9 +54,9 @@ def xkcd(bot, trigger):
""" """
latest = get_info() latest = get_info()
max_int = latest['num'] max_int = latest['num']
if trigger.group(2): if len(trigger.args) >= 2:
try: try:
num = int(trigger.group(2)) num = int(trigger.args[1])
except ValueError: except ValueError:
return bot.reply("Invalid input.") return bot.reply("Invalid input.")
num = validate_num(num, max_int) num = validate_num(num, max_int)

View File

@ -2,8 +2,6 @@
""" """
The trigger abstraction layer. The trigger abstraction layer.
""" """
import datetime
import config import config
def split_user(user): def split_user(user):
@ -18,75 +16,14 @@ def split_user(user):
return nick, ident, host return nick, ident, host
class Group(list):
"""
Custom list class that permits calling it like a function so as to
emulate a re.group instance.
"""
def __init__(self, message):
"""
Initializes the group class. If 'message' is a string, we split
it into groups according to the usual trigger.group structure.
Otherwise we assume it's already been split appropriately.
"""
if type(message) == str:
message = self.split_group(message)
list.__init__(self, message)
def __call__(self, n=0):
"""
Allows you to call the instance like a function. Or a re.group
instance ;^).
If calling would result in an index error, None is returned instead.
"""
try:
item = list.__getitem__(self, n)
except IndexError:
item = None
return item
def split_group(self, message):
"""
Splits the message by spaces.
group(0) is always the entire message.
group(1) is always the first word of the message minus the prefix, if
present. This is usually just the command.
group(2) is always the entire message after the first word.
group(3+) is always every individual word after the first word.
"""
group = []
group.append(message)
words = message.split()
group.append(words[0].replace(config.prefix, "", 1))
group.append(" ".join(words[1:]))
group += words[1:]
return group
class Trigger(): class Trigger():
def __init__(self, user, channel, message, event): def __init__(self, user, channel, message):
self.channel = channel self.channel = channel
""" """
The channel from which the message was sent. The channel from which the message was sent.
In a private message, this is the nick that sent the message. In a private message, this is the nick that sent the message.
""" """
self.time = datetime.datetime.now()
"""
A datetime object at which the message was received by the IRC server.
If the server does not support server-time, then `time` will be the time
that the message was received by Fulvia.
"""
self.raw = ""
"""
The entire message, as sent from the server. This includes the CTCP
\\x01 bytes and command, if they were included.
"""
self.is_privmsg = not channel.startswith("#") self.is_privmsg = not channel.startswith("#")
"""True if the trigger is from a user, False if it's from a channel.""" """True if the trigger is from a user, False if it's from a channel."""
@ -107,50 +44,15 @@ class Trigger():
self.host = host self.host = host
"""The hostname of the person who sent the message""" """The hostname of the person who sent the message"""
self.event = event self.args = message.strip().split(' ')
""" """Pseudo-ARGV for a bot command."""
The IRC event (e.g. ``PRIVMSG`` or ``MODE``) which triggered the
message.
"""
self.group = Group(message)
"""The ``group`` function of the ``match`` attribute.
See Python :mod:`re` documentation for details."""
self.args = ()
"""
A tuple containing each of the arguments to an event. These are the
strings passed between the event name and the colon. For example,
setting ``mode -m`` on the channel ``#example``, args would be
``('#example', '-m')``
"""
admins = config.admins + [config.owner] admins = config.admins + [config.owner]
self.admin = any([self.compare_hostmask(admin) for admin in admins]) self.admin = any([user == admin for admin in admins])
""" """
True if the nick which triggered the command is one of the bot's True if the user which triggered the command is one of the bot's
admins. admins.
""" """
self.owner = self.compare_hostmask(config.owner) self.owner = user == config.owner
"""True if the nick which triggered the command is the bot's owner.""" """True if the nick which triggered the command is the bot's owner."""
def compare_hostmask(self, compare_against):
"""
Compares the current hostmask against the given one. If ident is not
None, it uses that, otherwise it only uses <nick>@<host>.
"""
if self.ident:
return compare_against == self.hostmask
else:
return compare_against == "@".join(self.nick, self.host)
def set_group(self, line):
"""
Allows a you to easily change the current group to a new Group
instance.
"""
self.group = Group(line)