# frozen_string_literal: true require "ruby-bandwidth-iris" Faraday.default_adapter = :em_synchrony require_relative "form_template" class TelSelections THIRTY_DAYS = 60 * 60 * 24 * 30 def initialize(redis: REDIS) @redis = redis end def set(jid, tel) @redis.setex("pending_tel_for-#{jid}", THIRTY_DAYS, tel) end def delete(jid) @redis.del("pending_tel_for-#{jid}") end def [](jid) @redis.get("pending_tel_for-#{jid}").then do |tel| tel ? HaveTel.new(tel) : ChooseTel.new end end class HaveTel def initialize(tel) @tel = tel end def choose_tel EMPromise.resolve(@tel) end end class ChooseTel def choose_tel(error: nil) Command.reply { |reply| reply.allowed_actions = [:next] reply.command << FormTemplate.render("tn_search", error: error) }.then do |iq| choose_from_list(AvailableNumber.for(iq.form).tns) rescue StandardError choose_tel(error: $!.to_s) end end def choose_from_list(tns) if tns.empty? choose_tel(error: "No numbers found, try another search.") else Command.reply { |reply| reply.allowed_actions = [:next] reply.command << FormTemplate.render("tn_list", tns: tns) }.then { |iq| iq.form.field("tel").value.to_s.strip } end end class AvailableNumber def self.for(form) new( Q .for(form.field("q").value.to_s.strip).iris_query .merge(enableTNDetail: true, LCA: false) .merge(Quantity.for(form).iris_query) ) end def initialize(iris_query) @iris_query = iris_query end def tns Command.log.debug("BandwidthIris::AvailableNumber.list", @iris_query) BandwidthIris::AvailableNumber.list(@iris_query).map(&Tn.method(:new)) end class Quantity def self.for(form) rsm_max = form.find( "ns:set/ns:max", ns: "http://jabber.org/protocol/rsm" ).first if rsm_max new(rsm_max.content.to_i) else Default.new end end def initialize(quantity) @quantity = quantity end def iris_query { quantity: @quantity } end # NOTE: Gajim sends back the whole list on submit, so big # lists can cause issues class Default def iris_query { quantity: 10 } end end end end class Tn attr_reader :tel def initialize(full_number:, city:, state:, **) @tel = "+1#{full_number}" @locality = city @region = state end def formatted_tel @tel =~ /\A\+1(\d{3})(\d{3})(\d+)\Z/ "(#{$1}) #{$2}-#{$3}" end def option op = Blather::Stanza::X::Field::Option.new(value: tel, label: to_s) op << reference op end def reference Nokogiri::XML::Builder.new { |xml| xml.reference( xmlns: "urn:xmpp:reference:0", begin: 0, end: formatted_tel.length - 1, type: "data", uri: "tel:#{tel}" ) }.doc.root end def to_s "#{formatted_tel} (#{@locality}, #{@region})" end end class Q def self.register(regex, &block) @queries ||= [] @queries << [regex, block] end def self.for(q) @queries.each do |(regex, block)| match_data = (q =~ regex) return block.call($1 || $&, *$~.to_a[2..-1]) if match_data end raise "Format not recognized: #{q}" end def initialize(q) @q = q end { areaCode: [:AreaCode, /\A[2-9][0-9]{2}\Z/], npaNxx: [:NpaNxx, /\A(?:[2-9][0-9]{2}){2}\Z/], npaNxxx: [:NpaNxxx, /\A(?:[2-9][0-9]{2}){2}[0-9]\Z/], zip: [:PostalCode, /\A\d{5}(?:-\d{4})?\Z/], localVanity: [:LocalVanity, /\A~(.+)\Z/] }.each do |k, args| klass = const_set( args[0], Class.new(Q) { define_method(:iris_query) do { k => @q } end } ) args[1..-1].each do |regex| register(regex) { |q| klass.new(q) } end end class CityState Q.register(/\A([^,]+)\s*,\s*([a-zA-Z]{2})\Z/, &method(:new)) CITY_MAP = { "ajax" => "Ajax-Pickering", "kitchener" => "Kitchener-Waterloo", "new york" => "New York City", "pickering" => "Ajax-Pickering", "sault ste marie" => "sault sainte marie", "sault ste. marie" => "sault sainte marie", "south durham" => "Durham", "township of langley" => "Langley", "waterloo" => "Kitchener-Waterloo", "west durham" => "Durham" }.freeze STATE_MAP = { "QC" => "PQ" }.freeze def initialize(city, state) @city = CITY_MAP.fetch(city.downcase, city) @state = STATE_MAP.fetch(state.upcase, state.upcase) end def iris_query { city: @city, state: @state } end end end end end