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}
|
||||
|
||||
if nick == bot_nick:
|
||||
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)
|
||||
|
||||
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname) # FIXME
|
||||
ch = channels.add(chan, cli)
|
||||
|
||||
if ch not in user.channels:
|
||||
user.channels[ch] = modes
|
||||
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)
|
||||
|
||||
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")
|
||||
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}
|
||||
|
||||
if nick == bot_nick:
|
||||
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)
|
||||
|
||||
user = users._add(cli, nick=nick, ident=ident, host=host, realname=realname, account=account) # FIXME
|
||||
ch = channels.add(chan, cli)
|
||||
|
||||
if ch not in user.channels:
|
||||
user.channels[ch] = modes
|
||||
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)
|
||||
|
||||
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")
|
||||
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
|
||||
users.Bot.modes.update(mode)
|
||||
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
|
||||
|
||||
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.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(Features["CHANMODES"][0])
|
||||
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)
|
||||
user.channels[ch] = set()
|
||||
@ -540,7 +516,7 @@ def part_chan(cli, rawnick, chan, reason=""):
|
||||
"""
|
||||
|
||||
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!
|
||||
ch._clear()
|
||||
@ -566,8 +542,8 @@ def kicked_from_chan(cli, rawnick, chan, target, reason):
|
||||
"""
|
||||
|
||||
ch = channels.add(chan, cli)
|
||||
actor = users._get(rawnick, allow_bot=True) # FIXME
|
||||
user = users._get(target, allow_bot=True) # FIXME
|
||||
actor = users._add(cli, nick=rawnick) # FIXME
|
||||
user = users._add(cli, nick=target) # FIXME
|
||||
|
||||
if user is users.Bot:
|
||||
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):
|
||||
if user is users.Bot:
|
||||
|
145
src/users.py
145
src/users.py
@ -57,18 +57,14 @@ def _get(nick=None, ident=None, host=None, realname=None, account=None, *, allow
|
||||
if allow_bot:
|
||||
users.add(Bot)
|
||||
|
||||
for user in users:
|
||||
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
|
||||
sentinel = object()
|
||||
|
||||
temp = User(sentinel, nick, ident, host, realname, account)
|
||||
if temp.client is not sentinel:
|
||||
return temp # actual client
|
||||
|
||||
for user in users:
|
||||
if user == temp:
|
||||
if not potential or allow_multiple:
|
||||
potential.append(user)
|
||||
else:
|
||||
@ -103,14 +99,12 @@ 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:
|
||||
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
|
||||
if predicate(nick):
|
||||
cls = FakeUser
|
||||
|
||||
new = cls(cli, nick, ident, host, realname, account)
|
||||
if new is not Bot:
|
||||
_users.add(new)
|
||||
return new
|
||||
|
||||
@ -127,12 +121,21 @@ def _exists(nick=None, ident=None, host=None, realname=None, account=None, *, al
|
||||
|
||||
"""
|
||||
|
||||
try: # FIXME
|
||||
_get(nick, ident, host, realname, account, allow_multiple=allow_multiple, allow_bot=allow_bot)
|
||||
except (KeyError, ValueError):
|
||||
sentinel = object()
|
||||
|
||||
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 True
|
||||
return temp is not Bot or allow_bot
|
||||
|
||||
def exists(nick, *stuff, **morestuff): # backwards-compatible API
|
||||
return nick in var.USERS
|
||||
@ -141,15 +144,12 @@ def users_():
|
||||
"""Iterate over the users in the registry."""
|
||||
yield from _users
|
||||
|
||||
def users(): # backwards-compatible API
|
||||
class users: # backwards-compatible API
|
||||
def __iter__(self):
|
||||
yield from var.USERS
|
||||
|
||||
def _items(): # backwards-compat crap (really, it stinks)
|
||||
def items(self):
|
||||
yield from var.USERS.items()
|
||||
|
||||
users.items = _items
|
||||
del _items
|
||||
|
||||
_raw_nick_pattern = re.compile(r"^(?P<nick>.+?)(?:!(?P<ident>.+?)@(?P<host>.+))?$")
|
||||
|
||||
def parse_rawnick(rawnick, *, default=None):
|
||||
@ -171,14 +171,37 @@ class User(IRCContext):
|
||||
|
||||
_messages = defaultdict(list)
|
||||
|
||||
def __init__(self, cli, nick, ident, host, realname, account, **kwargs):
|
||||
super().__init__(nick, cli, **kwargs)
|
||||
self.nick = nick
|
||||
def __new__(cls, cli, nick, ident, host, realname, account, **kwargs):
|
||||
self = super().__new__(cls)
|
||||
super(cls, self).__init__(nick, cli, **kwargs)
|
||||
|
||||
self._ident = ident
|
||||
self._host = host
|
||||
self.realname = realname
|
||||
self.account = account
|
||||
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
|
||||
self.channels = {}
|
||||
|
||||
# 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):
|
||||
return "{self.__class__.__name__}: {self.nick}!{self.ident}@{self.host}#{self.realname}:{self.account}".format(self=self)
|
||||
@ -186,6 +209,25 @@ class User(IRCContext):
|
||||
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)
|
||||
|
||||
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):
|
||||
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
|
||||
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
|
||||
def account(self): # automatically converts "0" and "*" to None
|
||||
return self._account
|
||||
@ -424,9 +502,20 @@ class FakeUser(User):
|
||||
|
||||
is_fake = True
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.nick)
|
||||
|
||||
def queue_message(self, message):
|
||||
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
|
||||
def rawnick(self):
|
||||
return self.nick # we don't have a raw nick
|
||||
|
Loading…
Reference in New Issue
Block a user