aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStavros Korokithakis <hi@stavros.io>2018-12-06 05:06:18 +0200
committerStavros Korokithakis <hi@stavros.io>2018-12-06 05:47:59 +0200
commit1e9ed7db9489ddcc8238655e9235f5cca99b0adf (patch)
treeedad991f8d891682068fd38e916c6b4f2d6202ac
downloadpysignald-1e9ed7db9489ddcc8238655e9235f5cca99b0adf.tar.gz
pysignald-1e9ed7db9489ddcc8238655e9235f5cca99b0adf.zip
Initial commit
-rw-r--r--.gitignore127
-rw-r--r--.gitlab-ci.yml9
-rw-r--r--.pre-commit-config.yaml20
-rw-r--r--README.md35
-rw-r--r--pyproject.toml16
-rw-r--r--setup.cfg8
-rw-r--r--signald/__init__.py4
-rw-r--r--signald/main.py85
-rw-r--r--signald/types.py6
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/test_signald.py5
11 files changed, 315 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bb28ddb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,127 @@
+
+# Created by https://www.gitignore.io/api/python
+# Edit at https://www.gitignore.io/?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### Python Patch ###
+.venv/
+
+poetry.lock
+
+# End of https://www.gitignore.io/api/python
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000..20f143e
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,9 @@
+image: python:3.7
+
+test:
+ before_script:
+ - pip install poetry pre-commit
+ - poetry install
+ script:
+ - pre-commit run -a
+ - poetry run pytest
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..511cdae
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+- repo: https://github.com/ambv/black
+ rev: 18.9b0
+ hooks:
+ - id: black
+ args: [--line-length=120]
+- repo: git://github.com/doublify/pre-commit-isort
+ rev: v4.3.0
+ hooks:
+ - id: isort
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v2.0.0
+ hooks:
+ - id: flake8
+ additional_dependencies: [flake8~=3.6.0]
+- repo: git://github.com/skorokithakis/pre-commit-mypy
+ rev: v0.610
+ hooks:
+ - id: mypy
+ args: [-s]
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8d6755a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,35 @@
+pysignald
+=======
+
+[![pipeline status](https://gitlab.com/stavros/pysignald/badges/master/pipeline.svg)](https://gitlab.com/stavros/pysignald/commits/master)
+
+pysignald is a Python client for the excellent [signald](https://git.callpipe.com/finn/signald) project, which in turn
+is a command-line client for the Signal messaging service.
+
+pysignald allows you to programmatically send and receive messages to Signal.
+
+Installation
+------------
+
+You can install pysignald with pip:
+
+```
+$ pip install signald
+```
+
+
+Running
+-------
+
+Just make sure you have signald installed. Here's an example of how to use pysignald:
+
+
+```python
+from signald import Signal
+
+s = Signal("+1234567890")
+s.send_message("+1098765432", "Hello there!")
+
+for message in s.receive_messages():
+ print(message)
+```
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..25bbeef
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,16 @@
+[tool.poetry]
+name = "signald"
+version = "0.0.1"
+description = ""
+authors = ["Stavros Korokithakis <hi@stavros.io>"]
+
+[tool.poetry.dependencies]
+python = "^3.4"
+attrs = "^18.2"
+
+[tool.poetry.dev-dependencies]
+pytest = "^3.5"
+
+[build-system]
+requires = ["poetry>=0.12"]
+build-backend = "poetry.masonry.api"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..135eb6d
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,8 @@
+[flake8]
+ignore=F403,E128,E126,E123,E121,E265,E501,N802,N803,N806,C901
+
+[isort]
+include_trailing_comma = true
+line_length = 120
+force_grid_wrap = 0
+multi_line_output = 3
diff --git a/signald/__init__.py b/signald/__init__.py
new file mode 100644
index 0000000..2066083
--- /dev/null
+++ b/signald/__init__.py
@@ -0,0 +1,4 @@
+# flake8: noqa
+__version__ = "0.0.1"
+
+from .main import Signal
diff --git a/signald/main.py b/signald/main.py
new file mode 100644
index 0000000..61ae1a5
--- /dev/null
+++ b/signald/main.py
@@ -0,0 +1,85 @@
+import json
+import random
+import socket
+from typing import Iterator, List # noqa
+
+from .types import Message
+
+
+def readlines(s: socket.socket) -> Iterator[bytes]:
+ "Read a socket, line by line."
+ buf = [] # type: List[bytes]
+ while True:
+ char = s.recv(1)
+ if not char:
+ raise ConnectionResetError("connection was reset")
+
+ if char == b"\n":
+ yield b"".join(buf)
+ buf = []
+ else:
+ buf.append(char)
+
+
+class Signal:
+ def __init__(self, username, socket_path="/var/run/signald/signald.sock"):
+ self.username = username
+ self.socket_path = socket_path
+
+ def _get_id(self):
+ "Generate a random ID."
+ return "".join(random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for _ in range(10))
+
+ def _get_socket(self) -> socket.socket:
+ "Create a socket, connect to the server and return it."
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.socket_path)
+ return s
+
+ def _send_command(self, payload: dict):
+ s = self._get_socket()
+ msg_id = self._get_id()
+ payload["id"] = msg_id
+ s.recv(1024) # Flush the buffer.
+ s.send(json.dumps(payload).encode("utf8") + b"\n")
+
+ response = s.recv(4 * 1024)
+ for line in response.split(b"\n"):
+ if msg_id.encode("utf8") not in line:
+ continue
+
+ data = json.loads(line)
+
+ if data.get("id") != msg_id:
+ continue
+
+ if data["type"] == "unexpected_error":
+ raise ValueError("unexpected error occurred")
+
+ def register(self, voice=False):
+ payload = {"type": "register", "username": self.username, "voice": voice}
+ self._send_command(payload)
+
+ def verify(self, code: str):
+ payload = {"type": "verify", "username": self.username, "code": code}
+ self._send_command(payload)
+
+ def receive_messages(self) -> Iterator[Message]:
+ "Keep returning received messages."
+ s = self._get_socket()
+ s.send(json.dumps({"type": "subscribe", "username": self.username}).encode("utf8") + b"\n")
+
+ for line in readlines(s):
+ try:
+ message = json.loads(line.decode())
+ except json.JSONDecodeError:
+ print("Invalid JSON")
+
+ if message.get("type") != "message":
+ continue
+
+ yield message
+
+ def send_message(self, recipient: str, message: str) -> None:
+ payload = {"type": "send", "username": self.username, "recipientNumber": recipient, "messageBody": message}
+ self._send_command(payload)
diff --git a/signald/types.py b/signald/types.py
new file mode 100644
index 0000000..814b967
--- /dev/null
+++ b/signald/types.py
@@ -0,0 +1,6 @@
+import attr
+
+
+@attr.s
+class Message:
+ pass
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/test_signald.py b/tests/test_signald.py
new file mode 100644
index 0000000..8824272
--- /dev/null
+++ b/tests/test_signald.py
@@ -0,0 +1,5 @@
+from signald import Signal
+
+
+def test_signal():
+ Signal("+1234567890")