mirror of
https://github.com/jlu5/PyLink.git
synced 2024-11-27 13:09:23 +01:00
Rework irc.users and User() to transparently create a store of nicks -> UIDs
- This turns IRCNetwork.users into a new UserMapping class, which stores User objects by UID (str) and provides a 'bynick' dict storing case-normalized nicks to lists of UIDs. - Turn User.nick into a property, where the setter implicitly updates the 'bynick' index and computes a case-normalized version of the nick (User.lower_nick)
This commit is contained in:
parent
815535d76b
commit
a085aed924
188
classes.py
188
classes.py
@ -18,6 +18,8 @@ import queue
|
|||||||
import functools
|
import functools
|
||||||
import string
|
import string
|
||||||
import re
|
import re
|
||||||
|
import collections
|
||||||
|
import collections.abc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ircmatch
|
import ircmatch
|
||||||
@ -44,6 +46,131 @@ class ChannelState(structures.IRCCaseInsensitiveDict):
|
|||||||
|
|
||||||
return self._data[key]
|
return self._data[key]
|
||||||
|
|
||||||
|
|
||||||
|
class User():
|
||||||
|
"""PyLink IRC user class."""
|
||||||
|
def __init__(self, irc, nick, ts, uid, server, ident='null', host='null',
|
||||||
|
realname='PyLink dummy client', realhost='null',
|
||||||
|
ip='0.0.0.0', manipulatable=False, opertype='IRC Operator'):
|
||||||
|
self._nick = nick
|
||||||
|
self.lower_nick = irc.to_lower(nick)
|
||||||
|
|
||||||
|
self.ts = ts
|
||||||
|
self.uid = uid
|
||||||
|
self.ident = ident
|
||||||
|
self.host = host
|
||||||
|
self.realhost = realhost
|
||||||
|
self.ip = ip
|
||||||
|
self.realname = realname
|
||||||
|
self.modes = set() # Tracks user modes
|
||||||
|
self.server = server
|
||||||
|
self._irc = irc
|
||||||
|
|
||||||
|
# Tracks PyLink identification status
|
||||||
|
self.account = ''
|
||||||
|
|
||||||
|
# Tracks oper type (for display only)
|
||||||
|
self.opertype = opertype
|
||||||
|
|
||||||
|
# Tracks external services identification status
|
||||||
|
self.services_account = ''
|
||||||
|
|
||||||
|
# Tracks channels the user is in
|
||||||
|
self.channels = structures.IRCCaseInsensitiveSet(self._irc)
|
||||||
|
|
||||||
|
# Tracks away message status
|
||||||
|
self.away = ''
|
||||||
|
|
||||||
|
# This sets whether the client should be marked as manipulatable.
|
||||||
|
# Plugins like bots.py's commands should take caution against
|
||||||
|
# manipulating these "protected" clients, to prevent desyncs and such.
|
||||||
|
# For "serious" service clients, this should always be False.
|
||||||
|
self.manipulatable = manipulatable
|
||||||
|
|
||||||
|
# Cloaked host for IRCds that use it
|
||||||
|
self.cloaked_host = None
|
||||||
|
|
||||||
|
# Stores service bot name if applicable
|
||||||
|
self.service = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nick(self):
|
||||||
|
return self._nick
|
||||||
|
|
||||||
|
@nick.setter
|
||||||
|
def nick(self, newnick):
|
||||||
|
oldnick = self.lower_nick
|
||||||
|
self._nick = newnick
|
||||||
|
self.lower_nick = self._irc.to_lower(newnick)
|
||||||
|
|
||||||
|
# Update the irc.users bynick index:
|
||||||
|
if oldnick in self._irc.users.bynick:
|
||||||
|
# Remove existing value -> key mappings.
|
||||||
|
self._irc.users.bynick[oldnick].remove(self.uid)
|
||||||
|
|
||||||
|
# Remove now-empty keys as well.
|
||||||
|
if not self._irc.users.bynick[oldnick]:
|
||||||
|
del self._irc.users.bynick[oldnick]
|
||||||
|
|
||||||
|
# Update the new nick.
|
||||||
|
self._irc.users.bynick.setdefault(self.lower_nick, []).append(self.uid)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'User(%s/%s)' % (self.uid, self.nick)
|
||||||
|
IrcUser = User
|
||||||
|
|
||||||
|
# Bidirectional dict based off https://stackoverflow.com/a/21894086
|
||||||
|
class UserMapping(collections.abc.MutableMapping, structures.CopyWrapper):
|
||||||
|
"""
|
||||||
|
A mapping storing User objects by UID, as well as UIDs by nick via
|
||||||
|
the 'bynick' attribute
|
||||||
|
"""
|
||||||
|
def __init__(self, *, data=None):
|
||||||
|
if data is not None:
|
||||||
|
assert isinstance(data, dict)
|
||||||
|
self._data = data
|
||||||
|
else:
|
||||||
|
self._data = {}
|
||||||
|
self.bynick = collections.defaultdict(list)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._data[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, userobj):
|
||||||
|
assert isinstance(userobj, User), "UserMapping can only hold User objects"
|
||||||
|
if key in self._data:
|
||||||
|
log.warning('(%s) Attempting to replace User object for %r: %r -> %r', self.name,
|
||||||
|
key, self._data.get(key), userobj)
|
||||||
|
|
||||||
|
self._data[key] = userobj
|
||||||
|
self.bynick.setdefault(userobj.lower_nick, []).append(key)
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
# Remove this entry from the bynick index
|
||||||
|
if self[key].lower_nick in self.bynick:
|
||||||
|
self.bynick[self[key].lower_nick].remove(key)
|
||||||
|
|
||||||
|
if not self.bynick[self[key].lower_nick]:
|
||||||
|
del self.bynick[self[key].lower_nick]
|
||||||
|
|
||||||
|
del self._data[key]
|
||||||
|
|
||||||
|
# Generic container methods. XXX: consider abstracting this out in structures?
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%s)" % (self.__class__.__name__, self._data)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._data)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._data)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return self._data.__contains__(key)
|
||||||
|
|
||||||
|
def __copy__(self):
|
||||||
|
return self.__class__(data=self._data.copy())
|
||||||
|
|
||||||
class PyLinkNetworkCore(structures.CamelCaseToSnakeCase):
|
class PyLinkNetworkCore(structures.CamelCaseToSnakeCase):
|
||||||
"""Base IRC object for PyLink."""
|
"""Base IRC object for PyLink."""
|
||||||
|
|
||||||
@ -133,7 +260,7 @@ class PyLinkNetworkCore(structures.CamelCaseToSnakeCase):
|
|||||||
# Intialize the server, channel, and user indexes to be populated by
|
# Intialize the server, channel, and user indexes to be populated by
|
||||||
# our protocol module.
|
# our protocol module.
|
||||||
self.servers = {}
|
self.servers = {}
|
||||||
self.users = {}
|
self.users = UserMapping()
|
||||||
|
|
||||||
# Two versions of the channels index exist in PyLink 2.0, and they are joined together
|
# Two versions of the channels index exist in PyLink 2.0, and they are joined together
|
||||||
# - irc._channels which implicitly creates channels on access (mostly used
|
# - irc._channels which implicitly creates channels on access (mostly used
|
||||||
@ -445,9 +572,14 @@ class PyLinkNetworkCore(structures.CamelCaseToSnakeCase):
|
|||||||
def nick_to_uid(self, nick):
|
def nick_to_uid(self, nick):
|
||||||
"""Looks up the UID of a user with the given nick, if one is present."""
|
"""Looks up the UID of a user with the given nick, if one is present."""
|
||||||
nick = self.to_lower(nick)
|
nick = self.to_lower(nick)
|
||||||
for k, v in self.users.copy().items():
|
|
||||||
if self.to_lower(v.nick) == nick:
|
uids = self.users.bynick.get(nick, [])
|
||||||
return k
|
if len(uids) > 1:
|
||||||
|
log.warning('(%s) Multiple UIDs found for nick %r: %r', self.name, nick, uids)
|
||||||
|
try:
|
||||||
|
return uids[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
def is_internal_client(self, numeric):
|
def is_internal_client(self, numeric):
|
||||||
"""
|
"""
|
||||||
@ -1628,54 +1760,6 @@ class IRCNetwork(PyLinkNetworkCoreWithUtils):
|
|||||||
|
|
||||||
Irc = IRCNetwork
|
Irc = IRCNetwork
|
||||||
|
|
||||||
class User():
|
|
||||||
"""PyLink IRC user class."""
|
|
||||||
def __init__(self, irc, nick, ts, uid, server, ident='null', host='null',
|
|
||||||
realname='PyLink dummy client', realhost='null',
|
|
||||||
ip='0.0.0.0', manipulatable=False, opertype='IRC Operator'):
|
|
||||||
self.nick = nick
|
|
||||||
self.ts = ts
|
|
||||||
self.uid = uid
|
|
||||||
self.ident = ident
|
|
||||||
self.host = host
|
|
||||||
self.realhost = realhost
|
|
||||||
self.ip = ip
|
|
||||||
self.realname = realname
|
|
||||||
self.modes = set() # Tracks user modes
|
|
||||||
self.server = server
|
|
||||||
self._irc = irc
|
|
||||||
|
|
||||||
# Tracks PyLink identification status
|
|
||||||
self.account = ''
|
|
||||||
|
|
||||||
# Tracks oper type (for display only)
|
|
||||||
self.opertype = opertype
|
|
||||||
|
|
||||||
# Tracks external services identification status
|
|
||||||
self.services_account = ''
|
|
||||||
|
|
||||||
# Tracks channels the user is in
|
|
||||||
self.channels = structures.IRCCaseInsensitiveSet(self._irc)
|
|
||||||
|
|
||||||
# Tracks away message status
|
|
||||||
self.away = ''
|
|
||||||
|
|
||||||
# This sets whether the client should be marked as manipulatable.
|
|
||||||
# Plugins like bots.py's commands should take caution against
|
|
||||||
# manipulating these "protected" clients, to prevent desyncs and such.
|
|
||||||
# For "serious" service clients, this should always be False.
|
|
||||||
self.manipulatable = manipulatable
|
|
||||||
|
|
||||||
# Cloaked host for IRCds that use it
|
|
||||||
self.cloaked_host = None
|
|
||||||
|
|
||||||
# Stores service bot name if applicable
|
|
||||||
self.service = None
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'User(%s/%s)' % (self.uid, self.nick)
|
|
||||||
IrcUser = User
|
|
||||||
|
|
||||||
class Server():
|
class Server():
|
||||||
"""PyLink IRC server class.
|
"""PyLink IRC server class.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user