data_extensions.rb

  1# frozen_string_literal: true
  2
  3require "regexp-examples"
  4
  5class Rantly
  6	module Data
  7		module Extensions
  8			REGEXP_EXAMPLES_OPTS = {
  9				max_group_results: 10,
 10				max_repeater_variance: 10
 11			}.freeze
 12
 13			# @see https://github.com/mnestorov/regex-patterns
 14			# @return [String]
 15			FRENCH_PHONE = /\+33[1-9]\d{8}/.freeze
 16			# @return [String]
 17			GERMAN_PHONE = /\+49[1-9]\d{3,12}/.freeze
 18			# @return [String]
 19			UK_PHONE = /\+44[1-9]\d{9,10}/.freeze
 20			# @return [String]
 21			SPANISH_PHONE = /\+34[6-9]\d{8}/.freeze
 22			# @return [String]
 23			ITALIAN_PHONE = /\+39[0-9]{9,10}/.freeze
 24
 25			NON_ASCII_CHAR = /[À-ÿĀ-ſЀ-ӿ一-俿]/.freeze
 26			ASCII_PRINT_CHAR = /[[:print:]]/.freeze
 27
 28			SEGMENT_SIZE_GSM7 = 160
 29			SEGMENT_SIZE_UCS2 = 70
 30
 31			# @return [String]
 32			def french_phone
 33				FRENCH_PHONE.random_example(**REGEXP_EXAMPLES_OPTS)
 34			end
 35
 36			# @return [String]
 37			def german_phone
 38				GERMAN_PHONE.random_example(**REGEXP_EXAMPLES_OPTS)
 39			end
 40
 41			# @return [String]
 42			def uk_phone
 43				UK_PHONE.random_example(**REGEXP_EXAMPLES_OPTS)
 44			end
 45
 46			# @return [String]
 47			def spanish_phone
 48				SPANISH_PHONE.random_example(**REGEXP_EXAMPLES_OPTS)
 49			end
 50
 51			# @return [String]
 52			def italian_phone
 53				ITALIAN_PHONE.random_example(**REGEXP_EXAMPLES_OPTS)
 54			end
 55
 56			# @return [String]
 57			def non_nanp_phone
 58				send(
 59					choose(
 60						:french_phone, :german_phone, :uk_phone,
 61						:spanish_phone, :italian_phone
 62					)
 63				)
 64			end
 65
 66			# @note https://stackoverflow.com/questions/6478875/regular-expression-matching-e-164-formatted-phone-numbers
 67			# @return [String]
 68			def nanpa_phone
 69				"+1" +
 70					sized(1) { string(/[2-9]/) } +
 71					sized(2) { string(/[0-9]/) } +
 72					sized(1) { string(/[2-9]/) } +
 73					sized(6) { string(/[0-9]/) }
 74			end
 75
 76			# @note https://stackoverflow.com/questions/4894198/how-to-generate-a-random-date-in-ruby
 77			# @return [String]
 78			def iso8601(from = 0.0, to = Time.now)
 79				value { Time.at(from + float * (to.to_f - from.to_f)).iso8601 }
 80			end
 81
 82			# @return [String]
 83			def bare_jid
 84				local = sized(range(3, 12)) { string(:alnum) }
 85				domain = sized(range(3, 8)) { string(:lower) }
 86				"#{local}@#{domain}.example.com"
 87			end
 88
 89			# @return [String]
 90			def bw_message_id
 91				sized(range(6, 19)) { string(:alnum) }
 92			end
 93
 94			# @return [String]
 95			def shortcode
 96				range(10000, 999999).to_s
 97			end
 98
 99			# @return [Array<String>]
100			HTTP_ESCAPABLE = " &=?/+@#".chars.freeze
101
102			# @return [String]
103			def maybe_http_escapable_string
104				base = sized(range(3, 8)) { string(:alnum) }
105				choose(
106					base,
107					base + choose(*HTTP_ESCAPABLE) +
108						sized(range(1, 5)) { string(:alnum) }
109				)
110			end
111
112			# @return [Array<String>]
113			DELIVERABLE_MEDIA_EXTENSIONS = %w[.jpg .png .gif .mp4 .pdf].freeze
114			# @return [Array<String>]
115			CARRIER_MEDIA_EXTENSIONS = %w[.smil .txt .xml].freeze
116
117			# @param extensions [Array<String>]
118			# @return [String]
119			def media_url(extensions: DELIVERABLE_MEDIA_EXTENSIONS + CARRIER_MEDIA_EXTENSIONS)
120				user_id = sized(range(3, 10)) { string(:alnum) }
121				name = sized(range(3, 12)) { string(:alnum) }
122				ext = choose(*extensions)
123				"https://messaging.bandwidth.com/api/v2/users/#{user_id}/media/#{name}#{ext}"
124			end
125
126			# @return [String]
127			def deliverable_media_url
128				media_url(extensions: DELIVERABLE_MEDIA_EXTENSIONS)
129			end
130
131			# @param ascii_only [Boolean, nil] truthy=force ASCII, nil=random
132			# @param nil_pct [Integer] weight (out of 100) for nil result
133			# @param empty_pct [Integer] weight (out of 100) for empty string result
134			# @param segments [Integer, nil] exact number of segments, overrides length generation
135			# @return [String, nil]
136			def message_body(ascii_only: nil, nil_pct: 2, empty_pct: 2, segments: nil)
137				text_pct = 100 - nil_pct - empty_pct
138
139				freq(
140					[nil_pct, proc { nil }],
141					[empty_pct, proc { "" }],
142					[text_pct, proc { _message_body_text(ascii_only: ascii_only, segments: segments) }]
143				)
144			end
145
146			# @param ascii_only [Boolean, nil]
147			# @param segments [Integer, nil]
148			# @return [String]
149			def _message_body_text(ascii_only: nil, segments: nil)
150				use_ascii = ascii_only || boolean
151				segment_size = use_ascii ? SEGMENT_SIZE_GSM7 : SEGMENT_SIZE_UCS2
152
153				len = if segments
154					      range(segment_size * (segments - 1) + 1, segment_size * segments)
155				      else
156					      threshold = segment_size * 3
157					      freq(
158						      [70, proc { range(1, threshold) }],
159						      [30, proc { range(threshold + 1, threshold + segment_size * 2) }]
160					      )
161				      end
162
163				body = if use_ascii
164					       sized(len) { string(:print) }
165				       else
166					       _utf8_body(len)
167				       end
168
169				guard(!body.downcase.match?(BADWORDS))
170				body
171			end
172
173			# @param length [Integer]
174			# @param non_ascii_fraction [Float] 0..1
175			# @return [String]
176			def _utf8_body(length, non_ascii_fraction = 0.3)
177				n_non_ascii = [((length * non_ascii_fraction).ceil), 1].max
178				n_ascii = length - n_non_ascii
179
180				ascii_chars = Array.new(n_ascii) {
181					ASCII_PRINT_CHAR.random_example(**REGEXP_EXAMPLES_OPTS)
182				}
183				non_ascii_chars = Array.new(n_non_ascii) {
184					NON_ASCII_CHAR.random_example(**REGEXP_EXAMPLES_OPTS)
185				}
186
187				(ascii_chars + non_ascii_chars).shuffle.join
188			end
189		end
190	end
191
192	include Data::Extensions
193end