Compare commits

..

No commits in common. "d06b8f2fdc0fb86e7c9c45bce3d6fce26eb6348f" and "af08fe0f16ed5db69cf492271e9229d333fd6ab4" have entirely different histories.

12 changed files with 248 additions and 141 deletions

151
bot.py
View File

@ -62,6 +62,9 @@ class Fulvia(irc.IRCClient):
in them. in them.
""" """
self.users = tools.FulviaMemory()
"""A dictionary of all users the bot is aware of."""
self.memory = tools.FulviaMemory() self.memory = tools.FulviaMemory()
""" """
A thread-safe general purpose dictionary to be used by whatever A thread-safe general purpose dictionary to be used by whatever
@ -73,6 +76,12 @@ class Fulvia(irc.IRCClient):
A class with some basic interactions for the bot's sqlite3 databse. A class with some basic interactions for the bot's sqlite3 databse.
""" """
self._callables = {}
"""
A dictionary containing all callable functions loaded from
modules. Keys are the functions name.
"""
self._hooks = [] self._hooks = []
""" """
A list containing all function names to be hooked with every message A list containing all function names to be hooked with every message
@ -111,6 +120,7 @@ class Fulvia(irc.IRCClient):
Find and load all of our modules. Find and load all of our modules.
""" """
print(f"Loading modules...") print(f"Loading modules...")
self._callables = {}
self._hooks = [] self._hooks = []
self.commands = {} self.commands = {}
self._times = {} self._times = {}
@ -142,11 +152,20 @@ class Fulvia(irc.IRCClient):
convenient table. convenient table.
""" """
if hasattr(func, 'commands'): if hasattr(func, 'commands'):
self._callables[func.__name__] = func
for cmd in func.commands: for cmd in func.commands:
self.commands[cmd] = func self.commands[cmd] = tools.Command(cmd)
self.commands[cmd]._func_name = func.__name__
self.commands[cmd].priv = func.priv
self.commands[cmd].doc = func._docs
if cmd in func.aliases:
self.commands[cmd].canonical = False
aliases = [a for a in func.commands if a != cmd]
self.commands[cmd].aliases = aliases
if func.hook: if func.hook:
self._hooks.append(func) self._callables[func.__name__] = func
self._hooks.append(func.__name__)
if func.rate or func.channel_rate or func.global_rate: if func.rate or func.channel_rate or func.global_rate:
self._times[func.__name__] = {} self._times[func.__name__] = {}
@ -168,10 +187,12 @@ class Fulvia(irc.IRCClient):
return return
if hasattr(func, 'commands'): if hasattr(func, 'commands'):
self._callables.pop(func.__name__)
for command in func.commands: for command in func.commands:
self.commands.pop(command) self.commands.pop(command)
if func.hook: if func.hook:
self._callables.pop(func.__name__)
self._hooks.remove(func.__name__) self._hooks.remove(func.__name__)
if func.rate or func.channel_rate or func.global_rate: if func.rate or func.channel_rate or func.global_rate:
@ -240,25 +261,26 @@ class Fulvia(irc.IRCClient):
""" """
nick = user.partition("!")[0].partition("@")[0] nick = user.partition("!")[0].partition("@")[0]
if channel.startswith("#"): if channel.startswith("#"):
opSym = self.channels[channel].users[nick].op_level opSym = tools.getOpSym(self.channels[channel].privileges[nick])
else: else:
opSym = '' opSym = ""
channel = nick channel = nick
line = f"<{opSym}{nick}> {message}" line = "<" + opSym + nick + ">" + " " + message
self.log(channel, line) self.log(channel, line)
funcs = [] func_names = []
if message.startswith(self.prefix) and message != self.prefix: if message.startswith(self.prefix) and message != self.prefix:
command = message.partition(" ")[0] command, _, _ = message.partition(" ")
command = command.replace(self.prefix, "", 1) command = command.replace(self.prefix, "", 1)
cmd = self.commands.get(command) cmd = self.commands.get(command)
if not cmd: if not cmd:
return return
funcs.append(cmd) func_names.append(cmd._func_name)
funcs += self._hooks func_names += self._hooks
for func in funcs: for func_name in func_names:
func = self._callables[func_name]
trigger = Trigger(user, channel, message, "PRIVMSG", self.config) trigger = Trigger(user, channel, message, "PRIVMSG", self.config)
bot = FulviaWrapper(self, trigger) bot = FulviaWrapper(self, trigger)
@ -289,20 +311,19 @@ class Fulvia(irc.IRCClient):
def joined(self, channel): def joined(self, channel):
"""Called when the bot joins a new channel.""" """Called when the bot joins a new channel."""
line = f"-!- {self.nickname} [{self.username}@{self.host}] " line = "-!- " + self.nickname + " " + "[" + self.username + "@"
line += f"has joined {channel}" line += self.host + "] has joined " + channel
self.log(channel, line) self.log(channel, line)
print(f"Joined {channel}") print(f"Joined {channel}")
self.channels[channel] = tools.Channel(channel) if channel not in self.channels:
user = tools.User(self.nickname) self.channels[channel] = tools.Channel(channel)
self.channels[channel].users[self.nickname] = user
def left(self, channel, reason=""): def left(self, channel, reason=""):
"""Called when the bot leaves a channel.""" """Called when the bot leaves a channel."""
line = f"-!- {self.nickname} [{self.username}@{self.host}] " line = "-!- " + self.nickname + " " + "[" + self.username + "@"
line += f"has left {channel} [{reason}]" line += self.host + "] has left " + channel + " [" + reason + "]"
self.log(channel, line) self.log(channel, line)
print(f"Parted {channel}") print(f"Parted {channel}")
@ -317,7 +338,7 @@ class Fulvia(irc.IRCClient):
def modeChanged(self, user, channel, add_mode, modes, args): def modeChanged(self, user, channel, add_mode, modes, args):
"""Called when users or channel's modes are changed.""" """Called when users or channel's modes are changed."""
line = f"-!- mode/{channel} [" line = "-!- mode/" + channel + " ["
if add_mode: if add_mode:
line += "+" line += "+"
else: else:
@ -340,11 +361,11 @@ class Fulvia(irc.IRCClient):
elif mode in tools.op_level.keys(): # user mode, op_level mode elif mode in tools.op_level.keys(): # user mode, op_level mode
nick = args[n] nick = args[n]
user = self.channels[channel].users[nick] op_level = tools.op_level[mode]
if add_mode: if add_mode:
user.op_level += mode self.channels[channel].privileges[nick] += op_level
else: else:
user.op_level.replace(mode, '', 1) self.channels[channel].privileges[nick] -= op_level
else: # user mode, non-op_level mode else: # user mode, non-op_level mode
continue continue
@ -357,8 +378,8 @@ class Fulvia(irc.IRCClient):
print(f"Signed on as {self.nickname}") print(f"Signed on as {self.nickname}")
self.whois(self.nickname) self.whois(self.nickname)
line = f"*** Signed onto {self.hostname} as " line = "*** Signed onto " + self.hostname + " as "
line += f"{self.nickname}!{self.username}@{self.host}" line += self.nickname + "!" + self.username + "@" + self.host
self.log(self.hostname, line) self.log(self.hostname, line)
for channel in self.config.core.channels.split(","): for channel in self.config.core.channels.split(","):
@ -367,8 +388,8 @@ class Fulvia(irc.IRCClient):
def kickedFrom(self, channel, kicker, message): def kickedFrom(self, channel, kicker, message):
"""Called when the bot is kicked from a channel.""" """Called when the bot is kicked from a channel."""
line = f"-!- {self.nickname} was kicked from {channel} " line = "-!- " + self.nickname + " was kicked from " + channel
line += f"by {kicker} [{message}]" line += " by " + kicker + " [" + message + "]"
self.log(channel, line) self.log(channel, line)
self.channels.pop(channel) self.channels.pop(channel)
@ -376,14 +397,16 @@ class Fulvia(irc.IRCClient):
def nickChanged(self, nick): def nickChanged(self, nick):
"""Called when the bot changes it's nickname.""" """Called when the bot changes it's nickname."""
line = f"-!- you are now known as {nick}" line = "-!- you are now known as " + nick
for channel_name, channel in self.channels.items(): user = self.users.pop(self.nickname)
self.users[nick] = user
for key, channel in self.channels.items():
self.log(key, line) self.log(key, line)
user = channel.users.pop(self.nickname) channel.rename_user(self.nickname, nick)
user.nick = nick user.nick = nick
channel.users[nick] = user self.nickname = nick
## Actions the bot observes other users doing in the channel. ## Actions the bot observes other users doing in the channel.
@ -393,11 +416,12 @@ class Fulvia(irc.IRCClient):
"""Called when the bot sees another user join a channel.""" """Called when the bot sees another user join a channel."""
nick, _, host = user.partition("!") nick, _, host = user.partition("!")
line = f"-!- {nick} [{host}] has joined {channel}" line = "-!- " + nick + " " + "[" + host + "] has joined " + channel
self.log(channel, line) self.log(channel, line)
user = tools.User(nick) if nick not in self.users:
self.channels[channel].users[nick] = user self.users[nick] = tools.User(nick)
self.channels[channel].add_user(self.users[nick])
for func in self._user_joined: for func in self._user_joined:
trigger = Trigger(user, channel, f"{user} has joined", "PRIVMSG", self.config) trigger = Trigger(user, channel, f"{user} has joined", "PRIVMSG", self.config)
@ -410,38 +434,41 @@ class Fulvia(irc.IRCClient):
"""Called when the bot sees another user leave a channel.""" """Called when the bot sees another user leave a channel."""
nick, _, host = user.partition("!") nick, _, host = user.partition("!")
line = f"-!- {nick} [{host}] has left {channel} [{reason}]" line = "-!- " + nick + " " + "[" + host + "] has left "
line += channel + " [" + reason + "]"
self.log(channel, line) self.log(channel, line)
self.channels[channel].users.pop(nick) self.channels[channel].remove_user(nick)
def userQuit(self, user, quitMessage): def userQuit(self, user, quitMessage):
"""Called when the bot sees another user disconnect from the network.""" """Called when the bot sees another user disconnect from the network."""
nick, _, host = user.partition("!") nick, _, host = user.partition("!")
line = "-!- {nick} [{host}] has quit [{quitMessage}]" line = "-!- " + nick + " [" + host + "] has quit [" + quitMessage + "]"
for channel_name, channel in self.channels.items(): channels = list(self.users[nick].channels.keys())
if not nick in channel.users: for channel in channels:
continue
self.log(channel, line) self.log(channel, line)
channel.users.pop(nick) self.channels[channel].remove_user(nick)
self.users.pop(nick)
def userKicked(self, kickee, channel, kicker, message): def userKicked(self, kickee, channel, kicker, message):
""" """
Called when the bot sees another user getting kicked from the channel. Called when the bot sees another user getting kicked from the channel.
""" """
line =f"-!- {kickee} was kicked from {channel} by {kicker} [{message}]" line = "-!- " + kickee + " was kicked from " + channel
line += " by " + kicker + " [" + message + "]"
self.log(channel, line) self.log(channel, line)
self.channels[channel].users.pop(kickee) self.channels[channel].remove_user(kickee)
def topicUpdated(self, user, channel, newTopic): def topicUpdated(self, user, channel, newTopic):
"""Called when the bot sees a user update the channel's topic.""" """Called when the bot sees a user update the channel's topic."""
line = f"-!- {user} changed the topic of {channel} to: {newTopic}" line = "-!- " + user + " changed the topic of " + channel + " to: "
line += newTopic
self.log(channel, line) self.log(channel, line)
self.channels[channel].topic = newTopic self.channels[channel].topic = newTopic
@ -449,14 +476,15 @@ class Fulvia(irc.IRCClient):
def userRenamed(self, oldname, newname): def userRenamed(self, oldname, newname):
"""Called when the bot sees a user change their nickname.""" """Called when the bot sees a user change their nickname."""
line = "-!- {oldname} is now known as {newname}" line = "-!- " + oldname + " is now known as " + newname
for channel_name, channel in self.channels.items(): user = self.users.pop(oldname)
self.log(channel_name, line) self.users[newname] = user
for key, channel in user.channels.items():
self.log(key, line)
user = channel.users.pop(oldname) channel.rename_user(oldname, newname)
user.nick = newname user.nick = newname
channel.users[newname] = user
def namesReceived(self, channel, channel_type, nicklist): def namesReceived(self, channel, channel_type, nicklist):
@ -466,13 +494,12 @@ class Fulvia(irc.IRCClient):
""" """
self.channels[channel].channel_type = channel_type self.channels[channel].channel_type = channel_type
for nick in nicklist: for nick in nicklist:
op_level = '' op_level = tools.op_level.get(nick[0], 0)
if nick[0] in tools.op_level.keys(): if op_level > 0:
op_level = nick[0]
nick = nick[1:] nick = nick[1:]
user = tools.User(nick) self.users[nick] = tools.User(nick)
user.op_level = op_level self.channels[channel].add_user(self.users[nick])
self.channels[channel].users[nick] = user self.channels[channel].privileges[nick] = op_level
def whoisUser(self, nick, ident, host, realname): def whoisUser(self, nick, ident, host, realname):
@ -483,13 +510,6 @@ class Fulvia(irc.IRCClient):
self.username = ident self.username = ident
self.host = host self.host = host
self.realname = realname self.realname = realname
else:
for channel_name, channel in self.channels.items():
if nick in channel:
user = channel[nick]
user.ident = ident
user.host = host
user.realname = realname
def whoisIdle(self, nick, idle, signon): def whoisIdle(self, nick, idle, signon):
@ -514,10 +534,11 @@ class Fulvia(irc.IRCClient):
an appropriate length automatically. an appropriate length automatically.
""" """
if user.startswith("#"): if user.startswith("#"):
opSym = self.channels[user].users[self.nickname].op_level priv = self.channels[user].privileges[self.nickname]
opSym = tools.getOpSym(priv)
else: else:
opSym = '' opSym = ""
line = f"<{opSym}{self.nickname}> {message}" line = "<" + opSym + self.nickname + ">" + " " + message
self.log(user, line) self.log(user, line)
irc.IRCClient.msg(self, user, message, length=None) irc.IRCClient.msg(self, user, message, length=None)

View File

@ -30,7 +30,6 @@ def unload_module(bot, name):
bot.unregister_callable(obj) bot.unregister_callable(obj)
del old_module del old_module
delattr(sys.modules['modules'], name.rpartition('.')[2])
del sys.modules[name] del sys.modules[name]
@ -77,10 +76,36 @@ def process_callable(func, config):
Sets various helper atributes about a given function. Sets various helper atributes about a given function.
""" """
prefix = config.core.prefix prefix = config.core.prefix
doc = func.__doc__
if doc:
doc = doc.strip()
doc = doc.replace("\t", "")
doc = doc.replace("\n\n", "\x00")
doc = doc.replace("\n", " ")
doc = doc.replace("\x00", "\n")
func._docs = {}
func.example = getattr(func, "example", [(None, None)])
func.thread = getattr(func, "thread", True) func.thread = getattr(func, "thread", True)
func.hook = getattr(func, "hook", False) func.hook = getattr(func, "hook", False)
func.rate = getattr(func, "rate", 0) func.rate = getattr(func, "rate", 0)
func.channel_rate = getattr(func, "channel_rate", 0) func.channel_rate = getattr(func, "channel_rate", 0)
func.global_rate = getattr(func, "global_rate", 0) func.global_rate = getattr(func, "global_rate", 0)
func.priv = getattr(func, "priv", 0)
func.user_joined = getattr(func, "user_joined", False) func.user_joined = getattr(func, "user_joined", False)
if hasattr(func, 'commands'):
if len(func.commands) > 1:
func.aliases = func.commands[1:]
else:
func.aliases = []
if hasattr(func, 'example'):
for n, example in enumerate(func.example):
ex_input = example[0]
if not ex_input:
continue
if ex_input[0] != prefix:
ex_input = prefix + ex_input
func.example[n] = (ex_input, example[1])
if doc:
func._docs = (doc, func.example)

View File

@ -146,6 +146,32 @@ def require_chanmsg(message=None):
return actual_decorator return actual_decorator
def require_privilege(level, message=None):
"""
Decorate a function to require at least the given channel permission.
`level` can be one of the privilege levels defined in this module. If the
user does not have the privilege, `message` will be said if given. If it is
a private message, no checking will be done.
"""
def actual_decorator(function):
function.priv = level
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
# If this is a privmsg, ignore privilege requirements
if trigger.is_privmsg or trigger.admin:
return function(bot, trigger, *args, **kwargs)
channel_privs = bot.channels[trigger.channel].privileges
allowed = channel_privs.get(trigger.nick, 0) >= level
if not allowed:
if message and not callable(message):
bot.msg(message)
else:
return function(bot, trigger, *args, **kwargs)
return guarded
return actual_decorator
def require_admin(message=None): def require_admin(message=None):
""" """
Decorate a function to require the triggering user to be a bot admin. Decorate a function to require the triggering user to be a bot admin.
@ -153,7 +179,8 @@ def require_admin(message=None):
If they are not, `message` will be said if given. If they are not, `message` will be said if given.
""" """
def actual_decorator(function): def actual_decorator(function):
function.require_admin = True function.priv = 5
@functools.wraps(function) @functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs): def guarded(bot, trigger, *args, **kwargs):
if not trigger.admin: if not trigger.admin:
@ -175,7 +202,8 @@ def require_owner(message=None):
If they are not, `message` will be said if given. If they are not, `message` will be said if given.
""" """
def actual_decorator(function): def actual_decorator(function):
function.require_owner = True function.priv = 10
@functools.wraps(function) @functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs): def guarded(bot, trigger, *args, **kwargs):
if not trigger.owner: if not trigger.owner:
@ -208,7 +236,7 @@ def example(ex_input, ex_output=None):
def url_callback(url): def url_callback(url):
""" """
Decorate a function with a callback to the URL module. Decore a function with a callback to the URL module.
This URL will be added to the bot.url_callbacks dictionary in the bot's This URL will be added to the bot.url_callbacks dictionary in the bot's
memory which the URL module will compare it's URL's against. If a key in memory which the URL module will compare it's URL's against. If a key in

View File

@ -26,11 +26,13 @@ def new_bot_name(number):
@user_joined(True) @user_joined(True)
def adalwulf_(bot, trigger): def adalwulf_(bot, trigger):
"""Renames adalwulf__.""" """Renames adalwulf__."""
print(True)
print(trigger.nick)
if not trigger.nick.startswith('defaultnick'): if not trigger.nick.startswith('defaultnick'):
return return
nicks = bot.channels[trigger.channel].users.keys() names = bot.channels[trigger.channel].users
adals = [nick for nick in nicks if nick.startswith('Adalwulf__')] adals = [nick for nick in names if nick.startswith('Adalwulf__')]
adals += [nick for nick in nicks if nick.startswith('Adalwulf_|')] adals += [nick for nick in names if nick.startswith('Adalwulf_|')]
old_nick = trigger.nick old_nick = trigger.nick
new_nick = new_bot_name(len(adals) + 1) new_nick = new_bot_name(len(adals) + 1)
bot.sendLine(f"SANICK {old_nick} {new_nick}") bot.sendLine(f"SANICK {old_nick} {new_nick}")
@ -40,12 +42,12 @@ def adalwulf_(bot, trigger):
@commands('rename_hydra') @commands('rename_hydra')
def rename_hydra(bot, trigger): def rename_hydra(bot, trigger):
"""Renames defaultnick's appropriately.""" """Renames defaultnick's appropriately."""
for nick in bot.channels[trigger.channel].users.keys(): for nick in list(bot.channels[trigger.channel].users.keys()):
if not nick.startswith('defaultnick'): if not nick.startswith('defaultnick'):
continue continue
nicks = bot.channels[trigger.channel].users.keys() names = bot.channels[trigger.channel].users
adals = [nick for nick in nicks if nick.startswith('Adalwulf__')] adals = [nick for nick in names if nick.startswith('Adalwulf__')]
adals += [nick for nick in nicks if nick.startswith('Adalwulf_|')] adals += [nick for nick in names if nick.startswith('Adalwulf_|')]
old_nick = nick old_nick = nick
new_nick = new_bot_name(len(adals) + 1) new_nick = new_bot_name(len(adals) + 1)
print(f"SANICK {old_nick} {new_nick}") print(f"SANICK {old_nick} {new_nick}")

View File

@ -9,17 +9,18 @@ import re
import module import module
from tools import op_level, configureHostMask from tools import op_level, configureHostMask
op = ['~', '!', '@', '%'] OP = op_level["op"]
HALFOP = op_level["halfop"]
@module.require_chanmsg @module.require_chanmsg
@module.require_admin @module.require_privilege(OP, 'You are not a channel operator.')
@module.commands('kick') @module.commands('kick')
@module.example(".kick faggot being a faggot") @module.example(".kick faggot being a faggot")
def kick(bot, trigger): def kick(bot, trigger):
""" """
Kick a user from the channel. Kick a user from the channel.
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].privileges[bot.nick] < HALFOP:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if not trigger.group(2):
return bot.reply("Who do you want me to kick?") return bot.reply("Who do you want me to kick?")
@ -35,7 +36,7 @@ def kick(bot, trigger):
@module.require_chanmsg @module.require_chanmsg
@module.require_admin @module.require_privilege(OP, 'You are not a channel operator.')
@module.commands('ban') @module.commands('ban')
@module.example(".ban faggot") @module.example(".ban faggot")
def ban(bot, trigger): def ban(bot, trigger):
@ -43,7 +44,7 @@ def ban(bot, trigger):
This give admins the ability to ban a user. This give admins the ability to ban a user.
The bot must be a Channel Operator for this command to work. The bot must be a Channel Operator for this command to work.
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].privileges[bot.nick] < HALFOP:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if not trigger.group(2):
return bot.reply("Who do you want me to ban?") return bot.reply("Who do you want me to ban?")
@ -53,7 +54,7 @@ def ban(bot, trigger):
@module.require_chanmsg @module.require_chanmsg
@module.require_admin @module.require_privilege(OP, 'You are not a channel operator.')
@module.commands('unban') @module.commands('unban')
@module.example(".unban faggot") @module.example(".unban faggot")
def unban(bot, trigger): def unban(bot, trigger):
@ -61,7 +62,7 @@ def unban(bot, trigger):
This give admins the ability to unban a user. This give admins the ability to unban a user.
The bot must be a Channel Operator for this command to work. The bot must be a Channel Operator for this command to work.
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].privileges[bot.nick] < HALFOP:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if not trigger.group(2):
return bot.reply("Who do you want me to ban?") return bot.reply("Who do you want me to ban?")
@ -71,7 +72,7 @@ def unban(bot, trigger):
@module.require_chanmsg @module.require_chanmsg
@module.require_admin @module.require_privilege(OP, 'You are not a channel operator.')
@module.commands('kickban') @module.commands('kickban')
def kickban(bot, trigger): def kickban(bot, trigger):
""" """
@ -79,7 +80,7 @@ def kickban(bot, trigger):
The bot must be a Channel Operator for this command to work. The bot must be a Channel Operator for this command to work.
.kickban [#chan] user1 user!*@module.* get out of here .kickban [#chan] user1 user!*@module.* get out of here
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].privileges[bot.nick] < HALFOP:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if not trigger.group(2):
return bot.reply("Who do you want me to ban?") return bot.reply("Who do you want me to ban?")
@ -97,7 +98,7 @@ def kickban(bot, trigger):
@module.require_chanmsg @module.require_chanmsg
@module.require_admin @module.require_privilege(OP, 'You are not a channel operator.')
@module.commands('settopic') @module.commands('settopic')
@module.example(".settopic We're discussing penises, would you like to join?") @module.example(".settopic We're discussing penises, would you like to join?")
def settopic(bot, trigger): def settopic(bot, trigger):
@ -105,7 +106,7 @@ def settopic(bot, trigger):
This gives ops the ability to change the topic. This gives ops the ability to change the topic.
The bot must be a Channel Operator for this command to work. The bot must be a Channel Operator for this command to work.
""" """
if bot.channels[trigger.channel].users[bot.nick].op_level not in op: if bot.channels[trigger.channel].privileges[bot.nick] < HALFOP:
return bot.reply("I'm not a channel operator!") return bot.reply("I'm not a channel operator!")
if not trigger.group(2): if not trigger.group(2):
return bot.reply("What do you want the topic set to?") return bot.reply("What do you want the topic set to?")

View File

@ -14,6 +14,6 @@ def announce(bot, trigger):
if not trigger.admin: if not trigger.admin:
bot.reply("Sorry, I can't let you do that") bot.reply("Sorry, I can't let you do that")
return return
for channel in bot.channels.keys(): for channel in bot.channels:
bot.msg(channel, f"[ANNOUNCEMENT] {trigger.group(2)}") bot.msg(channel, f"[ANNOUNCEMENT] {trigger.group(2)}")
bot.reply('Announce complete.') bot.reply('Announce complete.')

View File

@ -54,7 +54,7 @@ def banheall(bot, trigger):
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
period = 0 period = 0
for nick in bot.channels[trigger.channel].users.keys(): for nick in bot.channels[trigger.channel].users:
banmask = configureHostMask(nick) banmask = configureHostMask(nick)
bot.mode(trigger.channel, True, "b", mask=banmask) bot.mode(trigger.channel, True, "b", mask=banmask)
@ -66,7 +66,7 @@ def banheall(bot, trigger):
bot.msg(f"Banned \x0304them all\x03 for \x0309{str(period)}\x03 seconds.") bot.msg(f"Banned \x0304them all\x03 for \x0309{str(period)}\x03 seconds.")
time.sleep(period) time.sleep(period)
for nick in bot.channels[trigger.channel].users.keys(): for nick in bot.channels[trigger.channel].users:
banmask = configureHostMask(nick) banmask = configureHostMask(nick)
bot.mode(trigger.channel, False, "b", mask=banmask) bot.mode(trigger.channel, False, "b", mask=banmask)

View File

@ -7,50 +7,38 @@ import random
from module import commands, example from module import commands, example
def clean_docstring(doc):
"""Cleans the docstring up a bit."""
if not doc:
return ''
doc = doc.strip()
doc = doc.replace("\t", "")
doc = doc.replace("\n\n", "\x00")
doc = doc.replace("\n", " ")
doc = doc.replace("\x00", "\n")
return doc
@commands('help', 'commands') @commands('help', 'commands')
@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 trigger.group(2):
command = trigger.group(2) name = trigger.group(2)
command = command.lower() name = name.lower()
if command not in bot.commands: if name not in bot.commands:
return bot.msg("Command not found.") return bot.msg("Command not found.")
cmd = bot.commands[name]
docstring, examples = cmd.doc
if examples:
ex = random.choice(examples)
func = bot.commands[command] bot.msg(docstring)
doc = clean_docstring(func.__doc__) if cmd.aliases:
bot.msg(doc) bot.msg("Aliases: " + ", ".join(cmd.aliases))
if ex[0]:
aliases = [c for c, f in bot.commands.items() if f == func]
aliases.remove(command)
if aliases:
bot.msg("Aliases: " + ", ".join(aliases))
if hasattr(func, 'example'):
ex = random.choice(func.example)
bot.msg("Ex. In: " + ex[0]) bot.msg("Ex. In: " + ex[0])
if ex[1]: if ex[1]:
bot.msg("Ex. Out: " + ex[1]) bot.msg("Ex. Out: " + ex[1])
else: else:
funcs = [func for cmd, func in bot.commands.items()] if trigger.owner:
if not trigger.owner: cmds = [cmd for _, cmd in bot.commands.items()]
funcs = [f for f in funcs if not hasattr(f, 'require_owner')] elif trigger.admin:
if not trigger.admin: cmds = [cmd for _, cmd in bot.commands.items() if cmd.priv <= 5]
funcs = [f for f in funcs if not hasattr(f, 'require_admin')] else:
priv = bot.channels[trigger.channel].privileges[trigger.nick]
cmds = [cmd for _, cmd in bot.commands.items() if cmd.priv <= priv]
cmds = {func.commands[0] for func in funcs} cmds = [cmd.name for cmd in cmds if cmd.canonical]
cmds = sorted(list(cmds)) cmds = sorted(cmds)
msg = "Available commands: " + ", ".join(cmds) msg = "Available commands: " + ", ".join(cmds)
bot.msg(msg) bot.msg(msg)

View File

@ -10,8 +10,8 @@ def pingAll(bot, trigger):
Says the nick of everyone in the channel. Great way to get thier Says the nick of everyone in the channel. Great way to get thier
attention, or just annoy them. attention, or just annoy them.
""" """
nicks = list(bot.channels[trigger.channel].users.keys()) names = list(bot.channels[trigger.channel].users.keys())
for nigger in ["Ishd", "Ishd2", "Ishd_"]: for nigger in ["Ishd", "Ishd2", "Ishd_"]:
if nigger in nicks: if nigger in names:
nicks.remove(nigger) names.remove(nigger)
bot.msg(" ".join(nicks)) bot.msg(" ".join(names))

View File

@ -72,4 +72,4 @@ def f_unload(bot, trigger):
return bot.msg(f"Module '{name}' not loaded, try the 'load' command.") return bot.msg(f"Module '{name}' not loaded, try the 'load' command.")
loader.unload_module(bot, name) loader.unload_module(bot, name)
bot.msg(f"Module '{name}' unloaded.") bot.msg(f"Module '{name}' unloaded.")

View File

@ -24,6 +24,7 @@ class MonitorThread(threading.Thread):
self.stop = threading.Event() self.stop = threading.Event()
def run(self): def run(self):
# while not self._bot.channels.keys():
while not self._bot.stillConnected(): while not self._bot.stillConnected():
time.sleep(1) time.sleep(1)
# don't try to say anything if we're not fully connected yet # don't try to say anything if we're not fully connected yet

View File

@ -111,8 +111,8 @@ class FulviaMemoryDefault(defaultdict):
return result return result
class User: class User(object):
"""A representation of a user in a channel.""" """A representation of a user Fulvia is aware of."""
def __init__(self, nick): def __init__(self, nick):
self.nick = nick self.nick = nick
"""The user's nickname.""" """The user's nickname."""
@ -124,21 +124,18 @@ class User:
self.host = "" self.host = ""
"""The user's hostname.""" """The user's hostname."""
self.realname = "" self.channels = {}
"""The user's realname.""" """The channels the user is in."""
self.away = None self.away = None
"""Whether the user is marked as away.""" """Whether the user is marked as away."""
self.op_level = '' hostmask = property(lambda self: '{}!{}@{}'.format(self.nick, self.user,
"""The user's op level in this channel.""" self.host))
"""The user's full hostmask."""
def hostmask(self):
"""Returns the user's full hostmask."""
return f"{self.nick}!{self.user}@{self.host}"
class Channel: class Channel(object):
"""A representation of a channel Fulvia is in.""" """A representation of a channel Fulvia is in."""
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
@ -154,11 +151,55 @@ class Channel:
"""The topic of the channel.""" """The topic of the channel."""
self.users = {} self.users = {}
"""The users in the channel.""" """The users in the channel. A set to ensure there are no duplicates."""
self.privileges = {}
"""The op levels of the users in the channel."""
self.modes = set() self.modes = set()
"""The current modes on the channel.""" """The current modes on the channel."""
def remove_user(self, nick):
"""
Removes a user from the channel.
"""
user = self.users.pop(nick, None)
self.privileges.pop(nick, None)
if user != None:
user.channels.pop(self.name, None)
def add_user(self, user):
"""
Adds a user to the channel.
"""
assert isinstance(user, User)
self.users[user.nick] = user
self.privileges[user.nick] = 0
user.channels[self.name] = self
def rename_user(self, old, new):
"""
Renames the user.
"""
if old in self.users:
self.users[new] = self.users.pop(old)
if old in self.privileges:
self.privileges[new] = self.privileges.pop(old)
class Command():
"""
A representation of a command and associated documentation and other
atributes.
"""
def __init__(self, name):
self.name = name
self._func_name = ""
self.priv = 0
self.doc = None
self.canonical = True
self.aliases = []
def configureHostMask(mask): def configureHostMask(mask):
""" """