tel_selections.rb

  1# frozen_string_literal: true
  2
  3require "ruby-bandwidth-iris"
  4Faraday.default_adapter = :em_synchrony
  5
  6require_relative "form_template"
  7
  8class TelSelections
  9	THIRTY_DAYS = 60 * 60 * 24 * 30
 10
 11	def initialize(redis: REDIS)
 12		@redis = redis
 13	end
 14
 15	def set(jid, tel)
 16		@redis.setex("pending_tel_for-#{jid}", THIRTY_DAYS, tel)
 17	end
 18
 19	def delete(jid)
 20		@redis.del("pending_tel_for-#{jid}")
 21	end
 22
 23	def [](jid)
 24		@redis.get("pending_tel_for-#{jid}").then do |tel|
 25			tel ? HaveTel.new(tel) : ChooseTel.new
 26		end
 27	end
 28
 29	class HaveTel
 30		def initialize(tel)
 31			@tel = tel
 32		end
 33
 34		def choose_tel
 35			EMPromise.resolve(@tel)
 36		end
 37	end
 38
 39	class ChooseTel
 40		def choose_tel(error: nil)
 41			Command.reply { |reply|
 42				reply.allowed_actions = [:next]
 43				reply.command << FormTemplate.render("tn_search", error: error)
 44			}.then { |iq| choose_from_list(AvailableNumber.for(iq.form).tns) }
 45		end
 46
 47		def choose_from_list(tns)
 48			if tns.empty?
 49				choose_tel(error: "No numbers found, try another search.")
 50			else
 51				Command.reply { |reply|
 52					reply.allowed_actions = [:next]
 53					reply.command << FormTemplate.render("tn_list", tns: tns)
 54				}.then { |iq| iq.form.field("tel").value.to_s.strip }
 55			end
 56		end
 57
 58		class AvailableNumber
 59			def self.for(form)
 60				new(
 61					Q
 62					.for(form.field("q").value.to_s.strip).iris_query
 63					.merge(enableTNDetail: true)
 64					.merge(Quantity.for(form).iris_query)
 65				)
 66			end
 67
 68			def initialize(iris_query)
 69				@iris_query = iris_query
 70			end
 71
 72			def tns
 73				Command.log.debug("BandwidthIris::AvailableNumber.list", @iris_query)
 74				BandwidthIris::AvailableNumber.list(@iris_query).map(&Tn.method(:new))
 75			end
 76
 77			class Quantity
 78				def self.for(form)
 79					rsm_max = form.find(
 80						"ns:set/ns:max",
 81						ns: "http://jabber.org/protocol/rsm"
 82					).first
 83					if rsm_max
 84						new(rsm_max.content.to_i)
 85					else
 86						Default.new
 87					end
 88				end
 89
 90				def initialize(quantity)
 91					@quantity = quantity
 92				end
 93
 94				def iris_query
 95					{ quantity: @quantity }
 96				end
 97
 98				# NOTE: Gajim sends back the whole list on submit, so big
 99				# lists can cause issues
100				class Default
101					def iris_query
102						{ quantity: 10 }
103					end
104				end
105			end
106		end
107
108		class Tn
109			attr_reader :tel
110
111			def initialize(full_number:, city:, state:, **)
112				@tel = "+1#{full_number}"
113				@locality = city
114				@region = state
115			end
116
117			def formatted_tel
118				@tel =~ /\A\+1(\d{3})(\d{3})(\d+)\Z/
119				"(#{$1}) #{$2}-#{$3}"
120			end
121
122			def option
123				op = Blather::Stanza::X::Field::Option.new(value: tel, label: to_s)
124				op << reference
125				op
126			end
127
128			def reference
129				Nokogiri::XML::Builder.new { |xml|
130					xml.reference(
131						xmlns: "urn:xmpp:reference:0",
132						begin: 0,
133						end: formatted_tel.length - 1,
134						type: "data",
135						uri: "tel:#{tel}"
136					)
137				}.doc.root
138			end
139
140			def to_s
141				"#{formatted_tel} (#{@locality}, #{@region})"
142			end
143		end
144
145		class Q
146			def self.register(regex, &block)
147				@queries ||= []
148				@queries << [regex, block]
149			end
150
151			def self.for(q)
152				@queries.each do |(regex, block)|
153					match_data = (q =~ regex)
154					return block.call($1 || $&, *$~.to_a[2..-1]) if match_data
155				end
156
157				raise "Format not recognized: #{q}"
158			end
159
160			def initialize(q)
161				@q = q
162			end
163
164			{
165				areaCode: [:AreaCode, /\A[2-9][0-9]{2}\Z/],
166				npaNxx: [:NpaNxx, /\A(?:[2-9][0-9]{2}){2}\Z/],
167				npaNxxx: [:NpaNxxx, /\A(?:[2-9][0-9]{2}){2}[0-9]\Z/],
168				zip: [:PostalCode, /\A\d{5}(?:-\d{4})?\Z/],
169				localVanity: [:LocalVanity, /\A~(.+)\Z/]
170			}.each do |k, args|
171				klass = const_set(
172					args[0],
173					Class.new(Q) do
174						define_method(:iris_query) do
175							{ k => @q }
176						end
177					end
178				)
179
180				args[1..-1].each do |regex|
181					register(regex) { |q| klass.new(q) }
182				end
183			end
184
185			class CityState
186				Q.register(/\A([^,]+)\s*,\s*([A-Z]{2})\Z/, &method(:new))
187				def initialize(city, state)
188					@city = city
189					@state = state
190				end
191
192				def iris_query
193					{ city: @city, state: @state }
194				end
195			end
196		end
197	end
198end