Massively improve user handling
This changes how users are handled, making it less likely to encounter duplicate users; in normal circumstances, duplicates shouldn't happen.
This commit is contained in:
parent
01924504fe
commit
360204bf43
54
src/hooks.py
54
src/hooks.py
@ -46,18 +46,9 @@ def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status
|
|||||||
|
|
||||||
modes = {Features["PREFIX"].get(s) for s in status} - {None}
|
modes = {Features["PREFIX"].get(s) for s in status} - {None}
|
||||||
|
|
||||||
if nick == bot_nick:
|
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname) # FIXME
|
||||||
users.Bot.nick = nick
|
|
||||||
cli.ident = users.Bot.ident = ident
|
|
||||||
cli.hostmask = users.Bot.host = host
|
|
||||||
cli.real_name = users.Bot.realname = realname
|
|
||||||
|
|
||||||
try: # FIXME: These function names are temporary until everything is moved over
|
|
||||||
user = users._get(nick, ident, host, realname, allow_bot=True)
|
|
||||||
except KeyError:
|
|
||||||
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname)
|
|
||||||
|
|
||||||
ch = channels.add(chan, cli)
|
ch = channels.add(chan, cli)
|
||||||
|
|
||||||
if ch not in user.channels:
|
if ch not in user.channels:
|
||||||
user.channels[ch] = modes
|
user.channels[ch] = modes
|
||||||
ch.users.add(user)
|
ch.users.add(user)
|
||||||
@ -70,7 +61,7 @@ def who_reply(cli, bot_server, bot_nick, chan, ident, host, server, nick, status
|
|||||||
event.dispatch(var, ch, user)
|
event.dispatch(var, ch, user)
|
||||||
|
|
||||||
if ch is channels.Main and not users.exists(nick): # FIXME
|
if ch is channels.Main and not users.exists(nick): # FIXME
|
||||||
users.add(nick, ident=ident,host=host,account="*",inchan=True,modes=modes,moded=set())
|
users.add(nick, ident=ident, host=host, account="*", inchan=True, modes=modes, moded=set())
|
||||||
|
|
||||||
@hook("whospcrpl")
|
@hook("whospcrpl")
|
||||||
def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address, host, server, nick, status, hop, idle, account, realname):
|
def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address, host, server, nick, status, hop, idle, account, realname):
|
||||||
@ -118,19 +109,9 @@ def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address,
|
|||||||
|
|
||||||
modes = {Features["PREFIX"].get(s) for s in status} - {None}
|
modes = {Features["PREFIX"].get(s) for s in status} - {None}
|
||||||
|
|
||||||
if nick == bot_nick:
|
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname, account=account) # FIXME
|
||||||
users.Bot.nick = nick
|
|
||||||
cli.ident = users.Bot.ident = ident
|
|
||||||
cli.hostmask = users.Bot.host = host
|
|
||||||
cli.real_name = users.Bot.realname = realname
|
|
||||||
users.Bot.account = account
|
|
||||||
|
|
||||||
try: # FIXME
|
|
||||||
user = users._get(nick, ident, host, realname, account, allow_bot=True)
|
|
||||||
except KeyError:
|
|
||||||
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname, account=account)
|
|
||||||
|
|
||||||
ch = channels.add(chan, cli)
|
ch = channels.add(chan, cli)
|
||||||
|
|
||||||
if ch not in user.channels:
|
if ch not in user.channels:
|
||||||
user.channels[ch] = modes
|
user.channels[ch] = modes
|
||||||
ch.users.add(user)
|
ch.users.add(user)
|
||||||
@ -143,7 +124,7 @@ def extended_who_reply(cli, bot_server, bot_nick, data, chan, ident, ip_address,
|
|||||||
event.dispatch(var, ch, user)
|
event.dispatch(var, ch, user)
|
||||||
|
|
||||||
if ch is channels.Main and not users.exists(nick): # FIXME
|
if ch is channels.Main and not users.exists(nick): # FIXME
|
||||||
users.add(nick, ident=ident,host=host,account=account,inchan=True,modes=modes,moded=set())
|
users.add(nick, ident=ident, host=host, account=account, inchan=True, modes=modes, moded=set())
|
||||||
|
|
||||||
@hook("endofwho")
|
@hook("endofwho")
|
||||||
def end_who(cli, bot_server, bot_nick, target, rest):
|
def end_who(cli, bot_server, bot_nick, target, rest):
|
||||||
@ -291,7 +272,7 @@ def mode_change(cli, rawnick, chan, mode, *targets):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
actor = users._get(rawnick, allow_none=True) # FIXME
|
actor = users._add(cli, nick=rawnick) # FIXME
|
||||||
if chan == users.Bot.nick: # we only see user modes set to ourselves
|
if chan == users.Bot.nick: # we only see user modes set to ourselves
|
||||||
users.Bot.modes.update(mode)
|
users.Bot.modes.update(mode)
|
||||||
return
|
return
|
||||||
@ -469,7 +450,7 @@ def on_nick_change(cli, old_nick, nick):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = users._get(old_nick, allow_bot=True) # FIXME
|
user = users._get(old_nick) # FIXME
|
||||||
user.nick = nick
|
user.nick = nick
|
||||||
|
|
||||||
Event("nick_change", {}).dispatch(var, user, old_nick)
|
Event("nick_change", {}).dispatch(var, user, old_nick)
|
||||||
@ -504,17 +485,12 @@ def join_chan(cli, rawnick, chan, account=None, realname=None):
|
|||||||
ch = channels.add(chan, cli)
|
ch = channels.add(chan, cli)
|
||||||
ch.state = channels._States.Joined
|
ch.state = channels._States.Joined
|
||||||
|
|
||||||
if users.parse_rawnick_as_dict(rawnick)["nick"] == users.Bot.nick: # we may not be fully set up yet
|
user = users._add(cli, nick=rawnick, realname=realname, account=account) # FIXME
|
||||||
|
|
||||||
|
if user is users.Bot:
|
||||||
ch.mode()
|
ch.mode()
|
||||||
ch.mode(Features["CHANMODES"][0])
|
ch.mode(Features["CHANMODES"][0])
|
||||||
ch.who()
|
ch.who()
|
||||||
user = users.Bot
|
|
||||||
|
|
||||||
else:
|
|
||||||
try: # FIXME
|
|
||||||
user = users._get(rawnick, account=account, realname=realname, allow_bot=True)
|
|
||||||
except KeyError:
|
|
||||||
user = users._add(cli, nick=rawnick, account=account, realname=realname)
|
|
||||||
|
|
||||||
ch.users.add(user)
|
ch.users.add(user)
|
||||||
user.channels[ch] = set()
|
user.channels[ch] = set()
|
||||||
@ -540,7 +516,7 @@ def part_chan(cli, rawnick, chan, reason=""):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
ch = channels.add(chan, cli)
|
ch = channels.add(chan, cli)
|
||||||
user = users._get(rawnick, allow_bot=True) # FIXME
|
user = users._add(cli, nick=rawnick) # FIXME
|
||||||
|
|
||||||
if user is users.Bot: # oh snap! we're no longer in the channel!
|
if user is users.Bot: # oh snap! we're no longer in the channel!
|
||||||
ch._clear()
|
ch._clear()
|
||||||
@ -566,8 +542,8 @@ def kicked_from_chan(cli, rawnick, chan, target, reason):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
ch = channels.add(chan, cli)
|
ch = channels.add(chan, cli)
|
||||||
actor = users._get(rawnick, allow_bot=True) # FIXME
|
actor = users._add(cli, nick=rawnick) # FIXME
|
||||||
user = users._get(target, allow_bot=True) # FIXME
|
user = users._add(cli, nick=target) # FIXME
|
||||||
|
|
||||||
if user is users.Bot:
|
if user is users.Bot:
|
||||||
ch._clear()
|
ch._clear()
|
||||||
@ -608,7 +584,7 @@ def on_quit(cli, rawnick, reason):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
user = users._get(rawnick, allow_bot=True) # FIXME
|
user = users._add(cli, nick=rawnick) # FIXME
|
||||||
|
|
||||||
for chan in set(user.channels):
|
for chan in set(user.channels):
|
||||||
if user is users.Bot:
|
if user is users.Bot:
|
||||||
|
163
src/users.py
163
src/users.py
@ -57,23 +57,19 @@ def _get(nick=None, ident=None, host=None, realname=None, account=None, *, allow
|
|||||||
if allow_bot:
|
if allow_bot:
|
||||||
users.add(Bot)
|
users.add(Bot)
|
||||||
|
|
||||||
for user in users:
|
sentinel = object()
|
||||||
if nick is not None and user.nick != nick:
|
|
||||||
continue
|
|
||||||
if ident is not None and user.ident != ident:
|
|
||||||
continue
|
|
||||||
if host is not None and user.host != host:
|
|
||||||
continue
|
|
||||||
if realname is not None and user.realname != realname:
|
|
||||||
continue
|
|
||||||
if account is not None and user.account != account:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not potential or allow_multiple:
|
temp = User(sentinel, nick, ident, host, realname, account)
|
||||||
potential.append(user)
|
if temp.client is not sentinel:
|
||||||
else:
|
return temp # actual client
|
||||||
raise ValueError("More than one user matches: " +
|
|
||||||
_arg_msg.format(nick, ident, host, realname, account, allow_bot))
|
for user in users:
|
||||||
|
if user == temp:
|
||||||
|
if not potential or allow_multiple:
|
||||||
|
potential.append(user)
|
||||||
|
else:
|
||||||
|
raise ValueError("More than one user matches: " +
|
||||||
|
_arg_msg.format(nick, ident, host, realname, account, allow_bot))
|
||||||
|
|
||||||
if not potential and not allow_none:
|
if not potential and not allow_none:
|
||||||
raise KeyError(_arg_msg.format(nick, ident, host, realname, account, allow_bot))
|
raise KeyError(_arg_msg.format(nick, ident, host, realname, account, allow_bot))
|
||||||
@ -103,15 +99,13 @@ def _add(cli, *, nick, ident=None, host=None, realname=None, account=None):
|
|||||||
if ident is None and host is None and nick is not None:
|
if ident is None and host is None and nick is not None:
|
||||||
nick, ident, host = parse_rawnick(nick)
|
nick, ident, host = parse_rawnick(nick)
|
||||||
|
|
||||||
if _exists(nick, ident, host, realname, account, allow_multiple=True, allow_bot=True): # FIXME
|
|
||||||
raise ValueError("User already exists: " + _arg_msg.format(nick, ident, host, realname, account, True))
|
|
||||||
|
|
||||||
cls = User
|
cls = User
|
||||||
if predicate(nick):
|
if predicate(nick):
|
||||||
cls = FakeUser
|
cls = FakeUser
|
||||||
|
|
||||||
new = cls(cli, nick, ident, host, realname, account)
|
new = cls(cli, nick, ident, host, realname, account)
|
||||||
_users.add(new)
|
if new is not Bot:
|
||||||
|
_users.add(new)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
def add(nick, **blah): # backwards-compatible API
|
def add(nick, **blah): # backwards-compatible API
|
||||||
@ -127,12 +121,21 @@ def _exists(nick=None, ident=None, host=None, realname=None, account=None, *, al
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try: # FIXME
|
sentinel = object()
|
||||||
_get(nick, ident, host, realname, account, allow_multiple=allow_multiple, allow_bot=allow_bot)
|
|
||||||
except (KeyError, ValueError):
|
if ident is None and host is None and nick is not None:
|
||||||
|
nick, ident, host = parse_rawnick(nick)
|
||||||
|
|
||||||
|
cls = User
|
||||||
|
if predicate(nick):
|
||||||
|
cls = FakeUser
|
||||||
|
|
||||||
|
temp = cls(sentinel, nick, ident, host, realname, account)
|
||||||
|
|
||||||
|
if temp.client is sentinel: # doesn't exist; if it did, the client would be an actual client
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return temp is not Bot or allow_bot
|
||||||
|
|
||||||
def exists(nick, *stuff, **morestuff): # backwards-compatible API
|
def exists(nick, *stuff, **morestuff): # backwards-compatible API
|
||||||
return nick in var.USERS
|
return nick in var.USERS
|
||||||
@ -141,14 +144,11 @@ def users_():
|
|||||||
"""Iterate over the users in the registry."""
|
"""Iterate over the users in the registry."""
|
||||||
yield from _users
|
yield from _users
|
||||||
|
|
||||||
def users(): # backwards-compatible API
|
class users: # backwards-compatible API
|
||||||
yield from var.USERS
|
def __iter__(self):
|
||||||
|
yield from var.USERS
|
||||||
def _items(): # backwards-compat crap (really, it stinks)
|
def items(self):
|
||||||
yield from var.USERS.items()
|
yield from var.USERS.items()
|
||||||
|
|
||||||
users.items = _items
|
|
||||||
del _items
|
|
||||||
|
|
||||||
_raw_nick_pattern = re.compile(r"^(?P<nick>.+?)(?:!(?P<ident>.+?)@(?P<host>.+))?$")
|
_raw_nick_pattern = re.compile(r"^(?P<nick>.+?)(?:!(?P<ident>.+?)@(?P<host>.+))?$")
|
||||||
|
|
||||||
@ -171,21 +171,63 @@ class User(IRCContext):
|
|||||||
|
|
||||||
_messages = defaultdict(list)
|
_messages = defaultdict(list)
|
||||||
|
|
||||||
def __init__(self, cli, nick, ident, host, realname, account, **kwargs):
|
def __new__(cls, cli, nick, ident, host, realname, account, **kwargs):
|
||||||
super().__init__(nick, cli, **kwargs)
|
self = super().__new__(cls)
|
||||||
self.nick = nick
|
super(cls, self).__init__(nick, cli, **kwargs)
|
||||||
self.ident = ident
|
|
||||||
self.host = host
|
self._ident = ident
|
||||||
|
self._host = host
|
||||||
self.realname = realname
|
self.realname = realname
|
||||||
self.account = account
|
self.account = account
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
|
|
||||||
|
if Bot is not None and Bot.nick == nick and {Bot.ident, Bot.host, Bot.realname, Bot.account} == {None}:
|
||||||
|
self = Bot
|
||||||
|
self.ident = ident
|
||||||
|
self.host = host
|
||||||
|
self.realname = realname
|
||||||
|
self.account = account
|
||||||
|
|
||||||
|
# check the set to see if this already exists
|
||||||
|
elif ident is not None and host is not None:
|
||||||
|
users = set(_users)
|
||||||
|
users.add(Bot)
|
||||||
|
if self in _users: # quirk: this actually checks for the hash first (also, this is O(1))
|
||||||
|
for user in _users:
|
||||||
|
if self == user:
|
||||||
|
self = user
|
||||||
|
break # this may only happen once
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __init__(*args, **kwargs):
|
||||||
|
pass # everything that needed to be done was done in __new__
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{self.__class__.__name__}: {self.nick}!{self.ident}@{self.host}#{self.realname}:{self.account}".format(self=self)
|
return "{self.__class__.__name__}: {self.nick}!{self.ident}@{self.host}#{self.realname}:{self.account}".format(self=self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{self.__class__.__name__}({self.nick!r}, {self.ident!r}, {self.host!r}, {self.realname!r}, {self.account!r}, {self.channels!r})".format(self=self)
|
return "{self.__class__.__name__}({self.nick!r}, {self.ident!r}, {self.host!r}, {self.realname!r}, {self.account!r}, {self.channels!r})".format(self=self)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
if self.ident is None or self.host is None:
|
||||||
|
raise ValueError("cannot hash a User with no ident or host")
|
||||||
|
return hash((self.ident, self.host))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, User):
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
|
done = False
|
||||||
|
for a, b in ((self.nick, other.nick), (self.ident, other.ident), (self.host, other.host), (self.realname, other.realname), (self.account, other.account)):
|
||||||
|
if a is None or b is None:
|
||||||
|
continue
|
||||||
|
done = True
|
||||||
|
if a != b:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return done
|
||||||
|
|
||||||
def lower(self):
|
def lower(self):
|
||||||
return type(self)(self.client, lower(self.nick), lower(self.ident), lower(self.host), lower(self.realname), lower(self.account), channels, ref=(self.ref or self))
|
return type(self)(self.client, lower(self.nick), lower(self.ident), lower(self.host), lower(self.realname), lower(self.account), channels, ref=(self.ref or self))
|
||||||
|
|
||||||
@ -390,6 +432,42 @@ class User(IRCContext):
|
|||||||
if self is Bot: # update the client's nickname as well
|
if self is Bot: # update the client's nickname as well
|
||||||
self.client.nickname = nick
|
self.client.nickname = nick
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ident(self): # prevent changing ident and host after they were set (so hash remains the same)
|
||||||
|
return self._ident
|
||||||
|
|
||||||
|
@ident.setter
|
||||||
|
def ident(self, ident):
|
||||||
|
if self._ident is None:
|
||||||
|
self._ident = ident
|
||||||
|
if self is Bot:
|
||||||
|
self.client.ident = ident
|
||||||
|
elif self._ident != ident:
|
||||||
|
raise ValueError("may not change the ident of a live user")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def host(self):
|
||||||
|
return self._host
|
||||||
|
|
||||||
|
@host.setter
|
||||||
|
def host(self, host):
|
||||||
|
if self._host is None:
|
||||||
|
self._host = host
|
||||||
|
if self is Bot:
|
||||||
|
self.client.hostmask = host
|
||||||
|
elif self._host != host:
|
||||||
|
raise ValueError("may not change the host of a live user")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def realname(self):
|
||||||
|
return self._realname
|
||||||
|
|
||||||
|
@realname.setter
|
||||||
|
def realname(self, realname):
|
||||||
|
self._realname = realname
|
||||||
|
if self is Bot:
|
||||||
|
self.client.real_name = realname
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def account(self): # automatically converts "0" and "*" to None
|
def account(self): # automatically converts "0" and "*" to None
|
||||||
return self._account
|
return self._account
|
||||||
@ -424,9 +502,20 @@ class FakeUser(User):
|
|||||||
|
|
||||||
is_fake = True
|
is_fake = True
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.nick)
|
||||||
|
|
||||||
def queue_message(self, message):
|
def queue_message(self, message):
|
||||||
self.send(message) # don't actually queue it
|
self.send(message) # don't actually queue it
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nick(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@nick.setter
|
||||||
|
def nick(self, nick):
|
||||||
|
raise ValueError("may not change the nick of a fake user")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rawnick(self):
|
def rawnick(self):
|
||||||
return self.nick # we don't have a raw nick
|
return self.nick # we don't have a raw nick
|
||||||
|
Loading…
Reference in New Issue
Block a user