usage: imapautofiler [-h] [-c CONFIG_FILE] [--list-mailboxes]
optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config-file CONFIG_FILE
  --list-mailboxes      instead of processing rules,
                          print a list of mailboxes
        server:
  hostname: "mail.example.com"
  username: "doughellmann@example.com"        cfg = config.get_config(args.config_file)
        conn = imapclient.IMAPClient(
            cfg['server']['hostname'],
            use_uid=True,
            ssl=True,
        )
        username = cfg['server']['username']
        password = cfg['server'].get('password')
        if not password:
            password = getpass.getpass(
                'Password for {}:'.format(username))
        conn.login(username, password)How do the rules work?
class Rule(metaclass=abc.ABCMeta):
    def __init__(self, rule_data, cfg):
        self._data = rule_data
        self._cfg = cfg
    @abc.abstractmethod
    def check(self):
        raise NotImplementedError()
    def get_action(self):
        return self._data.get('action', {})      - headers:
          - name: "subject"
            substring: "[pyatl]"
        action:
          name: "move"
          dest-mailbox: "INBOX.PyATL"class HeaderSubString(Rule):
    def __init__(self, rule_data, cfg):
        super().__init__(rule_data, cfg)
        self._header_name = rule_data['name']
        self._substring = rule_data['substring'].lower()
    def check(self, message):
        header_value = message.get(self._header_name, '').lower()
        return (self._substring in header_value)      - or:
          rules:
            - headers:
                - name: "to"
                  substring: "pyatl-list@meetup.com"
            - headers:
                - name: "cc"
                  substring: "pyatl-list@meetup.com"
        action:
          name: "move"
          dest-mailbox: "INBOX.PyATL"class Or(Rule):
    "True if any one of the sub-rules is true."
    def __init__(self, rule_data, cfg):
        super().__init__(rule_data, cfg)
        self._sub_rules = [
            factory(r, cfg)
            for r in rule_data['or'].get('rules', [])
        ]
    def check(self, message):
        return any(
            r.check(message)
            for r in self._sub_rules
        )      - recipient:
          substring: "pyatl-list@meetup.com"
        action:
          name: "move"
          dest-mailbox: "INBOX.PyATL"class Recipient(Or):
    "True if any recipient sub-rule matches."
    def __init__(self, rule_data, cfg):
        rules = []
        for header in ['to', 'cc']:
            header_data = {}
            header_data.update(rule_data['recipient'])
            header_data['name'] = header
            rules.append({'headers': [header_data]})
        rule_data['or'] = {
            'rules': rules,
        }
        super().__init__(rule_data, cfg)def factory(rule_data, cfg):
    """Create a rule processor.
    Using the rule type, instantiate a rule processor that can check
    the rule against a message.
    """
    if 'or' in rule_data:
        return Or(rule_data, cfg)
    if 'headers' in rule_data:
        return Headers(rule_data, cfg)
    if 'recipient' in rule_data:
        return Recipient(rule_data, cfg)
    raise ValueError('Unknown rule type {!r}'.format(rule_data))def process_rules(cfg, debug, conn):
    num_messages = 0
    num_processed = 0
    for mailbox in cfg['mailboxes']:      # multiple mailboxes allowed
        mailbox_name = mailbox['name']
        conn.select_folder(mailbox_name)
        mailbox_rules = [                 # convert data to instances
            rules.factory(r, cfg)
            for r in mailbox['rules']
        ]        msg_ids = conn.search(['ALL'])
        for msg_id in msg_ids:
            num_messages += 1
            message = get_message(conn, msg_id)
            for rule in mailbox_rules:
                if rule.check(message):
                    action = actions.factory(rule.get_action(), cfg)
                    action.invoke(conn, msg_id, message)
                    num_processed += 1
                    break
        # Remove messages that we just moved.
        conn.expunge()        msg_ids = conn.search(['ALL'])
        for msg_id in msg_ids:
            num_messages += 1
            message = get_message(conn, msg_id)
            for rule in mailbox_rules:
                if rule.check(message):
                    action = actions.factory(rule.get_action(), cfg)
                    action.invoke(conn, msg_id, message)
                    num_processed += 1
                    break
        # Remove messages that we just moved.
        conn.expunge()class Action(metaclass=abc.ABCMeta):
    def __init__(self, action_data, cfg):
        self._data = action_data
        self._cfg = cfg
    @abc.abstractmethod
    def invoke(self, conn, message_id, message):
        raise NotImplementedError()        action:
          name: "move"
          dest-mailbox: "INBOX.PyATL"class Move(Action):
    def __init__(self, action_data, cfg):
        super().__init__(action_data, cfg)
        self._dest_mailbox = self._data.get('dest-mailbox')
    def invoke(self, conn, message_id, message):
        conn.copy([message_id], self._dest_mailbox)
        conn.add_flags([message_id], [imapclient.DELETED])      - headers:
          - name: to
            substring: plans@tripit.com
        action:
          name: "trash"class Trash(Move):
    def __init__(self, action_data, cfg):
        super().__init__(action_data, cfg)
        if self._dest_mailbox is None:
            self._dest_mailbox = cfg.get('trash-mailbox')
        if self._dest_mailbox is None:
            raise ValueError('no "trash-mailbox" set in config')def factory(action_data, cfg):
    "Create an Action instance."
    name = action_data.get('name')
    if name == 'move':
        return Move(action_data, cfg)
    if name == 'delete':
        return Delete(action_data, cfg)
    if name == 'trash':
        return Trash(action_data, cfg)
    raise ValueError('unrecognized rule action {!r}'.format(action_data))$ imapautofiler Move: 13704 (Re: livarot france - Google Search) to INBOX.Theresa Trash: 13706 (Help Anne with theme work) to INBOX.Trash Trash: 13707 (Fwd: Change in openstack/oslo.db[master]: Warn on URL without a drivername) to INBOX.Trash Move: 13709 ([oslo][all][logging] logging debugging improvement work status) to INBOX.OpenStack.Dev List Move: 13712 (Re: ) to INBOX.Personal Trash: 13713 (Fwd: [Python-Dev] 2017 Python Language Summit coverage) to INBOX.Trash Move: 13732 (Re: [Openstack-operators] [openstack-dev] [upgrades][skip-level][leapfrog] - RFC - Skipping releases when upgrading) to INBOX.OpenStack.Misc Lists
            
               imapautofiler/imapautofiler
            
            
              http://imapautofiler.readthedocs.io/
            
          
            
               imapautofiler/presentation-organize-email-imapautofiler
            
            
              https://doughellmann.com/presentations/organize-email-imapautofiler
            
          
            
               This work is licensed under a Creativle Commons Attribution 4.0 International License.
            
             This work is licensed under a Creativle Commons Attribution 4.0 International License.