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