Only sort-mails on hera
This commit is contained in:
parent
5aca029c57
commit
8802d17cc9
|
@ -142,6 +142,7 @@ in
|
||||||
hera.default = makeConfig "hera" (on-my-machines ++ [
|
hera.default = makeConfig "hera" (on-my-machines ++ [
|
||||||
./roles/fetch-banking-timer.nix
|
./roles/fetch-banking-timer.nix
|
||||||
./roles/weechat
|
./roles/weechat
|
||||||
|
./roles/mail-sort.nix
|
||||||
./roles/mail2rss.nix
|
./roles/mail2rss.nix
|
||||||
./roles/headless-mpd.nix
|
./roles/headless-mpd.nix
|
||||||
./roles/headless.nix
|
./roles/headless.nix
|
||||||
|
|
105
home-manager/roles/mail-sort.nix
Normal file
105
home-manager/roles/mail-sort.nix
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
{ pkgs, lib, config, ... }:
|
||||||
|
let
|
||||||
|
quick-sync = pkgs.writeShellScript "quick-mail-and-todo-sync" ''
|
||||||
|
${pkgs.isync}/bin/mbsync hera:INBOX,Code,Move/todo
|
||||||
|
${pkgs.fd}/bin/fd -tf . ${maildir}/hera/Move/todo | ${pkgs.mblaze}/bin/mscan -f "E-Mail from %f: %S" | xargs -I '{}' ${pkgs.taskwarrior}/bin/task add '"{}"'
|
||||||
|
${pkgs.mblaze}/bin/mlist ${maildir}/hera/Move/todo | ${pkgs.mblaze}/bin/mflag -S
|
||||||
|
${pkgs.mblaze}/bin/mlist ${maildir}/hera/Move/todo | ${pkgs.mblaze}/bin/mrefile ${unsorted}
|
||||||
|
${pkgs.notmuch}/bin/notmuch new
|
||||||
|
'';
|
||||||
|
lists = pkgs.privateValue { sortLists = [ ]; stupidLists = [ ]; notifications = [ ]; } "mail/filters";
|
||||||
|
maildir = config.accounts.email.maildirBasePath;
|
||||||
|
# mhdr -h List-ID -d Maildir/hera/Archiv/unsortiert | sort | sed 's/^.*<\(.*\)>$/\1/' | uniq | xargs -I '{}' sh -c "notmuch count List:{} | sed 's/$/: {}/'" | sort
|
||||||
|
# To find candidates
|
||||||
|
archiveSuffix = "hera/Archiv";
|
||||||
|
unsortedSuffix = "${archiveSuffix}/unsortiert";
|
||||||
|
unsorted = "${maildir}/${unsortedSuffix}";
|
||||||
|
archive = "${maildir}/${archiveSuffix}";
|
||||||
|
filter = rec {
|
||||||
|
mailToFolder = name:
|
||||||
|
toFolder (lib.concatStringsSep "." (lib.splitString "@" name));
|
||||||
|
toFolder = name:
|
||||||
|
lib.concatStringsSep "/" (lib.reverseList (lib.splitString "." name));
|
||||||
|
simple = filter: target: { inherit filter target; };
|
||||||
|
notifications = notify:
|
||||||
|
simple "from:${notify}" "notifications/${mailToFolder notify}";
|
||||||
|
stupidList = list: simple "to:${list}" "list/${mailToFolder list}";
|
||||||
|
simpleSortList = listName:
|
||||||
|
simple "List:${listName}" "list/${toFolder listName}";
|
||||||
|
};
|
||||||
|
myFilters = builtins.map filter.simpleSortList lists.sortLists
|
||||||
|
++ builtins.map filter.stupidList lists.stupidLists
|
||||||
|
++ builtins.map filter.notifications lists.notifications;
|
||||||
|
sortMail = pkgs.writeHaskellScript
|
||||||
|
{
|
||||||
|
name = "sort-mail-archive";
|
||||||
|
bins = [ pkgs.notmuch pkgs.coreutils pkgs.mblaze pkgs.findutils ];
|
||||||
|
imports = [
|
||||||
|
"Text.Megaparsec"
|
||||||
|
"Text.Megaparsec.Char"
|
||||||
|
"Text.Megaparsec.Char.Lexer"
|
||||||
|
"qualified Data.List.NonEmpty as NE"
|
||||||
|
"qualified Data.Text as T"
|
||||||
|
"System.Environment (setEnv)"
|
||||||
|
];
|
||||||
|
} ''
|
||||||
|
reScan = notmuch "new" "--quiet"
|
||||||
|
|
||||||
|
findFilterMail :: (Text,Text) -> IO (Maybe (LByteString, Text, Text))
|
||||||
|
findFilterMail (filter_, target) = do
|
||||||
|
files <- notmuch "search" "--output" "files" (toString filter_) "folder:${unsortedSuffix}" |> capture
|
||||||
|
pure $ if (LBS.length files > 0) then Just (files, filter_, target) else Nothing
|
||||||
|
|
||||||
|
executeFilterMail :: (LByteString, Text, Text) -> IO ()
|
||||||
|
executeFilterMail (files, filter_, target) = do
|
||||||
|
say [i|Sorting "#{filter_}" into #{target}|]
|
||||||
|
writeOutput files |> mscan
|
||||||
|
mmkdir ([i|${archive}/#{target}|] :: String)
|
||||||
|
writeOutput files |> mrefile ([i|${archive}/#{target}|] :: String)
|
||||||
|
|
||||||
|
myFilters :: [(Text,Text)]
|
||||||
|
myFilters = [${
|
||||||
|
lib.concatStringsSep ","
|
||||||
|
(
|
||||||
|
builtins.map ({ filter, target }: ''("${filter}","${target}")'')
|
||||||
|
myFilters
|
||||||
|
)
|
||||||
|
}]
|
||||||
|
|
||||||
|
filtersFromTo :: Text -> Maybe (Text,Text)
|
||||||
|
filtersFromTo = filtersFromField "to" [toToName]
|
||||||
|
toToName :: Text -> Maybe Text
|
||||||
|
toToName (T.splitOn "@" -> [name, "maralorn.de"])
|
||||||
|
| not (T.isInfixOf "randy" name) = Just . ("to/" <>) . T.intercalate "_" . T.splitOn "." $ name
|
||||||
|
toToName _ = Nothing
|
||||||
|
filtersFromField :: Text -> [Text-> Maybe Text] -> Text -> Maybe (Text,Text)
|
||||||
|
filtersFromField field filters text = fmap ([i|#{field}:#{text}|],) . viaNonEmpty Relude.head . mapMaybe ($ text) $ filters
|
||||||
|
filtersFromListIDs :: Text -> Maybe (Text,Text)
|
||||||
|
filtersFromListIDs = filtersFromField "List" [githubNameFolderFromId, gitlabNameFolderFromId]
|
||||||
|
githubNameFolderFromId :: Text -> Maybe Text
|
||||||
|
githubNameFolderFromId (reverse . T.splitOn "." -> ("com":"github":org:name)) = Just [i|github/#{org}/#{T.intercalate "_" $ reverse name}|]
|
||||||
|
githubNameFolderFromId _ = Nothing
|
||||||
|
gitlabNameFolderFromId :: Text -> Maybe Text
|
||||||
|
gitlabNameFolderFromId (reverse . T.splitOn "." -> ("de":"ccc":"darmstadt":"git":org:name1:name)) = Just [i|cda-gitlab/#{org}/#{T.intercalate "_" . toList . Relude.tail $ NE.reverse (name1:|name)}|]
|
||||||
|
gitlabNameFolderFromId _ = Nothing
|
||||||
|
|
||||||
|
type Parser = Parsec Text Text
|
||||||
|
listId :: Parser Text
|
||||||
|
listId = manyTill anySingle (char '<') *> (toText <$> manyTill anySingle (char '>'))
|
||||||
|
|
||||||
|
main = do
|
||||||
|
setEnv "MBLAZE_PAGER" "cat"
|
||||||
|
setEnv "NOTMUCH_CONFIG" "${config.home.sessionVariables.NOTMUCH_CONFIG or ""}"
|
||||||
|
reScan
|
||||||
|
(listIDs,tos) <- concurrently (mhdr "-h" "List-ID" "-d" "${unsorted}" |> capture) (mhdr "-h" "To" "-d" "${unsorted}" "-A" |> capture)
|
||||||
|
let listFilters = mapMaybe filtersFromListIDs . sortNub . mapMaybe (parseMaybe listId) . lines . decodeUtf8 $ listIDs
|
||||||
|
toFilters = mapMaybe filtersFromTo . sortNub . fmap (\x -> maybe x Relude.id $ parseMaybe listId x) . lines . decodeUtf8 $ tos
|
||||||
|
applicableFilters <- catMaybes <$> forConcurrently (listFilters <> myFilters <> toFilters) findFilterMail
|
||||||
|
for_ applicableFilters executeFilterMail
|
||||||
|
reScan
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.mbsync.postExec = "${sortMail}/bin/sort-mail-archive";
|
||||||
|
accounts.email.accounts = lib.mkIf pkgs.withSecrets { hera.imapnotify.onNotify = lib.mkForce "${quick-sync}"; };
|
||||||
|
}
|
|
@ -4,104 +4,11 @@ let
|
||||||
name = "Malte Brandy";
|
name = "Malte Brandy";
|
||||||
mail = "malte.brandy@maralorn.de";
|
mail = "malte.brandy@maralorn.de";
|
||||||
alternates = pkgs.privateValue [ ] "mail/alternates";
|
alternates = pkgs.privateValue [ ] "mail/alternates";
|
||||||
lists = pkgs.privateValue { sortLists = [ ]; stupidLists = [ ]; notifications = [ ]; } "mail/filters";
|
quick-sync = pkgs.writeShellScriptBin "quick-mail-sync" ''
|
||||||
quick-sync = pkgs.writeShellScript "quick-mail-sync" ''
|
${pkgs.isync}/bin/mbsync hera:INBOX,Code
|
||||||
${pkgs.isync}/bin/mbsync hera:INBOX,Code,Move/todo
|
|
||||||
${pkgs.fd}/bin/fd -tf . ${maildir}/hera/Move/todo | ${pkgs.mblaze}/bin/mscan -f "E-Mail from %f: %S" | xargs -I '{}' ${pkgs.taskwarrior}/bin/task add '"{}"'
|
|
||||||
${pkgs.mblaze}/bin/mlist ${maildir}/hera/Move/todo | ${pkgs.mblaze}/bin/mflag -S
|
|
||||||
${pkgs.mblaze}/bin/mlist ${maildir}/hera/Move/todo | ${pkgs.mblaze}/bin/mrefile ${unsorted}
|
|
||||||
${pkgs.notmuch}/bin/notmuch new
|
${pkgs.notmuch}/bin/notmuch new
|
||||||
'';
|
'';
|
||||||
maildir = config.accounts.email.maildirBasePath;
|
maildir = config.accounts.email.maildirBasePath;
|
||||||
# mhdr -h List-ID -d Maildir/hera/Archiv/unsortiert | sort | sed 's/^.*<\(.*\)>$/\1/' | uniq | xargs -I '{}' sh -c "notmuch count List:{} | sed 's/$/: {}/'" | sort
|
|
||||||
# To find candidates
|
|
||||||
archiveSuffix = "hera/Archiv";
|
|
||||||
unsortedSuffix = "${archiveSuffix}/unsortiert";
|
|
||||||
unsorted = "${maildir}/${unsortedSuffix}";
|
|
||||||
archive = "${maildir}/${archiveSuffix}";
|
|
||||||
filter = rec {
|
|
||||||
mailToFolder = name:
|
|
||||||
toFolder (lib.concatStringsSep "." (lib.splitString "@" name));
|
|
||||||
toFolder = name:
|
|
||||||
lib.concatStringsSep "/" (lib.reverseList (lib.splitString "." name));
|
|
||||||
simple = filter: target: { inherit filter target; };
|
|
||||||
notifications = notify:
|
|
||||||
simple "from:${notify}" "notifications/${mailToFolder notify}";
|
|
||||||
stupidList = list: simple "to:${list}" "list/${mailToFolder list}";
|
|
||||||
simpleSortList = listName:
|
|
||||||
simple "List:${listName}" "list/${toFolder listName}";
|
|
||||||
};
|
|
||||||
myFilters = builtins.map filter.simpleSortList lists.sortLists
|
|
||||||
++ builtins.map filter.stupidList lists.stupidLists
|
|
||||||
++ builtins.map filter.notifications lists.notifications;
|
|
||||||
sortMail = pkgs.writeHaskellScript
|
|
||||||
{
|
|
||||||
name = "sort-mail-archive";
|
|
||||||
bins = [ pkgs.notmuch pkgs.coreutils pkgs.mblaze pkgs.findutils ];
|
|
||||||
imports = [
|
|
||||||
"Text.Megaparsec"
|
|
||||||
"Text.Megaparsec.Char"
|
|
||||||
"Text.Megaparsec.Char.Lexer"
|
|
||||||
"qualified Data.List.NonEmpty as NE"
|
|
||||||
"qualified Data.Text as T"
|
|
||||||
"System.Environment (setEnv)"
|
|
||||||
];
|
|
||||||
} ''
|
|
||||||
reScan = notmuch "new" "--quiet"
|
|
||||||
|
|
||||||
findFilterMail :: (Text,Text) -> IO (Maybe (LByteString, Text, Text))
|
|
||||||
findFilterMail (filter_, target) = do
|
|
||||||
files <- notmuch "search" "--output" "files" (toString filter_) "folder:${unsortedSuffix}" |> capture
|
|
||||||
pure $ if (LBS.length files > 0) then Just (files, filter_, target) else Nothing
|
|
||||||
|
|
||||||
executeFilterMail :: (LByteString, Text, Text) -> IO ()
|
|
||||||
executeFilterMail (files, filter_, target) = do
|
|
||||||
say [i|Sorting "#{filter_}" into #{target}|]
|
|
||||||
writeOutput files |> mscan
|
|
||||||
mmkdir ([i|${archive}/#{target}|] :: String)
|
|
||||||
writeOutput files |> mrefile ([i|${archive}/#{target}|] :: String)
|
|
||||||
|
|
||||||
myFilters :: [(Text,Text)]
|
|
||||||
myFilters = [${
|
|
||||||
lib.concatStringsSep ","
|
|
||||||
(
|
|
||||||
builtins.map ({ filter, target }: ''("${filter}","${target}")'')
|
|
||||||
myFilters
|
|
||||||
)
|
|
||||||
}]
|
|
||||||
|
|
||||||
filtersFromTo :: Text -> Maybe (Text,Text)
|
|
||||||
filtersFromTo = filtersFromField "to" [toToName]
|
|
||||||
toToName :: Text -> Maybe Text
|
|
||||||
toToName (T.splitOn "@" -> [name, "maralorn.de"])
|
|
||||||
| not (T.isInfixOf "randy" name) = Just . ("to/" <>) . T.intercalate "_" . T.splitOn "." $ name
|
|
||||||
toToName _ = Nothing
|
|
||||||
filtersFromField :: Text -> [Text-> Maybe Text] -> Text -> Maybe (Text,Text)
|
|
||||||
filtersFromField field filters text = fmap ([i|#{field}:#{text}|],) . viaNonEmpty Relude.head . mapMaybe ($ text) $ filters
|
|
||||||
filtersFromListIDs :: Text -> Maybe (Text,Text)
|
|
||||||
filtersFromListIDs = filtersFromField "List" [githubNameFolderFromId, gitlabNameFolderFromId]
|
|
||||||
githubNameFolderFromId :: Text -> Maybe Text
|
|
||||||
githubNameFolderFromId (reverse . T.splitOn "." -> ("com":"github":org:name)) = Just [i|github/#{org}/#{T.intercalate "_" $ reverse name}|]
|
|
||||||
githubNameFolderFromId _ = Nothing
|
|
||||||
gitlabNameFolderFromId :: Text -> Maybe Text
|
|
||||||
gitlabNameFolderFromId (reverse . T.splitOn "." -> ("de":"ccc":"darmstadt":"git":org:name1:name)) = Just [i|cda-gitlab/#{org}/#{T.intercalate "_" . toList . Relude.tail $ NE.reverse (name1:|name)}|]
|
|
||||||
gitlabNameFolderFromId _ = Nothing
|
|
||||||
|
|
||||||
type Parser = Parsec Text Text
|
|
||||||
listId :: Parser Text
|
|
||||||
listId = manyTill anySingle (char '<') *> (toText <$> manyTill anySingle (char '>'))
|
|
||||||
|
|
||||||
main = do
|
|
||||||
setEnv "MBLAZE_PAGER" "cat"
|
|
||||||
setEnv "NOTMUCH_CONFIG" "${config.home.sessionVariables.NOTMUCH_CONFIG or ""}"
|
|
||||||
reScan
|
|
||||||
(listIDs,tos) <- concurrently (mhdr "-h" "List-ID" "-d" "${unsorted}" |> capture) (mhdr "-h" "To" "-d" "${unsorted}" "-A" |> capture)
|
|
||||||
let listFilters = mapMaybe filtersFromListIDs . sortNub . mapMaybe (parseMaybe listId) . lines . decodeUtf8 $ listIDs
|
|
||||||
toFilters = mapMaybe filtersFromTo . sortNub . fmap (\x -> maybe x Relude.id $ parseMaybe listId x) . lines . decodeUtf8 $ tos
|
|
||||||
applicableFilters <- catMaybes <$> forConcurrently (listFilters <> myFilters <> toFilters) findFilterMail
|
|
||||||
for_ applicableFilters executeFilterMail
|
|
||||||
reScan
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -109,46 +16,12 @@ in
|
||||||
enable = true;
|
enable = true;
|
||||||
frequency = "*:0/30";
|
frequency = "*:0/30";
|
||||||
verbose = false;
|
verbose = false;
|
||||||
postExec = "${sortMail}/bin/sort-mail-archive";
|
|
||||||
};
|
};
|
||||||
systemd.user.timers.mbsync.Timer.RandomizedDelaySec = "10m";
|
systemd.user.timers.mbsync.Timer.RandomizedDelaySec = "10m";
|
||||||
|
|
||||||
accounts.email.accounts = pkgs.privateValue { } "mail/accounts";
|
accounts.email.accounts = pkgs.privateValue { } "mail/accounts";
|
||||||
systemd.user.services =
|
systemd.user.services =
|
||||||
let
|
let
|
||||||
mkService = name: account:
|
|
||||||
let
|
|
||||||
configjs = pkgs.writeText "config.js" ''
|
|
||||||
var child_process = require('child_process');
|
|
||||||
|
|
||||||
function getStdout(cmd) {
|
|
||||||
var stdout = child_process.execSync(cmd);
|
|
||||||
return stdout.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.host = "${account.imap.host}"
|
|
||||||
exports.port = 993
|
|
||||||
exports.tls = true;
|
|
||||||
exports.tlsOptions = { "rejectUnauthorized": false };
|
|
||||||
exports.username = "${account.userName}";
|
|
||||||
exports.password = getStdout("${toString account.passwordCommand}");
|
|
||||||
exports.onNotify = "${quick-sync}"
|
|
||||||
exports.boxes = [ "Inbox", "Code" ];
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
{
|
|
||||||
Unit.Description = "Run imapnotify for imap account ${name}";
|
|
||||||
Service = {
|
|
||||||
ExecStart = "${pkgs.imapnotify}/bin/imapnotify -c ${configjs}";
|
|
||||||
Restart = "always";
|
|
||||||
RestartSec = "1min";
|
|
||||||
};
|
|
||||||
Install.WantedBy = [ "default.target" ];
|
|
||||||
};
|
|
||||||
mkServiceWithName = name: account: {
|
|
||||||
name = "imapnotify-${name}-inbox";
|
|
||||||
value = mkService name account;
|
|
||||||
};
|
|
||||||
hasImapHost = name: account: account.imap != null;
|
hasImapHost = name: account: account.imap != null;
|
||||||
mkWatchService = name: account: {
|
mkWatchService = name: account: {
|
||||||
name = "watch-${name}-maildir";
|
name = "watch-${name}-maildir";
|
||||||
|
@ -166,9 +39,8 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
lib.mapAttrs' mkWatchService
|
lib.mapAttrs' mkWatchService (lib.filterAttrs hasImapHost config.accounts.email.accounts) //
|
||||||
(lib.filterAttrs hasImapHost config.accounts.email.accounts) // lib.mapAttrs' mkServiceWithName
|
{
|
||||||
(lib.filterAttrs hasImapHost config.accounts.email.accounts) // {
|
|
||||||
mbsync.Service = {
|
mbsync.Service = {
|
||||||
Environment = "PATH=${pkgs.coreutils}/bin";
|
Environment = "PATH=${pkgs.coreutils}/bin";
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
|
@ -176,9 +48,10 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
programs.msmtp.enable = true;
|
programs = {
|
||||||
programs.mbsync.enable = true;
|
msmtp.enable = true;
|
||||||
programs.notmuch = {
|
mbsync.enable = true;
|
||||||
|
notmuch = {
|
||||||
enable = config.accounts.email.accounts != { };
|
enable = config.accounts.email.accounts != { };
|
||||||
hooks.postInsert = ''
|
hooks.postInsert = ''
|
||||||
${pkgs.notmuch}/bin/notmuch tag +deleted -- "folder:/Trash/ (not tag:deleted)"
|
${pkgs.notmuch}/bin/notmuch tag +deleted -- "folder:/Trash/ (not tag:deleted)"
|
||||||
|
@ -192,13 +65,13 @@ in
|
||||||
};
|
};
|
||||||
maildir.synchronizeFlags = true;
|
maildir.synchronizeFlags = true;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
home = {
|
home = {
|
||||||
packages = [ sortMail ];
|
packages = [ quick-sync ];
|
||||||
file =
|
file =
|
||||||
let
|
let
|
||||||
mutt_alternates = "@maralorn.de "
|
mutt_alternates = "@maralorn.de " + (builtins.concatStringsSep " " alternates);
|
||||||
+ (builtins.concatStringsSep " " alternates);
|
|
||||||
show-sidebar = pkgs.writeText "show-sidebar" ''
|
show-sidebar = pkgs.writeText "show-sidebar" ''
|
||||||
set sidebar_visible=yes
|
set sidebar_visible=yes
|
||||||
bind index <up> sidebar-prev
|
bind index <up> sidebar-prev
|
||||||
|
|
2
private
2
private
|
@ -1 +1 @@
|
||||||
Subproject commit 0ced3f6ddbd29f4a4a4d43b8751a079209285658
|
Subproject commit e60d0d6c41d564d3d955ea6e0e58f24abd626a6a
|
Loading…
Reference in a new issue