Example #1
0
 async def channel_for_user(self, user):
     if not isinstance(user, SlackUser):
         return
     for direct in self._directs.values():
         if direct["user"] == user.id:
             return immp.Channel(self, direct["id"])
     # Private channel doesn't exist yet or isn't cached.
     params = {"user": user.id, "return_im": "true"}
     opened = await self._api("im.open", _Schema.im_open, params=params)
     return immp.Channel(self, opened["channel"]["id"])
Example #2
0
    def from_event(cls, github, type_, id_, event):
        """
        Convert a `GitHub webhook <https://developer.github.com/webhooks/>`_ payload to a
        :class:`.Message`.

        Args:
            github (.GitHubPlug):
                Related plug instance that provides the event.
            type (str):
                Event type name from the ``X-GitHub-Event`` header.
            id (str):
                GUID of the event delivery from the ``X-GitHub-Delivery`` header.
            event (dict):
                GitHub webhook payload.

        Returns:
            .GitHubMessage:
                Parsed message object.
        """
        text = None
        if event["repository"]:
            channel = immp.Channel(github, event["repository"]["full_name"])
            text = cls._repo_text(github, type_, event)
        if not text:
            raise NotImplementedError
        user = GitHubUser.from_sender(github, event["sender"])
        return immp.SentMessage(id_=id_,
                                channel=channel,
                                text=text,
                                user=user,
                                action=True,
                                raw=event)
Example #3
0
 async def channel_for_user(self, user):
     if not isinstance(user, DiscordUser):
         return None
     if not isinstance(user.raw, (discord.Member, discord.User)):
         return None
     dm = user.raw.dm_channel or (await user.raw.create_dm())
     return immp.Channel(self, dm.id)
Example #4
0
    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)
Example #5
0
File: irc.py Project: Terrance/IMMP
 async def public_channels(self):
     try:
         raw = await self._client.list()
     except IRCTryAgain:
         return None
     channels = (line.args[1] for line in raw)
     return [immp.Channel(self, channel) for channel in channels]
Example #6
0
    async def from_line(cls, irc, line):
        """
        Convert a :class:`.Line` into a :class:`.Message`.

        Args:
            irc (.IRCPlug):
                Related plug instance that provides the line.
            line (.Line):
                Raw message line from the server.

        Returns:
            .IRCMessage:
                Parsed message object.
        """
        channel = line.args[0]
        nick = line.source.split("!", 1)[0]
        if channel == irc.config["user"]["nick"]:
            # Private messages arrive "from A to B", and should be sent "from B to A".
            channel = nick
        user = IRCUser.from_id(irc, line.source)
        action = False
        joined = []
        left = []
        if line.command == "JOIN":
            text = "joined {}".format(channel)
            action = True
            joined.append(user)
        elif line.command == "PART":
            text = "left {}".format(channel)
            action = True
            left.append(user)
        elif line.command == "KICK":
            target = await irc.user_from_username(line.args[1])
            text = immp.RichText([
                immp.Segment("kicked "),
                immp.Segment(target.username, bold=True),
                immp.Segment(" ({})".format(line.args[2]))
            ])
            action = True
            left.append(target)
        elif line.command == "PRIVMSG":
            text = line.args[1]
            match = re.match(r"\x01ACTION ([^\x01]*)\x01", text)
            if match:
                text = match.group(1)
                action = True
        else:
            raise NotImplementedError
        return immp.SentMessage(id_=Line.next_ts(),
                                channel=immp.Channel(irc, channel),
                                text=text,
                                user=user,
                                action=action,
                                joined=joined,
                                left=left,
                                raw=line)
Example #7
0
 async def channel_for_user(self, user):
     for channel in self._filter_channels(
             hangouts_pb2.CONVERSATION_TYPE_ONE_TO_ONE):
         if any(part.id == user.id for part in await channel.members()):
             return channel
     request = hangouts_pb2.CreateConversationRequest(
         request_header=self._client.get_request_header(),
         type=hangouts_pb2.CONVERSATION_TYPE_ONE_TO_ONE,
         client_generated_id=self._client.get_client_generated_id(),
         invitee_id=[hangouts_pb2.InviteeID(gaia_id=user.id)])
     response = await self._client.create_conversation(request)
     return immp.Channel(self, response.conversation.conversation_id.id)
Example #8
0
 def _resolve_channel(self, request):
     try:
         if "name" in request.match_info:
             name = request.match_info["name"]
             return name, self.host.channels[name]
         elif "plug" in request.match_info:
             plug = self.host.plugs[request.match_info["plug"]]
             return None, immp.Channel(plug, request.match_info["source"])
         else:
             raise web.HTTPBadRequest
     except KeyError:
         raise web.HTTPNotFound
Example #9
0
File: irc.py Project: Terrance/IMMP
 async def private_channels(self):
     try:
         raw = await self._client.names()
     except IRCTryAgain:
         return None
     names = set(self._client.users)
     for line in raw:
         names.update(name.lstrip(self._client.prefixes) for name in line.args[3].split())
     names.discard(self._client.nick)
     for client in self._puppets.values():
         names.discard(client.nick)
     return [immp.Channel(self, name) for name in names]
Example #10
0
 async def channel_migration(self, request):
     _, old = self._resolve_channel(request)
     post = await request.post()
     if "name" in post:
         new = self.host.channels[post["name"]]
     elif "plug" in post and "source" in post:
         new = immp.Channel(self.host.plugs[post["plug"]], post["source"])
     else:
         raise web.HTTPBadRequest
     await self.host.channel_migrate(old, new)
     raise web.HTTPFound(
         self.ctx.url_for("channel", plug=old.plug.name, source=old.source))
Example #11
0
    def from_message(cls, discord_, message, edited=False, deleted=False):
        """
        Convert a :class:`discord.Message` into a :class:`.Message`.

        Args:
            discord (.DiscordPlug):
                Related plug instance that provides the event.
            message (discord.Message):
                Discord message object received from a channel.
            edited (bool):
                Whether this message comes from an edit event.
            deleted (bool):
                Whether this message comes from a delete event.

        Returns:
            .DiscordMessage:
                Parsed message object.
        """
        text = None
        channel = immp.Channel(discord_, message.channel.id)
        user = DiscordUser.from_user(discord_, message.author)
        attachments = []
        if message.content:
            text = DiscordRichText.from_markdown(discord_, message.content, channel)
        for attach in message.attachments:
            if attach.filename.endswith((".jpg", ".png", ".gif")):
                type_ = immp.File.Type.image
            elif attach.filename.endswith((".mp4", ".webm")):
                type_ = immp.File.Type.video
            else:
                type_ = immp.File.Type.unknown
            attachments.append(immp.File(title=attach.filename,
                                         type_=type_,
                                         source=attach.url))
        for embed in message.embeds:
            if embed.image.url and embed.image.url.rsplit(".", 1)[1] in ("jpg", "png", "gif"):
                attachments.append(immp.File(type_=immp.File.Type.image,
                                             source=embed.image.url))
        return immp.SentMessage(id_=message.id,
                                channel=channel,
                                # Timestamps are naive but in UTC.
                                at=message.created_at.replace(tzinfo=timezone.utc),
                                # Edited timestamp is blank for new messages, but updated in
                                # existing objects when the message is later edited.
                                revision=(message.edited_at or message.created_at).timestamp(),
                                edited=edited,
                                deleted=deleted,
                                text=text,
                                user=user,
                                attachments=attachments,
                                raw=message)
Example #12
0
    async def send(self, label, msg, origin=None, ref=None):
        """
        Send a message to all channels in this sync.

        Args:
            label (str):
                Bridge that defines the underlying synced channels to send to.
            msg (.Message):
                External message to push.  This should be the source copy when syncing a message
                from another channel.
            origin (.Receipt):
                Raw message that triggered this sync; if set and part of the sync, it will be
                skipped (used to avoid retransmitting a message we just received).  This should be
                the plug-native copy of a message when syncing from another channel.
            ref (.SyncRef):
                Existing sync reference, if message has been partially synced.
        """
        base = immp.Message(text=msg.text, user=msg.user, edited=msg.edited, action=msg.action,
                            reply_to=msg.reply_to, joined=msg.joined, left=msg.left,
                            title=msg.title, attachments=msg.attachments, raw=msg)
        queue = []
        for synced in self.channels[label]:
            if origin and synced == origin.channel:
                continue
            elif ref and ref.ids[synced]:
                log.debug("Skipping already-synced target channel %r: %r", synced, ref)
                continue
            local = base.clone()
            self._replace_recurse(local, self._replace_ref, synced)
            await self._alter_recurse(local, self._alter_identities, synced)
            await self._alter_recurse(local, self._alter_name)
            queue.append(self._send(synced, local))
        # Just like with plugs, when sending a new (external) message to all channels in a sync, we
        # need to wait for all plugs to complete and have their IDs cached before processing any
        # further messages.
        async with self._lock:
            ids = dict(await gather(*queue))
            if ref:
                ref.ids.update(ids)
            else:
                ref = SyncRef(ids, source=msg, origin=origin)
            self._cache.add(ref)
        # Push a copy of the message to the sync channel, if running.
        if self.plug:
            sent = immp.SentMessage(id_=ref.key, channel=immp.Channel(self.plug, label),
                                    text=msg.text, user=msg.user, action=msg.action,
                                    reply_to=msg.reply_to, joined=msg.joined, left=msg.left,
                                    title=msg.title, attachments=msg.attachments, raw=msg)
            self.plug.queue(sent)
        return ref
Example #13
0
 async def private_channels(self):
     try:
         raw = await self._client.names()
     except IRCTryAgain:
         return None
     names = set()
     for line in raw:
         names.update(
             name.lstrip(self._client.prefixes)
             for name in line.args[3].split())
     return [
         immp.Channel(self, name) for name in names
         if name != self.config["user"]["nick"]
     ]
Example #14
0
    async def from_event(cls, slack, json, parent=True):
        """
        Convert an API event :class:`dict` to a :class:`.Message`.

        Args:
            slack (.SlackPlug):
                Related plug instance that provides the event.
            json (dict):
                Slack API `message <https://api.slack.com/events/message>`_ event data.
            parent (bool):
                ``True`` (default) to retrieve the thread parent if one exists.

        Returns:
            .SlackMessage:
                Parsed message object.
        """
        event = _Schema.message(json)
        if event["hidden"]:
            # Ignore UI-hidden events (e.g. tombstones of deleted files).
            raise NotImplementedError
        if event["is_ephemeral"]:
            # Ignore user-private messages from Slack (e.g. over quota warnings, link unfurling
            # opt-in prompts etc.) which shouldn't be served to message processors.
            raise NotImplementedError
        if event["subtype"] == "file_comment":
            # Deprecated in favour of file threads, but Slack may still emit these.
            raise NotImplementedError
        channel = immp.Channel(slack, event["channel"])
        if event["subtype"] == "message_deleted":
            id_, at = cls._parse_meta(slack, event)
            return immp.SentMessage(id_=id_,
                                    channel=channel,
                                    at=at,
                                    revision=event["ts"],
                                    deleted=True,
                                    raw=json)
        elif event["subtype"] == "message_changed":
            if event["message"]["hidden"]:
                # In theory this should match event["hidden"], but redefined here just in case.
                raise NotImplementedError
            if event["message"]["text"] == event["previous_message"]["text"]:
                # Message remains unchanged.  Can be caused by link unfurling (adds an attachment)
                # or deleting replies (reply is removed from event.replies in new *and old*).
                raise NotImplementedError
            revision = event["ts"]
            # Original message details are under a nested "message" key.
            return await cls._parse_main(slack, json, event["message"],
                                         channel, parent, revision)
        else:
            return await cls._parse_main(slack, json, event, channel, parent)
Example #15
0
 def __init__(self, name, config, host):
     super().__init__(name, config, host)
     self.db = None
     # Message cache, stores IDs of all synced messages by channel.
     self._cache = SyncCache(self)
     # Hook lock, to put a hold on retrieving messages whilst a send is in progress.
     self._lock = BoundedSemaphore()
     # Add a virtual plug to the host, for external subscribers.
     if self.config["plug"]:
         log.debug("Creating virtual plug: %r", self.config["plug"])
         self.plug = SyncPlug(self.config["plug"], self, host)
         host.add_plug(self.plug)
         for label in self.config["channels"]:
             host.add_channel(label, immp.Channel(self.plug, label))
     else:
         self.plug = None
Example #16
0
    def sync_for(self, channel):
        """
        Produce a synced channel for the given source.

        Args:
            channel (.Channel):
                Original channel to lookup.

        Returns:
            .Channel:
                Sync channel containing the given channel as a source, or ``None`` if not synced.
        """
        for label, synced in self._hook.channels.items():
            if channel in synced:
                return immp.Channel(self, label)
        return None
Example #17
0
 async def named_channel_add(self, request):
     post = await request.post()
     try:
         plug = post["plug"]
         name = post["name"]
         source = post["source"]
     except KeyError:
         raise web.HTTPBadRequest
     if not (plug and name and source):
         raise web.HTTPBadRequest
     if name in self.host:
         raise web.HTTPConflict
     if plug not in self.host.plugs:
         raise web.HTTPNotFound
     self.host.add_channel(name, immp.Channel(self.host.plugs[plug],
                                              source))
     raise web.HTTPFound(self.ctx.url_for("plug", name=plug))
Example #18
0
    async def commands(self, channel, user):
        """
        Retrieve all commands, and filter against the mappings.

        Args:
            channel (.Channel):
                Source channel where the command will be executed.
            user (.User):
                Author of the message to trigger the command.

        Returns:
            (str, .BoundCommand) dict:
                Commands provided by all hooks, in this channel for this user, keyed by name.
        """
        log.debug("Collecting commands for %r in %r", channel, user)
        if isinstance(channel, immp.Plug):
            # Look for commands for a generic channel.
            plug = channel
            channel = immp.Channel(plug, "")
            private = False
        else:
            plug = None
            private = await channel.is_private()
        mappings = []
        for mapping in self.config["mapping"].values():
            for label in mapping["groups"]:
                group = self.host.groups[label]
                if plug and group.has_plug(plug, "anywhere", "named"):
                    mappings.append(mapping)
                elif not plug and await group.has_channel(channel):
                    mappings.append(mapping)
        cmds = set()
        for mapping in mappings:
            cmds.update(self._mapping_cmds(mapping, channel, user, private))
        mapped = {cmd.name: cmd for cmd in cmds}
        if len(cmds) > len(mapped):
            # Mapping by name silently overwrote at least one command with a duplicate name.
            raise immp.ConfigError(
                "Multiple applicable commands with the same name")
        return mapped
Example #19
0
def config_to_host(config, path, write):
    host = immp.Host()
    for name, spec in config["plugs"].items():
        cls = immp.resolve_import(spec["path"])
        host.add_plug(cls(name, spec["config"], host), spec["enabled"])
    for name, spec in config["channels"].items():
        plug = host.plugs[spec["plug"]]
        host.add_channel(name, immp.Channel(plug, spec["source"]))
    for name, group in config["groups"].items():
        host.add_group(immp.Group(name, group, host))
    for name, spec in config["hooks"].items():
        cls = immp.resolve_import(spec["path"])
        host.add_hook(cls(name, spec["config"], host), spec["enabled"],
                      spec["priority"])
    try:
        host.add_hook(RunnerHook("runner", {}, host))
    except immp.ConfigError:
        # Prefer existing hook defined within the config itself.
        pass
    host.resources[RunnerHook].load(config, path, write)
    host.loaded()
    return host
Example #20
0
File: irc.py Project: Terrance/IMMP
 async def channel_for_user(self, user):
     return immp.Channel(self, user.username)
Example #21
0
 async def private_channels(self):
     return [immp.Channel(self, channel.id) for channel in self._client.private_channels]
Example #22
0
 async def public_channels(self):
     return [immp.Channel(self, channel.id) for channel in self._client.get_all_channels()
             if isinstance(channel, discord.TextChannel)]
Example #23
0
 async def private_channels(self):
     return [immp.Channel(self, id_) for id_ in self._directs]
Example #24
0
    def from_event(cls, github, type_, id_, event):
        """
        Convert a `GitHub webhook <https://developer.github.com/webhooks/>`_ payload to a
        :class:`.Message`.

        Args:
            github (.GitHubPlug):
                Related plug instance that provides the event.
            type (str):
                Event type name from the ``X-GitHub-Event`` header.
            id (str):
                GUID of the event delivery from the ``X-GitHub-Delivery`` header.
            event (dict):
                GitHub webhook payload.

        Returns:
            .GitHubMessage:
                Parsed message object.
        """
        repo = event["repository"]["full_name"]
        channel = immp.Channel(github, repo)
        user = GitHubUser.from_sender(github, event["sender"])
        text = None
        if type_ == "push":
            count = len(event["commits"])
            desc = "{} commits".format(
                count) if count > 1 else event["after"][:7]
            ref = event["ref"].split("/")[1:]
            target = "/".join(ref[1:])
            if ref[0] == "tags":
                action, join = "tagged", "as"
            elif ref[0] == "heads":
                action, join = "pushed", "to"
            else:
                raise NotImplementedError
            text = immp.RichText([
                immp.Segment("{} ".format(action)),
                immp.Segment(desc, link=event["compare"]),
                immp.Segment(" {} {} {}".format(join, repo, target))
            ])
            for commit in event["commits"]:
                text.append(
                    immp.Segment("\n* "),
                    immp.Segment(commit["id"][:7], code=True),
                    immp.Segment(" - {}".format(
                        commit["message"].split("\n")[0])))
        elif type_ == "release":
            release = event["release"]
            desc = ("{} ({} {})".format(release["name"], repo,
                                        release["tag_name"])
                    if release["name"] else release["tag_name"])
            text = immp.RichText([
                immp.Segment("{} release ".format(event["action"])),
                immp.Segment(desc, link=release["html_url"])
            ])
        elif type_ == "issues":
            issue = event["issue"]
            desc = "{} ({}#{})".format(issue["title"], repo, issue["number"])
            text = immp.RichText([
                immp.Segment("{} issue ".format(event["action"])),
                immp.Segment(desc, link=issue["html_url"])
            ])
        elif type_ == "issue_comment":
            issue = event["issue"]
            comment = event["comment"]
            desc = "{} ({}#{})".format(issue["title"], repo, issue["number"])
            text = immp.RichText([
                immp.Segment("{} a ".format(event["action"])),
                immp.Segment("comment", link=comment["html_url"]),
                immp.Segment(" on issue "),
                immp.Segment(desc, link=issue["html_url"])
            ])
        elif type_ == "pull_request":
            pull = event["pull_request"]
            desc = "{} ({}#{})".format(pull["title"], repo, pull["number"])
            text = immp.RichText([
                immp.Segment("{} pull request ".format(event["action"])),
                immp.Segment(desc, link=pull["html_url"])
            ])
        elif type_ == "pull_request_review":
            pull = event["pull_request"]
            review = event["review"]
            desc = "{} ({}#{})".format(pull["title"], repo, pull["number"])
            text = immp.RichText([
                immp.Segment("{} a ".format(event["action"])),
                immp.Segment("review", link=review["html_url"]),
                immp.Segment(" on pull request "),
                immp.Segment(desc, link=pull["html_url"])
            ])
        elif type_ == "pull_request_review_comment":
            pull = event["pull_request"]
            comment = event["comment"]
            desc = "{} ({}#{})".format(pull["title"], repo, pull["number"])
            text = immp.RichText([
                immp.Segment("{} a ".format(event["action"])),
                immp.Segment("comment", link=comment["html_url"]),
                immp.Segment(" on pull request "),
                immp.Segment(desc, link=pull["html_url"])
            ])
        elif type_ == "project":
            project = event["project"]
            desc = "{} ({}#{})".format(project["name"], repo,
                                       project["number"])
            text = immp.RichText([
                immp.Segment("{} project ".format(event["action"])),
                immp.Segment(desc, link=project["html_url"])
            ])
        elif type_ == "project_card":
            card = event["project_card"]
            text = immp.RichText([
                immp.Segment("{} ".format(event["action"])),
                immp.Segment("card", link=card["html_url"]),
                immp.Segment(" in project:\n"),
                immp.Segment(card["note"])
            ])
        elif type_ == "gollum":
            text = immp.RichText()
            for i, page in enumerate(event["pages"]):
                if i:
                    text.append(immp.Segment(", "))
                text.append(
                    immp.Segment("{} {} wiki page ".format(
                        page["action"], repo)),
                    immp.Segment(page["title"], link=page["html_url"]))
        elif type_ == "fork":
            fork = event["forkee"]
            text = immp.RichText([
                immp.Segment("forked {} to ".format(repo)),
                immp.Segment(fork["full_name"], link=fork["html_url"])
            ])
        elif type_ == "watch":
            text = immp.RichText([immp.Segment("starred {}".format(repo))])
        if text:
            return immp.SentMessage(id_=id_,
                                    channel=channel,
                                    text=text,
                                    user=user,
                                    action=True,
                                    raw=event)
        else:
            raise NotImplementedError
Example #25
0
    async def from_event(cls, hangouts, event):
        """
        Convert a :class:`hangups.ChatMessageEvent` into a :class:`.Message`.

        Args:
            hangouts (.HangoutsPlug):
                Related plug instance that provides the event.
            event (hangups.ChatMessageEvent):
                Hangups message event emitted from a conversation.

        Returns:
            .HangoutsMessage:
                Parsed message object.
        """
        user = HangoutsUser.from_user(hangouts,
                                      hangouts._users.get_user(event.user_id))
        action = False
        joined = None
        left = None
        title = None
        attachments = []
        if isinstance(event, hangups.ChatMessageEvent):
            segments = [
                HangoutsSegment.from_segment(segment)
                for segment in event.segments
            ]
            text = immp.RichText(segments)
            if any(a.type == 4 for a in event._event.chat_message.annotation):
                # This is a /me message sent from desktop Hangouts.
                action = True
                # The user's first name prefixes the message text, so try to strip that.
                if user.real_name:
                    # We don't have a clear-cut first name, so try to match parts of names.
                    # Try the full name first, then split successive words off the end.
                    parts = user.real_name.split()
                    start = text[0].text
                    for pos in range(len(parts), 0, -1):
                        sub_name = " ".join(parts[:pos])
                        if start.startswith(sub_name):
                            text[0].text = start[len(sub_name) + 1:]
                            break
                    else:
                        # Couldn't match the user's name to the message text.
                        pass
            for attach in event._event.chat_message.message_content.attachment:
                embed = attach.embed_item
                if any(place in embed.type
                       for place in (hangouts_pb2.ITEM_TYPE_PLACE,
                                     hangouts_pb2.ITEM_TYPE_PLACE_V2)):
                    location = HangoutsLocation.from_embed(embed)
                    if str(text) == (
                            "https://maps.google.com/maps?q={0},{1}".format(
                                location.latitude, location.longitude)):
                        text = None
                    attachments.append(location)
                elif hangouts_pb2.ITEM_TYPE_PLUS_PHOTO in embed.type:
                    attachments.append(await HangoutsFile.from_embed(
                        hangouts, embed))
        elif isinstance(event, hangups.MembershipChangeEvent):
            action = True
            is_join = event.type_ == hangouts_pb2.MEMBERSHIP_CHANGE_TYPE_JOIN
            parts = [
                HangoutsUser.from_user(hangouts,
                                       hangouts._users.get_user(part_id))
                for part_id in event.participant_ids
            ]
            if len(parts) == 1 and parts[0].id == user.id:
                # Membership event is a user acting on themselves.
                segments = [
                    HangoutsSegment("{} the hangout".format(
                        "joined" if is_join else "left"))
                ]
            else:
                segments = [
                    HangoutsSegment("added " if is_join else "removed ")
                ]
                for part in parts:
                    link = "https://hangouts.google.com/chat/person/{}".format(
                        part.id)
                    segments.append(
                        HangoutsSegment(part.real_name, bold=True, link=link))
                    segments.append(HangoutsSegment(", "))
                # Replace trailing comma.
                segments[-1].text = " {} the hangout".format(
                    "to" if is_join else "from")
            if is_join:
                joined = parts
            else:
                left = parts
        elif isinstance(event, hangups.OTREvent):
            action = True
            is_history = (event.new_otr_status ==
                          hangouts_pb2.OFF_THE_RECORD_STATUS_ON_THE_RECORD)
            segments = [
                HangoutsSegment("{}abled hangout message history".format(
                    "en" if is_history else "dis"))
            ]
        elif isinstance(event, hangups.RenameEvent):
            action = True
            title = event.new_name
            segments = [
                HangoutsSegment("renamed the hangout to "),
                HangoutsSegment(event.new_name, bold=True)
            ]
        elif isinstance(event, hangups.GroupLinkSharingModificationEvent):
            action = True
            is_shared = event.new_status == hangouts_pb2.GROUP_LINK_SHARING_STATUS_ON
            segments = [
                HangoutsSegment("{}abled joining the hangout by link".format(
                    "en" if is_shared else "dis"))
            ]
        elif isinstance(event, hangups.HangoutEvent):
            action = True
            texts = {
                hangouts_pb2.HANGOUT_EVENT_TYPE_START: "started a call",
                hangouts_pb2.HANGOUT_EVENT_TYPE_END: "ended the call",
                hangouts_pb2.HANGOUT_EVENT_TYPE_JOIN: "joined the call",
                hangouts_pb2.HANGOUT_EVENT_TYPE_LEAVE: "left the call"
            }
            try:
                segments = [HangoutsSegment(texts[event.event_type])]
            except KeyError:
                raise NotImplementedError
        else:
            raise NotImplementedError
        if not isinstance(event, hangups.ChatMessageEvent):
            text = immp.RichText(segments)
        return immp.SentMessage(id_=event.id_,
                                channel=immp.Channel(hangouts,
                                                     event.conversation_id),
                                at=event.timestamp,
                                text=text,
                                user=user,
                                action=action,
                                joined=joined,
                                left=left,
                                title=title,
                                attachments=attachments,
                                raw=event)
Example #26
0
 async def public_channels(self):
     return [immp.Channel(self, id_) for id_ in self._channels]
Example #27
0
 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
Example #28
0
 def _filter_channels(self, type_):
     convs = self._convs.get_all(include_archived=True)
     return (immp.Channel(self, conv.id_) for conv in convs
             if conv._conversation.type == type_)
Example #29
0
    async def commands(self, channel, user):
        """
        Retrieve all commands, and filter against the mappings.

        Args:
            channel (.Channel):
                Source channel where the command will be executed.
            user (.User):
                Author of the message to trigger the command.

        Returns:
            (str, .BoundCommand) dict:
                Commands provided by all hooks, in this channel for this user, keyed by name.
        """
        log.debug("Collecting commands for %r in %r", user, channel)
        if isinstance(channel, immp.Plug):
            # Look for commands for a generic channel.
            plug = channel
            channel = immp.Channel(plug, "")
            private = False
        else:
            plug = None
            private = await channel.is_private()
        mappings = []
        identities = {}
        for label, mapping in self.config["mapping"].items():
            providers = mapping["identify"]
            if providers:
                for name, roles in providers.items():
                    if name not in identities:
                        try:
                            provider = self.host.hooks[name]
                            identities[
                                name] = await provider.identity_from_user(user)
                        except Exception:
                            log.exception(
                                "Exception retrieving identity from %r for map %r",
                                name, label)
                            identities[name] = None
                            continue
                    if not identities[name]:
                        continue
                    elif not roles or set(roles).intersection(
                            identities[name].roles):
                        log.debug("Identified %r as %r for map %r", user,
                                  identities[name], label)
                        break
                else:
                    log.debug("Could not identify %r for map %r, skipping",
                              user, label)
                    continue
            for name in mapping["groups"]:
                group = self.host.groups[name]
                if plug and group.has_plug(plug, "anywhere", "named"):
                    mappings.append(mapping)
                elif not plug and await group.has_channel(channel):
                    mappings.append(mapping)
        cmds = set()
        for mapping in mappings:
            cmds.update(self._mapping_cmds(mapping, channel, user, private))
        mapped = {cmd.name: cmd for cmd in cmds}
        if len(cmds) > len(mapped):
            # Mapping by name silently overwrote at least one command with a duplicate name.
            raise immp.ConfigError(
                "Multiple applicable commands with the same name")
        return mapped
Example #30
0
File: irc.py Project: Terrance/IMMP
    async def from_line(cls, irc, line):
        """
        Convert a :class:`.Line` into a :class:`.Message`.

        Args:
            irc (.IRCPlug):
                Related plug instance that provides the line.
            line (.Line):
                Raw message line from the server.

        Returns:
            .IRCMessage:
                Parsed message object.
        """
        channel = line.args[0]
        nick = line.source.split("!", 1)[0]
        if channel == irc.config["user"]["nick"]:
            # Private messages arrive "from A to B", and should be sent "from B to A".
            channel = nick
        user = IRCUser.from_id(irc, line.source)
        action = False
        joined = []
        left = []
        if line.command == "JOIN":
            text = "joined {}".format(channel)
            action = True
            joined.append(user)
        elif line.command == "PART":
            text = "left {}".format(channel)
            action = True
            left.append(user)
        elif line.command == "KICK":
            target = await irc.user_from_username(line.args[1])
            text = immp.RichText([immp.Segment("kicked "),
                                  immp.Segment(target.username, bold=True, mention=target),
                                  immp.Segment(" ({})".format(line.args[2]))])
            action = True
            left.append(target)
        elif line.command == "PRIVMSG":
            plain = line.args[1]
            match = re.match(r"\x01ACTION ([^\x01]*)\x01", plain)
            if match:
                plain = match.group(1)
                action = True
            text = immp.RichText()
            puppets = {client.nick: user for user, client in irc._puppets.items()}
            for match in re.finditer(r"[\w\d_\-\[\]{}\|`]+", plain):
                word = match.group(0)
                if word in puppets:
                    target = puppets[word]
                else:
                    target = irc.get_user(word)
                if target:
                    if len(text) < match.start():
                        text.append(immp.Segment(plain[len(text):match.start()]))
                    text.append(immp.Segment(word, mention=target))
            if len(text) < len(plain):
                text.append(immp.Segment(plain[len(text):]))
        else:
            raise NotImplementedError
        return immp.SentMessage(id_=Line.next_ts(),
                                channel=immp.Channel(irc, channel),
                                text=text,
                                user=user,
                                action=action,
                                joined=joined,
                                left=left,
                                raw=line)