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