From 2bdce115eea41b00c31c64e47d388decc4766fe7 Mon Sep 17 00:00:00 2001 From: Stavros Korokithakis Date: Mon, 10 Dec 2018 04:57:03 +0200 Subject: Add bot interface --- README.md | 19 +++++++++++++++++++ signald/main.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++-------- signald/types.py | 3 ++- 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 7a75823..4999174 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,25 @@ for message in s.receive_messages(): print(message) ``` +You can also use the chat decorator interface: + +```python +from signald import Signal + +s = Signal("+1234567890") + +@s.chat_handler("hello") # This is case-insensitive. +def hello(message, match): + return "Hello there!" + + +@s.chat_handler(re.compile("my name is (.*)")) # This is case-sensitive. +def name(message, match): + return "Hello %s." % match.group(1) + + +s.run_chat() +``` Various ------- diff --git a/signald/main.py b/signald/main.py index cfad0e8..15125d9 100644 --- a/signald/main.py +++ b/signald/main.py @@ -1,5 +1,6 @@ import json import random +import re import socket from typing import Iterator, List # noqa @@ -25,6 +26,7 @@ class Signal: def __init__(self, username, socket_path="/var/run/signald/signald.sock"): self.username = username self.socket_path = socket_path + self._chat_handlers = [] def _get_id(self): "Generate a random ID." @@ -93,19 +95,26 @@ class Signal: except json.JSONDecodeError: print("Invalid JSON") - if message.get("type") != "message": + if message.get("type") != "message" or ( + not message["data"]["isReceipt"] and message["data"]["dataMessage"] is None + ): + # If the message type isn't "message", or if it's a weird message whose + # purpose I don't know, return. I think the weird message is a typing + # notification. continue message = message["data"] + data_message = message["dataMessage"] if message["dataMessage"] else {} yield Message( username=message["username"], source=message["source"], - message=message["dataMessage"]["message"], + text=data_message.get("message"), source_device=message["sourceDevice"], - timestamp=message["dataMessage"]["timestamp"], + timestamp=data_message.get("timestamp"), timestamp_iso=message["timestampISO"], - expiration_secs=message["dataMessage"]["expiresInSeconds"], + expiration_secs=data_message.get("expiresInSeconds"), + is_receipt=message["isReceipt"], attachments=[ Attachment( content_type=attachment["contentType"], @@ -113,18 +122,47 @@ class Signal: size=attachment["size"], stored_filename=attachment["storedFilename"], ) - for attachment in message["dataMessage"]["attachments"] + for attachment in data_message.get("attachments", []) ], ) - def send_message(self, recipient: str, message: str, block: bool = True) -> None: + def send_message(self, recipient: str, text: str, block: bool = True) -> None: """ Send a message. recipient: The recipient's phone number, in E.123 format. - message: The message to send. + text: The text of the message to send. block: Whether to block while sending. If you choose not to block, you won't get an exception if there are any errors. """ - payload = {"type": "send", "username": self.username, "recipientNumber": recipient, "messageBody": message} + payload = {"type": "send", "username": self.username, "recipientNumber": recipient, "messageBody": text} self._send_command(payload, block) + + def chat_handler(self, regex): + """ + A decorator that registers a chat handler function with a regex. + """ + if not isinstance(regex, re._pattern_type): + regex = re.compile(regex, re.I) + + def decorator(func): + self._chat_handlers.append((regex, func)) + return func + + return decorator + + def run_chat(self): + """ + Start the chat event loop. + """ + for message in self.receive_messages(): + if not message.text: + continue + + for regex, func in self._chat_handlers: + match = re.search(regex, message.text) + if not match: + continue + + reply = func(message, match) + self.send_message(recipient=message.source, text=reply) diff --git a/signald/types.py b/signald/types.py index 75cdcbd..b1c4b51 100644 --- a/signald/types.py +++ b/signald/types.py @@ -13,10 +13,11 @@ class Attachment: class Message: username = attr.ib(type=str) source = attr.ib(type=str) - message = attr.ib(type=str) + text = attr.ib(type=str) source_device = attr.ib(type=int, default=0) timestamp = attr.ib(type=int, default=None) timestamp_iso = attr.ib(type=str, default=None) expiration_secs = attr.ib(type=int, default=0) + is_receipt = attr.ib(type=bool, default=False) attachments = attr.ib(type=list, default=[]) quote = attr.ib(type=str, default=None) -- cgit v1.2.3-54-g00ecf