#! @PYTHON@
#
# Copyright (C) 1998-2018 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

# Make sure that the list of email addresses doesn't contain any comments,
# like majordomo may throw in.  For now, you just have to remove them manually.

"""Add members to a list from the command line.

Usage:
    add_members [options] listname

Options:

    --regular-members-file=file
    -r file
        A file containing addresses of the members to be added, one
        address per line.  This list of people become non-digest
        members.  If file is `-', read addresses from stdin.

    --digest-members-file=file
    -d file
        Similar to above, but these people become digest members.

    --invite
    -i
        Specify this if you only want to invite the users to a list
        instead of subscribing them.

    --invite-msg-file=file
    -m file
        This will prepend the message in the file to the invite email that
        gets generated when --invite is set.

    --welcome-msg=<y|n>
    -w <y|n>
        Set whether or not to send the list members a welcome message,
        overriding whatever the list's `send_welcome_msg' setting is.  This
        is ignored and the list's setting at the time of acceptance is used
        if --invite is set.

    --admin-notify=<y|n>
    -a <y|n>
        Set whether or not to send the list administrators a notification on
        the success/failure of these subscriptions, overriding whatever the
        list's `admin_notify_mchanges' setting is.  This is ignored and the
        list's setting at the time of acceptance is used if --invite is set.

    --nomail
    -n
        Set the newly added members mail delivery to disabled by admin.  This
        is ignored if --invite is set.

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

    listname
        The name of the Mailman list you are adding members to.  It must
        already exist.

You must supply at least one of -r and -d options.  At most one of the
files can be `-'.
"""

import sys
import os
import getopt
from cStringIO import StringIO

import paths
# Import this /after/ paths so that the sys.path is properly hacked
from email.Utils import parseaddr

from Mailman import i18n
from Mailman import Utils
from Mailman import mm_cfg
from Mailman import Errors
from Mailman import Message
from Mailman import MailList
from Mailman import MemberAdaptor
from Mailman.UserDesc import UserDesc

_ = i18n._
C_ = i18n.C_



def usage(status, msg=''):
    if status:
        fd = sys.stderr
    else:
        fd = sys.stdout
    print >> fd, C_(__doc__)
    if msg:
        print >> fd, msg
    sys.exit(status)



def readfile(filename):
    if filename == '-':
        fp = sys.stdin
        closep = 0
    else:
        fp = open(filename)
        closep = 1
    # strip all the lines of whitespace and discard blank lines
    lines = filter(None, [line.strip() for line in fp.readlines()])
    if closep:
        fp.close()
    return lines



def readmsgfile(filename):
    lines = open(filename).read()
    return lines



class Tee:
    def __init__(self, outfp):
        self.__outfp = outfp

    def write(self, msg):
        sys.stdout.write(i18n.tolocale(msg))
        self.__outfp.write(msg)



def addall(mlist, members, digest, ack, outfp, nomail, invite, invite_msg):
    tee = Tee(outfp)
    for member in members:
        userdesc = UserDesc()
        userdesc.fullname, userdesc.address = parseaddr(member)
        userdesc.digest = digest

        try:
            if invite:
                # These are needed for an invitation.
                userdesc.password = Utils.MakeRandomPassword()
                userdesc.language = mlist.preferred_language
                # Don't forget the special invite hack.
                userdesc.invitation = mlist.internal_name()
                # InviteNewMember doesn't throw Errors.MMAlreadyAMember.
                if mlist.isMember(userdesc.address):
                    print >> tee, _('Already a member: %(member)s')
                    continue
                mlist.InviteNewMember(userdesc, invite_msg)
            else:
                mlist.ApprovedAddMember(userdesc,
                                        ack=ack,
                                        admin_notif=False,
                                        whence='bin/add_members',
                                       )
        except Errors.MMAlreadyAMember:
            print >> tee, _('Already a member: %(member)s')
        except Errors.MembershipIsBanned, pattern:
            print >> tee, ('%s:' %
                           member), _('Banned address (matched %(pattern)s)')
        except Errors.MMBadEmailError:
            if userdesc.address == '':
                print >> tee, _('Bad/Invalid email address: blank line')
            else:
                print >> tee, _('Bad/Invalid email address: %(member)s')
        except Errors.MMHostileAddress:
            print >> tee, _('Hostile address (illegal characters): %(member)s')
        else:
            if invite:
                print >> tee, _('Invited: %(member)s')
            else:
                print >> tee, _('Subscribed: %(member)s')
                if nomail:
                    mlist.setDeliveryStatus(
                        userdesc.address.lower(), MemberAdaptor.BYADMIN)



def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:],
                                   'a:r:d:w:im:nh',
                                   ['admin-notify=',
                                    'regular-members-file=',
                                    'digest-members-file=',
                                    'welcome-msg=',
                                    'invite',
                                    'invite-msg-file=',
                                    'nomail',
                                    'help',])
    except getopt.error, msg:
        usage(1, msg)

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

    listname = args[0].lower().strip()
    nfile = None
    dfile = None
    send_welcome_msg = None
    admin_notif = None
    invite = False
    invite_msg_file = None
    nomail = False
    for opt, arg in opts:
        if opt in ('-h', '--help'):
            usage(0)
        elif opt in ('-d', '--digest-members-file'):
            dfile = arg
        elif opt in ('-r', '--regular-members-file'):
            nfile = arg
        elif opt in ('-m', '--invite-msg-file'):
            invite_msg_file = arg
        elif opt in ('-i', '--invite'):
            invite = True
        elif opt in ('-w', '--welcome-msg'):
            if arg.lower()[0] == 'y':
                send_welcome_msg = 1
            elif arg.lower()[0] == 'n':
                send_welcome_msg = 0
            else:
                usage(1, C_('Bad argument to -w/--welcome-msg: %(arg)s'))
        elif opt in ('-a', '--admin-notify'):
            if arg.lower()[0] == 'y':
                admin_notif = 1
            elif arg.lower()[0] == 'n':
                admin_notif = 0
            else:
                usage(1, C_('Bad argument to -a/--admin-notify: %(arg)s'))
        elif opt in ('-n', '--nomail'):
            nomail = True

    if dfile is None and nfile is None:
        usage(1)

    if dfile == "-" and nfile == "-":
        usage(1, C_('Cannot read both digest and normal members '
                    'from standard input.'))

    if not invite and invite_msg_file <> None:
        usage(1, C_('Setting invite-msg-file requires --invite.'))

    try:
        mlist = MailList.MailList(listname)
    except Errors.MMUnknownListError:
        usage(1, C_('No such list: %(listname)s'))

    # Set up defaults
    if send_welcome_msg is None:
        send_welcome_msg = mlist.send_welcome_msg
    if admin_notif is None:
        admin_notif = mlist.admin_notify_mchanges

    otrans = i18n.get_translation()
    # Read the regular and digest member files
    try:
        dmembers = []
        if dfile:
            dmembers = readfile(dfile)

        nmembers = []
        if nfile:
            nmembers = readfile(nfile)

        invite_msg = ''
        if invite_msg_file:
            invite_msg = readmsgfile(invite_msg_file)

        if not dmembers and not nmembers:
            usage(0, C_('Nothing to do.'))

        s = StringIO()
        i18n.set_language(mlist.preferred_language)
        if nmembers:
            addall(mlist, nmembers, 0, send_welcome_msg, s, nomail, invite,
                   invite_msg)

        if dmembers:
            addall(mlist, dmembers, 1, send_welcome_msg, s, nomail, invite,
                   invite_msg)

        if admin_notif:
            realname = mlist.real_name
            subject = _('%(realname)s subscription notification')
            msg = Message.UserNotification(
                mlist.owner,
                Utils.get_site_email(mlist.host_name),
                subject,
                s.getvalue(),
                mlist.preferred_language)
            msg.send(mlist)

        mlist.Save()
    finally:
        mlist.Unlock()
        i18n.set_translation(otrans)


if __name__ == '__main__':
    main()
