Hallo,
ich möchte für den Messenger Signal einen Bot schreiben. Kennt jemand eine gescheite Api, worüber ich Nachrichten über Signal empfangen und versenden kann? Kann ich das auch ohne extra Handynummer?
Vielen Dank für Eure Hilfe!
Liebe Grüße,
Jakob
Signal-Messenger-Bot
-
- User
- Beiträge: 65
- Registriert: Samstag 27. Februar 2021, 12:18
Hallo zusammen,
da mich das Thema mittelfristig auch interessiert, habe ich mal kurz recherchiert...
Mein erster Weg führt mich bei solchen Themen immer zu Github und es gibt, wie erwartet, eine Github Seite von Signal: https://github.com/signalapp
OK, auf den ersten Blick nix in Python dabei, aber wenn man auf Github weiter sucht, findet man z.B. das hier, in Python geschrieben: https://github.com/filipre/signalbot
Am Ende der Seite gibt es auch links zu ähnlichen Projekten.
Und über eine große Suchmaschine findet man auch das: https://fabiobarbero.eu/posts/signalbot/
Für mich als leicht fortgeschrittenen Programmierer würde das für den Einstieg reichen.
Viel Erfolg & Grüße
Andy
da mich das Thema mittelfristig auch interessiert, habe ich mal kurz recherchiert...
Mein erster Weg führt mich bei solchen Themen immer zu Github und es gibt, wie erwartet, eine Github Seite von Signal: https://github.com/signalapp
OK, auf den ersten Blick nix in Python dabei, aber wenn man auf Github weiter sucht, findet man z.B. das hier, in Python geschrieben: https://github.com/filipre/signalbot
Am Ende der Seite gibt es auch links zu ähnlichen Projekten.
Und über eine große Suchmaschine findet man auch das: https://fabiobarbero.eu/posts/signalbot/
Für mich als leicht fortgeschrittenen Programmierer würde das für den Einstieg reichen.
Viel Erfolg & Grüße
Andy
Ich verwende(te) signal-cli in Python über 'subprocess', um mir von einem RaspberryPI aus Benachrichtigungen zu schicken. Das funktioniert auch so einigermaßen. Aber ob das "reaktionsfreudig" genug ist für einen Bot, der ein bisschen interaktiv sein soll, kann ich nicht sagen. Ich fand das alles eher etwas sperrig. Eine Telefonnummer wird auch benötigt und ich erinnere mich daran, dass die einmalige Einrichtung recht umständlich war (was aber auch am Raspi bzw. dem OS dort lag).
An der "signal-cli" führt wohl offenbar (auf die Schnelle) kein Weg vorbei. Und die braucht ein Java-JDK.
Dann kann man aber z.B. signal-cli als Server auf localhost starten, mit TCP- oder Unix-Socket.
In der "python-signal-cli-rest-api" findet man eine Vorlage für den Json-Client, den man sich anpassen und drumherum dann Funktionen zur Erzeugung der Json-Daten basteln kann (siehe API "signal-cli")..
Dann kann man aber z.B. signal-cli als Server auf localhost starten, mit TCP- oder Unix-Socket.
Code: Alles auswählen
signal-cli -u +491234567 daemon --tcp
Code: Alles auswählen
import socket
from json import dumps, loads
from uuid import uuid4
class JSONRPCResponseException(Exception):
"""
JSONRPCResponseException
"""
def jsonrpc(data: dict, host: str = "localhost", port: int = 7583):
"""
JSON RPC connection
signal-cli -u +491234567 daemon --tcp
"""
request_id = str(uuid4())
data.update({"jsonrpc": "2.0", "id": request_id})
recv_buffer = []
if host.startswith("unix://"):
sock_type = socket.AF_UNIX
sock_conn = host.replace("unix://", "")
else:
sock_type = socket.AF_INET
sock_conn = (host, port)
with socket.socket(sock_type, socket.SOCK_STREAM) as sock:
try:
sock.connect(sock_conn)
sock.settimeout(10)
#logger.debug("JSON RPC request: %s", data)
sock.sendall(dumps(data).encode("utf-8"))
sock.shutdown(socket.SHUT_WR)
while True:
chunk = sock.recv(1)
recv_buffer.append(chunk)
if chunk == b"\n":
recv_content = b"".join(recv_buffer).decode("utf-8")
res = loads(recv_content)
#logger.debug("JSON RPC response: %s", recv_content)
recv_buffer = []
if res.get("id") == request_id:
if res.get("error"):
raise JSONRPCResponseException(
res.get("error").get("message")
)
return res
except Exception as err:
error = getattr(err, "message", repr(err))
raise RuntimeError(f"signal-cli JSON RPC request failed: {error}") from err
#json_data ={"method": "version","params": {"account": "+491234567"}}
json_data ={"method": "version"}
res = jsonrpc(json_data).get('result')
print(res)
{'version': '0.11.11'}
Datenstrukturen die man einer Funktion übergibt, sollte man nicht ändern, wie es aber bei `data` der Fall ist. Variablen sollten erst dann initialisiert werden, wenn sie auch gebraucht werden und nicht schon 15 Zeilen vorher, wie bei `recv_buffer`.
Es ist sehr ineffizient, die Nachrichten in einzelnen Bytes zu lesen. Besser ist es, mit makefile zu arbeiten.
Das res.get innerhalb von JSONRPCResponseException ist falsch, da hier ja schon klar ist, dass es den Key "error" gibt. Je nachdem, wie das JSON-Format aufgebaut ist, sollte man die anderen get durch in-Vergleiche ersetzen.
getattr sollte man nicht verwenden. Die Exceptions, die in message-Attribut haben, müssen getrennt abgefangen werden.
Das ganze könnte also eher so aussehen:
Es ist sehr ineffizient, die Nachrichten in einzelnen Bytes zu lesen. Besser ist es, mit makefile zu arbeiten.
Das res.get innerhalb von JSONRPCResponseException ist falsch, da hier ja schon klar ist, dass es den Key "error" gibt. Je nachdem, wie das JSON-Format aufgebaut ist, sollte man die anderen get durch in-Vergleiche ersetzen.
getattr sollte man nicht verwenden. Die Exceptions, die in message-Attribut haben, müssen getrennt abgefangen werden.
Das ganze könnte also eher so aussehen:
Code: Alles auswählen
def jsonrpc(data, host="localhost", port=7583):
"""
JSON RPC connection
signal-cli -u +491234567 daemon --tcp
"""
request_id = str(uuid4())
data = dict(data, jsonrpc="2.0", id=request_id)
if host.startswith("unix://"):
family = socket.AF_UNIX
address = host.replace("unix://", "")
else:
family = socket.AF_INET
address = (host, port)
with socket.socket(family, socket.SOCK_STREAM) as connection:
try:
connection.connect(address)
connection.settimeout(10)
file = connection.makefile('rwb')
file.write(dumps(data).encode("utf-8"))
connection.shutdown(socket.SHUT_WR)
for line in file:
result = loads(line)
if result.get("id") == request_id:
if result.get("error"):
raise JSONRPCResponseException(
result["error"].get("message")
)
return result
except ExceptionWithMessage as err:
raise RuntimeError(f"signal-cli JSON RPC request failed: {err.message}") from err
except Exception as err:
raise RuntimeError(f"signal-cli JSON RPC request failed") from err
Vielen Dank für deine -wie immer- super Tipps!
Es fehlt aber offenbar noch ein flush im file-Objekt, oder?
Code: Alles auswählen
import socket, time
from json import dumps, loads
from uuid import uuid4
class ExceptionWithMessage(Exception):
"""
JSONRPCResponseException
"""
def jsonrpc(data, host="localhost", port=7583):
"""
JSON RPC connection
signal-cli -u +491234567 daemon --tcp
"""
request_id = str(uuid4())
data = dict(data, jsonrpc="2.0", id=request_id)
if host.startswith("unix://"):
family = socket.AF_UNIX
address = host.replace("unix://", "")
else:
family = socket.AF_INET
address = (host, port)
with socket.socket(family, socket.SOCK_STREAM) as connection:
try:
connection.connect(address)
connection.settimeout(10)
file = connection.makefile('rwb')
file.write(dumps(data).encode("utf-8"))
# >>>>>
file.flush()
# <<<<<
connection.shutdown(socket.SHUT_WR)
for line in file:
result = loads(line)
if result.get("id") == request_id:
if result.get("error"):
raise ExceptionWithMessage(
result["error"].get("message")
)
return result
except ExceptionWithMessage as err:
raise RuntimeError(f"signal-cli JSON RPC request failed: {err.message}") from err
except Exception as err:
raise RuntimeError(f"signal-cli JSON RPC request failed") from err
json_data ={"method": "version"}
res = jsonrpc(json_data)
print(res)