Signal-IRC bridge

From WTFwiki
Revision as of 14:14, 3 October 2019 by Stian (talk | contribs) (Created page with "Quick and dirty hack. This allows me to chat on Signal using Minirc. :) <syntaxhighlight lang="python"> #!/usr/bin/env python3 # You need https://github.com/AsamK/signal-cli...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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 Minirc. :)

#!/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 Minirc.

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"
# Minimum needed to placate Minirc.
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')
def ircmsg(source, message):
    to_b = f':{source}!signal@{ircd} PRIVMSG {nickname} :{message}\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('\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)
    elif message.startswith('PRIVMSG '):
        recipient = message.split()[1]
        tonumber = signal_nick_map.get(recipient, recipient)
        signal_message = message.split(':')[1]
        signal.sendMessage(signal_message, [], [tonumber])
        print(f"Sent to {tonumber}: {signal_message}")
    else:
        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)