Attaching files to emails with google-api-client gem in ruby - ruby

I've managed to send emails via Google API thanks to the google-api-client gem:
def send_message(from:, to:, subject:, body:)
message = RMail::Message.new
message.header['From'] = from
message.header['To'] = to
message.header['Subject'] = subject
message.body = body
#service.send_user_message(
'me',
upload_source: StringIO.new(message.to_s),
content_type: 'message/rfc822'
)
end
Now I'm trying to attach files to the emails, but I couldn't find examples on how to do it. The example included in the gem's repository doesn't explain the case. I've started doing reverse engineering, but after almost the whole day making attempts I've started doing crazy things.
My last attempt was the following:
upload = Google::Apis::Core::UploadIO.new('/path/to/image.png', 'image/png', 'image.png')
file_part = Google::Apis::Core::FilePart.new(nil, upload)
message_object = Google::Apis::GmailV1::Message.new(payload: file_part, raw: 'this is a test body')
service.send_user_message('me', message_object, content_type: 'message/rfc822')
The Email was bounced.
What's the proper way to attach files?

Turns out it was easier than I expected. Here is an example:
class Client
def initialize(service)
#service = service
end
def send_message(from:, to:, subject:, body:)
message = RMail::Message.new
message.header.set('From', from)
message.header.set('To', to)
message.header.set('Subject', subject)
message.body = [text_part(body), file_part]
#service.send_user_message(
'me',
upload_source: StringIO.new(message.to_s),
content_type: 'message/rfc822'
)
end
private
def text_part(body)
part = RMail::Message.new
part.body = body
part
end
def file_part
part = RMail::Message.new
part.header.set('Content-Disposition', 'attachment', 'filename' => File.basename('/path/to/image.png'))
part.body = File.read('/path/to/image.png')
part
end
end
I'll wait for further responses, maybe there's something I'm not taking into account.

Related

How to use webmock to simulate a request?

My app create a github gist using API. I need to simulate a request to the API with rspec. I'm using the webmock gem but I don't quite understand how to use it for my application. I need a little help to get started.
This is my spec/Git_Request_spec.rb
require_relative '../Gist_Request.rb'
require 'spec_helper'
RSpec.describe GistRequest do
describe "#post" do
it "crear gist" do
filename = "test.txt"
description = "descripción"
state = true
content = "contenido"
gist_create = GistRequest.new(description, state, filename, content)
gist_create.post()
expect(gist_create.response_status).to eq "201"
end
it "campos no válidos" do
filename = "test.txt"
description = "descripción"
state = true
content = "contenido"
gist_create = GistRequest.new(filename, content, state, description)
gist_create.post()
expect(gist_create.response_status).to eq "422"
end
end
end
Any ideas?
You need to use the method stub_request to simulate your interaction with api
stub_request(:http_method, url).with(`your data in
request`).to_return(`what do you expect to receive in response`).

Ruby Gmail API with OAUTH2 responds with Invalid credentials (Failure) when logging in

I am trying to connect to the Gmail api using the Gmail for Ruby gem. I'm following this google oauth2 guide for installed applications.
I have set my app up in the Google Developer's Console, I am able to send a request with my client_id and client_secret to obtain an authorization code. I am then able to send a request with my authorization code to obtain an access token and a refresh token. I am able to successfully send a request to refresh my access token, and it returns a new access token.
The problem arises when I try to connect to Gmail. First I set an instance variable #gmail = Gmail.connect(:xoauth2, #email, #client.access_token). Then, I attempt to login with #gmail.login(true). However, when I try that, I get the following error:
Couldn't login to given Gmail account: caiden.robinson35#gmail.com (Invalid credentials (Failure)) (Gmail::Client::AuthorizationError)
I am at a loss here, everything suggests I'm executing the oauth2 flow correctly, except the fact that when it comes time to login, I get invalid credentials. When generating my authorization code, I specifically click my email and allow my app to have access. The API is also enabled in my developers console. Here is the full code:
class GmailClient
def initialize
load_client_info
#email = "caiden.robinson35#gmail.com"
load_and_set_oauth2_tokens
sign_in_gmail
binding.pry
end
private
def sign_in_gmail
binding.pry
#gmail = Gmail.connect(:xoauth2, #email, #client.access_token)
######################
# RIGHT HERE IS WHERE IT FAIL
######################
#gmail.login true
binding.pry
end
def load_client_info
gmail_credentials = YAML.load_file('config/gmail.yml')
#client_id = gmail_credentials["client_id"]
#client_secret = gmail_credentials["client_secret"]
#redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
end
def load_and_set_oauth2_tokens use_cached_tokens = true
if use_cached_tokens && File.exist?("config/tokens.yml")
token_hash = YAML.load_file('config/tokens.yml')
#authorization_code = { code: token_hash["authorization_code"],
is_cached: true }
#client = signet_client(token_hash)
#token_hash = #client.refresh!
else
if !(instance_variable_defined?("#authorization_code") && #authorization_code[:is_cached] == false)
retrieve_and_set_authorization_code
end
#token_hash = set_client_and_retrieve_oauth2_tokens
end
write_tokens_to_file
end
def retrieve_and_set_authorization_code
puts "Go to the following url to enable the gmail cli app:"
puts "https://accounts.google.com/o/oauth2/auth?scope=email&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&client_id=#{#client_id}"
print "Paste your authorization code here: "
#authorization_code = { code: gets.chomp,
is_cached: false }
end
def set_client_and_retrieve_oauth2_tokens
#client = signet_client
#client.fetch_access_token!
end
def signet_client token_hash = nil
client = Signet::OAuth2::Client.new(
client_id: #client_id,
client_secret: #client_secret,
redirect_uri: #redirect_uri,
scope: 'email',
token_credential_uri: 'https://www.googleapis.com/oauth2/v4/token'
)
if token_hash.present?
client.refresh_token = token_hash["refresh_token"]
else
client.authorization_uri = 'https://accounts.google.com/o/oauth2/auth'
client.code = #authorization_code[:code]
end
client
end
def write_tokens_to_file
if File.exist?("config/tokens.yml")
data = YAML.load_file "config/tokens.yml"
#token_hash.each { |k, v| data[k] = v }
File.open('config/tokens.yml', 'w') do |file|
YAML.dump(data, file)
end
else
File.open('config/tokens.yml', 'w') do |file|
#token_hash.each { |k, v| file.write("#{k}: #{v}\n") }
file.write("authorization_code: #{#authorization_code[:code]}\n")
end
end
end
end
If my question is lacking any info, please just ask, I am eager to solve this.
Scopes matter. Here are right ones:
scope: ['https://mail.google.com/', 'https://www.googleapis.com/auth/userinfo.email' #,'https://www.googleapis.com/auth/gmail.send' - if you'd like to send emails as well]

Cleverbot ruby irc bot

I wrote this auto-reply bot with ruby, it is supposed to autoreply with cleverbot messages when im away:
require "cleverbot"
require "cinch"
$client = Cleverbot::Client.new
def get_answer(text)
reply = $client.write text
return reply
end
bot = Cinch::Bot.new do
configure do |c|
c.nick = "mybotsnickname"
c.server = "my.irc.testserver"
c.channels = ["#mychannel"]
end
on :message do |m|
m.reply m.user
m.reply get_answer(m.message)
end
end
bot.start
It works fine but the session id changes every message. What do i have to change to keep it? best case scenario is every user writing me gets a different session id at cleverbot so they have individual conversations.
I'm pretty new to ruby.
I used: https://github.com/benmanns/cleverbot
and https://github.com/cinchrb/cinch
Comparing this to the structure of my cinch bot, I'd try the following:
1) Make get_answer a helper block and place it inside the bot = Cinch::Bot.new block:
helpers do
def get_answer(text)
reply = $client.write text
return reply
end
end
2) Replace
on :message do |m|
with
on :message do |m, text|
3) Replace
m.reply get_answer(m.message)
with
m.reply get_answer(text)
I suspect this should work. But I'm a relatively new to Ruby as well.

Ruby Viewpoint Gem 1.27

I'm trying to send a message with the Viewpoint Ruby Gem to an EWS endpoint. I am only able to send the message in plain text format. How can I send it in html format?
Here is the code:
Viewpoint::EWS::EWS.endpoint=Conf.application.email.ews.endpoint
Viewpoint::EWS::EWS.set_auth(Conf.application.email.ews.username,Conf.application.email.ews.password)
Viewpoint::EWS::Message.send(options[:subject],msg_str,to_addresses)
I see that there is a text_only "instance" method, but am not able to initialize an instance of a message object to use it.
Trick is to set body_type. Note: This example is derived from the example at https://github.com/zenchild/Viewpoint based on v1.0beta.
require 'viewpoint'
include Viewpoint::EWS
endpoint = 'https://example.com/ews/Exchange.asmx'
user = 'username'
pass = 'password'
cli = Viewpoint::EWSClient.new endpoint, user, pass
cli.send_message do |m|
m.subject = "Test"
m.body = "<html><body><strong>Test</strong> message</body></html>"
m.body_type = 'HTML'
m.to_recipients << 'test#example.com'
end

Why is Net::HTTP timing out when I try to access a Prawn Generated PDF?

I am using Prawn to generate a PDF from my controller, and when accessed directly at the url, it works flawlessly, I.E. localhost:3000/responses/1.pdf
However, when I try to generate this file on the fly for inclusion in a Mailer, everything freezes up and it times out.
I have tried various methods for generating / attaching the file and none have changed the outcome.
I also tried modifying the timeout for Net::HTTP to no avail, it just takes LONGER to time out.
If I run this command on the Rails Console, I receive a PDF data stream.
Net::HTTP.get('127.0.0.1',"/responses/1.pdf", 3000)
But if I include this code in my controller, it times out.
I have tried two different methods, and both fail repeatedly.
Method 1
Controller:
http = Net::HTTP.new('localhost', 3000)
http.read_timeout = 6000
file = http.get(response_path(#response, :format => 'pdf')) #timeout here
ResponseMailer.confirmComplete(#response,file).deliver #deliver the mail!
Method 1 Mailer:
def confirmComplete(response,file)
email_address = response.supervisor_id
attachments["test.pdf"] = {:mime_type => "application/pdf", :content=> file}
mail to: email_address, subject: 'Thank you for your feedback!'
end
The above code times out.
Method 2 Controller:
ResponseMailer.confirmComplete(#response).deliver #deliver the mail!
Method 2 Mailer:
def confirmComplete(response)
email_address = response.supervisor_id
attachment "application/pdf" do |a|
a.body = Net::HTTP.get('127.0.0.1',"/responses/1.pdf", 3000) #timeout here
a.filename = "test.pdf"
end
mail to: email_address, subject: 'Thank you for your feedback!'
end
If I switch the a.body and a.filename, it errors out first with
undefined method `filename=' for #<Mail::Part:0x007ff620e05678>
Every example I find has a different syntax or suggestion but none fix the problem that Net::HTTP times out. Rails 3.1, Ruby 1.9.2
The problem is that, in development, you're only running one server process, which is busy generating the email. That process is sending another request (to itself) to generate a PDF and waiting for a response. The request for the PDF is basically standing in line at the server so that it can get it's PDF, but the server is busy generating the email and waiting to get the PDF before it can finish. And thus, you're waiting forever.
What you need to do is start up a second server process...
script/rails server -p 3001
and then get your PDF with something like ...
args = ['127.0.0.1','/responses/1.pdf']
args << 3001 unless Rails.env == 'production'
file = Net::HTTP.get(*args)
As an aside, depending on what server you're running on your production machine, you might run into issues with pointing at 127.0.0.1. You might need to make that dynamic and point to the full domain when in production, but that should be easy.
I agree with https://stackoverflow.com/users/811172/jon-garvin's analysis that you're only running one server process, but I would mention another solution. Refactor your PDF generation so you don't depend on your controller.
If you're using Prawnto, I'm guessing you have a view like
# app/views/response.pdf.prawn
pdf.text "Hello world"
Move this to your Response model: (or somewhere else more appropriate, like a presenter)
# app/models/response.rb
require 'tmpdir'
class Response < ActiveRecord::Base
def pdf_path
return #pdf_path if #pdf_generated == true
#pdf_path = File.join(Dir.tmpdir, rand(1e11).to_s)
Prawn::Document.generate(#pdf_path) do |pdf|
pdf.text "Hello world"
end
#pdf_generated = true
#pdf_path
end
def pdf_cleanup
if #pdf_generated and File.exist?(#pdf_path.to_s)
File.unlink #pdf_path
end
end
end
Then in your ResponsesController you can do:
# app/controllers/responses_controller.rb
def show
#response = Response.find params[:id]
respond_to do |format|
# this sends the PDF to the browser (doesn't email it)
format.pdf { send_file #response.pdf_path, :type => 'application/pdf', :disposition => 'attachment', :filename => 'test.pdf' }
end
end
And in your mailer you can do:
# this sends an email with the PDF attached
def confirm_complete(response)
email_address = response.supervisor_id
attachments['test.pdf'] = {:mime_type => "application/pdf", :content => File.read(response.pdf_path, :binmode => true) }
mail to: email_address, subject: 'Thank you for your feedback!'
end
Since you created it in the tmpdir, it will be automatically deleted when your server restarts. You can also call the cleanup function.
One final note: you might want to use a different model name like SupervisorReport or something - Response might get you in namespacing trouble later)

Resources