« ^ »

ThunderBirdのデータベースを手で操作する

所要時間: 約 4分

専用のメーラを使用する人は最近では少なくなったように感じる。例えばGmailを使用する人は、GmailのWeb UIを使ったり、スマホアプリを使うだろう。もしかしたらEメール自体を使わず、slackやdiscordなどのチャットツールやSNSのメッセージ機能を使用するといった人も結構いるだろう。もしかしたら、そのほうが主流かもしれない。いずれにしてもメーラーを使う機会は減った。世の中はそういう風に動いているけれど、私は世の中の流れに合せた結果、現時点では結局Eメールは今でもとても便利なツールであって、他のメッセンジャーが有利としている所は実はEメールの問題ではなく、その運用によるものやインターフェースの問題がほとんどを占めるという結論になった。またチャットツールよりもEメールの方が広範囲に浸透しており、オープンな技術で支えられているため、例えばLINEやslackといったチャットツールを使用するより、むしろ好ましいとさえ思う。なんか時代と逆行しているようにも思えるが、そういうことにあまり囚われず、考え事をしていきたい。そんななかで専用のメーラであるThunderBirdに今更だが興味が出てきた。そこでThunderBirdのデータベースから直接メールの情報を取得してみることにする。

macOSにインストールしたThunderBirdは $HOME/Library/Thunderbird/Profiles 配下に各プロフィール用のディレクトリを作成する。その中に global-messages-db.sqlite というSqlite3のデータベースファイルがあるのだが、そこにメールのデータが格納されている。

sqlite> .schema
.schema
CREATE TABLE folderLocations (id INTEGER PRIMARY KEY, folderURI TEXT NOT NULL, dirtyStatus INTEGER NOT NULL, name TEXT NOT NULL, indexingPriority INTEGER NOT NULL);
CREATE TABLE conversations (id INTEGER PRIMARY KEY, subject TEXT NOT NULL, oldestMessageDate INTEGER, newestMessageDate INTEGER);
CREATE VIRTUAL TABLE conversationsText USING fts3(tokenize mozporter, subject TEXT);
CREATE TABLE IF NOT EXISTS 'conversationsText_content'(docid INTEGER PRIMARY KEY, 'c0subject');
CREATE TABLE IF NOT EXISTS 'conversationsText_segments'(blockid INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'conversationsText_segdir'(level INTEGER,idx INTEGER,start_block INTEGER,leaves_end_block INTEGER,end_block INTEGER,root BLOB,PRIMARY KEY(level, idx));
CREATE INDEX subject ON conversations(subject);
CREATE INDEX oldestMessageDate ON conversations(oldestMessageDate);
CREATE INDEX newestMessageDate ON conversations(newestMessageDate);
CREATE TABLE messages (id INTEGER PRIMARY KEY, folderID INTEGER, messageKey INTEGER, conversationID INTEGER NOT NULL, date INTEGER, headerMessageID TEXT, deleted INTEGER NOT NULL default 0, jsonAttributes TEXT, notability INTEGER NOT NULL default 0);
CREATE VIRTUAL TABLE messagesText USING fts3(tokenize mozporter, body TEXT, subject TEXT, attachmentNames TEXT, author TEXT, recipients TEXT);
CREATE TABLE IF NOT EXISTS 'messagesText_content'(docid INTEGER PRIMARY KEY, 'c0body', 'c1subject', 'c2attachmentNames', 'c3author', 'c4recipients');
CREATE TABLE IF NOT EXISTS 'messagesText_segments'(blockid INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'messagesText_segdir'(level INTEGER,idx INTEGER,start_block INTEGER,leaves_end_block INTEGER,end_block INTEGER,root BLOB,PRIMARY KEY(level, idx));
CREATE INDEX messageLocation ON messages(folderID, messageKey);
CREATE INDEX headerMessageID ON messages(headerMessageID);
CREATE INDEX conversationID ON messages(conversationID);
CREATE INDEX date ON messages(date);
CREATE INDEX deleted ON messages(deleted);
CREATE TABLE attributeDefinitions (id INTEGER PRIMARY KEY, attributeType INTEGER NOT NULL, extensionName TEXT NOT NULL, name TEXT NOT NULL, parameter BLOB);
CREATE TABLE messageAttributes (conversationID INTEGER NOT NULL, messageID INTEGER NOT NULL, attributeID INTEGER NOT NULL, value NUMERIC);
CREATE INDEX attribQuery ON messageAttributes(attributeID, value, conversationID, messageID);
CREATE INDEX messageAttribFastDeletion ON messageAttributes(messageID);
CREATE TABLE contacts (id INTEGER PRIMARY KEY, directoryUUID TEXT, contactUUID TEXT, popularity INTEGER, frecency INTEGER, name TEXT, jsonAttributes TEXT);
CREATE INDEX popularity ON contacts(popularity);
CREATE INDEX frecency ON contacts(frecency);
CREATE TABLE contactAttributes (contactID INTEGER NOT NULL, attributeID INTEGER NOT NULL, value NUMERIC);
CREATE INDEX contactAttribQuery ON contactAttributes(attributeID, value, contactID);
CREATE TABLE identities (id INTEGER PRIMARY KEY, contactID INTEGER NOT NULL, kind TEXT NOT NULL, value TEXT NOT NULL, description NOT NULL, relay INTEGER NOT NULL);
CREATE INDEX contactQuery ON identities(contactID);
CREATE INDEX valueQuery ON identities(kind, value);
CREATE TABLE ext_mimeTypes (id INTEGER PRIMARY KEY, mimeType TEXT);
CREATE TABLE imConversations (id INTEGER PRIMARY KEY, title STRING, time NUMBER, path STRING, jsonAttributes STRING);
CREATE VIRTUAL TABLE imConversationsText USING fts3(tokenize mozporter, content STRING);
CREATE TABLE IF NOT EXISTS 'imConversationsText_content'(docid INTEGER PRIMARY KEY, 'c0content');
CREATE TABLE IF NOT EXISTS 'imConversationsText_segments'(blockid INTEGER PRIMARY KEY, block BLOB);
CREATE TABLE IF NOT EXISTS 'imConversationsText_segdir'(level INTEGER,idx INTEGER,start_block INTEGER,leaves_end_block INTEGER,end_block INTEGER,root BLOB,PRIMARY KEY(level, idx));
ThunderBirdのデータベースの定義

messages テーブルにメッセージの起点となるデータを格納しており messagesText_content テーブルにメッセージの表題や本文といった情報が含まれている。

これらを問い合わせることで、メールの情報を表示できる。例えば最新10件のメールのタイトルを表示するには以下のようになる。

SELECT m.id, m.date, c.c1subject FROM messages as m JOIN messagesText_content as c ON c.docid = m.id ORDER BY m.date DESC LIMIT 10;
最新10件のメールのタイトルを表示する

大切なメールも含まれているので結果を掲載できないが、ID、日付、表題を表示できるだろう。メールの検索や管理は正直ファイルやディレクトリとして保持したほうが、管理しやすいと思う。ストレージの値段は圧倒的に下がっているし、コンピュータの性能は圧倒的に向上しているため、個人が受信する量のメッセージであれば、ファイルやディレクトリとして管理し、grepやmvやrmを使って操作するようにしておくと、実はとても効率がよいと思う。昔からコンピュータを使用している人からすれば「それはそうだろう」というような事なのかもしれないが、デジタルネイティブな世代の人にとっては、こういった環境を自分の手で構築すること自体が難しくなっているように思う。

すぐにデータベースを開けるようなEmacs Lispを追加することにした。

(setq thunderbird-current-profile-name "プロフィール名")

(setq thunderbird-current-profile-database
      (format "~/Library/Thunderbird/Profiles/%s/global-messages-db.sqlite"
	      thunderbird-current-profile-name))

(defun sql-sqlite-thunderbird ()
  (interactive)
  (let ((sql-database thunderbird-current-profile-database))
    (sql-sqlite)))