I am in need of some help with setting up a Twilio SMS Broadcast App running on Sinatra. They build is based off this tutorial: Send Mass SMS Broadcasts in Ruby
When I make an HTTP POST I get this message in my Terminal when running Sinatra & Ngrok.
NameError - uninitialized constant Twilio::TwiML::Response
Did you mean? Twilio::Response:
broadcast.rb:75:in `send_to_me'
broadcast.rb:53:in `block in <main>'
The code it is having an issue with is:
def send_to_contacts(body, media_url = nil)
response = Twilio::TwiML::Response.new do |r|
contacts_numbers.each do |num|
r.Message to: num do |msg|
msg.Body body
msg.Media media_url unless media_url.nil?
end
end
end
response.text
end
def send_to_me(from, body, media_url = nil)
name = contact_name(from)
body = "#{name} (#{from}):\n#{body}"
response = Twilio::TwiML::Response.new do |r|
r.Message to: MY_NUMBER do |msg|
msg.Body body
msg.Media media_url unless media_url.nil?
end
end
response.text
end
I have noticed most new Twilio walkthrus are now using API Auths & Tokens with an
#client = Twilio::REST::Client.new account_sid, auth_token
Is this something I need to be implementing? Any guidance on how I can migrate these two methods to that type of format and keep my features?
Thanks!
Update:
Twilio::TwiML::Response has been replaced by Twilio::TwiML::VoiceResponse & Twilio::TwiML::MessagingResponse. It worked when I changed the code to this:
def send_to_contacts(body, media_url = nil)
response = Twilio::TwiML::MessagingResponse.new do |r|
contacts_numbers.each do |num|
r.message to: num do |msg|
msg.body body
msg.media media_url unless media_url.nil?
end
end
end
puts response
end
def send_to_me(from, body, media_url = nil)
name = contact_name(from)
body = "#{name} (#{from}):\n#{body}"
response = Twilio::TwiML::MessagingResponse.new do |r|
r.message to: MY_NUMBER do |msg|
msg.body body
msg.media media_url unless media_url.nil?
end
end
puts response
end
Related
I am having an issue with outbound SMS thru TwiML Message functions. I am able to see my post on my Sinatra server session here:
== Sinatra (v2.0.0) has taken the stage on 4567 for development with backup from WEBrick
[2018-01-24 15:30:55] INFO WEBrick::HTTPServer#start: pid=67403 port=4567
<?xml version="1.0" encoding="UTF-8"?><Response><Message to="+1904XXXXXXX"><Body>Text Message Test for Devotional App. Please reply.</Body></Message><Message to="+1786XXXXXXX"><Body>Text Message Test for Devotional App. Please reply.</Body></Message><Message to="+1904XXXXXXX"><Body>Text Message Test for Devotional App. Please reply.</Body></Message></Response>
50.235.219.155 - - [24/Jan/2018:15:31:16 -0500] "POST /message HTTP/1.1" 200 - 0.0022
::1 - - [24/Jan/2018:15:31:16 EST] "POST /message HTTP/1.1" 200 0
- -> /message
I see the inbound logs here, but nothing outbound. I have even elevated this to a paid account to make sure it wasn't a trail thing.
This code is based on this walkthru.
My full ruby code for the app is here:
require 'yaml'
require 'open-uri'
require 'sinatra'
require 'twilio-ruby'
MY_NUMBER = '+1904XXXXXXXX'
def spreadsheet_url
'contacts.yml'
end
def sanitize(number)
"+1" + number.gsub(/^1|\D/, "")
end
def data_from_spreadsheet
file = open(spreadsheet_url).read
YAML.load(file)
end
def contacts_from_spreadsheet
contacts = {}
data_from_spreadsheet.each do |entry|
name = entry['name']
number = entry['phone_number'].to_s
contacts[sanitize(number)] = name
end
contacts
end
def contacts_numbers
contacts_from_spreadsheet.keys
end
def contact_name(number)
contacts_from_spreadsheet[number]
end
get '/' do
"Devotional Broadcast is Up & Running!"
end
get '/message' do
"Things are Working!"
end
post '/message' do
from = params['From']
body = params['Body']
media_url = params['MediaUrl0']
if from == MY_NUMBER
twiml = send_to_contacts(body, media_url)
else
twiml = send_to_me(from, body, media_url)
end
content_type 'text/xml'
puts twiml
end
def send_to_contacts(body, media_url = nil)
response = Twilio::TwiML::MessagingResponse.new do |r|
contacts_numbers.each do |num|
r.message to: num do |msg|
msg.body body
msg.media media_url unless media_url.nil?
end
end
end
puts response
end
def send_to_me(from, body, media_url = nil)
name = contact_name(from)
body = "#{name} (#{from}):\n#{body}"
response = Twilio::TwiML::MessagingResponse.new do |r|
r.message to: MY_NUMBER do |msg|
msg.body body
msg.media media_url unless media_url.nil?
end
end
puts response
end
Any help or insight would be great! Thanks!
I think I got it. Swapped out puts for .to_s Documentation Example here: Receive & Reply to SMS & MMS
post '/message' do
from = params['From']
body = params['Body']
media_url = params['MediaUrl0']
if from == MY_NUMBER
twiml = send_to_contacts(body, media_url)
else
twiml = send_to_me(from, body, media_url)
end
content_type 'text/xml'
twiml.to_s
end
def send_to_contacts(body, media_url = nil)
response = Twilio::TwiML::MessagingResponse.new do |r|
contacts_numbers.each do |num|
r.message to: num do |msg|
msg.body body
msg.media media_url unless media_url.nil?
end
end
end
response.to_s
end
def send_to_me(from, body, media_url = nil)
name = contact_name(from)
body = "#{name} (#{from}):\n#{body}"
response = Twilio::TwiML::MessagingResponse.new do |r|
r.message to: MY_NUMBER do |msg|
msg.body body
msg.media media_url unless media_url.nil?
end
end
response.to_s
end
I'm learning how to work with HTTParty and API and I'm having an issue with my code.
Users/admin/.rbenv/versions/2.0.0-p481/lib/ruby/2.0.0/uri/generic.rb:214:in `initialize': the scheme http does not accept registry part: :80 (or bad hostname?)
I've tried using debug_output STDOUT both as an argument to my method and after including HTTParty to have a clue but with no success. Nothing gets displayed:
require 'httparty'
class LolObserver
include HTTParty
default_timeout(1) #timeout after 1 second
attr_reader :api_key, :playerid
attr_accessor :region
def initialize(region,playerid,apikey)
#region = region_server(region)
#playerid = playerid
#api_key = apikey
end
def region_server(region)
case region
when "euw"
self.class.base_uri "https://euw.api.pvp.net"
self.region = "EUW1"
when "na"
self.class.base_uri "https://na.api.pvp.net"
self.region = "NA1"
end
end
def handle_timeouts
begin
yield
#Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
#Timeout::Error, is raised if a connection cannot be created within the open_timeout.
rescue Net::OpenTimeout, Net::ReadTimeout
#todo
end
end
def base_path
"/observer-mode/rest/consumer/getSpectatorGameInfo"
end
def current_game_info
handle_timeouts do
url = "#{ base_path }/#{region}/#{playerid}?api_key=#{api_key}"
puts '------------------------------'
puts url
HTTParty.get(url,:debug_output => $stdout)
end
end
end
I verified my URL which is fine so I'm lost as to where the problem is coming from.
I tested with a static base_uri and it doesn't change anything.
The odd thing is when I do:
HTTParty.get("https://euw.api.pvp.net/observer-mode/rest/consumer/getSpectatorGameInfo/EUW1/randomid?api_key=myapikey")
Everything is working fine and I'm getting a response.
HTTParty doesn't seem to like the way you set your base_uri.
Unless you need it to be like that just add another attr_reader called domain and it will work.
require 'httparty'
class LolObserver
include HTTParty
default_timeout(1) #timeout after 1 second
attr_reader :api_key, :playerid, :domain
attr_accessor :region
def initialize(region,playerid,apikey)
#region = region_server(region)
#playerid = playerid
#api_key = apikey
end
def region_server(region)
case region
when "euw"
#domain = "https://euw.api.pvp.net"
self.region = "EUW1"
when "na"
#domain = "https://na.api.pvp.net"
self.region = "NA1"
end
end
def handle_timeouts
begin
yield
#Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
#Timeout::Error, is raised if a connection cannot be created within the open_timeout.
rescue Net::OpenTimeout, Net::ReadTimeout
#todo
end
end
def base_path
"/observer-mode/rest/consumer/getSpectatorGameInfo"
end
def current_game_info
handle_timeouts do
url = "#{domain}/#{ base_path }/#{region}/#{playerid}?api_key=#{api_key}"
puts '------------------------------'
puts url
HTTParty.get(url,:debug_output => $stdout)
end
end
end
Is there a way to send messages on different channels using the sinatra-websocket gem?
Basically I'm trying to replace Pusher with sinatra-websocket. Here's what I'm doing with Pusher:
Pusher["my_channel_A"].trigger('some_event_type', my_message)
Pusher["my_channel_B"].trigger('another_event_type', my_message)
What would be the equivalent of that syntax in this sinatra-websocket snippet?
request.websocket do |ws|
ws.onopen do
ws.send("Hello World!")
settings.sockets << ws
end
ws.onmessage do |msg|
EM.next_tick { settings.sockets.each{|s| s.send(msg) } }
end
ws.onclose do
warn("websocket closed")
settings.sockets.delete(ws)
end
end
Found an answer to this posted here:
get '/socket/live/game/:id' do
if !request.websocket?
puts "Not a websocket request"
else
request.websocket do |ws|
channel = params[:id]
#con = {channel: channel, socket: ws}
ws.onopen do
ws.send("Hello World!")
settings.sockets << #con
end
ws.onmessage do |msg|
return_array = []
settings.sockets.each do |hash|
#puts hash
#puts hash['channel']
if hash[:channel] == channel
#puts hash[:socket]
return_array << hash
puts "Same channel"
puts return_array
else
puts hash[:channel]
puts channel
puts "Not in same channel"
end
end
EM.next_tick { return_array.each{|s| s[:socket].send(msg) } }
end
ws.onclose do
warn("websocket closed")
settings.sockets.each do |hash|
if hash[:socket] == ws
settings.sockets.delete(hash)
puts "deleted"
else
puts "not deleted"
end
end
end
end
end
end
It's still quite verbose. I guess Pusher abstracts away all this through their API.
I am confused with how to write decent code when using a lot of asynchronous code.
In the following code snippet I log in to get the authentication cookie and use that cookie for the next request to get a list of projects name (as an example):
def self.populateProjectsTable(projects_controller)
payload = {email: "email", password: "pass"}
HTTP.post("http://example.com/login", {payload: payload}) do |response|
authCookie = response.headers['Set-Cookie']
HTTP.get("http://example.com/projects.json", {cookie: authCookie}) do |response|
projects = JSON.parse(response.body.to_str)
projects_controller.projects = projects
projects_controller.reloadData
end
end
end
While this will work the code feels dirty. Not really following the single responsibility principle. I would like to extract this in a few methods:
def self.populateProjectsTable(projects_controller)
#taskList = TaskList.new
#taskList.doLogin
projects = #taskList.getProjects
projects_controller.projects = projects
projects_controller.reloadData
end
def doLogin
payload = {email: "email", password: "pass"}
HTTP.post("http://example.com/login", {payload: payload}) do |response|
#authCookie = response.headers['Set-Cookie']
end
end
def getProjects
HTTP.get("http://example.com/projects.json", {cookie: #authCookie}) do |response|
projects = JSON.parse(response.body.to_str)
end
end
This obviously does not work. The getProjects method is called before doLogin is finished and the projects are only known in the scope of the block, not giving back the data to the populateProjectsTable method.
How does one program such applications without the nesting shown in the first example?
You're not going to totally get away from the nesting. Taking Alan's answer and massaging it a bit, this is what I've come up with. It involves passing a block through a couple of methods.
def self.populateProjectsTable(projects_controller)
#taskList = TaskList.new
#taskList.loginAndGetProjects do |projects|
projects_controller.projects = projects
projects_controller.reloadData
end
end
def loginAndGetProjects(&block)
payload = {email: "email", password: "pass"}
HTTP.post("http://example.com/login", {payload: payload}) do |response|
#authCookie = response.headers['Set-Cookie']
getProjects(&block)
end
end
def getProjects(&block)
HTTP.get("http://example.com/projects.json", {cookie: #authCookie}) do |response|
projects = JSON.parse(response.body.to_str)
block.call(projects)
end
end
I've had a similar problem trying to wrap methods that themselves took blocks. I wanted the new wrapper methods to still be able to take blocks. Here's what I did in ParseModel:
# with block:
# ParseModel::Cloud.callFunction("myFunction", {"myParam" => "myValue"}) do |result, error|
# # do something...
# end
# without block:
# ParseModel::Cloud.callFunction("myFunction", {"myParam" => "myValue"})
module ParseModel
class Cloud
def self.callFunction(function, params={}, &block)
return PFCloud.callFunction(function, withParameters:params) unless block_given?
PFCloud.callFunctionInBackground(function, withParameters:params, block:lambda do |result, error|
block.call(result, error)
end)
end
end
end
Applying this concept to your problem, you could rewrite your methods to take blocks themselves. Here's a bit of a refactor that I think might be helpful:
def self.populateProjectsTable(projects_controller)
#taskList = TaskList.new
#taskList.doLogin do |login_response|
authCookie = login_response.headers['Set-Cookie']
#taskList.getProjects(authCookie) do |projects_response|
projects = JSON.parse(projects_response.body.to_str)
projects_controller.projects = projects
projects_controller.reloadData
end
end
end
def doLogin(&block)
payload = {email: "email", password: "pass"}
HTTP.post("http://example.com/login", {payload: payload}) do |response|
block.call(response)
end
end
def getProjects(cookie, &block)
HTTP.get("http://example.com/projects.json", {cookie: cookie}) do |response|
block.call(response)
end
end
I don't think you're totally out of the woods regarding SRP, but this should be a good start.
+1 for Jamon's answer.
I might suggest using a class to manage your session and splitting out the API into a module if you like SRP. This is especially helpful as you add additional API calls. Here I queue up requests that will be satisfied once login is completed. Later you can add handling for timeouts, etc.
module ProjectApi
def get_projects(&block)
with_session do
HTTP.get("http://example.com/projects.json", {cookie: #auth_cookie}) do |response|
projects = JSON.parse(response.body.to_str)
block.call(projects)
end
end
end
end
class MySession
include ProjectApi
def initialize(login, password)
#login = login
#password = password
#state = nil
#requests = []
end
def active?
#state == :active
end
def with_session(&block)
#requests << &block
active? ? handle_requests : login(true)
end
private
def login(do_handle_requests = false)
payload = {login: #login, password: #password}
#state = nil
HTTP.post("http://example.com/login", {payload: payload}) do |response|
#state = :active
#auth_cookie = response.headers['Set-Cookie']}
handle_requests if do_handle_requests
end
end
def handle_requests
while request = #requests.shift do
request.call
end if active?
end
end
def self.populateProjectsTable(projects_controller)
#session ||= MySession.new('mylogin', 'mypassword')
#session.get_projects do |projects|
projects_controller.projects = projects
projects_controller.reloadData
end
end
Please consider this test:
def test_ok_on_second_request
bad_response = #request.get "/bad-response"
assert_equal 404, bad_response.status
good_response = #request.get "/test-title"
assert_equal 200, good_response.status
assert_equal "text/html", good_response.content_type
end
I have assured that /test-title is a valid path. The assertion that's supposed to return 200 is in fact returning 404. How is Rack behaving in order to return two different results for the same request?
This is the code for the Server class inside the project:
module Blogrite
class Server
attr_accessor :status, :mimetype, :body, :provider
def initialize *args, &block
#status, #mimetype = 200, "text/html"
provider = args[0][:with].nil? ? :filesystem : args[0][:with]
#provider = Blogrite.const_get(provider.capitalize).new
# p "Server is running with #{#provider.class}."
end
def call env
begin
article = go env['PATH_INFO'].delete("/")
rescue Blogrite::Article::NoBodyError
#status = 404
end
#status = 404 if !article
#status = 403 if env["REQUEST_METHOD"] == 'POST'
#mimetype = "text/css" if env["PATH_INFO"].include?("css")
#body = if article then article.render
elsif env.respond_to?(:to_yaml) then "<pre>#{env.to_yaml}</pre>"
else "oops"
end
[#status,{ "Content-Type" => #mimetype},[#body]]
end
def go path
f = #provider.fetch path
Article.parse f unless f.nil?
end
end
end
The whole workflow is too big for me to paste it in but you can check the project out on Github. I appreciate your help, thank you.
The solution for the problem is as simple as initializing #status inside the call function.
class Server
attr_accessor :status, :mimetype, :body, :provider
def initialize *args, &block
- #status, #mimetype = 200, "text/html"
provider = args[0][:with].nil? ? :filesystem : args[0][:with]
#provider = Blogrite.const_get(provider.capitalize).new
# p "Server is running with #{#provider.class}."
end
def call env
begin
- article = go env['PATH_INFO'].delete("/")
+ #status, #mimetype = 200, "text/html"
+ article = go env['PATH_INFO'].delete("/")
rescue Blogrite::Article::NoBodyError
#status = 404
end
That way the rack instance – that is called only once – stays out of the request's way. Every call function should have its own defaults, not the server class.
Thanks to #rubenfonseca for helping me out.