Record Voicemail Greeting command

Stephen Paul Weber created

Change summary

Gemfile               |  2 +
config-schema.dhall   |  4 ++
config.dhall.sample   |  4 ++
lib/backend_sgx.rb    |  4 ++
lib/customer.rb       |  2 
lib/customer_fwd.rb   |  9 +++++
sgx_jmp.rb            | 26 +++++++++++++---
views/record_ogm.slim |  5 +++
web.rb                | 69 +++++++++++++++++++++++++++++++++++++++-----
9 files changed, 110 insertions(+), 15 deletions(-)

Detailed changes

Gemfile 🔗

@@ -14,6 +14,8 @@ gem "em-synchrony"
 gem "em_promise.rb", "~> 0.0.3"
 gem "eventmachine"
 gem "money-open-exchange-rates"
+gem "multibases"
+gem "multihashes"
 gem "ougai"
 gem "roda"
 gem "ruby-bandwidth-iris"

config-schema.dhall 🔗

@@ -1,6 +1,7 @@
 { activation_amount : Natural
 , admins : List Text
 , adr : Text
+, bandwidth_app : Text
 , bandwidth_peer : Text
 , bandwidth_site : Text
 , braintree :
@@ -27,6 +28,8 @@
 , interac : Text
 , notify_admin : Text
 , notify_from : Text
+, ogm_path : Text
+, ogm_web_root : Text
 , oxr_app_id : Text
 , payable : Text
 , plans :
@@ -45,5 +48,6 @@
 , upstream_domain : Text
 , web : < Inet : { interface : Text, port : Natural } | Unix : Text >
 , web_register : { from : Text, to : Text }
+, web_root : Text
 , xep0157 : List { label : Text, value : Text, var : Text }
 }

config.dhall.sample 🔗

@@ -11,6 +11,9 @@ in
 	},
 	sgx = "component2.localhost",
 	web = ListenOn.Inet { interface = "::1", port = env:PORT ? 8080 },
+	web_root = "https://example.com",
+	ogm_path = "/var/www/media.example.com",
+	ogm_web_root = "https://media.example.com",
 	creds = {
 		account = "00000",
 		username = "dashboard user",
@@ -30,6 +33,7 @@ in
 	},
 	bandwidth_site = "",
 	bandwidth_peer = "",
+	bandwidth_app = "", -- This can be any voice app
 	braintree = {
 		environment = "sandbox",
 		merchant_id = "",

lib/backend_sgx.rb 🔗

@@ -37,4 +37,8 @@ class BackendSgx
 	def set_fwd_timeout(timeout)
 		REDIS.set("catapult_fwd_timeout-#{from_jid}", timeout)
 	end
+
+	def set_ogm_url(url)
+		REDIS.set("catapult_ogm_url-#{from_jid}", url)
+	end
 end

lib/customer.rb 🔗

@@ -22,7 +22,7 @@ class Customer
 	attr_reader :customer_id, :balance, :jid
 	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
 	               :currency, :merchant_account, :plan_name, :auto_top_up_amount
-	def_delegators :@sgx, :register!, :registered?,
+	def_delegators :@sgx, :register!, :registered?, :set_ogm_url,
 	               :set_fwd_timeout, :fwd, :transcription_enabled
 	def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage
 

lib/customer_fwd.rb 🔗

@@ -25,6 +25,15 @@ class CustomerFwd
 		end
 	end
 
+	def create_call_request
+		request = Bandwidth::ApiCreateCallRequest.new.tap do |cc|
+			cc.to = to
+			cc.call_timeout = timeout.to_i
+		end
+		yield request if block_given?
+		request
+	end
+
 	class Tel < CustomerFwd
 		attr_reader :timeout
 

sgx_jmp.rb 🔗

@@ -423,11 +423,7 @@ Command.new(
 # Commands that just pass through to the SGX
 {
 	"number-display" => ["Display JMP Number"],
-	"configure-calls" => ["Configure Calls"],
-	"record-voicemail-greeting" => [
-		"Record Voicemail Greeting",
-		list_for: ->(fwd: nil, **) { !!fwd }
-	]
+	"configure-calls" => ["Configure Calls"]
 }.each do |node, args|
 	Command.new(node, *args) {
 		Command.customer.then do |customer|
@@ -436,6 +432,26 @@ Command.new(
 	}.register(self, guards: [node: node]).then(&CommandList.method(:register))
 end
 
+Command.new(
+	"ogm",
+	"Record Voicemail Greeting",
+	list_for: ->(fwd: nil, **) { !!fwd },
+	customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new)
+) {
+	Command.customer.then do |customer|
+		BANDWIDTH_VOICE.create_call(
+			CONFIG[:creds][:account],
+			body: customer.fwd.create_call_request do |cc|
+				cc.from = customer.registered?.phone
+				cc.application_id = CONFIG[:bandwidth_app]
+				cc.answer_url = "#{CONFIG[:web_root]}/ogm/start?" \
+				                "customer_id=#{customer.customer_id}"
+			end
+		)
+		Command.finish("You will now receive a call.")
+	end
+}.register(self).then(&CommandList.method(:register))
+
 Command.new(
 	"credit cards",
 	"Credit Card Settings and Management"

views/record_ogm.slim 🔗

@@ -0,0 +1,5 @@
+doctype xml
+Response
+	SpeakSentence At the tone, say your new voicemail greeting. Then, hang up.
+	PlayAudio= "/beep.mp3"
+	Record recordingAvailableUrl="/ogm?customer_id=#{customer_id}" fileFormat="mp3"

web.rb 🔗

@@ -2,6 +2,8 @@
 
 require "digest"
 require "forwardable"
+require "multibases"
+require "multihashes"
 require "roda"
 require "thin"
 require "sentry-ruby"
@@ -11,6 +13,37 @@ require_relative "lib/roda_capture"
 require_relative "lib/roda_em_promise"
 require_relative "lib/rack_fiber"
 
+class OGMDownload
+	def initialize(url)
+		@digest = Digest::SHA512.new
+		@f = Tempfile.open("ogm")
+		@req = EM::HttpRequest.new(url, tls: { verify_peer: true })
+	end
+
+	def download
+		http = @req.aget
+		http.stream do |chunk|
+			@digest << chunk
+			@f.write chunk
+		end
+		http.then { @f.close }.catch do |e|
+			@f.close!
+			EMPromise.reject(e)
+		end
+	end
+
+	def cid
+		Multibases.encode(
+			"base58btc",
+			[1, 85].pack("C*") + Multihashes.encode(@digest.digest, "sha2-512")
+		).pack.to_s
+	end
+
+	def path
+		@f.path
+	end
+end
+
 # rubocop:disable Metrics/ClassLength
 class Web < Roda
 	use Rack::Fiber # Must go first!
@@ -225,17 +258,15 @@ class Web < Roda
 						sgx_repo: Bwmsgsv2Repo.new
 					).find_by_tel(params["to"]).then(&:fwd).then do |fwd|
 						if fwd
-							body = Bandwidth::ApiCreateCallRequest.new.tap do |cc|
-								cc.to = fwd.to
-								cc.from = params["from"]
-								cc.application_id = params["applicationId"]
-								cc.call_timeout = fwd.timeout.to_i
-								cc.answer_url = url inbound_calls_path(nil)
-								cc.disconnect_url = url inbound_calls_path(:transfer_complete)
-							end
 							true_inbound_call[pseudo_call_id] = params["callId"]
 							outbound_transfers[pseudo_call_id] = BANDWIDTH_VOICE.create_call(
-								CONFIG[:creds][:account], body: body
+								CONFIG[:creds][:account],
+								body: fwd.create_call_request do |cc|
+									cc.from = params["from"]
+									cc.application_id = params["applicationId"]
+									cc.answer_url = url inbound_calls_path(nil)
+									cc.disconnect_url = url inbound_calls_path(:transfer_complete)
+								end
 							).data.call_id
 							render :pause, locals: { duration: 300 }
 						else
@@ -268,6 +299,26 @@ class Web < Roda
 			end
 		end
 
+		r.on "ogm" do
+			r.post "start" do
+				render :record_ogm, locals: { customer_id: params["customer_id"] }
+			end
+
+			r.post do
+				jmp_media_url = params["mediaUrl"].sub(
+					/\Ahttps:\/\/voice.bandwidth.com\/api\/v2\/accounts\/\d+/,
+					"https://jmp.chat"
+				)
+				ogm = OGMDownload.new(jmp_media_url)
+				ogm.download.then do
+					File.rename(ogm.path, "#{CONFIG[:ogm_path]}/#{ogm.cid}")
+					CustomerRepo.new.find(params["customer_id"]).then do |customer|
+						customer.set_ogm_url("#{CONFIG[:ogm_web_root]}/#{ogm.cid}.mp3")
+					end
+				end
+			end
+		end
+
 		r.public
 	end
 end