💾 Archived View for kenogo.org › blog › 20230718.gmi captured on 2023-09-28 at 15:54:27. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-07-22)
-=-=-=-=-=-=-
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.
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.
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)