💾 Archived View for kenogo.org › blog › 20230718.gmi captured on 2024-08-31 at 11:51:57. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-07-22)

-=-=-=-=-=-=-

Back up all of signal-desktop's media files

Homepage

Blog

Introduction

I don't own a smartphone, so I need to use Signal's desktop client (registering my phone number with [signal-cli]) for my encrypted instant messaging needs. This situation kind of sucks, as Signal's desktop client is more of an afterthought and is missing many crucial features. One annoyance with it is that you can only view the recent media of a conversation in the desktop client. Pictures and videos that you received or sent some time ago are not visible. I worked around this problem by writing a Python script that directly accesses Signal's database and data store to dump all of the images and videos in a directory, grouping them by conversation.

signal-cli

Prerequisites

You need to install Python 3 and the module sqlcipher3. It can be installed

with:

    $ pip3 install --user sqlcipher3-binary

Then, you need to find your Signal config directory. On Linux systems, it is typically under ~/.config/Signal/, so I will use this path from here on. If your's is at a different path, you need to substitute all occurences of ~/.config/Signal/ down below with the proper path.

You also need to obtain the database key from ~/.config/Signal/config.json. The signal devs have decided to encrypt their sqlite database... and store the key in plaintext! What a senseless thing to do.

The script

Here's the script. You need to add your key at the top and modify the paths if necessary. Afterwards, you should be able to just run the script and find all of your exported media in OUTPUT_DIR.

    import json
    import os
    import shutil
    import sqlcipher3
    from mimetypes import guess_extension
  
    DB_PATH = "/home/user/.config/Signal/sql/db.sqlite"
    ATTACHMENTS_PATH = "/home/user/.config/Signal/attachments.noindex"
    DB_KEY = "paste_your_db_key_here"
    OUTPUT_DIR = "/path/to/output/dir"
  
    c = sqlcipher3.connect(DB_PATH)
    c.execute("PRAGMA key = \"x\'%s\'\";" % DB_KEY)
  
    resp = c.execute("SELECT json FROM messages "
  		   "WHERE hasVisualMediaAttachments = '1';")
    messages = resp.fetchall()
  
    for message in messages:
        message_json = message[0]
        message_parsed = json.loads(message_json)
  
        conversationid = message_parsed["conversationId"]
        resp = c.execute("SELECT profileFullName FROM conversations "
  		       "WHERE id = '%s';" % conversationid)
        name = resp.fetchone()[0]
        if not name:
            resp = c.execute("SELECT name FROM conversations WHERE id = '%s';"
                             % conversationid)
            name = resp.fetchone()[0]
        name = "".join([c if c.isalnum() else "_" for c in name])
  
        try:
            os.mkdir("%s/%s" % (OUTPUT_DIR, name))
        except FileExistsError:
            pass
  
        for attachment in message_parsed["attachments"]:
            try:
                dest_path = "%s/%s/%s" % (OUTPUT_DIR, name, attachment["fileName"])
            except KeyError:
                extension = guess_extension(attachment["contentType"])
                dest_path = "%s/%s/signal-%s%s" % (OUTPUT_DIR, name,
                                                   attachment["uploadTimestamp"],
                                                   extension)
            src_path = "%s/%s" % (ATTACHMENTS_PATH, attachment["path"])
            shutil.copy(src_path, dest_path)