Mite
This commit is contained in:
parent
90ca29d35c
commit
45058b4272
|
@ -29,6 +29,7 @@ import email.message
|
|||
import email.parser
|
||||
import html
|
||||
import logging
|
||||
import mailcap
|
||||
import os
|
||||
import pdb
|
||||
import re
|
||||
|
@ -45,6 +46,7 @@ import progressbar
|
|||
import xdg.BaseDirectory
|
||||
|
||||
MailLocation = typing.NewType('MailLocation', typing.Tuple[str, str, str])
|
||||
# MessageAction = typing.Callable[[notmuch.Message], None]
|
||||
|
||||
|
||||
class MelEngine:
|
||||
|
@ -82,7 +84,7 @@ class MelEngine:
|
|||
self.accounts[name] = section
|
||||
|
||||
def __init__(self, config_path: str) -> None:
|
||||
self.log = logging.getLogger("Mel")
|
||||
self.log = logging.getLogger("MelEngine")
|
||||
|
||||
self.config = self.load_config(config_path)
|
||||
|
||||
|
@ -93,7 +95,6 @@ class MelEngine:
|
|||
# All the emails the user is represented as:
|
||||
self.aliases: typing.Set[str] = set()
|
||||
# TODO If the user send emails to himself, maybe that wont cut it.
|
||||
self.mailbox_colors: typing.Dict[str, str] = dict()
|
||||
|
||||
self.generate_aliases()
|
||||
|
||||
|
@ -179,25 +180,6 @@ class MelEngine:
|
|||
assert state in {'cur', 'tmp', 'new'}
|
||||
return (mailbox, folder, state)
|
||||
|
||||
def get_mailbox_color(self, mailbox: str) -> str:
|
||||
"""
|
||||
Return the color of the given mailbox in a ready to print
|
||||
string with ASCII escape codes.
|
||||
"""
|
||||
assert self.config
|
||||
if mailbox not in self.mailbox_colors:
|
||||
# RGB colors (not supported everywhere)
|
||||
# color_str = self.config[mailbox]["color"]
|
||||
# color_str = color_str[1:] if color_str[0] == '#' else color_str
|
||||
# R = int(color_str[0:2], 16)
|
||||
# G = int(color_str[2:4], 16)
|
||||
# B = int(color_str[4:6], 16)
|
||||
# self.mailbox_colors[mailbox] = f"\x1b[38;2;{R};{G};{B}m"
|
||||
color_int = int(self.config[mailbox]["color16"])
|
||||
|
||||
self.mailbox_colors[mailbox] = f"\x1b[38;5;{color_int}m"
|
||||
return self.mailbox_colors[mailbox]
|
||||
|
||||
@staticmethod
|
||||
def is_uid(uid: typing.Any) -> bool:
|
||||
"""
|
||||
|
@ -273,7 +255,6 @@ class MelEngine:
|
|||
*args: typing.Any, show_progress: bool = False,
|
||||
write: bool = False, close_db: bool = True,
|
||||
**kwargs: typing.Any) -> int:
|
||||
# TODO Detail the typing.Callable
|
||||
"""
|
||||
Run a function on the messages selected by the given query.
|
||||
"""
|
||||
|
@ -286,11 +267,12 @@ class MelEngine:
|
|||
elements = query.search_messages()
|
||||
nb_msgs = query.count_messages()
|
||||
|
||||
iterator = progressbar.progressbar(
|
||||
elements, max_value=nb_msgs) if show_progress else elements
|
||||
iterator = progressbar.progressbar(elements, max_value=nb_msgs) \
|
||||
if show_progress and nb_msgs else elements
|
||||
|
||||
self.log.info("Executing %s", action)
|
||||
for msg in iterator:
|
||||
self.log.debug("On mail %s", msg)
|
||||
if write:
|
||||
msg.freeze()
|
||||
|
||||
|
@ -328,12 +310,18 @@ class MelOutput:
|
|||
return (None, None)
|
||||
|
||||
def __init__(self, engine: MelEngine) -> None:
|
||||
colorama.init()
|
||||
self.log = logging.getLogger("MelOutput")
|
||||
|
||||
self.engine = engine
|
||||
|
||||
self.light_background = True
|
||||
self.is_tty = sys.stdout.isatty()
|
||||
self.dest_width, self.subject_width = self.compute_line_format()
|
||||
self.mailbox_colors: typing.Dict[str, str] = dict()
|
||||
|
||||
# TODO Allow custom path
|
||||
self.caps = mailcap.getcaps()
|
||||
|
||||
@staticmethod
|
||||
def format_date(date: datetime.datetime) -> str:
|
||||
|
@ -345,6 +333,10 @@ class MelOutput:
|
|||
now = datetime.datetime.now()
|
||||
if now - date < datetime.timedelta(days=1):
|
||||
return date.strftime('%H:%M:%S')
|
||||
if now - date < datetime.timedelta(days=28):
|
||||
return date.strftime('%d %H:%M')
|
||||
if now - date < datetime.timedelta(days=365):
|
||||
return date.strftime('%m-%d %H')
|
||||
return date.strftime('%y-%m-%d')
|
||||
|
||||
@staticmethod
|
||||
|
@ -383,6 +375,26 @@ class MelOutput:
|
|||
remainder /= 1024.0
|
||||
return "%.1f %s%s" % (remainder, 'Yi', suffix)
|
||||
|
||||
def get_mailbox_color(self, mailbox: str) -> str:
|
||||
"""
|
||||
Return the color of the given mailbox in a ready to print
|
||||
string with ASCII escape codes.
|
||||
"""
|
||||
if not self.is_tty:
|
||||
return ''
|
||||
if mailbox not in self.mailbox_colors:
|
||||
# RGB colors (not supported everywhere)
|
||||
# color_str = self.config[mailbox]["color"]
|
||||
# color_str = color_str[1:] if color_str[0] == '#' else color_str
|
||||
# R = int(color_str[0:2], 16)
|
||||
# G = int(color_str[2:4], 16)
|
||||
# B = int(color_str[4:6], 16)
|
||||
# self.mailbox_colors[mailbox] = f"\x1b[38;2;{R};{G};{B}m"
|
||||
color_int = int(self.engine.config[mailbox]["color16"])
|
||||
|
||||
self.mailbox_colors[mailbox] = f"\x1b[38;5;{color_int}m"
|
||||
return self.mailbox_colors[mailbox]
|
||||
|
||||
def print_msg(self, msg: notmuch.Message) -> None:
|
||||
"""
|
||||
Print the given message header on one line.
|
||||
|
@ -394,25 +406,33 @@ class MelOutput:
|
|||
line = ""
|
||||
tags = set(msg.get_tags())
|
||||
mailbox, _, _ = self.engine.get_location(msg)
|
||||
if self.is_tty:
|
||||
line += self.engine.get_mailbox_color(mailbox)
|
||||
if 'unread' in tags or 'flagged' in tags:
|
||||
line += colorama.Style.BRIGHT
|
||||
# if 'flagged' in tags:
|
||||
# line += colorama.Style.BRIGHT
|
||||
# if 'unread' not in tags:
|
||||
# line += colorama.Style.DIM
|
||||
line += colorama.Back.LIGHTBLACK_EX if self.light_background \
|
||||
else colorama.Back.BLACK
|
||||
self.light_background = not self.light_background
|
||||
line += self.get_mailbox_color(mailbox)
|
||||
|
||||
# UID
|
||||
uid = None
|
||||
for tag in tags:
|
||||
if tag.startswith('tuid'):
|
||||
uid = tag[4:]
|
||||
assert uid and MelEngine.is_uid(
|
||||
uid), "{uid} ({type(UID)}) is not a valid UID."
|
||||
assert uid, f"No UID for message: {msg}."
|
||||
assert MelEngine.is_uid(uid), f"{uid} {type(uid)} is not a valid UID."
|
||||
line += uid
|
||||
|
||||
# Date
|
||||
line += sep
|
||||
line += sep + colorama.Fore.MAGENTA
|
||||
date = datetime.datetime.fromtimestamp(msg.get_date())
|
||||
line += self.format_date(date)
|
||||
|
||||
# Icons
|
||||
line += sep
|
||||
line += sep + colorama.Fore.RED
|
||||
|
||||
def tags2col1(tag1: str, tag2: str,
|
||||
characters: typing.Tuple[str, str, str, str]) -> None:
|
||||
|
@ -437,15 +457,16 @@ class MelOutput:
|
|||
tags2col1('unread', 'flagged', ('!', 'U', 'F', ' '))
|
||||
tags2col1('sent', 'replied', ('?', '↑', '↪', ' '))
|
||||
|
||||
# Opposed
|
||||
line += sep + colorama.Fore.BLUE
|
||||
if 'sent' in tags:
|
||||
dest = msg.get_header("to")
|
||||
else:
|
||||
dest = msg.get_header("from")
|
||||
line += sep
|
||||
line += MelOutput.clip_text(self.dest_width, dest)
|
||||
|
||||
# Subject
|
||||
line += sep
|
||||
line += sep + colorama.Fore.WHITE
|
||||
subject = msg.get_header("subject")
|
||||
line += MelOutput.clip_text(self.subject_width, subject)
|
||||
|
||||
|
@ -560,11 +581,41 @@ class MelOutput:
|
|||
print()
|
||||
|
||||
# Show text/plain
|
||||
# TODO Consider alternative
|
||||
for part in mail.walk():
|
||||
if part.get_content_type() == "text/plain":
|
||||
if part.is_multipart():
|
||||
continue
|
||||
payl = part.get_payload(decode=True)
|
||||
assert isinstance(payl, bytes)
|
||||
if part.get_content_type() == "text/plain":
|
||||
print(payl.decode())
|
||||
else:
|
||||
# TODO Use nametemplate from mailcap
|
||||
temp_file = '/tmp/melcap.html' # TODO Real temporary file
|
||||
# TODO FIFO if possible
|
||||
with open(temp_file, 'wb') as temp_filedesc:
|
||||
temp_filedesc.write(payl)
|
||||
command, _ = mailcap.findmatch(
|
||||
self.caps, part.get_content_type(), key='view', filename=temp_file)
|
||||
if command:
|
||||
os.system(command)
|
||||
|
||||
def print_dir_list(self) -> None:
|
||||
"""
|
||||
Print a colored directory list.
|
||||
Every line is easilly copiable.
|
||||
"""
|
||||
for arb in self.engine.list_folders():
|
||||
line = colorama.Fore.LIGHTBLACK_EX + "'"
|
||||
line += self.get_mailbox_color(arb[0])
|
||||
line += arb[0].replace("'", "\\'")
|
||||
line += colorama.Fore.LIGHTBLACK_EX
|
||||
for inter in arb[1:-1]:
|
||||
line += '/' + inter.replace("'", "\\'")
|
||||
line += '/' + colorama.Fore.WHITE + arb[-1].replace("'", "\\'")
|
||||
line += colorama.Fore.LIGHTBLACK_EX + "'"
|
||||
line += colorama.Style.RESET_ALL
|
||||
print(line)
|
||||
|
||||
|
||||
class MelCLI():
|
||||
|
@ -606,11 +657,12 @@ class MelCLI():
|
|||
|
||||
for message in messages:
|
||||
query_str = f'tag:tuid{message}'
|
||||
nb_msgs = self.engine.apply_msgs(
|
||||
query_str, action, write=write, close_db=False)
|
||||
nb_msgs = self.engine.apply_msgs(query_str, action,
|
||||
write=write, close_db=False)
|
||||
if nb_msgs < 1:
|
||||
self.log.error(
|
||||
"Couldn't execute function for message %s", message)
|
||||
self.engine.close_database()
|
||||
|
||||
def operation_default(self) -> None:
|
||||
"""
|
||||
|
@ -663,7 +715,7 @@ class MelCLI():
|
|||
mbsync_config_file = os.path.expanduser(
|
||||
"~/.config/mbsyncrc") # TODO Better
|
||||
cmd = ["mbsync", "--config", mbsync_config_file, "--all"]
|
||||
subprocess.run(cmd, check=True)
|
||||
subprocess.run(cmd, check=False)
|
||||
|
||||
# Index new mails
|
||||
self.engine.notmuch_new()
|
||||
|
@ -675,12 +727,17 @@ class MelCLI():
|
|||
self.engine.apply_msgs('tag:unprocessed', self.engine.retag_msg,
|
||||
show_progress=True, write=True)
|
||||
|
||||
def operation_list(self) -> None:
|
||||
"""
|
||||
List operation: Print all folders.
|
||||
"""
|
||||
self.output.print_dir_list()
|
||||
|
||||
def operation_debug(self) -> None:
|
||||
"""
|
||||
DEBUG
|
||||
"""
|
||||
from pprint import pprint
|
||||
pprint(self.engine.list_folders())
|
||||
print("UwU")
|
||||
|
||||
def operation_retag(self) -> None:
|
||||
"""
|
||||
|
@ -717,6 +774,10 @@ class MelCLI():
|
|||
|
||||
# list folder [--recurse]
|
||||
# List actions
|
||||
parser_list = subparsers.add_parser(
|
||||
"list", help="List all folders")
|
||||
# parser_list.add_argument('message', nargs='*', help="Messages")
|
||||
parser_list.set_defaults(operation=self.operation_list)
|
||||
|
||||
# flag msg...
|
||||
parser_flag = subparsers.add_parser(
|
||||
|
@ -811,7 +872,9 @@ class MelCLI():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
colorama.init()
|
||||
if not os.environ.get("MEL_DEBUG"):
|
||||
CLI = MelCLI()
|
||||
else:
|
||||
try:
|
||||
CLI = MelCLI()
|
||||
except:
|
||||
|
|
32
config/scripts/music_remove_dashes
Executable file
32
config/scripts/music_remove_dashes
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Small script to convert music files in the form:
|
||||
$(tracknumber) - $(title).$(ext)
|
||||
to the form
|
||||
$(tracknumber) $(title).$(ext)
|
||||
(note the absence of dash)
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""
|
||||
Function that executes the script.
|
||||
"""
|
||||
for root, _, files in os.walk('.'):
|
||||
for filename in files:
|
||||
match = re.match(r'^(\d+) - (.+)$', filename)
|
||||
if not match:
|
||||
continue
|
||||
new_filename = f"{match[1]} {match[2]}"
|
||||
old_path = os.path.join(root, filename)
|
||||
new_path = os.path.join(root, new_filename)
|
||||
print(old_path, '->', new_path)
|
||||
os.rename(old_path, new_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -146,6 +146,23 @@ do
|
|||
|
||||
done <<< "$(find "$dir/" -type f -iname "*.png")"
|
||||
|
||||
# FLAC (requires reflac)
|
||||
while read music
|
||||
do
|
||||
if [ -z "$music" ]; then continue; fi
|
||||
echo Processing $music
|
||||
|
||||
temp_dir=$(mktemp --directory)
|
||||
temp="$temp_dir/to_optimize.flac"
|
||||
cp "$music" "$temp"
|
||||
reflac --best "$temp_dir"
|
||||
echo "→ Optimize done"
|
||||
|
||||
replace "$temp" "$music"
|
||||
rm -rf "$temp_dir"
|
||||
|
||||
done <<< "$(find "$dir/" -type f -iname "*.flac")"
|
||||
|
||||
# # SVG (requires scour)
|
||||
# while read image
|
||||
# do
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
# Normalisation is done at the default of each program,
|
||||
# which is usually -89.0 dB
|
||||
|
||||
# TODO The simplifications/fixes I've done makes it consider
|
||||
# multi-discs albums as multiple albums
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
|
|
@ -152,6 +152,7 @@ then
|
|||
i notmuch # Index mail
|
||||
i neomutt || i mutt # CLI mail client
|
||||
i lynx # CLI web browser (for HTML mail)
|
||||
i tiv # CLI image viewer
|
||||
if $INSTALL_GUI
|
||||
then
|
||||
i thunderbird # GUI mail client (just in case)
|
||||
|
@ -175,6 +176,7 @@ then
|
|||
i duperemove # Find and merge dupplicates on BTRFS partitions
|
||||
i optipng # Optimize PNG files
|
||||
i jpegtran libjpeg-turbo # Compress JPEG files
|
||||
i reflac # Recompress FLAC files
|
||||
fi
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue