from twisted.internet.threads import deferToThread import main from modules import chankeep from utils.logging.debug import debug, trace from utils.logging.log import warn from utils.parsing import parsen def getWhoSingle(name, query): result = main.r.sscan("live.who." + name, 0, query, count=999999) if result[1] == []: return None return (i.decode() for i in result[1]) def getWho(query): result = {} for i in main.network.keys(): f = getWhoSingle(i, query) if f: result[i] = f return result def getChansSingle(name, nick): nick = ("live.chan." + name + "." + i for i in nick) result = main.r.sinter(*nick) if len(result) == 0: return None return (i.decode() for i in result) def getChanList(name, nick): chanspace = "live.chan." + name + "." + nick result = main.r.smembers(chanspace) if len(result) == 0: return None return (i.decode() for i in result) def getTotalChanNum(net): """ Get the number of channels a network has. """ chans = main.r.keys(f"live.who.{net}.*") return len(chans) def getUserNum(name, channels): """ Get the number of users on a list of channels. """ chanspace = ("live.who." + name + "." + i for i in channels) results = {} for channel, space in zip(channels, chanspace): results[channel] = main.r.scard(space) return results def getChanNum(name, nicks): """ Get the number of channels a list of users are on. """ nickspace = ("live.chan." + name + "." + i for i in nicks) results = {} for nick, space in zip(nicks, nickspace): results[nick] = main.r.scard(space) return results def getChans(nick): result = {} for i in main.network.keys(): f = getChansSingle(i, nick) if f: result[i] = f return result def getUsersSingle(name, nick): nick = ("live.who." + name + "." + i for i in nick) result = main.r.sinter(*nick) if len(result) == 0: return None return (i.decode() for i in result) def getUsers(nick): result = {} for i in main.network.keys(): f = getUsersSingle(i, nick) if f: result[i] = f return result def getNumWhoEntries(name): return main.r.scard("live.who." + name) def getNumTotalWhoEntries(): total = 0 for i in main.network.keys(): total += getNumWhoEntries(i) return total def getNamespace(name, channel, nick): gnamespace = "live.who.%s" % name namespace = "live.who.%s.%s" % (name, channel) chanspace = "live.chan.%s.%s" % (name, nick) mapspace = "live.map.%s" % name return (gnamespace, namespace, chanspace, mapspace) def _initialUsers(name, channel, users): gnamespace = "live.who.%s" % name mapspace = "live.map.%s" % name p = main.r.pipeline() for i in users: user = i[0] + "!" + i[1] + "@" + i[2] p.hset(mapspace, i[0], user) p.sadd(gnamespace, user) p.execute() def initialUsers(name, channel, users): trace("Initialising WHO records for %s on %s" % (channel, name)) deferToThread(_initialUsers, name, channel, users) # d.addCallback(testCallback) def _initialNames(name, channel, names): namespace = "live.who.%s.%s" % (name, channel) p = main.r.pipeline() for mode, nick in names: p.sadd(namespace, nick) p.sadd("live.chan." + name + "." + nick, channel) if mode: p.hset("live.prefix." + name + "." + channel, nick, mode) p.execute() def initialNames(name, channel, names): trace("Initialising NAMES records for %s on %s" % (channel, name)) deferToThread(_initialNames, name, channel, names) # d.addCallback(testCallback) def editUser(name, user): gnamespace = "live.who.%s" % name mapspace = "live.map.%s" % name parsed = parsen(user) p = main.r.pipeline() p.sadd(gnamespace, user) p.hset(mapspace, parsed[0], user) # add nick -> user mapping p.execute() def addUser(name, channel, nick, user): gnamespace, namespace, chanspace, mapspace = getNamespace(name, channel, nick) p = main.r.pipeline() p.sadd(gnamespace, user) p.sadd(namespace, nick) p.sadd(chanspace, channel) p.hset(mapspace, nick, user) p.execute() def delUser(name, channel, nick, user): gnamespace, namespace, chanspace, mapspace = getNamespace(name, channel, nick) p = main.r.pipeline() channels = main.r.smembers(chanspace) p.srem(namespace, nick) if channels == {channel.encode()}: # can we only see them on this channel? p.delete(chanspace) # remove channel tracking entry p.hdel("live.prefix." + name + "." + channel, nick) # remove prefix tracking entry p.hdel(mapspace, nick) # remove nick mapping entry if user: p.srem(gnamespace, user) # remove global userinfo entry else: warn("Attempt to delete nonexistent user: %s" % user) else: p.srem(chanspace, channel) # keep up - remove the channel from their list p.execute() def escape(text): chars = ["[", "]", "^", "-", "*", "?"] text = text.replace("\\", "\\\\") for i in chars: text = text.replace(i, "\\" + i) return text def getUserByNick(name, nick): gnamespace = "live.who.%s" % name # "nick": "nick!ident@host" mapspace = "live.map.%s" % name if main.r.hexists(mapspace, nick): return main.r.hget(mapspace, nick) else: warn("Entry doesn't exist: %s on %s - attempting auxiliary lookup" % (nick, mapspace)) # return False # legacy code below - remove when map is reliable usermatch = main.r.sscan(gnamespace, match=escape(nick) + "!*", count=999999999) if usermatch[1] == []: warn("No matches found for user query: %s on %s" % (nick, name)) return False else: if len(usermatch[1]) == 1: user = usermatch[1][0] return user else: warn("Auxiliary lookup failed: %s on %s" % (nick, gnamespace)) return False def renameUser(name, oldnick, olduser, newnick, newuser): gnamespace = "live.who.%s" % name chanspace = "live.chan.%s.%s" % (name, oldnick) mapspace = "live.map.%s" % name newchanspace = "live.chan.%s.%s" % (name, newnick) p = main.r.pipeline() p.srem(gnamespace, olduser) p.sadd(gnamespace, newuser) for i in main.r.smembers(chanspace): i = i.decode() p.srem("live.who." + name + "." + i, oldnick) p.sadd("live.who." + name + "." + i, newnick) p.hdel(mapspace, oldnick) p.hset(mapspace, newnick, newuser) if main.r.exists("live.prefix." + name + "." + i): # if there's a prefix entry for the channel if main.r.hexists("live.prefix." + name + "." + i, oldnick): # if the old nick is in it mode = main.r.hget("live.prefix." + name + "." + i, oldnick) # retrieve old modes p.hset("live.prefix." + name + "." + i, newnick, mode) # set old modes to new nickname if main.r.exists(chanspace): p.rename(chanspace, newchanspace) else: warn("Key doesn't exist: %s" % chanspace) p.execute() def delUserByNick(name, channel, nick): # kick user = getUserByNick(name, nick) if not user: return delUser(name, channel, nick, user) def delUserByNetwork(name, nick, user): # quit gnamespace = "live.who.%s" % name chanspace = "live.chan.%s.%s" % (name, nick) mapspace = "live.chan.%s" % name p = main.r.pipeline() p.srem(gnamespace, user) for i in main.r.smembers(chanspace): p.srem("live.who." + name + "." + i.decode(), nick) p.hdel("live.prefix." + name + "." + i.decode(), nick) p.delete(chanspace) p.hdel(mapspace, nick) p.execute() def _delChannels(net, channels): gnamespace = "live.who.%s" % net mapspace = "live.map.%s" % net p = main.r.pipeline() for channel in channels: namespace = "live.who.%s.%s" % (net, channel) for i in main.r.smembers(namespace): nick = i.decode() # user = getUserByNick(net, nick) -- far too many function calls user = main.r.hget(mapspace, nick) if not user: warn("User lookup failed: %s on %s" % (nick, net)) if main.r.smembers("live.chan." + net + "." + nick) == {channel.encode()}: if user: p.srem(gnamespace, user) p.delete("live.chan." + net + "." + nick) p.hdel(mapspace, nick) # remove map entry else: p.srem("live.chan." + net + "." + nick, channel) p.delete(namespace) p.delete("live.prefix." + net + "." + channel) p.execute() def delChannels(net, channels): # we have left a channel trace("Purging channel %s for %s" % (", ".join(channels), net)) dupes = chankeep.getDuplicateChannels(net, total=True) print("dupes: %s" % dupes) if not dupes: deferToThread(_delChannels, net, channels) else: for channel in channels: if channel in dupes[net]: if dupes[net][channel] != 0: channels.remove(channel) debug(f"Not removing channel {channel} as {net} has {dupes[net][channel]} other relays covering it") deferToThread(_delChannels, net, channels) # d.addCallback(testCallback)