From bafb2cd61f6a9644d6371b074cb434be3c8307da Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 21 Oct 2021 10:57:58 -0700 Subject: [PATCH] Added one folder --- signal_bot/bot_function.py | 22 +++ signal_bot/main.py | 278 +++++++++++++++++++++++++++++++++++ signal_bot/message.py | 25 ++++ signal_bot/modules.xml | 8 + signal_bot/pytest.ini | 5 + signal_bot/requirements.txt | 55 +++++++ signal_bot/setting.json | 15 ++ signal_bot/setup.py | 13 ++ signal_bot/signal.py | 16 ++ signal_bot/signalbot.iml.txt | 12 ++ 10 files changed, 449 insertions(+) create mode 100644 signal_bot/bot_function.py create mode 100644 signal_bot/main.py create mode 100644 signal_bot/message.py create mode 100644 signal_bot/modules.xml create mode 100644 signal_bot/pytest.ini create mode 100644 signal_bot/requirements.txt create mode 100644 signal_bot/setting.json create mode 100644 signal_bot/setup.py create mode 100644 signal_bot/signal.py create mode 100644 signal_bot/signalbot.iml.txt diff --git a/signal_bot/bot_function.py b/signal_bot/bot_function.py new file mode 100644 index 000000000..c98fe6e3c --- /dev/null +++ b/signal_bot/bot_function.py @@ -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)() \ No newline at end of file diff --git a/signal_bot/main.py b/signal_bot/main.py new file mode 100644 index 000000000..59a41105e --- /dev/null +++ b/signal_bot/main.py @@ -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) \ No newline at end of file diff --git a/signal_bot/message.py b/signal_bot/message.py new file mode 100644 index 000000000..163795e11 --- /dev/null +++ b/signal_bot/message.py @@ -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) \ No newline at end of file diff --git a/signal_bot/modules.xml b/signal_bot/modules.xml new file mode 100644 index 000000000..14c76987a --- /dev/null +++ b/signal_bot/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/signal_bot/pytest.ini b/signal_bot/pytest.ini new file mode 100644 index 000000000..e8527dd4a --- /dev/null +++ b/signal_bot/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +env_files = + .env + .test.env + .deploy.env \ No newline at end of file diff --git a/signal_bot/requirements.txt b/signal_bot/requirements.txt new file mode 100644 index 000000000..afd0191f1 --- /dev/null +++ b/signal_bot/requirements.txt @@ -0,0 +1,55 @@ +//Installing and configuring signal-cli + +export VERSION=0.8.1 +wget https://github.com/USer/signal-cli/releases/download/v"${VERSION}"/signal-cli-"${VERSION}".tar.gz +sudo tar xf signal-cli-"${VERSION}".tar.gz -C /opt +sudo ln -sf /opt/signal-cli-"${VERSION}"/bin/signal-cli /user/local/bin/ + +signal-cli -u +919452320987 register + +//Enable DBus service + + + + + + + + + + + + + + + + + + + +//Commands that can be run + +systemctl daemon-reload +systemctl enable signal.service +systemctl reload dbus.service + +//Configuring Python + +pip install git+https://github.com/LEW21/pydbus.git +sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-3.0 +sudo apt install libgirepository1.0-dev gcc libcairo2-dev pkg-config python3-dev gir1.2-gtk-3.0 +pip install pycairo +pip install PyGObject + +//Basic Requirements +docker +haikunator +emoji +urllib3 +pytest +pytest-html +pytest-cov +pytest-dotenv +coverage-badge +six \ No newline at end of file diff --git a/signal_bot/setting.json b/signal_bot/setting.json new file mode 100644 index 000000000..b23263be5 --- /dev/null +++ b/signal_bot/setting.json @@ -0,0 +1,15 @@ +{ + "python.pythonPath": "/Users/pblaas/.pyenv/versions/signalbot-3.6.10/bin/python", + "python.linting.pylintEnabled": true, + "python.linting.pydocstyleEnabled": true, + "python.linting.enabled": true, + "python.linting.flake8Enabled": true, + "python.linting.pylintArgs": [ + "--max-line-length=120", + "--disable=E501" + ], + "python.linting.flake8Args": [ + "--max-line-length=120", + "--ignore=E501", + ], +} \ No newline at end of file diff --git a/signal_bot/setup.py b/signal_bot/setup.py new file mode 100644 index 000000000..02f2ac057 --- /dev/null +++ b/signal_bot/setup.py @@ -0,0 +1,13 @@ +from setuptools import setup +from signalbot.metadata import version, author, authoremail + +setup( + name='SignalBot', + version=version, + description='Interactive bot for the Signal messenger platform', + packages=['signalbot'], + author=author, + author_email=authoremail, + license='MIT', + install_requires=['pytest', 'pytest-html', 'pytest-cov', 'pytest-dotenv', 'docker', 'haikunator', 'emoji', 'urllib3'] +) \ No newline at end of file diff --git a/signal_bot/signal.py b/signal_bot/signal.py new file mode 100644 index 000000000..ddb4effc1 --- /dev/null +++ b/signal_bot/signal.py @@ -0,0 +1,16 @@ +from pydbus import SystemBus +from gi.repository import GLib +bus = SystemBus() +loop = GLib.MainLoop() + +signal = bus.get('org.asamk.Signal', object_path='/org/asamk/Signal') + +def reply_ping(timestamp, source, groupID, message, attachments): + signal.sendMessage(message, [], [source]) + +signal.onMessageReceived = reply_ping +loop.run() + +//for debugging +//ps aux | grep signal-cli +kill PROCESS_ID \ No newline at end of file diff --git a/signal_bot/signalbot.iml.txt b/signal_bot/signalbot.iml.txt new file mode 100644 index 000000000..8b8c39547 --- /dev/null +++ b/signal_bot/signalbot.iml.txt @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file