#!/opt/mailman/venv/bin/python3

# Note: The line above should be the Python3 interpreter for your
# installed version of Mailman3. It's the same as the first line in
# your 'mailman' command, which might be named manage.py or something
# else in your environment.

# The venv version of Mailman used to develop this program is:
#   GNU Mailman 3.3.9 (Tom Sawyer)
#   Python 3.12.3 (main, Apr 10 2024, 05:33:47) [GCC 13.2.0]

# About:
# The multiple steps of creating a Mailman 3 list based on a legacy
# Mailman 2.1 list.

# gbn July 28 2024. This little program is granted to the public domain.


########################################################################
# Adjust these variables for your list, domain, MM2 and MM3 locations:
lists_to_import = [
     # listname, domainname
     ('testlist','example.org'),
     ('testlist2','example.org')
     # Add more lists as needed
]


########################################################################
# Load prerequisite functionality:
from mailman.config import config     # Needed for transaction.commit
from mailman.core.initialize import initialize # For initialize()
from mailman.app.lifecycle import create_list  # For create_list
import pickle                         # Pickle file handling
from contextlib import ExitStack      # Pickle file handling
from mailman.commands.cli_import import _Mailman, _Bouncer # ""
from mailman.utilities.modules import hacked_sys_modules   # ""
import subprocess                     # Subprocess for import21


########################################################################
# Step 0: Initialize the environment: 
# Without arguments, initialize() looks in the standard locations for your .cfg
def do_init():
     initialize()
     # Alternative: Load an explicit configuration file, such as:
     #   initialize('/etc/mailman3/mailman.cfg')

     
########################################################################
# Step 1: Create the list
# Note: This fails silently if the list already exists or something goes wrong
def do_create(new_list):
     try: # The list might exist or otherwise not be creatable
          l = create_list(new_list)
     except:
          # I did not find useful return values from create_list to diagnose
          print('create_list('+new_list+') failed. Trying to proceed anyway')
          
     # This saves your new list:
     transaction = config.db
     transaction.commit()

     
########################################################################
# Step 2: Rewrite the pickle file from the legacy list.
def do_pickle(old_pickle_file, new_pickle_file):
     
     # These definitions are so the old pickle file can be parsed:
     with ExitStack() as resources:
          resources.enter_context(hacked_sys_modules('Mailman', _Mailman))
          resources.enter_context(
               hacked_sys_modules('Mailman.Bouncer', _Bouncer))
     
          # Input the old pickle file from MM 2.1
          with open(old_pickle_file, 'rb') as fp:
               data = pickle.load(fp)
               # ban_list was a problematic value from my MM2.1 lists:
               #   print(data['ban_list'])
               data['ban_list'] = []
               # Any other settings you want to change? Add them here. These
               # variables may be seen with MM2's 'config_list' command
               data['require_explicit_destination']=1 # yes
               data['member_moderation_action']=1     # reject
               data['generic_nonmember_action']=2     # reject
               data['max_num_recipients']=2           # block cc/bcc to list 
               fp.seek(0)  # probably not needed
               fp.close()  # ditto

               # Output: Rewrite the file in the current MM3 format
               with open(new_pickle_file,'wb') as ofp:
                    pickle.dump(data, ofp)
                    ofp.close()

                    
########################################################################
# Step 3: Import the legacy settings to the newly created list
def do_import(new_list, new_pickle_file):
     
     # I did not see how to call import21 directly from Python, so am using
     # a subprocess which basically uses the CLI syntax.
     #
     # CLI syntax: mailman import21 [OPTIONS] LISTSPEC PICKLE_FILE
     def import_list(listname, pickle_file):
          command = [
               'mailman', 'import21',
               listname, pickle_file
          ]
          subprocess.run(command, check=True)
          #   print(f"List {listname} imported successfully.")

     # Import the list via a subprocess:
     import_list(new_list, new_pickle_file)

     # This saves your changes:
     transaction = config.db
     transaction.commit()


########################################################################
# Main process:
do_init()

# Loop for all lists we want to create & import:
for new_listname, new_domainname in lists_to_import:
     new_list = new_listname+'@'+new_domainname
     # Adjust for wherever your old MM2 lists are:
     old_pickle_file = '/var/lib/mailman/lists/'+new_listname+'/config.pck'

     print('Starting list '+new_list)
     
     # The new pickle_file doesn't need to be saved after this program exits
     new_pickle_file = '/opt/mailman/'+new_listname+'-new.pck'

     do_create(new_list)
     do_pickle(old_pickle_file, new_pickle_file)
     do_import(new_list, new_pickle_file)

     print('Done with list '+new_list)

# Don't forget to import and then index your list for full-text
# searching. We're not doing that here because it takes awhile
# (depending on the size of the archive). Use a command from the bash
# shell such as:
#   mailman-web hyperkitty_import --since=1971-01-01 -l listname@domainnamed /var/lib/mailman/archives/private/listname.mbox/listname.mbox
# followed by:
#   mailman-web update_index_one_list listname@domainname

