hulaki 0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +17 -7
- data/README.md +74 -20
- data/hulaki.gemspec +23 -16
- data/images/hulaki.png +0 -0
- data/lib/hulaki.rb +8 -76
- data/lib/hulaki/config/config.rb +22 -22
- data/lib/hulaki/config/sample.config.yml +34 -0
- data/lib/hulaki/config/sample.contact.csv +4 -0
- data/lib/hulaki/config/sample.template.html.erb +10 -0
- data/lib/hulaki/contact_parser.rb +9 -13
- data/lib/hulaki/core.rb +90 -0
- data/lib/hulaki/{email_validator.rb → email_handler/email_validator.rb} +3 -3
- data/lib/hulaki/email_handler/mailer.rb +73 -0
- data/lib/hulaki/option_parser.rb +77 -67
- data/lib/hulaki/presenter.rb +15 -9
- data/lib/hulaki/recursive_ostruct.rb +12 -0
- data/lib/hulaki/search_engine.rb +19 -10
- data/lib/hulaki/sms_handler/gateway_adapters/nexmo.rb +21 -0
- data/lib/hulaki/sms_handler/gateway_adapters/twilio.rb +8 -19
- data/lib/hulaki/sms_handler/sms_handler.rb +23 -14
- data/lib/hulaki/sms_handler/sms_validator.rb +3 -8
- data/lib/hulaki/string_modifier.rb +4 -0
- data/lib/hulaki/utils.rb +76 -0
- data/lib/hulaki/version.rb +1 -1
- metadata +79 -19
- data/lib/hulaki/config/config_sample.yml +0 -25
- data/lib/hulaki/mailer.rb +0 -59
@@ -1,22 +1,18 @@
|
|
1
1
|
class Hulaki::ContactParser
|
2
|
-
|
3
|
-
|
2
|
+
DEFAULT = File.expand_path('~/hulaki/contact.csv')
|
3
|
+
@@default_file_path = DEFAULT
|
4
4
|
|
5
5
|
def perform
|
6
|
-
parse
|
7
|
-
end
|
8
|
-
|
9
|
-
private
|
10
|
-
def parse
|
11
6
|
options = {:strings_as_keys => true, :downcase_header => true}
|
12
|
-
SmarterCSV.process(
|
7
|
+
SmarterCSV.process(@@default_file_path, options)
|
13
8
|
rescue Errno::ENOENT
|
14
|
-
|
15
|
-
puts @file_path
|
16
|
-
nil
|
9
|
+
raise Hulaki::InvalidFilePath
|
17
10
|
end
|
18
11
|
|
19
|
-
|
20
|
-
|
12
|
+
private
|
13
|
+
class << self
|
14
|
+
def default_file_path=(path)
|
15
|
+
@@default_file_path = File.expand_path(path)
|
16
|
+
end
|
21
17
|
end
|
22
18
|
end
|
data/lib/hulaki/core.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'clipboard'
|
2
|
+
|
3
|
+
class Hulaki::Core
|
4
|
+
def initialize(config)
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def perform
|
9
|
+
if @config.search_keyword
|
10
|
+
handle_search_and_clipboard
|
11
|
+
else
|
12
|
+
handle_messaging
|
13
|
+
end
|
14
|
+
rescue CSV::MalformedCSVError
|
15
|
+
puts 'Your contact.csv file is invalid or has invalid/malformed UTF characters.'.red
|
16
|
+
puts 'Please use a valid CSV file.'.red
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def handle_messaging
|
21
|
+
puts '~' * 100
|
22
|
+
puts 'Welcome to Hulaki : Your best companion! to make your day great.'
|
23
|
+
puts '~' * 100
|
24
|
+
|
25
|
+
# It supports multiple recipients like two phonenumbers separated by commas `,`
|
26
|
+
@config.to.each do |recipient|
|
27
|
+
if is_phone?(recipient)
|
28
|
+
handle_sms(recipient)
|
29
|
+
elsif is_email?(recipient)
|
30
|
+
handle_email(recipient)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
puts '~' * 100
|
35
|
+
puts @config.message
|
36
|
+
puts '~' * 100
|
37
|
+
end
|
38
|
+
|
39
|
+
def is_phone?(number)
|
40
|
+
Hulaki::SmsValidator.is_phone_number?(number)
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_email?(email)
|
44
|
+
Hulaki::EmailValidator.is_email?(email)
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_email(recipient)
|
48
|
+
puts "Sending email to #{recipient}"
|
49
|
+
email_handler = Hulaki::Mailer.new(
|
50
|
+
{
|
51
|
+
:to => recipient,
|
52
|
+
:message => @config.message,
|
53
|
+
:subject => @config.subject,
|
54
|
+
:from => @config.from
|
55
|
+
}).deliver
|
56
|
+
puts "Email sent to `#{recipient}`. from `#{get_sender}`"
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_sms(recipient)
|
60
|
+
puts "Sending SMS to #{recipient}"
|
61
|
+
sms_handler = Hulaki::SmsHandler.new(
|
62
|
+
{
|
63
|
+
:to => recipient,
|
64
|
+
:message => @config.message,
|
65
|
+
:gateway => @config.gateway,
|
66
|
+
:from => @config.from
|
67
|
+
})
|
68
|
+
if sms_handler.send
|
69
|
+
puts "SMS sent successfully to #{recipient} using `#{sms_handler.gateway.class}` gateway"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_sender
|
74
|
+
@config[:from] || Hulaki::Config['email']['from']
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_search_and_clipboard
|
78
|
+
puts
|
79
|
+
response = Hulaki::SearchEngine.new.perform(@config.search_keyword)
|
80
|
+
Hulaki::Presenter.new(response).display if response
|
81
|
+
|
82
|
+
if @config.copy_phone_number
|
83
|
+
number = response[0][0]['phone_1___value'].gsub(' ', '')
|
84
|
+
Clipboard.copy number
|
85
|
+
puts "Number '#{number.underline}' is copied to your clipboard"
|
86
|
+
puts
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -2,10 +2,10 @@ class Hulaki::EmailValidator
|
|
2
2
|
EmailRegex = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
|
3
3
|
|
4
4
|
def initialize(options = {})
|
5
|
-
@sender_email
|
5
|
+
@sender_email = options[:from]
|
6
6
|
@reciepient_email = options[:to]
|
7
|
-
@message
|
8
|
-
@errors
|
7
|
+
@message = options[:message]
|
8
|
+
@errors = {}
|
9
9
|
end
|
10
10
|
|
11
11
|
def validates_presence
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'mail'
|
2
|
+
|
3
|
+
class Hulaki::Mailer
|
4
|
+
attr_reader :params, :config
|
5
|
+
|
6
|
+
TEMPLATE_PATH = ENV['template_path'] || '~/hulaki/template.html.erb'
|
7
|
+
|
8
|
+
def initialize(params={})
|
9
|
+
@params = params
|
10
|
+
@config = Hulaki::Config["email"]
|
11
|
+
end
|
12
|
+
|
13
|
+
def deliver
|
14
|
+
# Fixme: validation has no purpose for the time being
|
15
|
+
validate(@reciever, config["from"], params[:message])
|
16
|
+
configure_defaults
|
17
|
+
prepare_email
|
18
|
+
@mail.deliver
|
19
|
+
"sent"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def prepare_email
|
24
|
+
email_body = params[:message]
|
25
|
+
|
26
|
+
# sender set from command-line will take precedence over that in `config.yml`
|
27
|
+
from = params[:from] || config["from"]
|
28
|
+
to = params[:to]
|
29
|
+
subject = params[:subject]
|
30
|
+
|
31
|
+
if config['use_template'] == true
|
32
|
+
content = ERB.new(File.read(File.expand_path(TEMPLATE_PATH))).result(binding)
|
33
|
+
else
|
34
|
+
content = email_body
|
35
|
+
end
|
36
|
+
|
37
|
+
@mail = Mail.new do
|
38
|
+
to to
|
39
|
+
from from
|
40
|
+
subject subject
|
41
|
+
|
42
|
+
html_part do
|
43
|
+
content_type 'text/html; charset=UTF-8'
|
44
|
+
body content
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def configure_defaults
|
50
|
+
# instance variables are not available inside the block below; so
|
51
|
+
# localizing the variable
|
52
|
+
env = @config
|
53
|
+
delivery_mode = ENV['mode'] == 'test' ? 'test' : :smtp
|
54
|
+
Mail.defaults do
|
55
|
+
delivery_method delivery_mode,
|
56
|
+
{
|
57
|
+
:address => env["address"],
|
58
|
+
:port => env["port"],
|
59
|
+
:domain => env["domain"],
|
60
|
+
:user_name => env["user_name"],
|
61
|
+
:password => env["password"],
|
62
|
+
:authentication => env["authentication"],
|
63
|
+
:enable_starttls_auto => true
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# returns [Hash] Error
|
69
|
+
def validate(reciever, sender, message)
|
70
|
+
validator = Hulaki::EmailValidator.new(from: reciever, to: sender, message: message)
|
71
|
+
validator.validates_format && validator.validates_presence
|
72
|
+
end
|
73
|
+
end
|
data/lib/hulaki/option_parser.rb
CHANGED
@@ -1,16 +1,18 @@
|
|
1
1
|
require_relative 'recursive_ostruct'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'optparse'
|
4
|
+
# require File.expand_path('../string_modifier', __FILE__)
|
4
5
|
require_relative 'presenter'
|
5
6
|
class Hulaki::OptionParser
|
6
7
|
def initialize
|
7
8
|
@config = RecursiveOstruct.ostruct(
|
8
9
|
{
|
9
10
|
to: [],
|
10
|
-
from:
|
11
|
+
from: nil,
|
11
12
|
subject: 'Mic testing',
|
12
13
|
message: 'sample message',
|
13
|
-
command: 'help'
|
14
|
+
command: 'help',
|
15
|
+
gateway: nil
|
14
16
|
})
|
15
17
|
end
|
16
18
|
|
@@ -21,96 +23,104 @@ class Hulaki::OptionParser
|
|
21
23
|
|
22
24
|
def options
|
23
25
|
OptionParser.new do |opts|
|
24
|
-
opts.banner =
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
opts.banner =
|
27
|
+
"Usage: \n"\
|
28
|
+
"------- Search --------\n"\
|
29
|
+
"$ hulaki -s search-string\n"\
|
30
|
+
"# Example: Hulaki features fuzzy search\n"\
|
31
|
+
"$ hulaki -s smithjohn\n"\
|
32
|
+
"$ hulaki -s johsmith\n"\
|
33
|
+
"$ hulaki -s smijohnth\n"\
|
34
|
+
"\n"\
|
35
|
+
"------- Copy PhoneNumber to ClipBoard -----------\n"\
|
36
|
+
"$ hulaki -s smithjohn -c \n"\
|
37
|
+
"# You will see phone_number of the top result copied to ClipBoard \n"\
|
38
|
+
"# Number '+97798xxx66455' is copied to your clipboard\n"\
|
39
|
+
"\n"\
|
40
|
+
"------- SMS --------\n"\
|
41
|
+
"$ hulaki -t +977xxxxxxxxxx -m \"Message to be sent\"\n"\
|
42
|
+
"\n"\
|
43
|
+
"# You can even select a specific SMS Gateway\n"\
|
44
|
+
"$ hulaki -t +977xxxxxxxxxx -m \"Message to be sent\" -g nexmo\n"\
|
45
|
+
"$ hulaki -t +61xxxxxxxxxx -m \"Message to be sent\" -g twilio\n"\
|
46
|
+
"$ hulaki -t +1xxxxxxxxxx -m \"Message to be sent\" -g sparrow\n"\
|
47
|
+
"\n"\
|
48
|
+
"# You can even broadcast SMSes selecting a specific SMS Gateway\n"\
|
49
|
+
"# However, keep in mind that multiple SMS request will go to the server\n"\
|
50
|
+
"$ hulaki -t +977xxxxxxxxxx,+9779832xxxxxx -m \"Message to be sent\" -g nexmo\n"\
|
51
|
+
"\n"\
|
52
|
+
"# You can also change the name that appears on recipient's Phone using `-f` switch. This only works with Nexmo\n"\
|
53
|
+
"$ hulaki -t +977xxxxxxxxxx,+9779832xxxxxx -m \"Message to be sent\" -g nexmo -f \"Hero Dai!\"\n"\
|
54
|
+
"\n"\
|
55
|
+
"------- EMAIL --------\n"\
|
56
|
+
"$ hulaki -t someone@example.com -S \"Subject of the email\" -m \"Message to be sent\"\n"\
|
57
|
+
"$ hulaki -t someone@example.com --subject \"Subject of the email\" -m \"Message to be sent\"\n"\
|
58
|
+
"\n"\
|
59
|
+
"# You can even broadcast emails, i.e. mutiple recipients\n"\
|
60
|
+
"# However, keep in mind that multiple SMTP request will go to the server. No `CC`, `BCC` will be used\n"\
|
61
|
+
"$ hulaki -t someone@example.com,nextperson@email.com --subject \"Subject of the email\" -m \"Message to be sent\"\n"\
|
62
|
+
"\n"\
|
63
|
+
"# You can also change your sender id using `-f` switch\n"\
|
64
|
+
"$ hulaki -t someone@example.com -S \"Subject of the email\" -m \"Message to be sent\" -f \"My Name<anonymous@example.com>\" \n"\
|
65
|
+
"\n"\
|
66
|
+
"------- EMAIL TEMPLATES --------\n"\
|
67
|
+
"# You are allowed to have an Email template in HTML format at `~/hulaki/template.html.erb` which\n"\
|
68
|
+
"# will be copied when you use `-i` switch. If you have `use_template` setting set to `true` then only\n"\
|
69
|
+
"# you will be able to use the template\n"\
|
70
|
+
"$ hulaki -t someone@example.com -S \"Subject of the email\" -m \"Messagopts.to_se to be sent\"\n"
|
71
|
+
|
72
|
+
|
37
73
|
|
38
74
|
opts.separator ''
|
39
75
|
opts.separator 'Specific options:'
|
40
76
|
|
41
|
-
|
42
|
-
|
77
|
+
# This can be list of emails or phonenumbers separated by commas `,`
|
78
|
+
opts.on('-t x,y,z', '--to x,y,z', Array, 'list of recipient, can be') do |recipient_list|
|
79
|
+
@config.to = recipient_list
|
43
80
|
end
|
44
81
|
|
45
|
-
opts.on('-m [Message]', '--message [Message]', String, 'Message to be sent to recipient') do |
|
46
|
-
@config.message =
|
82
|
+
opts.on('-m [Message]', '--message [Message]', String, 'Message to be sent to recipient') do |message|
|
83
|
+
@config.message = message
|
47
84
|
end
|
85
|
+
"\n"\
|
48
86
|
|
49
|
-
opts.on('-S [Subject]', '--subject [Subject]', String, 'Subject to email') do |
|
50
|
-
@config.subject =
|
87
|
+
opts.on('-S [Subject]', '--subject [Subject]', String, 'Subject to email') do |subject|
|
88
|
+
@config.subject = subject
|
51
89
|
end
|
52
90
|
|
53
|
-
opts.on('-
|
54
|
-
@config.
|
91
|
+
opts.on('-c', '--copy', nil, 'Copy phone-number at top to ClipBoard; Linux users need to install `xclip`.') do
|
92
|
+
@config.copy_phone_number = true
|
55
93
|
end
|
56
94
|
|
57
|
-
opts.on('-
|
58
|
-
|
59
|
-
|
60
|
-
|
95
|
+
opts.on('-g [Gateway Name]', '--gateway [Gateway Name]', String, 'Name of the gateway, `nexmo`, `twilio`, `sparrow` are currently supported') do |gateway|
|
96
|
+
@config.gateway = gateway
|
97
|
+
end
|
98
|
+
|
99
|
+
opts.on('-f [Sender]', '--from [Sender]', String, "name <email> | PhoneNumber; this will take precedence over `from` in `config.yml`.") do |sender|
|
100
|
+
@config.from = sender
|
101
|
+
end
|
102
|
+
|
103
|
+
opts.on('-s [name/contact]', '--search [name/contact]', String, 'Search keyword') do |search_keyword|
|
104
|
+
@config.search_keyword = search_keyword
|
61
105
|
end
|
62
106
|
|
63
107
|
# ----------------------------------------------------------------------
|
64
108
|
opts.on('-h', '--help', 'Help / Examples') do
|
65
|
-
|
109
|
+
puts Utils.present(opts)
|
66
110
|
exit
|
67
111
|
end
|
68
112
|
|
69
113
|
opts.on('-l', '--list', 'list all the options available') do
|
70
|
-
puts opts
|
114
|
+
puts Utils.present(opts)
|
71
115
|
exit
|
72
116
|
end
|
73
117
|
|
74
|
-
opts.on('-i', '--install', 'Creates ~/hulaki/config.yml') do
|
75
|
-
|
76
|
-
|
118
|
+
opts.on('-i', '--install', 'Creates ~/hulaki/config.yml, `template.html.erb`. Will ask you if have to replace them') do
|
119
|
+
Utils.install_dependencies
|
120
|
+
Utils.create_dir
|
121
|
+
Utils.start_copying_file
|
77
122
|
exit
|
78
123
|
end
|
79
124
|
end
|
80
125
|
end
|
81
|
-
|
82
|
-
def create_dir
|
83
|
-
FileUtils.mkdir(File.expand_path('~/hulaki'))
|
84
|
-
rescue Errno::EEXIST
|
85
|
-
puts 'Directory already exists.'
|
86
|
-
end
|
87
|
-
|
88
|
-
def start_copying_file
|
89
|
-
this_file = __FILE__
|
90
|
-
file_path = File.expand_path('../../lib/hulaki/config/config_sample.yml',
|
91
|
-
File.dirname(this_file))
|
92
|
-
desc_file = File.expand_path('~/hulaki/config.yml')
|
93
|
-
if File.exist?(desc_file)
|
94
|
-
puts "Looks like the file '#{desc_file}' already exists."
|
95
|
-
puts 'shall we forcefully override the file?(yes/no)'
|
96
|
-
handle_conflict(file_path, desc_file)
|
97
|
-
else
|
98
|
-
copy_file(file_path, desc_file)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
def handle_conflict(file_path, desc_file)
|
103
|
-
input = gets().chomp()
|
104
|
-
if %w{yes y}.include?(input.downcase)
|
105
|
-
copy_file(file_path, desc_file)
|
106
|
-
else
|
107
|
-
puts 'You choose to leave it as it is.'
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
def copy_file(file_path, desc_file)
|
112
|
-
puts "Creating file '~/hulaki/config.yml' ..."
|
113
|
-
FileUtils.cp(file_path, desc_file)
|
114
|
-
puts "Created file '~/hulaki/config.yml' ..."
|
115
|
-
end
|
116
126
|
end
|
data/lib/hulaki/presenter.rb
CHANGED
@@ -1,17 +1,23 @@
|
|
1
|
+
require 'terminal-table'
|
1
2
|
class Hulaki::Presenter
|
2
3
|
def initialize(data)
|
3
|
-
|
4
|
+
no_of_results = Hulaki::Config['search'] && Hulaki::Config['search']['no_of_results'] || 10
|
5
|
+
@data = data[0..(no_of_results - 1)]
|
4
6
|
end
|
5
7
|
|
6
8
|
def display
|
7
|
-
|
8
|
-
|
9
|
-
puts '~' * 100
|
10
|
-
@data[0..5].each do |row|
|
11
|
-
email = row.fetch('e_mail_1___value', 'N/A') rescue 'N/A'
|
12
|
-
phone_number = row.fetch('phone_1___value', 'N/A') rescue 'N/A'
|
13
|
-
puts "name: #{row['name']}, phone: #{phone_number}, email: #{email}" rescue nil
|
9
|
+
data = @data.map(&:first).map.with_index do |row, index|
|
10
|
+
[index+1, row['name'].bold, row['phone_1___value'].to_s.green, row['phone_2___value'], row['email']]
|
14
11
|
end
|
15
|
-
|
12
|
+
|
13
|
+
table = Terminal::Table.new :title => "Hulaki #{Hulaki::VERSION}",
|
14
|
+
:headings => ['S.N', 'Name', 'Phone 1', 'Phone 2', 'Email'],
|
15
|
+
:rows => data
|
16
|
+
|
17
|
+
table.align_column(2, :right)
|
18
|
+
table.align_column(3, :right)
|
19
|
+
table.align_column(4, :right)
|
20
|
+
|
21
|
+
puts table
|
16
22
|
end
|
17
23
|
end
|
@@ -1,4 +1,16 @@
|
|
1
1
|
require 'ostruct'
|
2
|
+
# = RecursiveOstruct, a tool to convert every hash inside a Hash/Array to OpenStruct object
|
3
|
+
# One of the benifit is, it will respond to very sort of messages like
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# newHash = {'a' => 12}
|
7
|
+
# wrappedHash = RecursiveOstruct.new(newHash)
|
8
|
+
# wrappedHash.a # => 12 : a valid call
|
9
|
+
# wrappedHash['a'] # => 12 : a valid call
|
10
|
+
# wrappedHash[:a] # => 12 : a valid call
|
11
|
+
#
|
12
|
+
# It is a option for those who do not want to use Rails::ActiveSupport's `HashWithIndifferentAccess`
|
13
|
+
#
|
2
14
|
class RecursiveOstruct
|
3
15
|
def self.ostruct(object)
|
4
16
|
if object.is_a?(Hash)
|