Skip to content

API overview

This bot showcases how to use most of the features in the library. Check the commands section to see the implementation of each command. The code shown here can be found the examples folder.

This bot use additional libraries, install them with:

pip install signalbot[examples]


Bot code:

import logging
import os

from examples.commands import (
    AttachmentCommand,
    DeleteCommand,
    DeleteLocalAttachmentCommand,
    EditCommand,
    HelpCommand,
    PingCommand,
    ReactionCommand,
    ReceiveDeleteCommand,
    RegexTriggeredCommand,
    ReplyCommand,
    StylesCommand,
    ThumbsUpCommand,
    TriggeredCommand,
    TypingCommand,
)
from signalbot import SignalBot, enable_console_logging


def main() -> None:
    enable_console_logging(logging.INFO)

    signal_service = os.environ["SIGNAL_SERVICE"]
    phone_number = os.environ["PHONE_NUMBER"]

    config = {
        "signal_service": signal_service,
        "phone_number": phone_number,
    }
    bot = SignalBot(config)

    bot.register(HelpCommand())

    # enable a chat command for all contacts and all groups
    bot.register(PingCommand())
    bot.register(ReplyCommand())

    # enable a chat command only for groups
    bot.register(AttachmentCommand(), contacts=False, groups=True)

    # enable a chat command for one specific group with the name "My Group"
    bot.register(TypingCommand(), groups=["My Group"])

    # chat command is enabled for all groups and one specific contact
    bot.register(TriggeredCommand(), contacts=["+490123456789"], groups=True)

    bot.register(RegexTriggeredCommand())
    bot.register(ReactionCommand())
    bot.register(ThumbsUpCommand())

    bot.register(EditCommand())
    bot.register(DeleteCommand())
    bot.register(ReceiveDeleteCommand())
    bot.register(DeleteLocalAttachmentCommand())
    bot.register(StylesCommand())
    bot.start()


if __name__ == "__main__":
    main()

Commands

AttachmentCommand
import base64

from anyio import Path

from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class AttachmentCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "friday: 🦀 Send and delete an image."

    @triggered("friday")
    async def handle(self, context: Context) -> None:
        image_path = Path(__file__).parent / "image.jpeg"
        async with await image_path.open(mode="rb") as f:
            image = str(base64.b64encode(await f.read()), encoding="utf-8")

        await context.send(
            "https://www.youtube.com/watch?v=pU2SdH1HBuk",
            base64_attachments=[image],
        )
DeleteCommand & DeleteLocalAttachmentCommand & ReceiveDeleteCommand
import asyncio
from datetime import datetime

from anyio import Path

from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, MessageType, triggered


class DeleteCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "delete: 🗑️ Delete a message."

    @triggered("delete")
    async def handle(self, context: Context) -> None:
        timestamp = await context.send("This message will be deleted in two seconds.")
        await asyncio.sleep(2)
        await context.remote_delete(timestamp=timestamp)


class DeleteLocalAttachmentCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "delete_attachment: 🗑️ Delete the local copy of an attachment."

    @triggered("delete_attachment")
    async def handle(self, context: Context) -> None:
        local_filenames = context.message.attachments_local_filenames
        if local_filenames is None or len(local_filenames) == 0:
            await context.send("Please send an attachment to delete.")

        for attachment_filename in local_filenames:
            attachment_path: Path = (
                await Path.home()
                / ".local/share/signal-api/attachments"
                / attachment_filename
            )

            if await attachment_path.exists():
                print(f"Received file {attachment_path}")  # noqa: T201

            await context.bot.delete_attachment(attachment_filename)

            if not await attachment_path.exists():
                print(f"Deleted file {attachment_path}")  # noqa: T201


class ReceiveDeleteCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "N/A: 🗑️ Receive a message has been deleted notification."

    async def handle(self, context: Context) -> None:
        if context.message.type == MessageType.DELETE_MESSAGE:
            deleted_at = datetime.fromtimestamp(  # noqa: DTZ006
                context.message.remote_delete_timestamp / 1000
            )
            message = f"You've deleted a message, which was sent at {deleted_at}."
            await context.send(message)
EditCommand
import asyncio

from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class EditCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "edit: ✏️ Edit a message."

    @triggered("edit")
    async def handle(self, context: Context) -> None:
        timestamp = await context.send("This message will be edited in two seconds.")
        await asyncio.sleep(2)
        await context.edit("This message has been edited.", timestamp)
HelpCommand
from abc import abstractmethod

from signalbot import Command, Context, triggered


class CommandWithHelpMessage(Command):
    @abstractmethod
    def help_message(self) -> str:
        pass


class HelpCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "help: 🆘 Shows information about available commands."

    @triggered("help")
    async def handle(self, context: Context) -> None:
        help_message = "Available commands:\n"
        command: CommandWithHelpMessage
        for command, _, _, _ in self.bot.commands:
            help_message += f"\t - {command.help_message()}\n"
        await context.send(help_message)
TriggeredCommand
from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class TriggeredCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "command_1, command_2 or command_3: 😤😤😤 Decorator example."

    # add case_sensitive=True for case sensitive triggers
    @triggered("command_1", "Command_2", "CoMmAnD_3")
    async def handle(self, context: Context) -> None:
        await context.send("I am triggered")
PingCommand
from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class PingCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "ping: 🏓 Listen for a ping and send a pong reply."

    @triggered("ping")
    async def handle(self, context: Context) -> None:
        await context.send("pong")
ReactionCommand & ThumbsUpCommand
from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, reaction_triggered


class ReactionCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "react with any emoji: 👍 Reaction decorator example."

    @reaction_triggered()
    async def handle(self, context: Context) -> None:
        reaction = context.message.reaction
        if reaction.is_remove:
            await context.send(f"You removed your {reaction.emoji} reaction")
            return
        await context.send(
            f"{reaction.emoji} from {context.message.source} "
            f"on message at {reaction.target_sent_timestamp}"
        )


class ThumbsUpCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "react with 👍 or ❤️: 🎯 Filtered reaction decorator example."

    @reaction_triggered("👍", "❤️")
    async def handle(self, context: Context) -> None:
        await context.send("Thanks for the love!")
RegexTriggeredCommand
from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, regex_triggered


class RegexTriggeredCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "^[\\w\\.-]+@gmail\\.com$: 😤 Regular expression decorator example."

    @regex_triggered(r"^[\w\.-]+@gmail\.com$")
    async def handle(self, context: Context) -> None:
        await context.send("Detected a Gmail address!")
ReplyCommand
from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class ReplyCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "reply: 💬 Reply to a message."

    @triggered("reply")
    async def handle(self, context: Context) -> None:
        await context.reply("This is a reply.")
StylesCommand
from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class StylesCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "styles: 🎨 Demonstrates different text styles."

    @triggered("styles")
    async def handle(self, context: Context) -> None:
        await context.send("**Bold style**", text_mode="styled")
        await context.send("*Italic style*", text_mode="styled")
        await context.send("~Strikethrough style~", text_mode="styled")
        await context.send("||Spoiler style||", text_mode="styled")
        await context.send("`Monospaced style`", text_mode="styled")
TypingCommand
import asyncio

from examples.commands.help import CommandWithHelpMessage
from signalbot import Context, triggered


class TypingCommand(CommandWithHelpMessage):
    def help_message(self) -> str:
        return "typing: ⌨️ Demonstrates typing indicator for a few seconds."

    @triggered("typing")
    async def handle(self, context: Context) -> None:
        await context.start_typing()
        seconds = 5
        await asyncio.sleep(seconds)
        await context.stop_typing()
        await context.send(f"Typed for {seconds}s")