On Sat, May 17, 2025 at 04:39:07PM +0200, Sébastien Hinderer wrote:
> Hello Chris, thanks a lot for your feedback!
> 
> Chris Green (2025/05/17 14:39 +0100):
> > On Sat, May 17, 2025 at 12:49:16PM +0200, Sébastien Hinderer wrote:
> > > For instance here is the configuration I have for this list:
> > > 
> > > subscribe mutt-users@mutt.org
> > > fcc-hook '~t mutt-users@mutt.org|~c mutt-users@mutt.org' 
> > > '=list/mutt-users/'
> > > save-hook '~t mutt-users@mutt.org|~c mutt-users@mutt.org' 
> > > '=list/mutt-users/'
> > > 
> > > It's definitely not unbearable, but each time I add a mailing list and
> > > duplicate such lines I feel a bit sad about the redundancy, to the point
> > > that I regularly find myself thinking that one day I will write a
> > > generator to generate my Mutt configuration from a less verbose format.
> > > 
> > Yes. What I do is have a configuration file in which I have all my
> > lists and this is used to autogenerate the required subscribe and
> > other mutt configuration.
> 
> Yes it's what I said I intended to do. You may want to share you
> rscript!
> 
I think I have done before but see attached files anyway.

The mutt configuration works using two very simple Python scripts as
follows (I've attached these two scripts as well) :-

    #
    #
    # Mailing lists, see ~/.mutt/filter and ~/mutt/bin/filter.py for details.
    #
    lists `~/.mutt/bin/getLists.py`
    subscribe `~/.mutt/bin/getLists.py`
    #
    #
    # getAliases.py gets aliases for mailing lists from the filter file
    #
    source ~/.mutt/bin/getAliases.py|

Here's a section of the file 'filter' which is what I change to add or
remove mailing lists (and some other things):-

    letsencrypt     Li  list-id         help.community.letsencrypt.org
    mercurial       Li  list-post       mercur...@mercurial-scm.org
    mercurial       Li  list-post       mercur...@lists.mercurial-scm.org
    mopidy          Li  list-id         q&a.discourse.mopidy.com
    mutt            Li  list-post       mutt-users@mutt.org
    netman          Li  list-post       networkmana...@lists.freedesktop.org
    owfs            Li  list-post       owfs-develop...@lists.sourceforge.net   
Owfs-developers
    openLibrary     Li  list-post       ol-disc...@archive.org
    photini         Li  list-post       phot...@googlegroups.com
    postfix         Li  list-post       postfix-us...@postfix.org               
pfx
    quodlibet       Li  list-post       quod-libet-developm...@googlegroups.com


> > The same file is used by a Python script
> > that routes incoming list mail to separate mailboxes.
> 
> Which is the part I would not keep, to save me from not reading most of
> my e-mails ahah. :) But the script could also generate procmail rules...
> 
My Python script does what procmail does, no need for procmail as well.

> Best wishes,
> 
> Seb.
> 

-- 
Chris Green
#!/usr/bin/python3
#
#
# license    Apache v2 (http://www.apache.org/licenses/LICENSE-2.0) 
# author     Chris Green - ch...@isbd.co.uk
# 
#
#
# Mail filtering script
#
import mailbox
import os
import sys
import time
import mailLib
import shlex
#
#
# Redirect any exceptions to a file
#
sys.stderr = open("/home/chris/tmp/mail.err", 'a')
#
#
# Some constants (i.e. configuration)
#
home = "/home/chris"
logfile = home + "/tmp/mail.log"
filtfile = home + "/.mutt/filter"
mldir = home + "/mail/"
indir = mldir + "In/"
#
#
# Set to log to mail.log in ~/tmp with name 'filter' and the envelope/from
#
log = mailLib.initLog("filter")
#
#
# Initialise destination mailbox name to its default "In/default"
#
dest = indir + "default"
#
#
# Read the message from standard input and make a message object from it
#
msg = mailbox.MaildirMessage(sys.stdin.buffer.read())
#
#
# See if there's a match in our filter file
#
f = open(filtfile, 'r')
for ln in f:                    # for each line in filter
    if ln[0] == '#':            # ignore comments
        continue
    #
    #
    # split the line into fields, shlex.split() does quoted strings
    #
    fld = shlex.split(ln)
    #
    #
    # copy the fields into better named variables
    #
    nm = fld[0]                 # name/alias
    destdir = fld[1] + "/"      # the destination directory
    header = fld[2]             # the header to find the match in
    address = fld[3].lower()    # list address to match
    if len(fld) == 5:           # if there's a 5th field
        sbstrip = '[' + fld[4] + ']'        # string to strip out of subject
        dbasbstrip = '(' + fld[4] + ')'     # string to strip from DBA subject
    #
    #
    # Does the address in the header match this entry
    #
    if (address in str(msg.get(header, "unknown").lower())):
        #
        #
        # set the destination directory
        #
        if nm[0:3] == "dba":
            #
            #
            # For some reason when the Subject: has accented characters in it the returned
            # utf-8 string has spaces replaced by underscores, so we need to change them back
            #
            subject = msg.get("subject", "unknown").replace("_", " ")
            #
            #
            # For DBA messages strip out the (<forum section>) and put the
            # destination in From: and change To: so we can get the DBA alias when replying
            #
            if len(fld) == 5 and dbasbstrip in subject:
                msg.replace_header("Subject", str(subject.replace(dbasbstrip, '')))
                dest = mldir + destdir + nm
                msg.replace_header("From", dest)
                msg.replace_header("To", "barges.org")
                break   # match found so stop searching
        else:
            dest = mldir + destdir + nm
            #
            #
            # Strip out list name (4th field) from subject if it's there
            #
            if len(fld) == 5 and sbstrip in str(msg.get("subject", "unknown")):
                msg.replace_header("Subject", str(msg.get("subject").replace(sbstrip, '')))
            break       # match found so stop searching
        #
        #
        # match not found so continue the for loop
        #
#
#
# deliver the message
#
mailLib.spamFilter(msg)
mailLib.smsFilter(msg)
mailLib.deliverMdMsg(dest, msg, log)
#!/usr/bin/python3
#
#
# license    Apache v2 (http://www.apache.org/licenses/LICENSE-2.0) 
# author     Chris Green - ch...@isbd.co.uk
# 
#
#
# Get mailing list addresses from filter file to provide mutt aliases
#
import sys
#
#
# Set directories and filenames
#
home = "/home/chris"
filtfile = home + "/.mutt/filter"
#
#
# Read from 'filter' line by line
#
f = open(filtfile, 'r')
for ln in f:
    if ln[0] == '#':            # ignore comments
        continue
    #
    #
    # split the line into fields
    #
    fld = ln.split()
    #
    #
    # Only write aliases for mailing lists
    #
    if ((fld[1] == "Li") or (fld[1] == "Gm")):
        sys.stdout.write("alias ")
        sys.stdout.write(fld[0] + " ")
        #
        #
        # List-Id matches need the list name converted to an E-Mail address
        #
        if (fld[2].lower() == "list-id"):
            sys.stdout.write(fld[3].replace(".", "@", 1) + "\n")
        else:
            sys.stdout.write(fld[3] + "\n")
#!/usr/bin/python3
#
#
# license    Apache v2 (http://www.apache.org/licenses/LICENSE-2.0) 
# author     Chris Green - ch...@isbd.co.uk
# 
#
#
# Get mailing lists from filter for mutt 'lists' command
#
import sys
#
#
# Set directory and filename
#
home = "/home/chris"
filtfile = home + "/.mutt/filter"
#
#
# Read filter line by line
#
f = open(filtfile, 'r')
for ln in f:
    if ln[0] == '#':            # ignore comments
        continue
    #
    #
    # split the line into fields
    #
    fld = ln.split()
    #
    #
    # output the address if 'Li' or 'Gm' in second column
    #
    if ((fld[1] == "Li") or (fld[1] == "Gm")):
        #
        #
        # List-Id matches need the list name converted to an E-Mail address
        #
        if (fld[2].lower() == "list-id"):
            sys.stdout.write(fld[3].replace(".", "@", 1) + " ")
        else:
            sys.stdout.write(fld[3] + " ")
#
#
# license    Apache v2 (http://www.apache.org/licenses/LICENSE-2.0) 
# author     Chris Green - ch...@isbd.co.uk
# 
import mailbox
import logging
import logging.handlers
import os
import time

from email.header import decode_header
from datetime import datetime
#
#
# log a message
#
def initLog(name):
    log = logging.getLogger(name)
    log.setLevel(logging.DEBUG)
    f = logging.handlers.RotatingFileHandler("/home/chris/tmp/mail.log", 'a', 1000000, 4)
    f.setLevel(logging.DEBUG)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    f.setFormatter(formatter)
    log.addHandler(f)
    return log
#
#
# Deliver a message to a local maildir
#
def deliverMdMsg(dest, msg, log):
    #
    #
    # Create the destination maildir instance
    #
    md = mailbox.Maildir(dest, factory=None)

    log.info("\n  From: " + str(msg.get("From", "unknown")) + "\n  To:" + dest + "\n")
    #
    #
    # Put the incoming message in the appropriate maildir
    # No need to lock, it's a maildir
    #
    try:
        md.add(msg)
    except Exception as e:
        log.info("Failed to store message:" + str(e))

    return
#
#
# Get a message header as a string
#
def getHdr(msg, header):
   hdr = str(msg.get(header, "empty"))
   res = decode_header(hdr)[0][0]
   return str("\n  " + header + ": " + str(res))
#
#
# Spam filter, initially just for junk in BBB list
#
def spamFilter(msg):
    if "@gmail.com" in msg.get("from", "unknown"):
        if msg.get("to", "unknown") == "BeagleBone <beagleb...@googlegroups.com>":
            dt = datetime.now().isoformat(timespec='minutes')
            f = open("/home/chris/tmp/spam.log")
            f.write(dt + msg.get("subject") + " from " + msg.get("from"))
            f.close()
#
#
# Change the From: address on SMS->E-Mail messages from my AandA VOIP
#
def smsFilter(msg):
    if "s...@aa.net.uk" in msg.get("from", "unknown"):
        if "+447906626760" in msg.get("from", "unknown"):
            msg.replace_header("From", "SMS from Maxine <max...@isbd.co.uk>");
        elif "+447906226675" in msg.get("from", "unknown"):
            msg.replace_header("From", "SMS from Chris <ch...@isbd.co.uk>");
        elif "+447537170394" in msg.get("from", "unknown"):
            msg.replace_header("From", "SMS from Chris <ch...@isbd.co.uk>");
        elif "+447780705062" in msg.get("from", "unknown"):
            msg.replace_header("From", "SMS from Zelma <ze...@isbd.co.uk>");

Reply via email to