lib/rumble/cli.rb
# frozen_string_literal: true
# Copyright (c) 2018-2022 Yegor Bugayenko
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the 'Software'), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
require 'English'
require 'mail'
require 'uuidtools'
require 'liquid'
require 'csv'
require 'tmpdir'
require 'redcarpet'
require 'redcarpet/render_strip'
require 'rainbow'
require_relative 'version'
# Rumble main script.
# Author:: Yegor Bugayenko (yegor256@gmail.com)
# Copyright:: Copyright (c) 2018-2022 Yegor Bugayenko
# License:: MIT
class Rumble::CLI
# Make an instance.
def initialize(opts)
@opts = opts
end
# Send a letter, reading options from the opts.
def send
letter = Liquid::Template.parse(
File.read(File.expand_path(@opts[:letter]))
)
skip = @opts[:skip] ? File.readlines(@opts[:skip]).map(&:strip) : []
if @opts[:test]
rcpt = []
rcpt[@opts['col-first'].to_i] = 'John'
rcpt[@opts['col-last'].to_i] = 'Doe'
rcpt[@opts['col-email'].to_i] = @opts[:test]
emails = [rcpt]
else
raise '--csv is required' unless @opts[:csv]
emails = CSV.read(@opts[:csv])
end
total = 0
sent = []
ignore = !@opts[:resume].nil? && !@opts[:test]
from = @opts[:from].strip
puts "Sending #{emails.length} email(s) as #{from}"
domain = from.strip.gsub(/^.+@|>$/)
emails.each do |array|
email = array[@opts['col-email'].to_i]
unless email
puts "Email is #{Rainbow('absent').red} \
at the column ##{@opts['col-email'].to_i}: #{array}"
next
end
email = email.strip.downcase
if sent.include?(email)
puts "#{Rainbow('Duplicate').red} at: #{array}"
next
end
sent.push(email)
first = (array[@opts['col-first'].to_i] || '').strip
last = (array[@opts['col-last'].to_i] || '').strip
first, last = first.split(' ', 2) if last.empty? && first.include?(' ')
name = "#{first.strip} #{last.strip}".strip
address = email
address = "#{name} <#{email}>" unless name.empty?
print "Sending to #{address}... "
markdown = letter.render(
'email' => email, 'first' => first, 'last' => last
)
html = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
.render(markdown)
text = Redcarpet::Markdown.new(Redcarpet::Render::StripDown)
.render(markdown)
if ignore
if @opts[:resume].downcase != email
puts "#{Rainbow('ignored').orange}, waiting for #{@opts[:resume]}"
next
end
ignore = false
end
if skip.include?(email)
puts Rainbow('skipped').red
next
end
subject = @opts[:subject]
mail = Mail.new do
from from
to address
subject subject
message_id "<#{UUIDTools::UUID.random_create}@#{domain}>"
text_part do
content_type 'text/plain; charset=UTF-8'
body text
end
html_part do
content_type 'text/html; charset=UTF-8'
body html
end
end
if @opts[:attach]
Dir.mktmpdir do |dir|
`#{@opts[:attach]} "#{email}" "#{name}" "#{dir}"`
raise 'Failed to exec' unless $CHILD_STATUS.success?
Dir[File.join(dir, '*')].each do |f|
mail.add_file(filename: File.basename(f), content: File.read(f))
end
end
end
mail.deliver! unless @opts[:dry]
total += 1
puts "#{Rainbow('done').green} ##{total}"
end
puts "Processed #{sent.size} emails"
end
end