mirror of
https://github.com/vincentbernat/i3wm-configuration.git
synced 2025-07-03 14:54:21 +02:00
thunderbird: add an action to open message
This commit is contained in:
parent
d71dd264c3
commit
3985ffa085
1 changed files with 54 additions and 33 deletions
|
@ -4,42 +4,49 @@
|
||||||
new messages. This should be a builtin feature in Thunderbird, but it
|
new messages. This should be a builtin feature in Thunderbird, but it
|
||||||
is not."""
|
is not."""
|
||||||
|
|
||||||
# TODO:
|
# This is quite basic. Notably, it relies on some quirks of the
|
||||||
# - handle emails received at the same second
|
# Thunderbird mbox format where the From line contains a timestamp of
|
||||||
# - add an action to Open TB (thunderbird mid:....)
|
# the received message and therefore can be used as a watermark as
|
||||||
|
# long as we don't receive more than 1 message per second.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import argparse
|
import argparse
|
||||||
import pyinotify
|
|
||||||
import os
|
import os
|
||||||
|
import subprocess
|
||||||
import email.parser
|
import email.parser
|
||||||
import email.header
|
import email.header
|
||||||
from pydbus import SessionBus
|
from pydbus import SessionBus
|
||||||
|
from gi.repository import GLib, Gio
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
|
parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
|
||||||
parser.add_argument("root", metavar="FILE", help="path to Thunderbird mail folder")
|
parser.add_argument("root", metavar="FILE", help="path to Thunderbird mail folder")
|
||||||
parser.add_argument("folder", metavar="FOLDER", help="folder to monitor", nargs="+")
|
parser.add_argument("folder", metavar="FOLDER", help="folder to monitor", nargs="+")
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
watermarks = {}
|
|
||||||
notify = SessionBus().get(".Notifications")
|
def decode(header):
|
||||||
|
return " ".join(
|
||||||
|
(
|
||||||
|
decoded.decode(charset or "ascii") if type(decoded) is bytes else decoded
|
||||||
|
for decoded, charset in email.header.decode_header(header)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EventHandler(pyinotify.ProcessEvent):
|
def process(path):
|
||||||
def process_IN_CLOSE_WRITE(self, event):
|
|
||||||
watermark = process(event.pathname, watermarks[event.pathname])
|
|
||||||
watermarks[event.pathname] = watermark
|
|
||||||
|
|
||||||
|
|
||||||
def process(path, watermark):
|
|
||||||
new_watermark = None
|
new_watermark = None
|
||||||
|
watermark = watermarks.get(path)
|
||||||
|
count = 0
|
||||||
with open(path, "rb") as mbox:
|
with open(path, "rb") as mbox:
|
||||||
marker = b"\nFrom "
|
marker = b"\nFrom "
|
||||||
chunk = 1024
|
chunk = 1024
|
||||||
|
|
||||||
|
# Find next message from the end
|
||||||
mbox.seek(0, os.SEEK_END)
|
mbox.seek(0, os.SEEK_END)
|
||||||
begin = mbox.tell()
|
begin = mbox.tell()
|
||||||
while begin > 0:
|
while begin > 0 and count < 10:
|
||||||
|
count += 1
|
||||||
|
# Look for the beginning of a message
|
||||||
while True:
|
while True:
|
||||||
begin -= 1024
|
begin -= 1024
|
||||||
if begin < 0:
|
if begin < 0:
|
||||||
|
@ -52,6 +59,7 @@ def process(path, watermark):
|
||||||
continue
|
continue
|
||||||
begin = begin + idx
|
begin = begin + idx
|
||||||
break
|
break
|
||||||
|
# Look for the end of this message
|
||||||
end = begin + 1
|
end = begin + 1
|
||||||
while True:
|
while True:
|
||||||
mbox.seek(end)
|
mbox.seek(end)
|
||||||
|
@ -65,47 +73,60 @@ def process(path, watermark):
|
||||||
continue
|
continue
|
||||||
end = end + idx
|
end = end + idx
|
||||||
break
|
break
|
||||||
|
|
||||||
|
# Check if we have hit our watermark
|
||||||
mbox.seek(begin + 1)
|
mbox.seek(begin + 1)
|
||||||
message = mbox.read(end - begin).split(b"\n")
|
message = mbox.read(end - begin).split(b"\n")
|
||||||
if message[0] == watermark:
|
if message[0] == watermark:
|
||||||
return new_watermark
|
return
|
||||||
if new_watermark is None:
|
if new_watermark is None:
|
||||||
new_watermark = message[0]
|
new_watermark = message[0]
|
||||||
|
watermarks[path] = new_watermark
|
||||||
if watermark is None:
|
if watermark is None:
|
||||||
return new_watermark
|
return
|
||||||
|
|
||||||
|
# Parse the message to extract subject, author and Message-ID
|
||||||
message = b"\n".join(message[1:])
|
message = b"\n".join(message[1:])
|
||||||
parsed = email.parser.BytesParser().parsebytes(message, headersonly=True)
|
parsed = email.parser.BytesParser().parsebytes(message, headersonly=True)
|
||||||
subject = parsed.get("subject")
|
subject = parsed.get("subject")
|
||||||
author = parsed.get("from")
|
author = parsed.get("from")
|
||||||
|
messageid = parsed.get("message-id")
|
||||||
if subject is not None and author is not None:
|
if subject is not None and author is not None:
|
||||||
subject, charset = email.header.decode_header(subject)
|
actions = [] if messageid is None else [messageid.strip("<>"), "Open"]
|
||||||
if charset is not None:
|
|
||||||
subject = subject.decode(charset)
|
|
||||||
author, charset = email.header.decode_header(author)
|
|
||||||
if charset is not None:
|
|
||||||
author = author.decode(charset)
|
|
||||||
notify.Notify(
|
notify.Notify(
|
||||||
"Thunderbird",
|
"Thunderbird",
|
||||||
0,
|
0,
|
||||||
"thunderbird",
|
"thunderbird",
|
||||||
f"Mail from {author}",
|
f"Mail from {decode(author)}",
|
||||||
subject,
|
decode(subject),
|
||||||
[],
|
actions,
|
||||||
{},
|
{},
|
||||||
10000,
|
10000,
|
||||||
)
|
)
|
||||||
return new_watermark
|
|
||||||
|
|
||||||
|
|
||||||
wm = pyinotify.WatchManager()
|
notify = SessionBus().get(".Notifications")
|
||||||
mask = pyinotify.IN_CLOSE_WRITE
|
monitors = []
|
||||||
handler = EventHandler()
|
watermarks = {}
|
||||||
notifier = pyinotify.Notifier(wm, handler)
|
|
||||||
for folder in options.folder:
|
for folder in options.folder:
|
||||||
folder = folder.split("/")
|
folder = folder.split("/")
|
||||||
folder = [f"{f}.sbd" for f in folder[:-1]] + [folder[-1]]
|
folder = [f"{f}.sbd" for f in folder[:-1]] + [folder[-1]]
|
||||||
folder = os.path.join(os.path.expanduser(options.root), *folder)
|
folder = os.path.join(os.path.expanduser(options.root), *folder)
|
||||||
print(f"Watch {folder}...", flush=True)
|
print(f"Watch {folder}...", flush=True)
|
||||||
watermarks[folder] = process(folder, None)
|
# Take a not of the last message in the folder
|
||||||
wm.add_watch(folder, mask)
|
process(folder)
|
||||||
notifier.loop()
|
# Monitor it for change
|
||||||
|
gfile = Gio.File.new_for_path(folder)
|
||||||
|
monitor = gfile.monitor_file(Gio.FileMonitorFlags.NONE, None)
|
||||||
|
monitor.connect(
|
||||||
|
"changed",
|
||||||
|
lambda m, f1, f2, event: event == Gio.FileMonitorEvent.CHANGES_DONE_HINT
|
||||||
|
and process(folder),
|
||||||
|
)
|
||||||
|
monitors.append(monitor)
|
||||||
|
|
||||||
|
# Reply to notification actions
|
||||||
|
notify.ActionInvoked.connect(
|
||||||
|
lambda _, mid: subprocess.call(["thunderbird", f"mid:{mid}"])
|
||||||
|
)
|
||||||
|
GLib.MainLoop().run()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue