hulaki 0.1 → 1.0.2
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 +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)
|