Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions signal_bot/bot_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import functions


class SwitchCase(
functions.Help, functions.Bored, functions.Chuck, functions.Dog, functions.Flip, functions.Gif,
functions.Gnews, functions.Haiku, functions.Hn, functions.Me, functions.Rand, functions.Test,
functions.Trivia, functions.Twitch, functions.Version, functions.Winamp, functions.TestEmoji,
functions.Xkcd, functions.Tmdb
):
"""SwitchCase class to switch bot functions."""

def __init__(self, version, author, signalexecutorlocal, messageobject):
"""Initialize SwitchCase with version, author, signalexecutor and the messagestring."""
self._version = version
self._author = author
self._signalexecutorlocal = signalexecutorlocal
self._messageobject = messageobject

def switch(self, action):
"""Switch function to switch between available functions."""
default = "Invalid option."
return getattr(self, str(action)[1:].split()[0], lambda: default)()
278 changes: 278 additions & 0 deletions signal_bot/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@

import os
import pprint
import json
import time
import docker
import subprocess
import shlex
import logging
import sys
from distutils.util import strtobool
from message import Message
from botfunctions import SwitchCase
from metadata import version, author

__author__ = author
__version__ = version
SIGNALCLIIMAGE = "pblaas/signalcli:latest"

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

if 'REGISTEREDNR' not in os.environ:
logging.error("Mandatory variable not set: REGISTEREDNR")
exit(1)
else:
REGISTEREDNR = os.environ.get('REGISTEREDNR')

if 'DEBUG' in os.environ:
DEBUG = bool(strtobool(os.environ.get('DEBUG')))
else:
DEBUG = False

if 'SIGNALEXECUTORLOCAL' in os.environ:
SIGNALEXECUTORLOCAL = bool(strtobool(os.environ.get('SIGNALEXECUTORLOCAL')))
else:
SIGNALEXECUTORLOCAL = True

if 'READY' in os.environ:
READY = bool(strtobool(os.environ.get('READY')))
else:
READY = False

if 'PRIVATECHAT' in os.environ:
PRIVATECHAT = bool(strtobool(os.environ.get('PRIVATECHAT')))
else:
PRIVATECHAT = False

if 'GROUPCHAT' in os.environ:
GROUPCHAT = bool(strtobool(os.environ.get('GROUPCHAT')))
else:
GROUPCHAT = True

if 'BLACKLIST' in os.environ:
blacklist = os.environ.get('BLACKLIST').split(',')
else:
blacklist = []

if 'WHITELIST' in os.environ:
whitelist = os.environ.get('WHITELIST').split(',')
else:
whitelist = []


def init_program():
"""Initialize start of program."""
try:

homedir = os.environ['HOME']
if SIGNALEXECUTORLOCAL:
out = subprocess.run(["/signal/bin/signal-cli", "--config", "/config", "-o", "json", "-u", REGISTEREDNR, "receive"], stdout=subprocess.PIPE, text=True)
output = out.stdout
else:
dockerclient = docker.from_env()
out = dockerclient.containers.run(
SIGNALCLIIMAGE,
"-o json -u " + REGISTEREDNR + " receive",
auto_remove=True,
volumes={homedir + '/signal': {'bind': '/config', 'mode': 'rw'}}
)
output = out.decode('utf-8')
lines = []
for line in output.split("\n"):
lines.append(line)

for index, value in enumerate(lines):
if value:
parse_message(value.replace(u"\u2018", "'").replace(u"\u2019", "'"))

except docker.errors.NotFound:
logging.error("Unable to retrieve container. Please verify container.")
except docker.errors.APIError as e_error:
logging.error("Docker API error due to: " + e_error)


def parse_message(value):
"""Create message object from input."""
res = json.loads(value)
if DEBUG:
pprint.pprint(res)

# Messages send by the registered number itself.
if "syncMessage" in res['envelope']:
if "sentMessage" in res['envelope']['syncMessage']:
if "groupInfo" in res['envelope']['syncMessage']['sentMessage']:
messageobject = Message(
res['envelope']['source'],
res['envelope']['syncMessage']['sentMessage']['message'],
res['envelope']['syncMessage']['sentMessage']['groupInfo']['groupId'],
res['envelope']['syncMessage']['sentMessage']['timestamp']
)
if DEBUG:
logging.info(pprint.pprint(res))
logging.info(messageobject.getsource())
logging.info(messageobject.getgroupinfo())
logging.info(messageobject.getmessage())
if READY:
if group_not_in_blacklist(messageobject, blacklist) and group_in_whitelist(messageobject, whitelist):
run_signalcli(messageobject)
else:
logging.info("Group" + messageobject.getgroupinfo() + " is in the blacklist OR not in whitelist.")
else:
logging.info("NOOP due to ready mode set to false.")
if PRIVATECHAT and "groupInfo" not in res['envelope']['syncMessage']['sentMessage']:
messageobject = Message(
res['envelope']['source'],
res['envelope']['syncMessage']['sentMessage']['message'],
None,
res['envelope']['syncMessage']['sentMessage']['timestamp']
)
if DEBUG:
logging.info(pprint.pprint(res))
logging.info(messageobject.getsource())
logging.info(messageobject.getgroupinfo())
logging.info(messageobject.getmessage())
if READY:
run_signalcli(messageobject)
else:
logging.info("NOOP due to ready mode set to false.")

# Messages send by others.
if "dataMessage" in res['envelope']:
if "message" in res['envelope']['dataMessage']:
if "groupInfo" in res['envelope']['dataMessage']:
messageobject = Message(
res['envelope']['source'],
res['envelope']['dataMessage']['message'],
res['envelope']['dataMessage']['groupInfo']['groupId'],
res['envelope']['dataMessage']['timestamp']
)
if DEBUG:
logging.info(pprint.pprint(res))
logging.info(messageobject.getsource())
logging.info(messageobject.getgroupinfo())
logging.info(messageobject.getmessage())
if READY:
if group_not_in_blacklist(messageobject, blacklist) and group_in_whitelist(messageobject, whitelist):
run_signalcli(messageobject)
else:
logging.info("Group" + messageobject.getgroupinfo() + " is in the blacklist OR not in whitelist.")
else:
logging.info("NOOP due to ready mode set to false.")
if PRIVATECHAT and "groupInfo" not in res['envelope']['dataMessage']:
messageobject = Message(
res['envelope']['source'],
res['envelope']['dataMessage']['message'],
None,
res['envelope']['dataMessage']['timestamp']
)
if DEBUG:
logging.info(pprint.pprint(res))
logging.info(messageobject.getsource())
logging.info(messageobject.getgroupinfo())
logging.info(messageobject.getmessage())
if READY:
run_signalcli(messageobject)
else:
logging.info("NOOP due to ready mode set to false.")


def run_signalcli(messageobject):
"""Collect variables which should be passes to the cli send command."""
global client, home
if isinstance(messageobject.getmessage(), str) and messageobject.getmessage().startswith('!'):

action = SwitchCase(__version__, __author__, SIGNALEXECUTORLOCAL, messageobject.getmessage())
actionmessage = action.switch(messageobject.getmessage()).replace('"', '')

if not SIGNALEXECUTORLOCAL:
client = docker.from_env()
home = os.environ['HOME']
signal_cli_send(REGISTEREDNR, PRIVATECHAT, GROUPCHAT, SIGNALEXECUTORLOCAL, messageobject, actionmessage)


def signal_cli_send(registerednr, privatechat, groupchat, signalexecutorlocal, messageobject, actionmessage):
""" Signal CLI command execution"""
localsignalcli = "/signal/bin/signal-cli --config /config "

if messageobject.getgroupinfo() is None and privatechat:
# this is a private one on one chat
if messageobject.getmessage() == "!gif" and actionmessage == "Gif":
target_param = " -u " + registerednr + " send " + messageobject.getsource() + " -a /tmp/signal/giphy.gif " + " -m "
elif messageobject.getmessage() == "!xkcd" and actionmessage == "xkcd":
target_param = " -u " + registerednr + " send " + messageobject.getsource() + " -a /tmp/signal/image.png " + " -m "
elif messageobject.getmessage() == "!me":
target_param = " -u " + registerednr + " sendReaction " + messageobject.getsource() + " -a " + messageobject.getsource() + " -t " + messageobject.gettimestamp() + " -e "
else:
target_param = " -u " + registerednr + " send " + messageobject.getsource() + " -m "

if signalexecutorlocal:
args = shlex.split(localsignalcli + target_param)
args.append(actionmessage)
subprocess.Popen(args)
else:
client.containers.run(
SIGNALCLIIMAGE,
target_param + "\"" + actionmessage + "\"",
auto_remove=True,
volumes={home + '/signal': {'bind': '/config', 'mode': 'rw'},
'/tmp/signal': {'bind': '/tmp/signal', 'mode': 'rw'}}
)
else:
if groupchat:
# this is a group chat
if messageobject.getmessage() == "!gif" and actionmessage == "Gif":
target_param = "-u " + registerednr + " send -g " + messageobject.getgroupinfo() + " -a /tmp/signal/giphy.gif " + " -m "
elif messageobject.getmessage() == "!xkcd" and actionmessage == "xkcd":
target_param = " -u " + registerednr + " send -g " + messageobject.getgroupinfo() + " -a /tmp/signal/image.png " + " -m "
elif messageobject.getmessage() == "!me":
target_param = " -u " + registerednr + " sendReaction " + "-g " + messageobject.getgroupinfo() + " -a " + messageobject.getsource() + " -t " + messageobject.gettimestamp() + " -e "
elif messageobject.getmessage() == "!rand":
target_param = "-u " + registerednr + " updateGroup -g " + messageobject.getgroupinfo() + " -n "
else:
target_param = "-u " + registerednr + " send -g " + messageobject.getgroupinfo() + " -m "

if signalexecutorlocal:
args = shlex.split(localsignalcli + target_param)
args.append(actionmessage)
subprocess.Popen(args)
else:
client.containers.run(
SIGNALCLIIMAGE,
target_param + "\"" + actionmessage + "\"",
auto_remove=True,
volumes={home + '/signal': {'bind': '/config', 'mode': 'rw'},
'/tmp/signal': {'bind': '/tmp/signal', 'mode': 'rw'}}
)


def group_not_in_blacklist(messageobject, blist):
for groupid in blist:
if groupid == messageobject.getgroupinfo():
return False
return True


def group_in_whitelist(messageobject, wlist):
if len(wlist) > 0:
for groupid in wlist:
if groupid == messageobject.getgroupinfo():
return True
return False
else:
return True


if __name__ == '__main__':

logging.info("Signal bot " + __version__ + " started.")
logging.info("Debug " + str(DEBUG))
logging.info("Local Executor " + str(SIGNALEXECUTORLOCAL))
logging.info("Ready " + str(READY))
logging.info("Private chat " + str(PRIVATECHAT))
logging.info("Group Chat " + str(GROUPCHAT))
logging.info("Blacklists: " + str(blacklist))
logging.info("Whitelits: " + str(whitelist))
while True:
init_program()
time.sleep(2)
25 changes: 25 additions & 0 deletions signal_bot/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Message:
"""Message class to model data."""

def __init__(self, source, message, groupinfo, timestamp):
"""Initialize Message object with source, message and groupinfo."""
self._source = source
self._message = message
self._groupinfo = groupinfo
self._timestamp = timestamp

def getgroupinfo(self):
"""Return groupID."""
return self._groupinfo

def getmessage(self):
"""Return message string."""
return self._message

def getsource(self):
"""Return source string."""
return self._source

def gettimestamp(self):
"""Return timestamp string."""
return str(self._timestamp)
8 changes: 8 additions & 0 deletions signal_bot/modules.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/signalbot.iml" filepath="$PROJECT_DIR$/.idea/signalbot.iml" />
</modules>
</component>
</project>
5 changes: 5 additions & 0 deletions signal_bot/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[pytest]
env_files =
.env
.test.env
.deploy.env
Loading