refactor bot.channels, privileges and more

This commit is contained in:
iou1name 2019-10-08 11:45:21 -04:00
parent c09b4c04d3
commit d06b8f2fdc
11 changed files with 102 additions and 179 deletions

112
bot.py
View File

@ -62,9 +62,6 @@ class Fulvia(irc.IRCClient):
in them.
"""
self.users = tools.FulviaMemory()
"""A dictionary of all users the bot is aware of."""
self.memory = tools.FulviaMemory()
"""
A thread-safe general purpose dictionary to be used by whatever
@ -243,11 +240,11 @@ class Fulvia(irc.IRCClient):
"""
nick = user.partition("!")[0].partition("@")[0]
if channel.startswith("#"):
opSym = tools.getOpSym(self.channels[channel].privileges[nick])
opSym = self.channels[channel].users[nick].op_level
else:
opSym = ""
opSym = ''
channel = nick
line = "<" + opSym + nick + ">" + " " + message
line = f"<{opSym}{nick}> {message}"
self.log(channel, line)
funcs = []
@ -292,19 +289,20 @@ class Fulvia(irc.IRCClient):
def joined(self, channel):
"""Called when the bot joins a new channel."""
line = "-!- " + self.nickname + " " + "[" + self.username + "@"
line += self.host + "] has joined " + channel
line = f"-!- {self.nickname} [{self.username}@{self.host}] "
line += f"has joined {channel}"
self.log(channel, line)
print(f"Joined {channel}")
if channel not in self.channels:
self.channels[channel] = tools.Channel(channel)
user = tools.User(self.nickname)
self.channels[channel].users[self.nickname] = user
def left(self, channel, reason=""):
"""Called when the bot leaves a channel."""
line = "-!- " + self.nickname + " " + "[" + self.username + "@"
line += self.host + "] has left " + channel + " [" + reason + "]"
line = f"-!- {self.nickname} [{self.username}@{self.host}] "
line += f"has left {channel} [{reason}]"
self.log(channel, line)
print(f"Parted {channel}")
@ -319,7 +317,7 @@ class Fulvia(irc.IRCClient):
def modeChanged(self, user, channel, add_mode, modes, args):
"""Called when users or channel's modes are changed."""
line = "-!- mode/" + channel + " ["
line = f"-!- mode/{channel} ["
if add_mode:
line += "+"
else:
@ -342,11 +340,11 @@ class Fulvia(irc.IRCClient):
elif mode in tools.op_level.keys(): # user mode, op_level mode
nick = args[n]
op_level = tools.op_level[mode]
user = self.channels[channel].users[nick]
if add_mode:
self.channels[channel].privileges[nick] += op_level
user.op_level += mode
else:
self.channels[channel].privileges[nick] -= op_level
user.op_level.replace(mode, '', 1)
else: # user mode, non-op_level mode
continue
@ -359,8 +357,8 @@ class Fulvia(irc.IRCClient):
print(f"Signed on as {self.nickname}")
self.whois(self.nickname)
line = "*** Signed onto " + self.hostname + " as "
line += self.nickname + "!" + self.username + "@" + self.host
line = f"*** Signed onto {self.hostname} as "
line += f"{self.nickname}!{self.username}@{self.host}"
self.log(self.hostname, line)
for channel in self.config.core.channels.split(","):
@ -369,8 +367,8 @@ class Fulvia(irc.IRCClient):
def kickedFrom(self, channel, kicker, message):
"""Called when the bot is kicked from a channel."""
line = "-!- " + self.nickname + " was kicked from " + channel
line += " by " + kicker + " [" + message + "]"
line = f"-!- {self.nickname} was kicked from {channel} "
line += f"by {kicker} [{message}]"
self.log(channel, line)
self.channels.pop(channel)
@ -378,16 +376,14 @@ class Fulvia(irc.IRCClient):
def nickChanged(self, nick):
"""Called when the bot changes it's nickname."""
line = "-!- you are now known as " + nick
line = f"-!- you are now known as {nick}"
user = self.users.pop(self.nickname)
self.users[nick] = user
for key, channel in self.channels.items():
for channel_name, channel in self.channels.items():
self.log(key, line)
channel.rename_user(self.nickname, nick)
user = channel.users.pop(self.nickname)
user.nick = nick
self.nickname = nick
channel.users[nick] = user
## Actions the bot observes other users doing in the channel.
@ -397,12 +393,11 @@ class Fulvia(irc.IRCClient):
"""Called when the bot sees another user join a channel."""
nick, _, host = user.partition("!")
line = "-!- " + nick + " " + "[" + host + "] has joined " + channel
line = f"-!- {nick} [{host}] has joined {channel}"
self.log(channel, line)
if nick not in self.users:
self.users[nick] = tools.User(nick)
self.channels[channel].add_user(self.users[nick])
user = tools.User(nick)
self.channels[channel].users[nick] = user
for func in self._user_joined:
trigger = Trigger(user, channel, f"{user} has joined", "PRIVMSG", self.config)
@ -415,41 +410,38 @@ class Fulvia(irc.IRCClient):
"""Called when the bot sees another user leave a channel."""
nick, _, host = user.partition("!")
line = "-!- " + nick + " " + "[" + host + "] has left "
line += channel + " [" + reason + "]"
line = f"-!- {nick} [{host}] has left {channel} [{reason}]"
self.log(channel, line)
self.channels[channel].remove_user(nick)
self.channels[channel].users.pop(nick)
def userQuit(self, user, quitMessage):
"""Called when the bot sees another user disconnect from the network."""
nick, _, host = user.partition("!")
line = "-!- " + nick + " [" + host + "] has quit [" + quitMessage + "]"
line = "-!- {nick} [{host}] has quit [{quitMessage}]"
channels = list(self.users[nick].channels.keys())
for channel in channels:
for channel_name, channel in self.channels.items():
if not nick in channel.users:
continue
self.log(channel, line)
self.channels[channel].remove_user(nick)
self.users.pop(nick)
channel.users.pop(nick)
def userKicked(self, kickee, channel, kicker, message):
"""
Called when the bot sees another user getting kicked from the channel.
"""
line = "-!- " + kickee + " was kicked from " + channel
line += " by " + kicker + " [" + message + "]"
line =f"-!- {kickee} was kicked from {channel} by {kicker} [{message}]"
self.log(channel, line)
self.channels[channel].remove_user(kickee)
self.channels[channel].users.pop(kickee)
def topicUpdated(self, user, channel, newTopic):
"""Called when the bot sees a user update the channel's topic."""
line = "-!- " + user + " changed the topic of " + channel + " to: "
line += newTopic
line = f"-!- {user} changed the topic of {channel} to: {newTopic}"
self.log(channel, line)
self.channels[channel].topic = newTopic
@ -457,15 +449,14 @@ class Fulvia(irc.IRCClient):
def userRenamed(self, oldname, newname):
"""Called when the bot sees a user change their nickname."""
line = "-!- " + oldname + " is now known as " + newname
line = "-!- {oldname} is now known as {newname}"
user = self.users.pop(oldname)
self.users[newname] = user
for key, channel in user.channels.items():
self.log(key, line)
for channel_name, channel in self.channels.items():
self.log(channel_name, line)
channel.rename_user(oldname, newname)
user = channel.users.pop(oldname)
user.nick = newname
channel.users[newname] = user
def namesReceived(self, channel, channel_type, nicklist):
@ -475,12 +466,13 @@ class Fulvia(irc.IRCClient):
"""
self.channels[channel].channel_type = channel_type
for nick in nicklist:
op_level = tools.op_level.get(nick[0], 0)
if op_level > 0:
op_level = ''
if nick[0] in tools.op_level.keys():
op_level = nick[0]
nick = nick[1:]
self.users[nick] = tools.User(nick)
self.channels[channel].add_user(self.users[nick])
self.channels[channel].privileges[nick] = op_level
user = tools.User(nick)
user.op_level = op_level
self.channels[channel].users[nick] = user
def whoisUser(self, nick, ident, host, realname):
@ -491,6 +483,13 @@ class Fulvia(irc.IRCClient):
self.username = ident
self.host = host
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):
@ -515,11 +514,10 @@ class Fulvia(irc.IRCClient):
an appropriate length automatically.
"""
if user.startswith("#"):
priv = self.channels[user].privileges[self.nickname]
opSym = tools.getOpSym(priv)
opSym = self.channels[user].users[self.nickname].op_level
else:
opSym = ""
line = "<" + opSym + self.nickname + ">" + " " + message
opSym = ''
line = f"<{opSym}{self.nickname}> {message}"
self.log(user, line)
irc.IRCClient.msg(self, user, message, length=None)

View File

@ -78,25 +78,9 @@ def process_callable(func, config):
"""
prefix = config.core.prefix
func.example = getattr(func, "example", [(None, None)])
func.thread = getattr(func, "thread", True)
func.hook = getattr(func, "hook", False)
func.rate = getattr(func, "rate", 0)
func.channel_rate = getattr(func, "channel_rate", 0)
func.global_rate = getattr(func, "global_rate", 0)
func.priv = getattr(func, "priv", 0)
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])

View File

@ -146,32 +146,6 @@ def require_chanmsg(message=None):
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):
"""
Decorate a function to require the triggering user to be a bot admin.
@ -179,8 +153,7 @@ def require_admin(message=None):
If they are not, `message` will be said if given.
"""
def actual_decorator(function):
function.priv = 5
function.require_admin = True
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
if not trigger.admin:
@ -202,8 +175,7 @@ def require_owner(message=None):
If they are not, `message` will be said if given.
"""
def actual_decorator(function):
function.priv = 10
function.require_owner = True
@functools.wraps(function)
def guarded(bot, trigger, *args, **kwargs):
if not trigger.owner:

View File

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

View File

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

View File

@ -54,7 +54,7 @@ def banheall(bot, trigger):
except (IndexError, KeyError, ValueError, TypeError):
period = 0
for nick in bot.channels[trigger.channel].users:
for nick in bot.channels[trigger.channel].users.keys():
banmask = configureHostMask(nick)
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.")
time.sleep(period)
for nick in bot.channels[trigger.channel].users:
for nick in bot.channels[trigger.channel].users.keys():
banmask = configureHostMask(nick)
bot.mode(trigger.channel, False, "b", mask=banmask)

View File

@ -44,13 +44,11 @@ def help(bot, trigger):
bot.msg("Ex. Out: " + ex[1])
else:
if trigger.owner:
funcs = [func for cmd, func in bot.commands.items()]
elif trigger.admin:
funcs = [func for cmd, func in bot.commands.items() if cmd.priv <= 5]
else:
priv = bot.channels[trigger.channel].privileges[trigger.nick]
funcs = [func for cmd, func in bot.commands.items() if cmd.priv <= priv]
if not trigger.owner:
funcs = [f for f in funcs if not hasattr(f, 'require_owner')]
if not trigger.admin:
funcs = [f for f in funcs if not hasattr(f, 'require_admin')]
cmds = {func.commands[0] for func in funcs}
cmds = sorted(list(cmds))

View File

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

View File

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

View File

@ -111,8 +111,8 @@ class FulviaMemoryDefault(defaultdict):
return result
class User(object):
"""A representation of a user Fulvia is aware of."""
class User:
"""A representation of a user in a channel."""
def __init__(self, nick):
self.nick = nick
"""The user's nickname."""
@ -124,18 +124,21 @@ class User(object):
self.host = ""
"""The user's hostname."""
self.channels = {}
"""The channels the user is in."""
self.realname = ""
"""The user's realname."""
self.away = None
"""Whether the user is marked as away."""
hostmask = property(lambda self: '{}!{}@{}'.format(self.nick, self.user,
self.host))
"""The user's full hostmask."""
self.op_level = ''
"""The user's op level in this channel."""
def hostmask(self):
"""Returns the user's full hostmask."""
return f"{self.nick}!{self.user}@{self.host}"
class Channel(object):
class Channel:
"""A representation of a channel Fulvia is in."""
def __init__(self, name):
self.name = name
@ -151,41 +154,11 @@ class Channel(object):
"""The topic of the channel."""
self.users = {}
"""The users in the channel. A set to ensure there are no duplicates."""
self.privileges = {}
"""The op levels of the users in the channel."""
"""The users in the channel."""
self.modes = set()
"""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)
def configureHostMask(mask):
"""