I use below function to create a hash of the whole content of a directory so I can send all files as attachments.
def get_attachments_from_directory(dir)
attachment_to_send = Hash.new
Dir[dir.gsub("\\","/")+"/*"].each {|file|
file_to_send = File.read(file)
#file_to_send = File.read(file, :binmode => true)
attachment_to_send[File.basename(file)]=file_to_send
}
return attachment_to_send
end
and then I use below function to send the attachments out
def email_it(body, subject, to, from, attachment_to_send)
$smtp = 'mail.com'
$smtp_port = 25
Pony.mail(
:to => to,
:from => from,
:subject => subject,
:body => Nokogiri::HTML(body).text,
:html_body => body
:attachments => attachment_to_send,
:via => :smtp,
:via_options => {
:address => $smtp,
:port => $smtp_port,
:enable_starttls_auto => false
}
)
end
There are two files in my testing directory: .log and .png. Both of them are sent and received but .png is corrupted. gmail said that the image file cannot be displayed because it contains errors. The file name of .png file is correct in my gmail account. The file size is wrong. Much much smaller.
Show original in gmail gives me
----==_mimepart_4fd9515347359_fc1e853c88342d
Date: Thu, 14 Jun 2012 12:49:55 +1000
Mime-Version: 1.0
Content-Type: image/png;
charset=UTF-8;
filename="error_when_time_out - login at 2012-06-14 12.48.55.png"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
filename="error_when_time_out - login at 2012-06-14 12.48.55.png"
Content-ID: <4fd95153648c7_fc1e853c883518#RATionalxp.mail>
iVBORwq/XNErlnbxOOrmtdDZDYaMWm16lTatQptSpk4t12RW6HNq6IJGvvyB
sabbUovDe5+loc9U3yPX9Yr1vWJDv9Q4KNcPydUDQnkfV9LNFrTTOc2GrEZd
Rr0uo06fUUdn1jOZ9RxRnZBZJ2bXG3M4yoTplFqeWJFGFoVBjDanha5JoWOM
bx3hi0aTQPSQNcikNoMVeYrndSi9YeVl1jxLr07oNrrn11F1kv3AeL9C8Mpi
bkTrjvku73RaeOP6/KvXVv5yzfC6vfCqHf/H64Y/XNf//obujzf0f7lp+PMt
... it continues ....
Xvjq8X//p/Ocdy68s2/DZ//5/Muvf/rvt319XzQf8p9J+7wpSTTguXYPo3Dy
TYiIaNAvYXs5ir9gv4akEz5MOO6DxGPf150oPfApIe6Yu5SVblRBYgL1TrWq
QqWsUnFag5rYTagbCD4lJCgO2hYdpGzQteqR9NCgo3ZTmh0=
----==_mimepart_4fd9515347359_fc1e853c88342d--
inspect of the hash outputs
{"error_when_time_out - login at 2012-06-14 12.50.12.png"=>"\211PNG\n\277\\\321
+\226v\3618\352\346\265\320\331\r\206\214Zmz\2256\255B\233R\246N-\327dV\350sj\35
0\202F\276\374\201\261\246\333R\213\303{\237\245\241\317T\337#\327\365\212\365\2
75bC\277\3248(\327\017\311\325\003By\037W\322\315\026\264\3239\315\206\254F]F\27
5.\243N\237QGg\3263\231\365\034Q\235\220Y'f\327\es8\312\204\351\224Z\236X\221F\0
26\205A\2146\247\205\256I\241c\214o\035\341\213F\223#\364\2205\310\2446\203\025y
If I try to read the file with #file_to_send = File.read(file, :binmode => true)
I get an error: TypeError - can't convert Hash into Integer:
ruby 1.8.7 (2010-08-16 patchlevel 302) [i386-mingw32]
mime-types (1.16)
pony (1.3)
The conventional way to read binary data without any CR+LF translation is:
File.open(file, 'rb').read
Ruby 1.9 introduces a few new ways to do this that you might be inadvertently trying in your 1.8.7 environment. The second argument to read is the number of bytes you want to read, not the mode of the file.
Be sure to read the documentation on any method you're unfamiliar with. Sometimes things aren't quite what you'd expect.
Related
This works (on ubuntu):
curl -v -g 'https://example.com/api/path/to/service?json={"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}'
And this works too (in ruby):
require 'rest-client'
resource = RestClient::Resource.new(
'https://example.com/api/path/to/service?json={"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}')
resp = resource.get
However, I would like to split request host and body (in ruby).
I tried this:
require 'rest-client'
resource = RestClient::Resource.new(
'https://example.com/api/path/to/service')
resp = resource.get(:data => 'json={"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}', :content_type => :json, :accept => :json)
but the server returns:
# => 400 BadRequest | application/json 49 bytes
No such encoding: "utf8"
and also with this variation:
resp = resource.get(:json => '{"select":["cats","dogs","cows"],"from":"20170115","to":"20170117"}', :content_type => :json, :accept => :json)
and equivalent with :payload => but got same error result.
I have looked stackoverflow thin but nothing I tried seems to work. I have a feeling it is something with the way ruby is escaping the double quotes before sending - just a guess.
Any ideas?
All help would be greatly appreciated. Even a link to the right stackoverflow answer :-)
Thanks.
With Ruby, my app:
checks if the page status is 200
Parses the PDF files if so
sends via email the result of scraping
Having tested all the parts of the code, everything works fine, except one thing, the mail that is sent doesn't contain the result of my scrpaing;
What is the issue, is it related to the variable #monscrape that may be not recongnised in the final party of the code ?
My code:
require 'open-uri'
require "net/http"
require 'rubygems'
require 'pdf/reader'
require 'mail'
options = { :address => "smtp.gmail.com",
:port => 587,
:domain => 'gmail.com',
:user_name => 'mail#gmail.com',
:password => 'pwd',
:authentication => 'plain',
:enable_starttls_auto => true
}
lien= "http://www.example.com"
url = URI.parse(lien)
req = Net::HTTP.new(url.host, url.port)
res = req.request_head(url.path)
if res.code == "200"
io = open('http://www.example.com')
reader = PDF::Reader.new(io)
reader.pages.each do |page|
res = page.text
#monscrape = res.scan(/text[\s\S]*text/)
end
Mail.defaults do
delivery_method :smtp, options
end
Mail.deliver do
to 'mail#hotmail.com'
from 'Author <mail#gmail.com>'
subject 'testing sendmail'
html_part do
content_type 'text/html; charset=UTF-8'
body '<h1>Please find below the scrape <%= #monscrape %></h1>'
end
end
else
puts "the link doenst work"
end
The problem is the Mail.deliver block is evaluated using instance_eval. Therefore no local instance #variables will be visible to the Mail block.
So #monscrape will always be nil inside the Mail.deliver block.
One solution is to use a local (non-instance) variable instead:
monscrape = "test"
Mail.deliver do
...
body "<h1>Please find below the scrape #{monscrape}</h1>"
...
end
Also note that Mail does not support ERB(!) therefore you cannot use something like <%= monscrape %> in the body. You have to treat it like a normal string using string expansion with double quotes " and not single quotes '.
See further discussion and options here:
Why can't the Mail block see my variable?
You can't use
res = req.request_head(url.path)
when url.path returns "". request_head expects a path of at least "/". That implies you need to fix up the URL being passed so it at least has the root path "/".
url = URI.parse('http://www.example.com')
url.path # => ""
req.request_head(url.path)
*** ArgumentError Exception: HTTP request path is empty
vs.
url = URI.parse('http://www.example.com/')
url.path # => "/"
req.request_head(url.path)
#<Net::HTTPOK 200 OK readbody=true>
The second problem is you're trying to read something as PDF that isn't a PDF file. Example.com returns HTML, which is text. You can't use:
io = open('http://www.example.com')
reader = PDF::Reader.new(io)
Trying to returns "PDF does not contain EOF marker".
It's really important that you understand what types of objects/resources are being returned by a site when you request a URL. You can't declare them willy-nilly and expect code to accept it without errors.
using mail for ruby I am getting this message:
mail.rb:22:in `encode': "\xC7" from ASCII-8BIT to UTF-8 (Encoding::UndefinedConversionError)
from mail.rb:22:in `<main>'
If I remove encode I get a message ruby
/var/lib/gems/1.9.1/gems/bson-1.7.0/lib/bson/bson_ruby.rb:63:in `rescue in to_utf8_binary': String not valid utf-8: "<div dir=\"ltr\"><div class=\"gmail_quote\">l<br><br><br><div dir=\"ltr\"><div class=\"gmail_quote\"><br><br><br><div dir=\"ltr\"><div class=\"gmail_quote\"><br><br><br><div dir=\"ltr\"><div dir=\"rtl\">\xC7\xE1\xE4\xD5 \xC8\xC7\xE1\xE1\xDB\xC9 \xC7\xE1\xDA\xD1\xC8\xED\xC9</div></div>\r\n</div><br></div>\r\n</div><br></div>\r\n</div><br></div>" (BSON::InvalidStringEncoding)
This is my code:
require 'mail'
require 'mongo'
connection = Mongo::Connection.new
db = connection.db("DB")
db = Mongo::Connection.new.db("DB")
newsCollection = db["news"]
Mail.defaults do
retriever_method :pop3, :address => "pop.gmail.com",
:port => 995,
:user_name => 'my_username',
:password => '*****',
:enable_ssl => true
end
emails = Mail.last
#Checks if email is multipart and decods accordingly. Put to extract UTF8 from body
plain_part = emails.multipart? ? (emails.text_part ? emails.text_part.body.decoded : nil) : emails.body.decoded
html_part = emails.html_part ? emails.html_part.body.decoded : nil
mongoMessage = {"date" => emails.date.to_s , "subject" => emails.subject , "body" => plain_part.encode('UTF-8') }
msgID = newsCollection.insert(mongoMessage) #add the document to the database and returns it's ID
puts msgID
For English and Hebrew it works perfectly but it seems gmail is sending arabic with different encoding. Replacing UTF-8 with ASCII-8BIT gives a similar error.
I get the same result when using plain_part for plain email messages. I am handling emails from one specific source so I can put html_part with confidence it's not causing the error.
To make it extra weird Subject in Arabic is rendered perfectly.
What encoding should I use?
If you use encode without options, it will raise this error, if you're string pretends to be an encoding but contains characters from another encoding.
try it in this way:
plain_part.encode('UTF-8', {:invalid => :replace, :undef => :replace, :replace => '?'})
this replaces invalid and undefined chars for the given encoding with an "?"(more info). If this is not sufficent for your needs, you need to find a way to check if your plain_part string is valid.
For example you can use valid_encoding?(more info) for this.
I recently stumbled across a similar problem, where I couldn't be sure what encoding it really is, so I wrote this (maybe a little humble) method. May it helps you, to find a way to fix your problem.
def self.encode!(str)
return nil if str.nil?
known_encodings = %w(
UTF-8
ISO-8859-1
)
begin
str.encode(Encoding.find('UTF-8'))
rescue Encoding::UndefinedConversionError
fixed_str = ""
known_encodings.each do |encoding|
fixed_str = str
if fixed_str.force_encoding(encoding).valid_encoding?
return fixed_str.encode(Encoding.find('UTF-8'))
end
end
return str.encode(Encoding.find('UTF-8'), {:invalid => :replace, :undef => :replace, :replace => '?'})
end
end
I found a work around.
Since only specific emails will be sent to this account to just to use on this application I have full control over formatting. For some reason mail decodes text/plain attachment perfectly
so:
emails.attachments.each do | attachment |
if (attachment.content_type.start_with?('text/plain'))
# extracting txt file
begin
body = attachment.body.decoded
rescue Exception => e
puts "Unable to save data for #{filename} because #{e.message}"
end
end
end
mongoMessage = {"date" => emails.date.to_s , "subject" => emails.subject , "body" => body }
I've looked at all the SMTP ruby-docs and can't figure out where I'm going wrong:
def send(username, password, data, toAddress, fromAddress)
smtp = Net::SMTP.new('my.smtp.host', 25)
smtp.start('thisisunimportant', username, password, "plain") do |sender|
sender.send_message(data, fromAddress, toAddress)
end
end
send(user, pass, rcpt, "Hey!")
Gives an unexpected kind of error:
/usr/lib/ruby/1.9.1/net/smtp.rb:725:in authenticate': wrong number of arguments (3 for 4) (ArgumentError)
from /usr/lib/ruby/1.9.1/net/smtp.rb:566:indo_start'
from /usr/lib/ruby/1.9.1/net/smtp.rb:531:in start'
from gmx_pop.rb:24:insend'
from gmx_pop.rb:30:in `'
I've tried kicking my computer a couple times but the problem persists.
Here's a description of the Net::SMTP#start call:
http://ruby-doc.org/stdlib-1.9.1/libdoc/net/smtp/rdoc/Net/SMTP.html#method-i-start
the page mentions that you can just do SMTP.start to do everything at once.
Look like you are missing the port parameter. Try port 587 for secure authentication, if that doesn't work, port 25. (check the tutorial mentioned below)
Your call should look like this:
message_body = <<END_OF_EMAIL
From: Your Name <your.name#gmail.com>
To: Other Email <other.email#somewhere.com>
Subject: text message
This is a test message.
END_OF_EMAIL
server = 'smtp.gmail.com'
mail_from_domain = 'gmail.com'
port = 587 # or 25 - double check with your provider
username = 'your.name#gmail.com'
password = 'your_password'
smtp = Net::SMTP.new(server, port)
smtp.enable_starttls_auto
smtp.start(server,username,password, :plain)
smtp.send_message(message_body, fromAddress, toAddress) # see note below!
Important:
Please note that you need to add To: , From: , Subject: headers to your message_body!
the Message-Id: and Date: headers will be added by your SMTP server
Check also:
Tutorial : http://www.java-samples.com/showtutorial.php?tutorialid=1121
the source code for Net::SMTP under ~/.rvm/src/ruby-1.9.1*/lib/net/smtp.rb
(Ruby) Getting Net::SMTP working with Gmail...?
Another way to send emails from Ruby:
You can use the ActionMailer gem from Rails to send emails from Ruby (without Rails).
At first this seems like overkill, but it makes it much easier, because you don't have to format the message body with To: , From: , Subject: , Date: , Message-Id: Headers.
# usage:
# include Email
#
# TEXT EMAIL :
# send_text_email( 'sender#somewhere.com', 'sender#somewhere.com,receiver#other.com', 'test subject', 'some body text' )
# HTML EMAIL :
# send_html_email( 'sender#somewhere.com', 'sender#somewhere.com,receiver#other.com', 'test subject', '<html><body><h1>some title</h1>some body text</body></html>' )
require 'action_mailer'
# ActionMailer::Base.sendmail_settings = {
# :address => "Localhost",
# :port => 25,
# :domain => "yourdomain.com"
# }
ActionMailer::Base.smtp_settings = { # if you're using GMail
:address => 'smtp.gmail.com',
:port => 587,
:domain => 'gmail.com',
:user_name => "your-username#gmail.com"
:password => "your-password"
:authentication => "plain",
:enable_starttls_auto => true
}
class SimpleMailer < ActionMailer::Base
def simple_email(the_sender, the_recepients, the_subject, the_body , contenttype = nil)
from the_sender
recipients the_recepients
subject the_subject
content_type contenttype == 'html' ? 'text/html' : 'text/plain'
body the_body
end
end
# see http://guides.rails.info/action_mailer_basics.html
# for explanation of dynamic ActionMailer deliver_* methods.. paragraph 2.2
module Email
# call this with a message body formatted as plain text
#
def send_text_email( sender, recepients, subject, body)
SimpleMailer.deliver_simple_email( sender , recepients , subject , body)
end
# call this with an HTML formatted message body
#
def send_html_email( sender, recepients, subject, body)
SimpleMailer.deliver_simple_email( sender , recepients , subject , body, 'html')
end
endsubject , body, 'html')
end
end
e.g. the code above works if you want to use Gmail's SMTP server to send email via your Gmail account.. Other SMTP servers may need other values for :port, :authentication and :enable_starttls_auto depending on the SMTP server setup
Try this code
Net::SMTP.smtp.start('my.smtp.host', 25, 'mail.from.domain', username, password, :plain) do |smtp|
smtp.send_message data, fromAddress, toAddress
end
I have testmail.rb on a CENTOS 5 VM with SELinux permissive and IPtables off:
require 'rubygems'
require 'mail'
options = { :address => "mail.domain.com",
:port => 466,
:domain => 'otherdomain.com',
:user_name => 'somedude#domain.com',
:password => 'topsecret',
:authentication => 'plain',
:enable_starttls_auto => true }
Mail.defaults do
delivery_method :smtp, options
end
mail = Mail.new do
from 'somedude#otherdomain.com'
to 'admin#domain.com'
subject 'This is a test email'
body File.read('body.txt')
end
puts mail.to_s
The result when the script is run is this:
Date: Tue, 30 Nov 2010 12:12:58 -0500
From: somedude#otherdomain.com
To: admin#domain.com
Message-ID: <4cf5309a2f074_284015c5c4de91b8270b2#apvdbs03.3rdomain.local.mail>
Subject: This is a test email
Mime-Version: 1.0
Content-Type: text/plain;
charset=UTF-8
Content-Transfer-Encoding: 7bit
test!
"test!" is the content of body.txt.
No email ever reaches the sent to account. The smtp settings we got from the sent to domain admin. I used telnet to successfully send an email to the domain on the unencrypted port (25) but got no response from the encrypted port (466), possibly because my telnet session was unencrypted?
What are some ways I can see what's going on during the script execution to troubleshoot?
Update:
Tried redirecting: > log.log 2>&1, but that didn't provide any additional info.
You're missing the line to actually send. Try adding
mail.deliver!
to the end of your script.