class Line: """ Low-level representation of an IRC message, either sent or received. Calling :func:`str` on a line will encode it suitable for transmission. Attributes: command (str): IRC verb or numeric. args (str list): Additional arguments for this message. source (str list): Optional source of this message. tags ((str, str) dict): Any tags attached to the message. """ _format = re.compile("(?:@(?P<tags>[a-z0-9-]+(?:=[^; ]+)?(?:;[a-z0-9-]+(?:=[^; ]+)?)*) +)?" "(?::(?P<source>[^ ]+) +)?(?P<command>[a-z]+|[0-9]{3})" "(?P<args>(?: +[^: ][^ ]*)*)(?: +:(?P<trailing>.*))?", re.I) def __init__(self, command, *args, source=None, tags=None): self.command = command.upper() self.args = args self.source = source self.tags = tags # Conveniently, this generates timestamp identifiers of the desired format. next_ts = immp.IDGen() @classmethod def parse(cls, line): """ Take a raw IRC line and decode it into a :class:`.Line`. Args: line (str): Raw IRC message. Returns: .Line: Parsed line. """ match = cls._format.match(line) if not match: raise ValueError("Invalid line: '{}'".format(line)) tagpart, source, command, argpart, trailing = match.groups() tags = {} args = [] if tagpart: for item in tagpart.split(";"): key, *val = item.split("=", 1) tags[key] = val[0] if val else True if argpart: args = argpart.split() if trailing: args.append(trailing) return cls(command, *args, source=source, tags=tags) def __str__(self): line = self.command if self.source: line = ":{} {}".format(self.source, line) if self.tags: tagpart = [] for key, value in self.tags: tagpart.append(key if value is True else "{}={}".format(key, value)) line = "@{} {}".format(";".join(tagpart), line) if self.args: line = " ".join([line, *self.args[:-1], ":{}".format(self.args[-1])]) return line def __repr__(self): return "<{}: {}{}{}>".format(self.__class__.__name__, self.command, " @ {}".format(repr(self.source)) if self.source else "", " {}".format(repr(list(self.args))) if self.args else "")
def __init__(self, name, config, host): super().__init__(name, config, host) self.counter = immp.IDGen() self.user = immp.User(id_="dummy", real_name=name) self.channel = immp.Channel(self, "dummy") self._task = None
class SyncRef: """ Representation of a single synced message. Attributes: key (str): Unique synced message identifier, used by :class:`.SyncPlug` when yielding messages. ids ((.Channel, str) dict): Mapping from :class:`.Channel` to a list of echoed message IDs. revisions ((.Channel, (str, set) dict) dict): Mapping from :class:`.Channel` to message ID to synced revisions of that message. source (.Message): Original copy of the source message, if we have it. """ next_key = immp.IDGen() @classmethod def from_backref_map(cls, key, mapped, host): """ Take a mapping generated in :meth:`.SyncBackRef.map_from_key` and produce a local reference suitable for the memory cache. Args: key (str): Synced message identifier. mapped (((str, str), .SyncBackRef list) dict): Generated reference mapping. host (.Host): Parent host instance, needed to resolve network IDs to plugs. Returns: .SyncRef: Newly created reference. """ ids = {} for (network, source), synced in mapped.items(): for plug in host.plugs.values(): if plug.network_id == network: ids[immp.Channel(plug, source)] = [backref.message for backref in synced] return cls(ids, key=key) def __init__(self, ids, *, key=None, source=None, origin=None): self.key = key or self.next_key() self.ids = defaultdict(list, ids) self.revisions = defaultdict(lambda: defaultdict(set)) self.source = source if origin: self.ids[origin.channel].append(origin.id) self.revision(origin) def revision(self, sent): """ Log a new revision of a message. Args: sent (.Receipt): Updated message relating to a previously synced message. Returns: bool: ``True`` if this is an edit (i.e. we've already seen a base revision for this message) and needs syncing to other channels. """ self.revisions[sent.channel][sent.id].add(sent.revision) return len(self.revisions[sent.channel][sent.id]) > 1 def __repr__(self): return "<{}: #{} x{}{}>".format(self.__class__.__name__, self.key, len(self.ids), " {}".format(repr(self.source)) if self.source else "")