[Mailman-Users] Patch: list_members/sync_members and nomail/hide options

Brent J. Nordquist brent-nordquist at bethel.edu
Tue Sep 4 00:30:54 CEST 2001


Inspired by nomail.py by Dan Mick <dmick at utopia.West.Sun.COM>, I've made
some patches to 2.0.6 (attached) that allow list_members and sync_members
to deal with the "nomail" and "hide" options (using a new -N flag).  This
is to allow me to add multiple addresses for each user, with all but one
set to "nomail+hide", to solve the only-members-can-post problem.

I want to emphasise that this is pretty much a hack.  I've been coding in
Python for (/me checks watch) about 2 1/2 hours now <grin> but this seems
to do what I needed, so I'm offering it in case others find it helpful.

-- 
Brent J. Nordquist <brent-nordquist at bethel.edu> N0BJN
Yahoo!: Brent_Nordquist / AIM: BrentJNordquist / ICQ: 76158942
-------------- next part --------------
--- list_members.orig	Mon Sep  3 17:23:59 2001
+++ list_members	Mon Sep  3 15:53:44 2001
@@ -18,7 +18,7 @@
 
 """List all the members of a mailing list.
 
-Usage: %(program)s [-o file] [-r] [-d] [-p] [-h] listname
+Usage: %(program)s [-o file] [-r] [-d] [-p] [-N] [-h] listname
 
 Where:
 
@@ -39,6 +39,11 @@
         Output member addresses case preserved the way they were added to the
         list.  Otherwise, addresses are printed in all lowercase.
 
+    --nomail
+    -N
+        After each email address, also print the status of the nomail and
+        hide options (in that order).
+
     --help
     -h
         Print this help message and exit.
@@ -58,6 +63,7 @@
 import paths
 from Mailman import MailList
 from Mailman import Errors
+from Mailman import mm_cfg
 
 program = sys.argv[0]
 
@@ -73,8 +79,8 @@
     try:
         opts, args = getopt.getopt(
             sys.argv[1:],
-            'dpro:h',
-            ['digest', 'regular', 'preserve', 'output=', 'help'])
+            'dpro:Nh',
+            ['digest', 'regular', 'preserve', 'output=', 'nomail', 'help'])
     except getopt.error, msg:
         usage(1, msg)
 
@@ -82,6 +88,7 @@
         usage(1)
 
     listname = string.lower(args[0])
+    nomail = None
     outfile = None
     regular = None
     digest = None
@@ -90,6 +97,8 @@
     for opt, arg in opts:
         if opt in ('-h', '--help'):
             usage(0)
+        elif opt in ('-N', '--nomail'):
+            nomail = 1
         elif opt in ('-o', '--output'):
             outfile = arg
         elif opt in ('-r', '--regular'):
@@ -129,10 +138,46 @@
         sys.stdout = fp
         if regular:
             for addr in rmembers:
-                print addr
+                print addr,
+                if nomail:
+                    try:
+                        if mlist.user_options[addr] & mm_cfg.DisableDelivery:
+                            print "yes",
+                        else:
+                            print "no",
+                    except:
+                        print "no",
+                        pass
+                    try:
+                        if mlist.user_options[addr] & mm_cfg.ConcealSubscription:
+                            print "yes",
+                        else:
+                            print "no",
+                    except:
+                        print "no",
+                        pass
+                print
         if digest:
             for addr in dmembers:
-                print addr
+                print addr,
+                if nomail:
+                    try:
+                        if mlist.user_options[addr] & mm_cfg.DisableDelivery:
+                            print "yes",
+                        else:
+                            print "no",
+                    except:
+                        print "no",
+                        pass
+                    try:
+                        if mlist.user_options[addr] & mm_cfg.ConcealSubscription:
+                            print "yes",
+                        else:
+                            print "no",
+                    except:
+                        print "no",
+                        pass
+                print
     finally:
         sys.stdout = stdout
 
-------------- next part --------------
#! /usr/bin/env python
#
# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""List all the members of a mailing list.

Usage: %(program)s [-o file] [-r] [-d] [-p] [-N] [-h] listname

Where:

    --output file
    -o file
        Write output to specified file instead of standard out.

    --regular
    -r
        Print just the regular (non-digest) members.

    --digest
    -d
        Print just the digest members.

    --preserve
    -p
        Output member addresses case preserved the way they were added to the
        list.  Otherwise, addresses are printed in all lowercase.

    --nomail
    -N
        After each email address, also print the status of the nomail and
        hide options (in that order).

    --help
    -h
        Print this help message and exit.

    listname is the name of the mailing list to use.

Note that if neither -r or -d is supplied, both regular members are printed
first, followed by digest members, but no indication is given as to address
status.

"""

import sys
import string
import getopt

import paths
from Mailman import MailList
from Mailman import Errors
from Mailman import mm_cfg

program = sys.argv[0]

def usage(status, msg=''):
    print __doc__ % globals()
    if msg:
        print msg
    sys.exit(status)



def main():
    try:
        opts, args = getopt.getopt(
            sys.argv[1:],
            'dpro:Nh',
            ['digest', 'regular', 'preserve', 'output=', 'nomail', 'help'])
    except getopt.error, msg:
        usage(1, msg)

    if len(args) <> 1:
        usage(1)

    listname = string.lower(args[0])
    nomail = None
    outfile = None
    regular = None
    digest = None
    preserve = None

    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage(0)
        elif opt in ('-N', '--nomail'):
            nomail = 1
        elif opt in ('-o', '--output'):
            outfile = arg
        elif opt in ('-r', '--regular'):
            regular = 1
        elif opt in ('-d', '--digest'):
            digest = 1
        elif opt in ('-p', '--preserve'):
            preserve = 1

    if regular is None and digest is None:
        regular = digest = 1

    if outfile:
        try:
            fp = open(outfile, 'w')
        except IOError:
            print 'Could not open file for writing:', outfile
            sys.exit(1)
    else:
        fp = sys.stdout

    try:
        mlist = MailList.MailList(listname, lock=0)
    except Errors.MMListError, e:
        print 'No such list "%s"\n%s' % (listname, e)
        sys.exit(1)

    if preserve:
        rmembers = mlist.GetDeliveryMembers()
        dmembers = mlist.GetDigestDeliveryMembers()
    else:
        rmembers = mlist.GetMembers()
        dmembers = mlist.GetDigestMembers()

    stdout = sys.stdout
    try:
        sys.stdout = fp
        if regular:
            for addr in rmembers:
                print addr,
                if nomail:
                    try:
                        if mlist.user_options[addr] & mm_cfg.DisableDelivery:
                            print "yes",
                        else:
                            print "no",
                    except:
                        print "no",
                        pass
                    try:
                        if mlist.user_options[addr] & mm_cfg.ConcealSubscription:
                            print "yes",
                        else:
                            print "no",
                    except:
                        print "no",
                        pass
                print
        if digest:
            for addr in dmembers:
                print addr,
                if nomail:
                    try:
                        if mlist.user_options[addr] & mm_cfg.DisableDelivery:
                            print "yes",
                        else:
                            print "no",
                    except:
                        print "no",
                        pass
                    try:
                        if mlist.user_options[addr] & mm_cfg.ConcealSubscription:
                            print "yes",
                        else:
                            print "no",
                    except:
                        print "no",
                        pass
                print
    finally:
        sys.stdout = stdout


if __name__ == '__main__':
    main()
-------------- next part --------------
--- sync_members.orig	Mon Sep  3 17:23:55 2001
+++ sync_members	Mon Sep  3 17:15:59 2001
@@ -61,6 +61,13 @@
         against.  Email addresses must appear one per line.  If filename is
         `-' then stdin is used.
 
+    --nomail
+    -N
+        Expect two fields following each email address, each containing
+        `yes' or `no' to reflect the status of the nomail and hide options
+        (in that order), as produced by `list_members -N'.  These fields
+        are used ONLY IF ADDING the address; otherwise they are ignored.
+
     --help
     -h
         Print this message.
@@ -76,6 +83,7 @@
 from Mailman import MailList
 from Mailman import Errors
 from Mailman import Utils
+from Mailman import mm_cfg
 
 
 
@@ -116,6 +124,9 @@
     filename = None
     listname = None
     notifyadmin = None
+    nomail = None
+    nomailopt = {}
+    hideopt = {}
 
     i = 1
     while i < len(sys.argv):
@@ -152,6 +163,9 @@
         elif startswith(opt, '-a=') or startswith(opt, '--notifyadmin='):
             notifyadmin = yesno(opt)
             i = i + 1
+        elif opt in ('-N', '--nomail'):
+            nomail = 1
+            i = i + 1
         elif opt[0] == '-':
             usage(1, 'Illegal option: ' + opt)
         else:
@@ -186,7 +200,15 @@
             del filemembers[i]
             print 'Ignore  :  %30s' % addr
 
-    # first filter out any invalid addresses
+    # if in --nomail mode, split those fields out into separate lists
+    if nomail:
+        for i in range(len(filemembers)-1, -1, -1):
+            fields = string.split(filemembers[i])
+            filemembers[i] = fields[0]
+            nomailopt[fields[0]] = fields[1]
+            hideopt[fields[0]] = fields[2]
+
+    # filter out any invalid addresses
     filemembers = Utils.ParseAddrs(filemembers)
     invalid = 0
     for addr in filemembers:
@@ -239,6 +261,17 @@
                     mlist.ApprovedAddMember(addr, pw, digest,
                                             welcome, notifyadmin)
                 print 'Added  : %30s (%30s)' % (laddr, addr)
+                # if in --nomail mode, hack the account flags
+                # (clone_member doesn't inspire us to use SetUserOption())
+                if nomail:
+                    if yesno(nomailopt[addr]):
+                        if not dryrun:
+                            mlist.user_options[addr] = mlist.user_options[addr] | mm_cfg.DisableDelivery;
+                        print 'NoMail : %30s (%30s)' % (laddr, addr)
+                    if yesno(hideopt[addr]):
+                        if not dryrun:
+                            mlist.user_options[addr] = mlist.user_options[addr] | mm_cfg.ConcealSubscription;
+                        print 'Hide   : %30s (%30s)' % (laddr, addr)
             except Errors.MMAlreadyAMember:
                 pass
 
-------------- next part --------------
#! /usr/bin/env python
#
# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software 
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

"""Synchronize a mailing list's membership with a flat file.

This script is useful if you have a Mailman mailing list and a sendmail
:include: style list of addresses (also as is used in Majordomo).  For every
address in the file that does not appear in the mailing list, the address is
added.  For every address in the mailing list that does not appear in the
file, the address is removed.  Other options control what happens when an
address is added or removed.

Usage: %(program)s [options] -f file listname

Where `options' are:

    --no-change
    -n
        Don't actually make the changes.  Instead, print out what would be
        done to the list.

    --welcome-msg[=<yes|no>]
    -w[=<yes|no>]
        Sets whether or not to send the newly added members a welcome
        message, overriding whatever the list's `send_welcome_msg' setting
        is.  With -w=yes or -w, the welcome message is sent.  With -w=no, no
        message is sent.

    --digest[=<yes|no>]
    -d[=<yes|no>]
        Selects whether to make newly added members receive messages in
        digests.  With -d=yes or -d, they become digest members.  With -d=no
        (or if no -d option given) they are added as regular members.

    --notifyadmin[=<yes|no>]
    --a[=<yes|no>]
        Specifies whether the admin should be notified for each subscription
        or unsubscription.  If you're adding a lot of addresses, you
        definitely want to turn this off!  With -a=yes or -a, the admin is
        notified.  With -a=no, the admin is not notified.  With no -a option,
        the default for the list is used.

    --file <filename | ->
    -f <filename | ->
        This option is required.  It specifies the flat file to synchronize
        against.  Email addresses must appear one per line.  If filename is
        `-' then stdin is used.

    --nomail
    -N
        Expect two fields following each email address, each containing
        `yes' or `no' to reflect the status of the nomail and hide options
        (in that order), as produced by `list_members -N'.  These fields
        are used ONLY IF ADDING the address; otherwise they are ignored.

    --help
    -h
        Print this message.

    listname
        Required.  This specifies the list to synchronize.
"""

import sys
import string

import paths
from Mailman import MailList
from Mailman import Errors
from Mailman import Utils
from Mailman import mm_cfg



program = sys.argv[0]

def usage(status, msg=''):
    print __doc__ % globals()
    if msg:
        print msg
    sys.exit(status)



def startswith(s, prefix):
    return s[:len(prefix)] == prefix

def endswith(s, suffix):
    return s[-len(suffix):] == suffix



def yesno(opt):
    i = string.find(opt, '=')
    yesno = string.lower(opt[i+1:])
    if yesno in ('y', 'yes'):
        return 1
    elif yesno in ('n', 'no'):
        return 0
    else:
        usage(1, 'Bad choice: ' + yesno)
        # no return


def main():
    dryrun = 0
    digest = 0
    welcome = None
    filename = None
    listname = None
    notifyadmin = None
    nomail = None
    nomailopt = {}
    hideopt = {}

    i = 1
    while i < len(sys.argv):
        opt = sys.argv[i]
        if opt in ('-h', '--help'):
            usage(0)
        elif opt in ('-n', '--no-change'):
            dryrun = 1
            i = i + 1
            print 'Dry run mode'
        elif opt in ('-d', '--digest'):
            digest = 1
            i = i + 1
        elif startswith(opt, '-d=') or startswith(opt, '--digest='):
            digest = yesno(opt)
            i = i + 1
        elif opt in ('-w', '--welcome-msg'):
            welcome = 1
            i = i + 1
        elif startswith(opt, '-w=') or startswith(opt, '--welcome-msg='):
            welcome = yesno(opt)
            i = i + 1
        elif opt in ('-f', '--file'):
            if filename is not None:
                usage(1, 'Only one -f switch allowed')
            try:
                filename = sys.argv[i+1]
            except IndexError:
                usage(1, 'No argument to -f given')
            i = i + 2
        elif opt in ('-a', '--notifyadmin'):
            notifyadmin = 1
            i = i + 1
        elif startswith(opt, '-a=') or startswith(opt, '--notifyadmin='):
            notifyadmin = yesno(opt)
            i = i + 1
        elif opt in ('-N', '--nomail'):
            nomail = 1
            i = i + 1
        elif opt[0] == '-':
            usage(1, 'Illegal option: ' + opt)
        else:
            try:
                listname = string.lower(sys.argv[i])
                i = i + 1
            except IndexError:
                usage(1, 'No listname given')
            break
        
    if listname is None or filename is None:
        usage(1, 'Must have a listname and a filename')

    # read the list of addresses to sync to from the file
    if filename == '-':
        filemembers = sys.stdin.readlines()
    else:
        try:
            fp = open(filename)
        except IOError, (code, msg):
            usage(1, 'Cannot read address file: %s: %s' % (filename, msg))
        try:
            filemembers = fp.readlines()
        finally:
            fp.close()

    # strip out lines we don't care about, they are comments (# in first
    # non-whitespace) or are blank
    for i in range(len(filemembers)-1, -1, -1):
        addr = string.strip(filemembers[i])
        if addr == '' or addr[:1] == '#':
            del filemembers[i]
            print 'Ignore  :  %30s' % addr

    # if in --nomail mode, split those fields out into separate lists
    if nomail:
        for i in range(len(filemembers)-1, -1, -1):
            fields = string.split(filemembers[i])
            filemembers[i] = fields[0]
            nomailopt[fields[0]] = fields[1]
            hideopt[fields[0]] = fields[2]

    # filter out any invalid addresses
    filemembers = Utils.ParseAddrs(filemembers)
    invalid = 0
    for addr in filemembers:
        try:
            Utils.ValidateEmail(addr)
        except Errors.EmailAddressError:
            print 'Invalid :  %30s' % addr
            invalid = 1
    if invalid:
        print 'You must fix the preceding invalid addresses first.'
        sys.exit(1)

    # get the locked list object
    try:
        mlist = MailList.MailList(listname)
    except Errors.MMListError, e:
        print 'No such list "%s"\n%s' % (listname, e)
        sys.exit(1)

    try:
        # get the list of addresses currently subscribed
        addrs = {}
        needsadding = {}
        for addr in (mlist.GetDeliveryMembers() +
                     mlist.GetDigestDeliveryMembers()):
            addrs[string.lower(addr)] = addr

        for addr in filemembers:
            # any address found in the file that is also in the list can be
            # ignored.  if not found in the list, it must be added later
            laddr = string.lower(addr)
            if addrs.has_key(laddr):
                del addrs[laddr]
            else:
                needsadding[laddr] = addr

        if not needsadding and not addrs:
            print 'Nothing to do.'
            sys.exit(0)

        # addrs contains now all the addresses that need removing
        for laddr, addr in needsadding.items():
            pw = Utils.MakeRandomPassword()
            # should not already be subscribed, otherwise our test above is
            # broken.  Bogosity is if the address is listed in the file more
            # than once.  Second and subsequent ones trigger an
            # MMAlreadyAMember error.  Just catch it and go on.
            try:
                if not dryrun:
                    mlist.ApprovedAddMember(addr, pw, digest,
                                            welcome, notifyadmin)
                print 'Added  : %30s (%30s)' % (laddr, addr)
                # if in --nomail mode, hack the account flags
                # (clone_member doesn't inspire us to use SetUserOption())
                if nomail:
                    if yesno(nomailopt[addr]):
                        if not dryrun:
                            mlist.user_options[addr] = mlist.user_options[addr] | mm_cfg.DisableDelivery;
                        print 'NoMail : %30s (%30s)' % (laddr, addr)
                    if yesno(hideopt[addr]):
                        if not dryrun:
                            mlist.user_options[addr] = mlist.user_options[addr] | mm_cfg.ConcealSubscription;
                        print 'Hide   : %30s (%30s)' % (laddr, addr)
            except Errors.MMAlreadyAMember:
                pass

        for laddr, addr in addrs.items():
            # should be a member, otherwise our test above is broken
            if not dryrun:
                mlist.DeleteMember(addr, admin_notif=notifyadmin)
            print 'Removed: %30s (%30s)' % (laddr, addr)

        mlist.Save()
    finally:
        mlist.Unlock()


if __name__ == '__main__':
    main()


More information about the Mailman-Users mailing list