Package: signalbot
Signal Bot FrameworkPython package to build your own Signal bots. To run the the bot you need to start thesignal-cli-rest-apiservice and link your device with it. Please refer to that project for more details. The API server must run injson-rpcmode.Getting StartedBelow you can find a minimal example on how to use the package. There is also a bigger example in theexamplefolder.importosfromsignalbotimportSignalBot,Command,ContextfromcommandsimportPingCommandclassPingCommand(Command):asyncdefhandle(self,c:Context):ifc.message.text=="Ping":awaitc.send("Pong")if__name__=="__main__":bot=SignalBot({"signal_service":os.environ["SIGNAL_SERVICE"],"phone_number":os.environ["PHONE_NUMBER"]})bot.register(PingCommand())# all contacts and groupsbot.start()Please check outhttps://github.com/bbernhard/signal-cli-rest-api#getting-startedto learn aboutsignal-cli-rest-apiandsignal-cli. A good first step is to make the example above work.Run signal-cli-rest-api innormalmode first.dockerrun-p8080:8080\-v$(PWD)/signal-cli-config:/home/.local/share/signal-cli\-e'MODE=normal'bbernhard/signal-cli-rest-api:0.57Openhttp://127.0.0.1:8080/v1/qrcodelink?device_name=localto link your account with the signal-cli-rest-api serverIn your Signal app, open settings and scan the QR code. The server can now receive and send messages. The access key will be stored in$(PWD)/signal-cli-config.Restart the server injson-rpcmode.dockerrun-p8080:8080\-v$(PWD)/signal-cli-config:/home/.local/share/signal-cli\-e'MODE=json-rpc'bbernhard/signal-cli-rest-api:0.57The logs should show something like this. You can also confirm that the server is running in the correct mode by visitinghttp://127.0.0.1:8080/v1/about....
time="2022-03-07T13:02:22Z" level=info msg="Found number +491234567890 and added it to jsonrpc2.yml"
...
time="2022-03-07T13:02:24Z" level=info msg="Started Signal Messenger REST API"Use the following snippet to get a group'sid:curl-XGET'http://127.0.0.1:8080/v1/groups/+49123456789'|python-mjson.toolInstallsignalbotand start your python script. You need to pass following environment variables to make the example run:SIGNAL_SERVICE: Address of the signal service without protocol, e.g.127.0.0.1:8080PHONE_NUMBER: Phone number of the bot, e.g.+49123456789exportSIGNAL_SERVICE="127.0.0.1"exportPHONE_NUMBER="+49123456789"pipinstallsignalbot
pythonbot.pyThe logs should indicate that one "producer" and three "consumers" have started. The producer checks for new messages sent to the linked account using a web socket connection. It creates a task for every registered command and the consumers work off the tasks. In case you are working with many blocking function calls, you may need to adjust the number of consumers such that the bot stays reactive.INFO:root:[Bot] Producer #1 started
INFO:root:[Bot] Consumer #1 started
INFO:root:[Bot] Consumer #2 started
INFO:root:[Bot] Consumer #3 startedSend the messagePing(case sensitive) to the group that the bot is listening to. The bot (i.e. the linked account) should respond with aPong. Confirm that the bot received a raw message, that the consumer worked on the message and that a new message has been sent.INFO:root:[Raw Message] {"envelope":{"source":"+49123456789","sourceNumber":"+49123456789","sourceUuid":"fghjkl-asdf-asdf-asdf-dfghjkl","sourceName":"René","sourceDevice":3,"timestamp":1646000000000,"syncMessage":{"sentMessage":{"destination":null,"destinationNumber":null,"destinationUuid":null,"timestamp":1646000000000,"message":"Pong","expiresInSeconds":0,"viewOnce":false,"groupInfo":{"groupId":"asdasdfweasdfsdfcvbnmfghjkl=","type":"DELIVER"}}}},"account":"+49123456789","subscription":0}
INFO:root:[Bot] Consumer #2 got new job in 0.00046 seconds
INFO:root:[Bot] Consumer #2 got new job in 0.00079 seconds
INFO:root:[Bot] Consumer #2 got new job in 0.00093 seconds
INFO:root:[Bot] Consumer #2 got new job in 0.00106 seconds
INFO:root:[Bot] New message 1646000000000 sent:
PongClasses and APIDocumentation work in progress. Feel free to open an issue for questions.The package provides methods to easily listen for incoming messages and responding or reacting on them. It also provides a class to develop new commands which then can be registered within the bot.Signalbotbot.register(command, contacts=True, groups=True): Register a new command, listen in all contacts and groups, same asbot.register(command)bot.register(command, contacts=False, groups=["Hello World"]): Only listen in the "Hello World" groupbot.register(command, contacts=["+49123456789"], groups=False): Only respond to one contactbot.start(): Start the botbot.send(receiver, text): Send a new messagebot.react(message, emoji): React to a messagebot.start_typing(receiver): Start typingbot.stop_typing(receiver): Stop typingbot.scheduler: APScheduler > AsyncIOScheduler, seeherebot.storage: In-memory or Redis stroage, seestorage.pyCommandTo implement your own commands, you need to inherentCommandand overwrite following methods:setup(self): Start any task that requires to send messages already, optionaldescribe(self): String to describe your command, optionalhandle(self, c: Context): Handle an incoming message. By default, any command will read any incoming message.Contextcan be used to easily send (c.send(text)), reply (c.reply(text)), react (c.react(emoji)) and to type in a group (c.start_typing()andc.stop_typing()). You can use the@triggereddecorator to listen for specific commands or you can inspectc.message.text.Unit TestingNote: deprecated, I want to switch to pytest eventuallyIn many cases, we can mock receiving and sending messages to speed up development time. To do so, you can usesignalbot.utils.ChatTestCasewhich sets up a "skeleton" bot. Then, you can send messages using the@chatdecorator insignalbot.utilslike this:classPingChatTest(ChatTestCase):defsetUp(self):# initialize self.singal_botsuper().setUp()# all that is left to do is to register the commands that you want to testself.signal_bot.register(PingCommand())@chat("ping","ping")asyncdeftest_ping(self,query,replies,reactions):self.assertEqual(replies.call_count,2)forrecipient,messageinreplies.results():self.assertEqual(recipient,ChatTestCase.group_secret)self.assertEqual(message,"pong")Insignalbot.utils, check outReceiveMessagesMock,SendMessagesMockandReactMessageMockto learn more about their API.TroubleshootingCheck that you linked your account successfullyIs the API server running injson-rpcmode?Can you receive messages usingwscat(websockets) and send messages usingcurl(http)?Do you see incoming messages in the API logs?Do you see the "raw" messages in the bot's logs?Do you see "consumers" picking up jobs and handling incoming messages?Do you see the response in the bot's logs?Local development and packageSection work in progress. Feel free to open an issue for questions.poetryinstall
poetryrunpre-commitinstallOther ProjectsThere are a few other related projects similar to this one. You may want to check them out and see if it fits your needs.ProjectDescriptionLanguagehttps://github.com/lwesterhof/semaphoreBot FrameworkPythonhttps://git.sr.ht/~nicoco/aiosignaldsignald Library / Bot FrameworkPythonhttps://gitlab.com/stavros/pysignald/signald Library / Bot FrameworkPythonhttps://gitlab.com/signald/signald-gosignald LibraryGohttps://github.com/signal-bot/signal-botBot Framework using Signal CLIPythonhttps://github.com/bbernhard/signal-cli-rest-apiREST API Wrapper for Signal CLIGohttps://github.com/bbernhard/pysignalclirestapiPython Wrapper for REST APIPythonhttps://github.com/AsamK/signal-cliA CLI and D-Bus interface for SignalJavahttps://github.com/signalapp/libsignal-service-javaSignal LibraryJavahttps://github.com/aaronetz/signal-botBot FrameworkJava