mail_room 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ log
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p362
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mail_room.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Tony Pitale
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # mail_room #
2
+
3
+ mail_room is a configuration based process that will idle on IMAP connections and POST to a delivery URL whenever a new message is received on the configured mailbox and folder.
4
+
5
+ ## Installation ##
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'mail_room'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install mail_room
18
+
19
+ ## Usage ##
20
+
21
+ bin/mail_room -f /path/to/config.yml -d
22
+
23
+ ## Configuration ##
24
+
25
+ ---
26
+ :mailboxes:
27
+ -
28
+ :email: "user1@gmail.com"
29
+ :password: "password"
30
+ :name: "inbox"
31
+ :delivery_url: "http://localhost:3000/inbox"
32
+ :delivery_token: "abcdefg"
33
+ -
34
+ :email: "user2@gmail.com"
35
+ :password: "password"
36
+ :name: "inbox"
37
+ :delivery_url: "http://localhost:3000/inbox"
38
+ :delivery_token: "abcdefg"
39
+
40
+ ## Dependencies ##
41
+
42
+ * celluloid
43
+
44
+ ## Contributing ##
45
+
46
+ 1. Fork it
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Pull Request
51
+ 6. If accepted, ask for commit rights
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/mail_room ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby -Ilib
2
+
3
+ require 'mail_room'
4
+
5
+ MailRoom::CLI.new(ARGV).run
@@ -0,0 +1,130 @@
1
+ module MailRoom
2
+ class CLI
3
+ attr_accessor :configuration
4
+
5
+ def initialize(args)
6
+ options = {}
7
+
8
+ OptionParser.new do |parser|
9
+ parser.banner = [
10
+ "Usage: #{@name} [-c config_file]\n",
11
+ " #{@name} --help\n"
12
+ ].compact.join
13
+
14
+ parser.on('-c', '--config FILE') do |path|
15
+ options[:config_path] = path
16
+ end
17
+
18
+ parser.on("-d", "--daemon", "Daemonize mode") do |v|
19
+ options[:daemonize] = v
20
+ end
21
+
22
+ parser.on("-l", "--log FILE") do |path|
23
+ options[:log_path] = path
24
+ end
25
+
26
+ parser.on("-p", "--pid FILE") do |path|
27
+ options[:pid_path] = path
28
+ end
29
+
30
+ parser.on_tail("-?", "--help", "Display this usage information.") do
31
+ puts "#{parser}\n"
32
+ exit
33
+ end
34
+ end.parse!(args)
35
+
36
+ self.configuration = Configuration.new(options)
37
+ end
38
+
39
+ def run
40
+ if configuration.daemonize?
41
+ daemonize
42
+ else
43
+ start
44
+ end
45
+ end
46
+
47
+ def running?
48
+ @running
49
+ end
50
+
51
+ def daemonize
52
+ exit if fork
53
+ Process.setsid
54
+ exit if fork
55
+ store_pid(Process.pid)
56
+ File.umask 0000
57
+ redirect_output!
58
+
59
+ start
60
+
61
+ clean_pid
62
+ end
63
+
64
+ def start
65
+ @running = true
66
+
67
+ @coordinator ||= Coordinator.new(configuration.mailboxes)
68
+ @coordinator.run
69
+
70
+ Signal.trap(:INT) do
71
+ stop
72
+ end
73
+
74
+ Signal.trap(:TERM) do
75
+ exit
76
+ end
77
+
78
+ while(running?) do; sleep 1; end
79
+ end
80
+
81
+ def stop
82
+ return unless @coordinator
83
+
84
+ @coordinator.quit
85
+
86
+ @running = false
87
+ end
88
+
89
+ def store_pid(pid)
90
+ pid_path = configuration.pid_path
91
+
92
+ puts pid_path
93
+
94
+ FileUtils.mkdir_p(File.dirname(pid_path))
95
+ File.open(pid_path, 'w'){|f| f.write("#{pid}\n")}
96
+ end
97
+
98
+ def clean_pid
99
+ pid_path = configuration.pid_path
100
+
101
+ begin
102
+ FileUtils.rm pid_path if File.exists?(pid_path)
103
+ rescue => e
104
+ end
105
+ end
106
+
107
+
108
+ def redirect_output!
109
+ if log_path = configuration.log_path
110
+ # if the log directory doesn't exist, create it
111
+ FileUtils.mkdir_p File.dirname(log_path), :mode => 0755
112
+ # touch the log file to create it
113
+ FileUtils.touch log_path
114
+ # Set permissions on the log file
115
+ File.chmod(0644, log_path)
116
+ # Reopen $stdout (NOT +STDOUT+) to start writing to the log file
117
+ $stdout.reopen(log_path, 'a')
118
+ # Redirect $stderr to $stdout
119
+ $stderr.reopen $stdout
120
+ $stdout.sync = true
121
+ else # redirect to /dev/null
122
+ # We're not bothering to sync if we're dumping to /dev/null
123
+ # because /dev/null doesn't care about buffered output
124
+ $stdin.reopen '/dev/null'
125
+ $stdout.reopen '/dev/null', 'a'
126
+ $stderr.reopen $stdout
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,24 @@
1
+ module MailRoom
2
+ class Configuration
3
+ attr_accessor :mailboxes, :daemonize, :log_path, :pid_path
4
+
5
+ def initialize(options={})
6
+ config_file = YAML.load_file(options[:config_path]) if options.has_key?(:config_path)
7
+
8
+ self.mailboxes = []
9
+ load_mailboxes(config_file[:mailboxes])
10
+
11
+ self.daemonize = options.fetch(:daemonize, false)
12
+ self.log_path = options.fetch(:log_path, nil)
13
+ self.pid_path = options.fetch(:pid_path, "/var/run/mail_room.pid")
14
+ end
15
+
16
+ def load_mailboxes(mailboxes_config)
17
+ mailboxes_config.each do |attributes|
18
+ self.mailboxes << Mailbox.new(attributes)
19
+ end
20
+ end
21
+
22
+ alias :daemonize? :daemonize
23
+ end
24
+ end
@@ -0,0 +1,19 @@
1
+ module MailRoom
2
+ class Coordinator
3
+ attr_accessor :handlers
4
+
5
+ def initialize(mailboxes)
6
+ self.handlers = []
7
+
8
+ mailboxes.each {|mb| self.handlers << MessageHandler.new(mb)}
9
+ end
10
+
11
+ def run
12
+ handlers.each(&:run!)
13
+ end
14
+
15
+ def quit
16
+ handlers.each(&:quit!)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module MailRoom
2
+ Mailbox = Struct.new(:email, :password, :name, :delivery_url, :delivery_token)
3
+
4
+ class Mailbox
5
+ def initialize(attributes={})
6
+ super(*attributes.values_at(*members))
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,125 @@
1
+ module MailRoom
2
+ class MessageHandler
3
+ include Celluloid
4
+
5
+ def initialize(mailbox)
6
+ @mailbox = mailbox
7
+ @imap = Net::IMAP.new('imap.gmail.com', :port => 993, :ssl => true)
8
+
9
+ @running = false
10
+ @logged_in = false
11
+ @idling = false
12
+ end
13
+
14
+ def running?
15
+ @running
16
+ end
17
+
18
+ def logged_in?
19
+ @logged_in
20
+ end
21
+
22
+ def idling?
23
+ @idling
24
+ end
25
+
26
+ def run
27
+ setup
28
+
29
+ @running = true
30
+
31
+ @idling_thread = Thread.start do
32
+ while(running?) do
33
+ idle
34
+ fetch_new_messages
35
+ end
36
+ end
37
+ end
38
+
39
+ def setup
40
+ log_in
41
+ examine_mailbox
42
+ end
43
+
44
+ def log_in
45
+ @imap.login(@mailbox.email, @mailbox.password)
46
+ # @imap.authenticate('XOAUTH2', @mailbox["email"], @mailbox["token"])
47
+ @logged_in = true
48
+ end
49
+
50
+ def examine_mailbox
51
+ return unless logged_in?
52
+
53
+ @imap.select(@mailbox.name)
54
+ end
55
+
56
+ def fetch_new_messages
57
+ return if idling? || !running?
58
+
59
+ new_messages.each do |msg|
60
+ puts msg.attr['RFC822']
61
+ # post_message(msg)
62
+ end
63
+ end
64
+
65
+ def new_messages
66
+ messages_for_ids(new_message_ids)
67
+ end
68
+
69
+ # def label_message_with(id, lbl)
70
+ # in_current_fiber do |f|
71
+ # @imap.store(id, "+X-GM-LABELS", [lbl]).errback {f.resume}.callback {f.resume}
72
+ # end
73
+ # end
74
+
75
+ def new_message_ids
76
+ @imap.search('UNSEEN')
77
+ end
78
+
79
+ def messages_for_ids(ids)
80
+ return [] if ids.empty?
81
+
82
+ @imap.fetch(ids, "RFC822")
83
+ end
84
+
85
+ def post_message(msg)
86
+ # connection = Faraday.new
87
+ # connection.token_auth @mailbox["delivery_token"]
88
+
89
+ # connection.post do |request|
90
+ # request.url @mailbox["delivery_url"]
91
+ # request.options[:timeout] = 3
92
+ # request.headers['Content-Type'] = 'application/json'
93
+ # request.body = msg
94
+ # end
95
+ end
96
+
97
+ def idle
98
+ return unless logged_in?
99
+
100
+ @idling = true
101
+
102
+ @imap.idle do |response|
103
+ if response.respond_to?(:name) && response.name == 'EXISTS'
104
+ @imap.idle_done
105
+ end
106
+ end
107
+
108
+ @idling = false
109
+ end
110
+
111
+ def stop_idling
112
+ return unless idling?
113
+
114
+ @imap.idle_done
115
+ @idling_thread.join
116
+ @idling = false
117
+ end
118
+
119
+ def quit
120
+ @running = false
121
+ puts "Quitting"
122
+ stop_idling
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,3 @@
1
+ module MailRoom
2
+ VERSION = "0.0.1"
3
+ end
data/lib/mail_room.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'celluloid'
2
+ require 'net/imap'
3
+ require 'fileutils'
4
+ require 'optparse'
5
+ require 'yaml'
6
+
7
+ module MailRoom
8
+ end
9
+
10
+ require "mail_room/version"
11
+ require "mail_room/configuration"
12
+ require "mail_room/mailbox"
13
+ require "mail_room/message_handler"
14
+ require "mail_room/coordinator"
15
+ require "mail_room/cli"
data/mail_room.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mail_room/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "mail_room"
8
+ gem.version = MailRoom::VERSION
9
+ gem.authors = ["Tony Pitale"]
10
+ gem.email = ["tpitale@gmail.com"]
11
+ gem.description = %q{mail_room will proxy email (gmail) from IMAP to a callback URL}
12
+ gem.summary = %q{mail_room will proxy email (gmail) from IMAP to a callback URL}
13
+ gem.homepage = "http://github.com/tpitale/mail_room"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'celluloid'
21
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail_room
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tony Pitale
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: celluloid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: mail_room will proxy email (gmail) from IMAP to a callback URL
31
+ email:
32
+ - tpitale@gmail.com
33
+ executables:
34
+ - mail_room
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - .gitignore
39
+ - .ruby-version
40
+ - Gemfile
41
+ - LICENSE.txt
42
+ - README.md
43
+ - Rakefile
44
+ - bin/mail_room
45
+ - lib/mail_room.rb
46
+ - lib/mail_room/cli.rb
47
+ - lib/mail_room/configuration.rb
48
+ - lib/mail_room/coordinator.rb
49
+ - lib/mail_room/mailbox.rb
50
+ - lib/mail_room/message_handler.rb
51
+ - lib/mail_room/version.rb
52
+ - mail_room.gemspec
53
+ homepage: http://github.com/tpitale/mail_room
54
+ licenses: []
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ! '>='
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 1.8.23
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: mail_room will proxy email (gmail) from IMAP to a callback URL
77
+ test_files: []