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 option
114 { value: tel, label: to_s }
115 end
116
117 def to_s
118 "#{@tel} (#{@locality}, #{@region})"
119 end
120 end
121
122 class Q
123 def self.register(regex, &block)
124 @queries ||= []
125 @queries << [regex, block]
126 end
127
128 def self.for(q)
129 @queries.each do |(regex, block)|
130 match_data = (q =~ regex)
131 return block.call($1 || $&, *$~.to_a[2..-1]) if match_data
132 end
133
134 raise "Format not recognized: #{q}"
135 end
136
137 def initialize(q)
138 @q = q
139 end
140
141 {
142 areaCode: [:AreaCode, /\A[2-9][0-9]{2}\Z/],
143 npaNxx: [:NpaNxx, /\A(?:[2-9][0-9]{2}){2}\Z/],
144 npaNxxx: [:NpaNxxx, /\A(?:[2-9][0-9]{2}){2}[0-9]\Z/],
145 zip: [:PostalCode, /\A\d{5}(?:-\d{4})?\Z/],
146 localVanity: [:LocalVanity, /\A~(.+)\Z/]
147 }.each do |k, args|
148 klass = const_set(
149 args[0],
150 Class.new(Q) do
151 define_method(:iris_query) do
152 { k => @q }
153 end
154 end
155 )
156
157 args[1..-1].each do |regex|
158 register(regex) { |q| klass.new(q) }
159 end
160 end
161
162 class CityState
163 Q.register(/\A([^,]+)\s*,\s*([A-Z]{2})\Z/, &method(:new))
164 def initialize(city, state)
165 @city = city
166 @state = state
167 end
168
169 def iris_query
170 { city: @city, state: @state }
171 end
172 end
173 end
174 end
175end