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