Cleanly tear down a deltachat bot connection

Hello,

I’m calling the API with the Python bindings from within a bash script in the following way:

test.sh

#!/usr/bin/env bash

set -uo pipefail

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

test () {
    python -c "import deltachat
ac = deltachat.account.Account('$SCRIPT_DIR/deltachat_db/db')
ac.run_account(addr='xxxxxx@nine.testrun.org', password='xxxxxx')
chat = [c for c in ac.get_chats() if c.get_name() == 'Test'][0]
chat.send_text('msg from $(hostname)')
"
}

test
echo "done"

However, the message is not delivered. Only when using a bash timeout and ac.wait_shutdown(), the message is sent successfully:

test2.sh

#!/usr/bin/env bash

set -uo pipefail

SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )

test () {

timeout 2 python -c "import deltachat

ac = deltachat.account.Account('$SCRIPT_DIR/deltachat_db/db')

ac.run_account(addr='xxxxxx@nine.testrun.org', password='xxxxxx')

chat = [c for c in ac.get_chats() if c.get_name() == 'Test'][0]

chat.send_text('msg from $(hostname)')

ac.wait_shutdown()

"

}

test
echo "done"

I guess this is because the test.sh script exits while the sending action is still in progress and has not yet completed. However, my workaround from test2.sh seems not clean, because if the process takes longer than the 2 seconds granted, it may still fail.

I was unable to find a function in the bot API that blocks until all events are handled. Only wait_shutdown was able to process the events, but it blocks forever. Is there any API function that can do this? I have tried maybe_network(), stop_io(), stop_ongoing() and shutdown() but they all have no effect.

Thanks in adance.

There is no easy way to do this, bots are supposed to be long-running processes. If you want to use the bot to send some sort of notifications, better start a bot process that takes new notifications from some IPC channel or just a spool folder and sends them out without terminating the connection to SMTP and IMAP each time.

There is a dc_send_msg_sync C API that can be used to send a single message over a dedicated SMTP connection and wait for sending to finish, but it is not even exposed to high-level Python API.

Thanks for your answer. I see that I’m using it in an unintended way. However, writing a full-blown bot would be too much of an overkill for my setup. I thought I could replicate the Telegram API where you can just send a curl request to get the message through.

Seems like I have to cultivate my workaround :wink:

I have made some scripts in the past to send a message with DC core and terminate, when you send the message, you get back the message ID, with that, you can listen to the core events and wait for the “message delivered” event with the corresponding message ID of the message you just sent, then terminate/stop IO

here is a small bot script I did to send a file and exit using deltabot-cli library:

#!/usr/bin/env python3
import sys

from deltachat2 import Bot, EventType, MsgData, events

from deltabot_cli import BotCli

cli = BotCli("sendbot")


@cli.on(events.RawEvent)
def log_event(bot: Bot, _accid: int, event) -> None:
    if event.kind == EventType.INFO:
        bot.logger.debug(event.msg)
    elif event.kind == EventType.WARNING:
        bot.logger.warning(event.msg)
    elif event.kind == EventType.ERROR:
        bot.logger.error(event.msg)


def send(cli, bot, args):
    """send file"""
    if args.account:
        accid = cli.get_account(bot.rpc, args.account)
        if not accid:
            bot.logger.error(f"unknown account: {args.account!r}")
            sys.exit(1)
    else:
        accid = bot.rpc.get_all_account_ids()[0]
    conid = bot.rpc.create_contact(accid, args.email, None)
    chatid = bot.rpc.create_chat_by_contact_id(accid, conid)
    msgid = bot.rpc.send_msg(accid, chatid, MsgData(file=args.file))
    bot.run_until(
        lambda ev: ev.event.kind in (EventType.MSG_DELIVERED, EventType.MSG_FAILED)
        and ev.event.msg_id == msgid
    )


if __name__ == "__main__":
    subcmd = cli.add_subcommand(send)
    subcmd.add_argument("email", help="email addr to send the file to")
    subcmd.add_argument("file", help="file path")
    cli.start()

it could be easily modified to send text message instead of text + file, or to send to a group chat instead of to a single email address

Thanks, yeah that would do it. However, I want to avoid a separate, long running process.

One idea, though. If such an agent would run on the DeltaChat servers, that would be great. The agent would

  • Generate tokens for valid user accounts
  • A user can then send a curl message with his/her token, message und chat id to the agent
  • The agent would deliver it to the respective account

it is not a long-running process, as I said it is:

so it does what you want, just send a message with SMTP and terminate execution

note: what the script does is to add a sub-command to the bot CLI “send” that can be used to send files and exit instead of using the long-running sub-command “serve” example usage:

./bot.py send foo@example.org ~/attachment.pdf

I see. Thank you then.