aboutsummaryrefslogtreecommitdiffstats
path: root/signald
diff options
context:
space:
mode:
authorStavros Korokithakis <hi@stavros.io>2018-12-10 04:57:03 +0200
committerStavros Korokithakis <hi@stavros.io>2018-12-10 04:57:03 +0200
commit2bdce115eea41b00c31c64e47d388decc4766fe7 (patch)
treebb5c6c56a2aa39e183a17189596ecf8abe968468 /signald
parent0aca0ae50a7336c705a92f2444d6fcd1b3e283e9 (diff)
downloadpysignald-2bdce115eea41b00c31c64e47d388decc4766fe7.tar.gz
pysignald-2bdce115eea41b00c31c64e47d388decc4766fe7.zip
Add bot interface
Diffstat (limited to 'signald')
-rw-r--r--signald/main.py54
-rw-r--r--signald/types.py3
2 files changed, 48 insertions, 9 deletions
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)