imap_processor 1.1.1 → 1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -3
- data/.autotest +3 -18
- data/History.rdoc +103 -0
- data/Manifest.txt +18 -2
- data/{README.txt → README.rdoc} +35 -5
- data/Rakefile +8 -5
- data/bin/imap_archive +5 -0
- data/bin/imap_cleanse +5 -0
- data/bin/imap_flag +5 -0
- data/bin/imap_idle +6 -0
- data/bin/imap_learn +5 -0
- data/bin/imap_mkdir +5 -0
- data/lib/imap_processor.rb +304 -154
- data/lib/imap_processor/archive.rb +128 -0
- data/lib/imap_processor/cleanse.rb +67 -0
- data/lib/imap_processor/client.rb +145 -0
- data/lib/imap_processor/flag.rb +121 -0
- data/lib/imap_processor/idle.rb +74 -0
- data/lib/imap_processor/keywords.rb +9 -14
- data/lib/imap_processor/learn.rb +231 -0
- data/lib/imap_processor/mkdir.rb +25 -0
- data/lib/imap_sasl_plain.rb +1 -26
- data/lib/net/imap/date.rb +24 -0
- data/lib/net/imap/idle.rb +48 -0
- data/test/test_imap_processor.rb +185 -0
- metadata +120 -71
- metadata.gz.sig +0 -0
- data/History.txt +0 -22
    
        checksums.yaml
    ADDED
    
    | @@ -0,0 +1,7 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            SHA256:
         | 
| 3 | 
            +
              metadata.gz: 632f5afa32e055708809e6817e2532f5a5f880b48d9bb808102e63e2bad6cff7
         | 
| 4 | 
            +
              data.tar.gz: 145bf8485b69a4629b32b12748edc780899db542e6a180db88a61ee6fc93ba56
         | 
| 5 | 
            +
            SHA512:
         | 
| 6 | 
            +
              metadata.gz: e1c5b332b9d8e3dbb9f105d888cb796a1e901633f40147a241ed2ac0ec7f63664f8a9b78a0eed149d8653eace16de68190d2737462294192ecaaa0bf331d1746
         | 
| 7 | 
            +
              data.tar.gz: 5561900484e14b7fe02508402af345a07a8999c56b2fdf05dd91989698b4b5a87bdd9f09acebf84232e7505b3a9ce8e57e8e9fc42041567555cf889592ec055c
         | 
    
        checksums.yaml.gz.sig
    ADDED
    
    | Binary file | 
    
        data.tar.gz.sig
    CHANGED
    
    | @@ -1,3 +1,2 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 3 | 
            -
            ��в��o��G���\i�֬�)a�(�5c͌S�@"�Ŭ�=�͞nM��H��|���.�poa�et'�ruCw��8ƌ�?Zz�C(�iA�0�`?��"L�cp�q��ŋ��3�-�@!�YЌժU�Dr-"&
         | 
| 1 | 
            +
            C��>Ƙo�3����0��:��Oɩ��KiXZ��$���foL(�=��=��W-
         | 
| 2 | 
            +
            5m���݆+cV�1���㹶-\�GLD��W���U�����0�&1-�͔C���*��0��n~��C��8�;)���8���x���.ϔ1�/�e����1��AN$�F�ͭDK9�6��V5��g�
         | 
    
        data/.autotest
    CHANGED
    
    | @@ -2,22 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'autotest/restart'
         | 
| 4 4 |  | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
            #   at.libs << ":../some/external"
         | 
| 9 | 
            -
            #
         | 
| 10 | 
            -
            #   at.add_exception 'vendor'
         | 
| 11 | 
            -
            #
         | 
| 12 | 
            -
            #   at.add_mapping(/dependency.rb/) do |f, _|
         | 
| 13 | 
            -
            #     at.files_matching(/test_.*rb$/)
         | 
| 14 | 
            -
            #   end
         | 
| 15 | 
            -
            #
         | 
| 16 | 
            -
            #   %w(TestA TestB).each do |klass|
         | 
| 17 | 
            -
            #     at.extra_class_map[klass] = "test/test_misc.rb"
         | 
| 18 | 
            -
            #   end
         | 
| 19 | 
            -
            # end
         | 
| 5 | 
            +
            Autotest.add_hook :initialize do |at|
         | 
| 6 | 
            +
              at.testlib = 'minitest/autorun'
         | 
| 7 | 
            +
            end
         | 
| 20 8 |  | 
| 21 | 
            -
            # Autotest.add_hook :run_command do |at|
         | 
| 22 | 
            -
            #   system "rake build"
         | 
| 23 | 
            -
            # end
         | 
    
        data/History.rdoc
    ADDED
    
    | @@ -0,0 +1,103 @@ | |
| 1 | 
            +
            === 1.7 / 2020-06-04
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            * 4 minor enhancements:
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              * Added #noop? to improve readability.
         | 
| 6 | 
            +
              * Documented how to use this with gmail.
         | 
| 7 | 
            +
              * Improved imap_archive output by distinguishing hosts.
         | 
| 8 | 
            +
              * Improved show_messages output to be cleaner and have better info (from/to).
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            * 2 bug fixes:
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              * Fixed --noop by covering all imap calls that modify w/ checks.
         | 
| 13 | 
            +
              * Fixed option processing with multiple accounts by dup'ing args to OptionParser.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            === 1.6 / 2014-10-17
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            * 1 minor enhancement:
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              * Add XOAUTH2 authentication type. (mattbeedle)
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            === 1.5 / 2014-08-06
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            * 3 major enhancements:
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              * IMAPProcessor#process_args now returns an array of option hashes.
         | 
| 26 | 
            +
              * IMAPProcessor.run now enumerates the array returned from process_args.
         | 
| 27 | 
            +
              * You can now specify multiple host configs w/ an array of hashes in your config files.
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            * 4 minor enhancements:
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              * Added --merge to imap_archive.
         | 
| 32 | 
            +
              * Added --noop/-n to manually disable destructive actions. (needs to propagate down).
         | 
| 33 | 
            +
              * Added imap_cleanse, imap_flag, imap_learn; migrated from IMAPCleanse.
         | 
| 34 | 
            +
              * Added support for LOGIN. (bleything)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            * 7 bug fixes:
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              * Fixed initializers in flag and cleanse.
         | 
| 39 | 
            +
              * Fixed odd bug w/ running on empty folders. Never saw that before. odd...
         | 
| 40 | 
            +
              * Handle unparsable date entries. Stupid spammers...
         | 
| 41 | 
            +
              * Now calculating latest month when not splitting directly from the date
         | 
| 42 | 
            +
              * Removed 1.9/2.0 warnings.
         | 
| 43 | 
            +
              * Removed dead rubyforge setting in Rakefile
         | 
| 44 | 
            +
              * Split was still defaulting to true.
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            === 1.4 / 2011-01-10
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            * 6 minor enhancements:
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              * Added explicit help option (-h didn't work)
         | 
| 51 | 
            +
              * Added folder separator support (osx server uses '.' not '/')
         | 
| 52 | 
            +
              * Added imap_mkdir command
         | 
| 53 | 
            +
              * Added opts_file_name class var so subclass option processing can refer to file
         | 
| 54 | 
            +
              * Extended imap_archive to archive multiple months per box, as necessary. Allowing easy archiving of big mailboxes
         | 
| 55 | 
            +
              * Handles server-provided CAPABILITY to avoid an extra round-trip
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            * 1 bug fix:
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              * Fixed doco.
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            === 1.3 / 2009-08-04
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            * 1 major enhancement
         | 
| 64 | 
            +
              * IMAP IDLE support now matches ruby trunk's support.  See Net::IMAP#idle
         | 
| 65 | 
            +
                and Net::IMAP#idle_done
         | 
| 66 | 
            +
             | 
| 67 | 
            +
            === 1.2 / 2009-06-02
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            * 2 major enhancements
         | 
| 70 | 
            +
              * imap_archive which archives old mail to dated mailboxes
         | 
| 71 | 
            +
              * imap_idle which lists messages that were added or expunged from a mailbox
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            * 4 minor enhancements
         | 
| 74 | 
            +
              * Added IMAPProcessor#create_mailbox
         | 
| 75 | 
            +
              * Added IMAPProcessor#delete_messages
         | 
| 76 | 
            +
              * Added IMAPProcessor#move_messages
         | 
| 77 | 
            +
              * Disabled verification of SSL certs for 1.9
         | 
| 78 | 
            +
             | 
| 79 | 
            +
            * 1 bug fix
         | 
| 80 | 
            +
              * Fixed options file names, they should be Symbol keys
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            === 1.1.1 / 2009-05-19
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            * 1 bug fix
         | 
| 85 | 
            +
              * Got the skip test backwards
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            === 1.1 / 2009-05-18
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            * 1 minor enhancement
         | 
| 90 | 
            +
              * IMAPProcessor#each_message allows messages to be omitted from the returned
         | 
| 91 | 
            +
                uid list (skipped)
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            === 1.0.1 / 2009-05-15
         | 
| 94 | 
            +
             | 
| 95 | 
            +
            * 2 bug fix
         | 
| 96 | 
            +
              * Show correct name of options file for --password help
         | 
| 97 | 
            +
              * Fix --quiet
         | 
| 98 | 
            +
             | 
| 99 | 
            +
            === 1.0.0 / 2009-05-12
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            * 1 major enhancement
         | 
| 102 | 
            +
              * Birthday!
         | 
| 103 | 
            +
             | 
    
        data/Manifest.txt
    CHANGED
    
    | @@ -1,9 +1,25 @@ | |
| 1 1 | 
             
            .autotest
         | 
| 2 | 
            -
            History. | 
| 2 | 
            +
            History.rdoc
         | 
| 3 3 | 
             
            Manifest.txt
         | 
| 4 | 
            -
            README. | 
| 4 | 
            +
            README.rdoc
         | 
| 5 5 | 
             
            Rakefile
         | 
| 6 | 
            +
            bin/imap_archive
         | 
| 7 | 
            +
            bin/imap_cleanse
         | 
| 8 | 
            +
            bin/imap_flag
         | 
| 9 | 
            +
            bin/imap_idle
         | 
| 6 10 | 
             
            bin/imap_keywords
         | 
| 11 | 
            +
            bin/imap_learn
         | 
| 12 | 
            +
            bin/imap_mkdir
         | 
| 7 13 | 
             
            lib/imap_processor.rb
         | 
| 14 | 
            +
            lib/imap_processor/archive.rb
         | 
| 15 | 
            +
            lib/imap_processor/cleanse.rb
         | 
| 16 | 
            +
            lib/imap_processor/client.rb
         | 
| 17 | 
            +
            lib/imap_processor/flag.rb
         | 
| 18 | 
            +
            lib/imap_processor/idle.rb
         | 
| 8 19 | 
             
            lib/imap_processor/keywords.rb
         | 
| 20 | 
            +
            lib/imap_processor/learn.rb
         | 
| 21 | 
            +
            lib/imap_processor/mkdir.rb
         | 
| 9 22 | 
             
            lib/imap_sasl_plain.rb
         | 
| 23 | 
            +
            lib/net/imap/date.rb
         | 
| 24 | 
            +
            lib/net/imap/idle.rb
         | 
| 25 | 
            +
            test/test_imap_processor.rb
         | 
    
        data/{README.txt → README.rdoc}
    RENAMED
    
    | @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            = imap_processor
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 3 | 
            +
            home :: https://github.com/seattlerb/imap_processor
         | 
| 4 | 
            +
            rdoc :: http://docs.seattlerb.org/imap_processor
         | 
| 4 5 |  | 
| 5 6 | 
             
            == DESCRIPTION:
         | 
| 6 7 |  | 
| @@ -8,8 +9,16 @@ IMAPProcessor is a client for processing messages on an IMAP server.  It | |
| 8 9 | 
             
            provides some basic mechanisms for connecting to an IMAP server, determining
         | 
| 9 10 | 
             
            capabilities and handling messages.
         | 
| 10 11 |  | 
| 11 | 
            -
            IMAPProcessor ships with  | 
| 12 | 
            -
             | 
| 12 | 
            +
            IMAPProcessor ships with several executables which can query and
         | 
| 13 | 
            +
            manipulate IMAP mailboxes in several different ways:
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            imap_archive  :: Archives old messages to a new dated mailbox.
         | 
| 16 | 
            +
            imap_cleanse  :: Delete messages older than a certain age in specified mailboxes.
         | 
| 17 | 
            +
            imap_flag     :: Flag messages to/from certain people.
         | 
| 18 | 
            +
            imap_idle     :: Shows new messages in a mailbox.
         | 
| 19 | 
            +
            imap_keywords :: Queries an IMAP server for keywords set on messages
         | 
| 20 | 
            +
            imap_learn    :: Flags messages based on what you've flagged before.
         | 
| 21 | 
            +
            imap_mkdir    :: Ensures that certain mailboxes exist.
         | 
| 13 22 |  | 
| 14 23 | 
             
            == FEATURES/PROBLEMS:
         | 
| 15 24 |  | 
| @@ -19,7 +28,28 @@ server for keywords set on messages in mailboxes. | |
| 19 28 |  | 
| 20 29 | 
             
            == SYNOPSIS:
         | 
| 21 30 |  | 
| 22 | 
            -
             | 
| 31 | 
            +
            Run any command with --help for details.
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            == Google Mail:
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            This is kinda painful. You need to have Two Factor Authentication
         | 
| 36 | 
            +
            enabled with google, then you need to create an app specific password
         | 
| 37 | 
            +
            as described here: https://support.google.com/accounts/answer/185833
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            Then, your config needs to be set up like this:
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                - :Host: imap.googlemail.com
         | 
| 42 | 
            +
                  :SSL: true
         | 
| 43 | 
            +
                  :Auth: PLAIN
         | 
| 44 | 
            +
                  :Username: your.address@gmail.com
         | 
| 45 | 
            +
                  :Password: app-specific-password
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            Specifically, you need to set auth to PLAIN, and your password needs
         | 
| 48 | 
            +
            to your app specific password. Run with --debug to help figure
         | 
| 49 | 
            +
            problems out.
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            Google is threatening to turn this off at some point and require
         | 
| 52 | 
            +
            oauth... at which point... I have no idea. I give up I guess.
         | 
| 23 53 |  | 
| 24 54 | 
             
            == REQUIREMENTS:
         | 
| 25 55 |  | 
| @@ -33,7 +63,7 @@ See IMAPProcessor and IMAPProcessor::Keywords for details | |
| 33 63 |  | 
| 34 64 | 
             
            (The MIT License)
         | 
| 35 65 |  | 
| 36 | 
            -
            Copyright (c)  | 
| 66 | 
            +
            Copyright (c) Eric Hodel, Ryan Davis, Seattle.rb
         | 
| 37 67 |  | 
| 38 68 | 
             
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 39 69 | 
             
            a copy of this software and associated documentation files (the
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -2,12 +2,15 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require 'rubygems'
         | 
| 4 4 | 
             
            require 'hoe'
         | 
| 5 | 
            -
            $:.unshift 'lib'
         | 
| 6 | 
            -
            require 'imap_processor'
         | 
| 7 5 |  | 
| 8 | 
            -
            Hoe. | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 6 | 
            +
            Hoe.plugin :seattlerb
         | 
| 7 | 
            +
            Hoe.plugin :rdoc
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            Hoe.spec 'imap_processor' do
         | 
| 10 | 
            +
              developer 'Ryan Davis', 'ryand-ruby@zenspider.com'
         | 
| 11 | 
            +
              developer 'Eric Hodel', 'drbrain@segment7.net'
         | 
| 12 | 
            +
             | 
| 13 | 
            +
              license "MIT"
         | 
| 11 14 | 
             
            end
         | 
| 12 15 |  | 
| 13 16 | 
             
            # vim: syntax=Ruby
         | 
    
        data/bin/imap_archive
    ADDED
    
    
    
        data/bin/imap_cleanse
    ADDED
    
    
    
        data/bin/imap_flag
    ADDED
    
    
    
        data/bin/imap_idle
    ADDED
    
    
    
        data/bin/imap_learn
    ADDED
    
    
    
        data/bin/imap_mkdir
    ADDED
    
    
    
        data/lib/imap_processor.rb
    CHANGED
    
    | @@ -1,7 +1,9 @@ | |
| 1 1 | 
             
            require 'rubygems'
         | 
| 2 2 | 
             
            require 'optparse'
         | 
| 3 3 | 
             
            require 'net/imap'
         | 
| 4 | 
            +
            require 'net/imap/date'
         | 
| 4 5 | 
             
            require 'imap_sasl_plain'
         | 
| 6 | 
            +
            require 'yaml'
         | 
| 5 7 |  | 
| 6 8 | 
             
            ##
         | 
| 7 9 | 
             
            # IMAPProcessor is a client for processing messages on an IMAP server.
         | 
| @@ -13,13 +15,24 @@ require 'imap_sasl_plain' | |
| 13 15 | 
             
            # * An initialize method that connects to an IMAP server and sets the @imap
         | 
| 14 16 | 
             
            #   instance variable
         | 
| 15 17 | 
             
            # * A run method that uses the IMAP connection to process messages.
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
            # Reference:
         | 
| 20 | 
            +
            #
         | 
| 21 | 
            +
            #     email: http://www.ietf.org/rfc/rfc0822.txt
         | 
| 22 | 
            +
            #      imap: http://www.ietf.org/rfc/rfc3501.txt
         | 
| 16 23 |  | 
| 17 24 | 
             
            class IMAPProcessor
         | 
| 18 25 |  | 
| 19 26 | 
             
              ##
         | 
| 20 27 | 
             
              # The version of IMAPProcessor you are using
         | 
| 21 28 |  | 
| 22 | 
            -
              VERSION =  | 
| 29 | 
            +
              VERSION = "1.7"
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              ##
         | 
| 32 | 
            +
              # Base IMAPProcessor error class
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              class Error < RuntimeError
         | 
| 35 | 
            +
              end
         | 
| 23 36 |  | 
| 24 37 | 
             
              ##
         | 
| 25 38 | 
             
              # A Connection Struct that has +imap+ and +capability+ accessors
         | 
| @@ -67,7 +80,7 @@ class IMAPProcessor | |
| 67 80 | 
             
                  opts.on(      "--move=MAILBOX",
         | 
| 68 81 | 
             
                          "Mailbox to move message to",
         | 
| 69 82 | 
             
                          "Default: #{options[:MoveTo].inspect}",
         | 
| 70 | 
            -
                          "Options file name: MoveTo") do |mailbox|
         | 
| 83 | 
            +
                          "Options file name: :MoveTo") do |mailbox|
         | 
| 71 84 | 
             
                    options[:MoveTo] = mailbox
         | 
| 72 85 | 
             
                  end
         | 
| 73 86 | 
             
                end
         | 
| @@ -84,24 +97,28 @@ class IMAPProcessor | |
| 84 97 | 
             
              #       required_options = {
         | 
| 85 98 | 
             
              #         :MoveTo => [nil, "MoveTo not set"],
         | 
| 86 99 | 
             
              #       }
         | 
| 87 | 
            -
              # | 
| 88 | 
            -
              # | 
| 89 | 
            -
              # | 
| 90 | 
            -
              # | 
| 91 | 
            -
              # | 
| 92 | 
            -
              # | 
| 93 | 
            -
              # | 
| 94 | 
            -
              # | 
| 95 | 
            -
              # | 
| 100 | 
            +
              #
         | 
| 101 | 
            +
              #       super __FILE__, args, required_options do |opts, options|
         | 
| 102 | 
            +
              #         opts.banner << "Explain my_processor's executable"
         | 
| 103 | 
            +
              #
         | 
| 104 | 
            +
              #         opts.on(      "--move=MAILBOX",
         | 
| 105 | 
            +
              #                 "Mailbox to move message to",
         | 
| 106 | 
            +
              #                 "Default: #{options[:MoveTo].inspect}",
         | 
| 107 | 
            +
              #                 "Options file name: :MoveTo") do |mailbox|
         | 
| 108 | 
            +
              #           options[:MoveTo] = mailbox
         | 
| 109 | 
            +
              #         end
         | 
| 96 110 | 
             
              #       end
         | 
| 97 111 | 
             
              #     end
         | 
| 98 112 | 
             
              #   end
         | 
| 113 | 
            +
              #
         | 
| 114 | 
            +
              # NOTE:  You can add a --move option using ::add_move
         | 
| 99 115 |  | 
| 100 116 | 
             
              def self.process_args(processor_file, args,
         | 
| 101 117 | 
             
                                    required_options = {}) # :yield: OptionParser
         | 
| 102 | 
            -
                opts_file_name = File.basename processor_file, '.rb'
         | 
| 103 | 
            -
                 | 
| 104 | 
            -
             | 
| 118 | 
            +
                @@opts_file_name = File.basename processor_file, '.rb'
         | 
| 119 | 
            +
                @@opts_file_name = "imap_#{@@opts_file_name}" unless
         | 
| 120 | 
            +
                  @@opts_file_name =~ /^imap_/
         | 
| 121 | 
            +
                opts_file = File.expand_path "~/.#{@@opts_file_name}"
         | 
| 105 122 |  | 
| 106 123 | 
             
                if required_options then
         | 
| 107 124 | 
             
                  required_options.each do |option, (default, message)|
         | 
| @@ -111,6 +128,8 @@ class IMAPProcessor | |
| 111 128 | 
             
                  end
         | 
| 112 129 | 
             
                end
         | 
| 113 130 |  | 
| 131 | 
            +
                defaults = [{}]
         | 
| 132 | 
            +
             | 
| 114 133 | 
             
                if File.exist? opts_file then
         | 
| 115 134 | 
             
                  unless File.stat(opts_file).mode & 077 == 0 then
         | 
| 116 135 | 
             
                    $stderr.puts "WARNING! #{opts_file} is group/other readable or writable!"
         | 
| @@ -118,165 +137,188 @@ class IMAPProcessor | |
| 118 137 | 
             
                    exit 1
         | 
| 119 138 | 
             
                  end
         | 
| 120 139 |  | 
| 121 | 
            -
                   | 
| 140 | 
            +
                  defaults = Array(YAML.load_file(opts_file))
         | 
| 122 141 | 
             
                end
         | 
| 123 142 |  | 
| 124 | 
            -
                 | 
| 125 | 
            -
             | 
| 126 | 
            -
                options[:Root]     ||= nil
         | 
| 127 | 
            -
                options[:Verbose]  ||= false
         | 
| 128 | 
            -
                options[:Debug]    ||= false
         | 
| 143 | 
            +
                defaults.map { |default|
         | 
| 144 | 
            +
                  options = default.merge @@options.dup
         | 
| 129 145 |  | 
| 130 | 
            -
             | 
| 131 | 
            -
                  options[ | 
| 132 | 
            -
             | 
| 146 | 
            +
                  options[:SSL]        = true unless options.key? :SSL
         | 
| 147 | 
            +
                  options[:Username] ||= ENV['USER']
         | 
| 148 | 
            +
                  options[:Root]     ||= nil
         | 
| 149 | 
            +
                  options[:Verbose]  ||= false
         | 
| 150 | 
            +
                  options[:Debug]    ||= false
         | 
| 133 151 |  | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
                   | 
| 152 | 
            +
                  required_options.each do |k,(v,_)|
         | 
| 153 | 
            +
                    options[k]       ||= v
         | 
| 154 | 
            +
                  end
         | 
| 137 155 |  | 
| 138 | 
            -
                   | 
| 139 | 
            -
             | 
| 156 | 
            +
                  op = OptionParser.new do |opts|
         | 
| 157 | 
            +
                    opts.program_name = File.basename $0
         | 
| 158 | 
            +
                    opts.banner = "Usage: #{opts.program_name} [options]\n\n"
         | 
| 140 159 |  | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
                          "Default: #{options[:Host].inspect}",
         | 
| 144 | 
            -
                          "Options file name: Host") do |host|
         | 
| 145 | 
            -
                    options[:Host] = host
         | 
| 146 | 
            -
                  end
         | 
| 160 | 
            +
                    opts.separator ''
         | 
| 161 | 
            +
                    opts.separator 'Connection options:'
         | 
| 147 162 |  | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
                    options[:Port] = port
         | 
| 153 | 
            -
                  end
         | 
| 163 | 
            +
                    opts.on_tail("-h", "--help", "Show this message") do
         | 
| 164 | 
            +
                      puts opts
         | 
| 165 | 
            +
                      exit
         | 
| 166 | 
            +
                    end
         | 
| 154 167 |  | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 168 | 
            +
                    opts.on("-H", "--host HOST",
         | 
| 169 | 
            +
                            "IMAP server host",
         | 
| 170 | 
            +
                            "Default: #{options[:Host].inspect}",
         | 
| 171 | 
            +
                            "Options file name: :Host") do |host|
         | 
| 172 | 
            +
                      options[:Host] = host
         | 
| 173 | 
            +
                    end
         | 
| 161 174 |  | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 175 | 
            +
                    opts.on("-P", "--port PORT",
         | 
| 176 | 
            +
                            "IMAP server port",
         | 
| 177 | 
            +
                            "Default: The correct port SSL/non-SSL mode",
         | 
| 178 | 
            +
                            "Options file name: :Port") do |port|
         | 
| 179 | 
            +
                      options[:Port] = port
         | 
| 180 | 
            +
                    end
         | 
| 168 181 |  | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 182 | 
            +
                    opts.on("-s", "--[no-]ssl",
         | 
| 183 | 
            +
                            "Use SSL for IMAP connection",
         | 
| 184 | 
            +
                            "Default: #{options[:SSL].inspect}",
         | 
| 185 | 
            +
                            "Options file name: :SSL") do |ssl|
         | 
| 186 | 
            +
                      options[:SSL] = ssl
         | 
| 187 | 
            +
                    end
         | 
| 171 188 |  | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 189 | 
            +
                    opts.on(      "--[no-]debug",
         | 
| 190 | 
            +
                            "Display Net::IMAP debugging info",
         | 
| 191 | 
            +
                            "Default: #{options[:Debug].inspect}",
         | 
| 192 | 
            +
                            "Options file name: :Debug") do |debug|
         | 
| 193 | 
            +
                      options[:Debug] = debug
         | 
| 194 | 
            +
                    end
         | 
| 178 195 |  | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
                          "Default: Read from ~/.#{opts_file_name}",
         | 
| 182 | 
            -
                          "Options file name: Password") do |password|
         | 
| 183 | 
            -
                    options[:Password] = password
         | 
| 184 | 
            -
                  end
         | 
| 196 | 
            +
                    opts.separator ''
         | 
| 197 | 
            +
                    opts.separator 'Login options:'
         | 
| 185 198 |  | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 188 | 
            -
             | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
                          "Default: #{options[:Auth].inspect}",
         | 
| 193 | 
            -
                          "Options file name: Auth") do |auth|
         | 
| 194 | 
            -
                    options[:Auth] = auth
         | 
| 195 | 
            -
                  end
         | 
| 199 | 
            +
                    opts.on("-u", "--username USERNAME",
         | 
| 200 | 
            +
                            "IMAP username",
         | 
| 201 | 
            +
                            "Default: #{options[:Username].inspect}",
         | 
| 202 | 
            +
                            "Options file name: :Username") do |username|
         | 
| 203 | 
            +
                      options[:Username] = username
         | 
| 204 | 
            +
                    end
         | 
| 196 205 |  | 
| 197 | 
            -
             | 
| 198 | 
            -
             | 
| 206 | 
            +
                    opts.on("-p", "--password PASSWORD",
         | 
| 207 | 
            +
                            "IMAP password",
         | 
| 208 | 
            +
                            "Default: Read from ~/.#{@@opts_file_name}",
         | 
| 209 | 
            +
                            "Options file name: :Password") do |password|
         | 
| 210 | 
            +
                      options[:Password] = password
         | 
| 211 | 
            +
                    end
         | 
| 199 212 |  | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 213 | 
            +
                    authenticators = Net::IMAP.send :class_variable_get, :@@authenticators
         | 
| 214 | 
            +
                    auth_types = authenticators.keys.sort.join ', '
         | 
| 215 | 
            +
                    opts.on("-a", "--auth AUTH", auth_types,
         | 
| 216 | 
            +
                            "IMAP authentication type override",
         | 
| 217 | 
            +
                            "Authentication type will be auto-",
         | 
| 218 | 
            +
                            "discovered",
         | 
| 219 | 
            +
                            "Default: #{options[:Auth].inspect}",
         | 
| 220 | 
            +
                            "Options file name: :Auth") do |auth|
         | 
| 221 | 
            +
                      options[:Auth] = auth
         | 
| 222 | 
            +
                    end
         | 
| 206 223 |  | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
                          "to search",
         | 
| 210 | 
            -
                          "Default: #{options[:Boxes].inspect}",
         | 
| 211 | 
            -
                          "Options file name: Boxes") do |boxes|
         | 
| 212 | 
            -
                    options[:Boxes] = boxes
         | 
| 213 | 
            -
                  end
         | 
| 224 | 
            +
                    opts.separator ''
         | 
| 225 | 
            +
                    opts.separator "IMAP options:"
         | 
| 214 226 |  | 
| 215 | 
            -
             | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 227 | 
            +
                    opts.on("-r", "--root ROOT",
         | 
| 228 | 
            +
                            "Root of mailbox hierarchy",
         | 
| 229 | 
            +
                            "Default: #{options[:Root].inspect}",
         | 
| 230 | 
            +
                            "Options file name: :Root") do |root|
         | 
| 231 | 
            +
                      options[:Root] = root
         | 
| 232 | 
            +
                    end
         | 
| 221 233 |  | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
             | 
| 225 | 
            -
             | 
| 234 | 
            +
                    opts.on("-b", "--boxes BOXES", Array,
         | 
| 235 | 
            +
                            "Comma-separated list of mailbox names",
         | 
| 236 | 
            +
                            "to search",
         | 
| 237 | 
            +
                            "Default: #{options[:Boxes].inspect}",
         | 
| 238 | 
            +
                            "Options file name: :Boxes") do |boxes|
         | 
| 239 | 
            +
                      options[:Boxes] = boxes
         | 
| 240 | 
            +
                    end
         | 
| 226 241 |  | 
| 227 | 
            -
             | 
| 228 | 
            -
             | 
| 229 | 
            -
             | 
| 242 | 
            +
                    opts.on("-v", "--[no-]verbose",
         | 
| 243 | 
            +
                            "Be verbose",
         | 
| 244 | 
            +
                            "Default: #{options[:Verbose].inspect}",
         | 
| 245 | 
            +
                            "Options file name: :Verbose") do |verbose|
         | 
| 246 | 
            +
                      options[:Verbose] = verbose
         | 
| 247 | 
            +
                    end
         | 
| 230 248 |  | 
| 231 | 
            -
                     | 
| 232 | 
            -
             | 
| 249 | 
            +
                    opts.on("-n", "--noop",
         | 
| 250 | 
            +
                            "Perform no destructive operations",
         | 
| 251 | 
            +
                            "Best used with the verbose option",
         | 
| 252 | 
            +
                            "Default: #{options[:Noop].inspect}",
         | 
| 253 | 
            +
                            "Options file name: Noop") do |noop|
         | 
| 254 | 
            +
                      options[:Noop] = noop
         | 
| 255 | 
            +
                    end
         | 
| 233 256 |  | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 257 | 
            +
                    opts.on("-q", "--quiet",
         | 
| 258 | 
            +
                            "Be quiet") do
         | 
| 259 | 
            +
                      options[:Verbose] = false
         | 
| 260 | 
            +
                    end
         | 
| 237 261 |  | 
| 238 | 
            -
             | 
| 262 | 
            +
                    if block_given? then
         | 
| 263 | 
            +
                      opts.separator ''
         | 
| 264 | 
            +
                      opts.separator "#{self} options:"
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                      yield opts, options if block_given?
         | 
| 267 | 
            +
                    end
         | 
| 268 | 
            +
             | 
| 269 | 
            +
                    @@extra_options.each do |block|
         | 
| 270 | 
            +
                      block.call opts, options
         | 
| 271 | 
            +
                    end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                    opts.separator ''
         | 
| 239 274 |  | 
| 240 | 
            -
             | 
| 275 | 
            +
                    opts.banner << <<-EOF
         | 
| 241 276 |  | 
| 242 | 
            -
            Options may also be set in the options file ~/.#{opts_file_name}
         | 
| 277 | 
            +
            Options may also be set in the options file ~/.#{@@opts_file_name}
         | 
| 243 278 |  | 
| 244 | 
            -
            Example ~/.#{opts_file_name}:
         | 
| 279 | 
            +
            Example ~/.#{@@opts_file_name}:
         | 
| 245 280 | 
             
            \tHost=mail.example.com
         | 
| 246 281 | 
             
            \tPassword=my password
         | 
| 247 282 |  | 
| 248 | 
            -
             | 
| 249 | 
            -
             | 
| 283 | 
            +
                    EOF
         | 
| 284 | 
            +
             | 
| 285 | 
            +
                  end # OptionParser.new do
         | 
| 286 | 
            +
             | 
| 287 | 
            +
                  op.parse! args.dup
         | 
| 250 288 |  | 
| 251 | 
            -
             | 
| 252 | 
            -
             | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
             | 
| 258 | 
            -
             | 
| 259 | 
            -
             | 
| 260 | 
            -
             | 
| 261 | 
            -
             | 
| 262 | 
            -
             | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 265 | 
            -
                    $stderr.puts missing_message if options[option_name].nil?
         | 
| 289 | 
            +
                  options[:Port] ||= options[:SSL] ? 993 : 143
         | 
| 290 | 
            +
             | 
| 291 | 
            +
                  # HACK: removed :Boxes -- push down
         | 
| 292 | 
            +
                  required_keys = [:Host, :Password] + required_options.keys
         | 
| 293 | 
            +
                  if required_keys.any? { |k| options[k].nil? } then
         | 
| 294 | 
            +
                    $stderr.puts op
         | 
| 295 | 
            +
                    $stderr.puts
         | 
| 296 | 
            +
                    $stderr.puts "Host name not set" if options[:Host].nil?
         | 
| 297 | 
            +
                    $stderr.puts "Password not set"  if options[:Password].nil?
         | 
| 298 | 
            +
                    $stderr.puts "Boxes not set"     if options[:Boxes].nil?
         | 
| 299 | 
            +
                    required_options.each do |option_name, (_, missing_message)|
         | 
| 300 | 
            +
                      $stderr.puts missing_message if options[option_name].nil?
         | 
| 301 | 
            +
                    end
         | 
| 302 | 
            +
                    exit 1
         | 
| 266 303 | 
             
                  end
         | 
| 267 | 
            -
                  exit 1
         | 
| 268 | 
            -
                end
         | 
| 269 304 |  | 
| 270 | 
            -
             | 
| 305 | 
            +
                  options
         | 
| 306 | 
            +
                } # defaults.map
         | 
| 271 307 | 
             
              end
         | 
| 272 308 |  | 
| 273 309 | 
             
              ##
         | 
| 274 310 | 
             
              # Sets up an IMAP processor's options then calls its \#run method.
         | 
| 275 311 |  | 
| 276 312 | 
             
              def self.run(args = ARGV, &block)
         | 
| 277 | 
            -
                 | 
| 278 | 
            -
                 | 
| 279 | 
            -
             | 
| 313 | 
            +
                client = nil
         | 
| 314 | 
            +
                multi_options = process_args args
         | 
| 315 | 
            +
             | 
| 316 | 
            +
                multi_options.each do |options|
         | 
| 317 | 
            +
                  client = new(options, &block)
         | 
| 318 | 
            +
                  client.run
         | 
| 319 | 
            +
                end
         | 
| 320 | 
            +
              rescue Interrupt
         | 
| 321 | 
            +
                exit
         | 
| 280 322 | 
             
              rescue SystemExit
         | 
| 281 323 | 
             
                raise
         | 
| 282 324 | 
             
              rescue Exception => e
         | 
| @@ -285,7 +327,7 @@ Example ~/.#{opts_file_name}: | |
| 285 327 |  | 
| 286 328 | 
             
                exit 1
         | 
| 287 329 | 
             
              ensure
         | 
| 288 | 
            -
                client.imap.logout if client
         | 
| 330 | 
            +
                client.imap.logout if client and client.imap
         | 
| 289 331 | 
             
              end
         | 
| 290 332 |  | 
| 291 333 | 
             
              ##
         | 
| @@ -299,34 +341,102 @@ Example ~/.#{opts_file_name}: | |
| 299 341 | 
             
                Net::IMAP.debug = options[:Debug]
         | 
| 300 342 | 
             
              end
         | 
| 301 343 |  | 
| 344 | 
            +
              ##
         | 
| 345 | 
            +
              # Extracts capability information for +imap+ from +res+ or by contacting the
         | 
| 346 | 
            +
              # server.
         | 
| 347 | 
            +
             | 
| 348 | 
            +
              def capability imap, res = nil
         | 
| 349 | 
            +
                return imap.capability unless res
         | 
| 350 | 
            +
             | 
| 351 | 
            +
                data = res.data
         | 
| 352 | 
            +
             | 
| 353 | 
            +
                if data.code and data.code.name == 'CAPABILITY' then
         | 
| 354 | 
            +
                  data.code.data.split ' '
         | 
| 355 | 
            +
                else
         | 
| 356 | 
            +
                  imap.capability
         | 
| 357 | 
            +
                end
         | 
| 358 | 
            +
              end
         | 
| 359 | 
            +
             | 
| 302 360 | 
             
              ##
         | 
| 303 361 | 
             
              # Connects to IMAP server +host+ at +port+ using ssl if +ssl+ is true then
         | 
| 304 | 
            -
              #  | 
| 305 | 
            -
              # work with PLAIN auth on SSL sockets.
         | 
| 362 | 
            +
              # authenticates with +username+ and +password+.  IMAPProcessor is only known
         | 
| 363 | 
            +
              # to work with PLAIN auth on SSL sockets.  IMAPProcessor does not support
         | 
| 364 | 
            +
              # LOGIN.
         | 
| 306 365 | 
             
              #
         | 
| 307 366 | 
             
              # Returns a Connection object.
         | 
| 308 367 |  | 
| 309 | 
            -
              def connect(host | 
| 310 | 
            -
             | 
| 368 | 
            +
              def connect(host = @options[:Host],
         | 
| 369 | 
            +
                          port = @options[:Port],
         | 
| 370 | 
            +
                          ssl = @options[:SSL],
         | 
| 371 | 
            +
                          username = @options[:Username],
         | 
| 372 | 
            +
                          password = @options[:Password],
         | 
| 373 | 
            +
                          auth = @options[:Auth]) # :yields: Connection
         | 
| 374 | 
            +
                imap = Net::IMAP.new host, port, ssl, nil, false
         | 
| 311 375 | 
             
                log "Connected to imap://#{host}:#{port}/"
         | 
| 312 376 |  | 
| 313 | 
            -
                 | 
| 377 | 
            +
                capabilities = capability imap, imap.greeting
         | 
| 314 378 |  | 
| 315 | 
            -
                log "Capabilities: #{ | 
| 379 | 
            +
                log "Capabilities: #{capabilities.join ', '}"
         | 
| 316 380 |  | 
| 317 | 
            -
                auth_caps =  | 
| 381 | 
            +
                auth_caps = capabilities.select { |c| c =~ /^AUTH/ }
         | 
| 318 382 |  | 
| 319 383 | 
             
                if auth.nil? then
         | 
| 320 384 | 
             
                  raise "Couldn't find a supported auth type" if auth_caps.empty?
         | 
| 321 385 | 
             
                  auth = auth_caps.first.sub(/AUTH=/, '')
         | 
| 322 386 | 
             
                end
         | 
| 323 387 |  | 
| 324 | 
            -
                 | 
| 325 | 
            -
                 | 
| 326 | 
            -
                 | 
| 327 | 
            -
                 | 
| 388 | 
            +
                # Net::IMAP supports using AUTHENTICATE with LOGIN, PLAIN, and
         | 
| 389 | 
            +
                # CRAM-MD5... if the server reports a different AUTH method, then we
         | 
| 390 | 
            +
                # should fall back to using LOGIN
         | 
| 391 | 
            +
                if %w( LOGIN PLAIN CRAM-MD5 XOAUTH2 ).include?( auth.upcase )
         | 
| 392 | 
            +
                  auth = auth.upcase
         | 
| 393 | 
            +
                  log "Trying #{auth} authentication"
         | 
| 394 | 
            +
                  res = imap.authenticate auth, username, password
         | 
| 395 | 
            +
                  log "Logged in as #{username} using AUTHENTICATE"
         | 
| 396 | 
            +
                else
         | 
| 397 | 
            +
                  log "Trying to authenticate via LOGIN"
         | 
| 398 | 
            +
                  res = imap.login username, password
         | 
| 399 | 
            +
                  log "Logged in as #{username} using LOGIN"
         | 
| 400 | 
            +
                end
         | 
| 401 | 
            +
             | 
| 402 | 
            +
                # CAPABILITY may have changed
         | 
| 403 | 
            +
                capabilities = capability imap, res
         | 
| 404 | 
            +
             | 
| 405 | 
            +
                connection = Connection.new imap, capabilities
         | 
| 406 | 
            +
             | 
| 407 | 
            +
                if block_given? then
         | 
| 408 | 
            +
                  begin
         | 
| 409 | 
            +
                    yield connection
         | 
| 410 | 
            +
                  ensure
         | 
| 411 | 
            +
                    connection.imap.logout
         | 
| 412 | 
            +
                  end
         | 
| 413 | 
            +
                else
         | 
| 414 | 
            +
                  return connection
         | 
| 415 | 
            +
                end
         | 
| 416 | 
            +
              end
         | 
| 417 | 
            +
             | 
| 418 | 
            +
              ##
         | 
| 419 | 
            +
              # Create the mailbox +name+ if it doesn't exist.  Note that this will SELECT
         | 
| 420 | 
            +
              # the mailbox if it exists.
         | 
| 421 | 
            +
             | 
| 422 | 
            +
              def create_mailbox name
         | 
| 423 | 
            +
                log "LIST #{name}"
         | 
| 424 | 
            +
                list = imap.list '', name
         | 
| 425 | 
            +
                return if list
         | 
| 426 | 
            +
                log "CREATE #{name}"
         | 
| 427 | 
            +
                imap.create name unless noop?
         | 
| 428 | 
            +
              end
         | 
| 328 429 |  | 
| 329 | 
            -
             | 
| 430 | 
            +
              ##
         | 
| 431 | 
            +
              # Delete and +expunge+ the specified +uids+.
         | 
| 432 | 
            +
             | 
| 433 | 
            +
              def delete_messages uids, expunge = true
         | 
| 434 | 
            +
                log "DELETING [...#{uids.size} uids]"
         | 
| 435 | 
            +
                imap.store uids, '+FLAGS.SILENT', [:Deleted] unless noop?
         | 
| 436 | 
            +
                if expunge then
         | 
| 437 | 
            +
                  log "EXPUNGE"
         | 
| 438 | 
            +
                  imap.expunge unless noop?
         | 
| 439 | 
            +
                end
         | 
| 330 440 | 
             
              end
         | 
| 331 441 |  | 
| 332 442 | 
             
              ##
         | 
| @@ -347,8 +457,6 @@ Example ~/.#{opts_file_name}: | |
| 347 457 | 
             
                uids = []
         | 
| 348 458 |  | 
| 349 459 | 
             
                each_part parts, true do |uid, message|
         | 
| 350 | 
            -
                  skip = false
         | 
| 351 | 
            -
             | 
| 352 460 | 
             
                  mail = TMail::Mail.parse message
         | 
| 353 461 |  | 
| 354 462 | 
             
                  begin
         | 
| @@ -379,7 +487,7 @@ Example ~/.#{opts_file_name}: | |
| 379 487 | 
             
                  sequence.unshift "BODY[#{section}.MIME]" unless section == 'TEXT'
         | 
| 380 488 | 
             
                  sequence.unshift 'BODY[HEADER]' if header
         | 
| 381 489 |  | 
| 382 | 
            -
                  body =  | 
| 490 | 
            +
                  body = imap.fetch(uid, sequence).first
         | 
| 383 491 |  | 
| 384 492 | 
             
                  sequence = sequence.map { |item| body.attr[item] }
         | 
| 385 493 |  | 
| @@ -411,7 +519,7 @@ Example ~/.#{opts_file_name}: | |
| 411 519 | 
             
              def mime_parts(uids, mime_type)
         | 
| 412 520 | 
             
                media_type, subtype = mime_type.upcase.split('/', 2)
         | 
| 413 521 |  | 
| 414 | 
            -
                structures =  | 
| 522 | 
            +
                structures = imap.fetch uids, 'BODYSTRUCTURE'
         | 
| 415 523 |  | 
| 416 524 | 
             
                structures.zip(uids).map do |body, uid|
         | 
| 417 525 | 
             
                  section = nil
         | 
| @@ -436,6 +544,43 @@ Example ~/.#{opts_file_name}: | |
| 436 544 | 
             
                end.compact
         | 
| 437 545 | 
             
              end
         | 
| 438 546 |  | 
| 547 | 
            +
              ##
         | 
| 548 | 
            +
              # Move the specified +uids+ to a new +destination+ then delete and +expunge+
         | 
| 549 | 
            +
              # them.  Creates the destination mailbox if it doesn't exist.
         | 
| 550 | 
            +
             | 
| 551 | 
            +
              def move_messages uids, destination, expunge = true
         | 
| 552 | 
            +
                return if uids.empty?
         | 
| 553 | 
            +
                log "COPY [...#{uids.size} uids]"
         | 
| 554 | 
            +
             | 
| 555 | 
            +
                begin
         | 
| 556 | 
            +
                  imap.copy uids, destination unless noop?
         | 
| 557 | 
            +
                rescue Net::IMAP::NoResponseError
         | 
| 558 | 
            +
                  unless noop? then
         | 
| 559 | 
            +
                    create_mailbox destination
         | 
| 560 | 
            +
                    imap.copy uids, destination
         | 
| 561 | 
            +
                  end
         | 
| 562 | 
            +
                end
         | 
| 563 | 
            +
             | 
| 564 | 
            +
                delete_messages uids, expunge
         | 
| 565 | 
            +
              end
         | 
| 566 | 
            +
             | 
| 567 | 
            +
              ##
         | 
| 568 | 
            +
              # Displays Date, Subject and Message-Id from messages in +uids+
         | 
| 569 | 
            +
             | 
| 570 | 
            +
              def show_messages(uids)
         | 
| 571 | 
            +
                return if uids.nil? or (Array === uids and uids.empty?)
         | 
| 572 | 
            +
             | 
| 573 | 
            +
                fetch_data = 'BODY.PEEK[HEADER.FIELDS (DATE FROM TO SUBJECT)]'
         | 
| 574 | 
            +
                messages = imap.fetch uids, fetch_data
         | 
| 575 | 
            +
                fetch_data.sub! '.PEEK', '' # stripped by server
         | 
| 576 | 
            +
             | 
| 577 | 
            +
                messages ||= []
         | 
| 578 | 
            +
             | 
| 579 | 
            +
                messages.each do |res|
         | 
| 580 | 
            +
                  puts res.attr[fetch_data].delete("\r").gsub(/^/, "  ")
         | 
| 581 | 
            +
                end
         | 
| 582 | 
            +
              end
         | 
| 583 | 
            +
             | 
| 439 584 | 
             
              ##
         | 
| 440 585 | 
             
              # Did the user set --verbose?
         | 
| 441 586 |  | 
| @@ -443,5 +588,10 @@ Example ~/.#{opts_file_name}: | |
| 443 588 | 
             
                @verbose
         | 
| 444 589 | 
             
              end
         | 
| 445 590 |  | 
| 446 | 
            -
             | 
| 591 | 
            +
              ##
         | 
| 592 | 
            +
              # Did the user set --noop?
         | 
| 447 593 |  | 
| 594 | 
            +
              def noop?
         | 
| 595 | 
            +
                options[:Noop]
         | 
| 596 | 
            +
              end
         | 
| 597 | 
            +
            end
         |