Signal-IRC bridge
Jump to navigation
Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
Quick and dirty hack. This allows me to chat on Signal using an IRC client. :)
#!/usr/bin/env python3
# You need https://github.com/AsamK/signal-cli installed and working and
# paired up to your phone before any of this can be used.
#
# 1) Start signal-cli in dbus daemon mode: signal-cli -u <+phone> daemon
# 2) Start this thing.
# 3) Connect to localhost:60667 with an IRC client.
import sys
import socket
from gi.repository import GLib
from pydbus import SessionBus
# For simplicity, accept just one client and set all the rest of it up after.
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('127.0.0.1', 60667))
server_socket.listen(0)
client_socket, client_address = server_socket.accept()
server_socket.close()
ircd = "signal-ircd.local"
handshake = client_socket.recv(512).decode('utf-8')
if not handshake.startswith('NICK '):
# We don't care about USER.
handshake = client_socket.recv(512).decode('utf-8')
if not handshake.startswith('NICK '):
sys.exit(f"Unexpected IRC handshake: {handshake}")
nickname = handshake.split()[1]
def irc(action, message):
to_b = f':{ircd} {action} {nickname} :{message}\r\n'
client_socket.send(to_b.encode('utf-8'))
irc('001', 'Signal-IRC bridge ready')
irc('251', 'There are 1 users and 0 invisible on 1 servers')
irc('255', 'I have 1 clients and 1 servers')
def ircmsg(source, message):
for m in message.split('\r\n'):
to_b = f':{source}!signal@{ircd} PRIVMSG {nickname} :{m}\r\n'
client_socket.send(to_b.encode('utf-8'))
# This can be prepopulated.
signal_nick_map = {}
bus = SessionBus()
signal = bus.get('org.asamk.Signal')
def receive(timestamp, source, group_id, message, attachments):
print(f"Message from {source}: {message}")
name = signal.getContactName(source)
if name:
# Make phonebook name acceptable to IRC
fromnick = name.replace(' ', '_').replace(':', '')
if not fromnick in signal_nick_map:
signal_nick_map[fromnick] = source
ircmsg(fromnick, message)
else:
ircmsg(source, message)
return True
signal.onMessageReceived = receive
def transmit(channel, condition):
message = channel.read().decode('utf-8')
if message == '':
sys.exit("EOF from client, exiting")
lines = message.split('\r\n')
assert lines[-1] == '' and len(lines) == 2,\
f"If this fails we need more complex line handling: {lines}"
message = lines[0]
if message.startswith('PING '):
challenge = message.split()[1]
irc('PONG', challenge)
print("Pingpong")
elif message.startswith('PRIVMSG '):
recipient = message.split()[1]
tonumber = signal_nick_map.get(recipient, recipient)
signal_message = message.split(':', 1)[1]
signal.sendMessage(signal_message, [], [tonumber])
print(f"Sent to {tonumber}: {signal_message}")
else:
irc('421', 'Unknown command')
print(f"Unhandled IRC protocol message: {message}")
return True
socket_ch = GLib.IOChannel(filedes=client_socket.fileno())
socket_ch.set_flags(socket_ch.get_flags() | GLib.IOFlags.NONBLOCK)
GLib.io_add_watch(socket_ch, 0, GLib.IOCondition.IN, transmit)
loop = GLib.MainLoop()
try:
loop.run()
except KeyboardInterrupt:
sys.exit(0)