blackstack-core 1.2.2 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/functions.rb CHANGED
@@ -1,829 +1,824 @@
1
-
2
- module BlackStack
3
-
4
- # -----------------------------------------------------------------------------------------
5
- # PRY Supporting Functions
6
- # -----------------------------------------------------------------------------------------
7
- module Debugging
8
- @@allow_breakpoints = false
9
- @@verbose = false
10
-
11
- # return true if breakpoints are allowed
12
- def self.allow_breakpoints
13
- @@allow_breakpoints
14
- end
15
-
16
- # set breakpoints allowed if the hash contains a key :allow_breakpoints with a value of true
17
- def self.set(h)
18
- @@allow_breakpoints = h[:allow_breakpoints] if h[:allow_breakpoints].is_a?(TrueClass)
19
- @@verbose = h[:verbose] if h[:verbose].is_a?(TrueClass)
20
-
21
- if !@@allow_breakpoints
22
- # monkey patching the pry method to not break on breakpoints
23
- new_pry = lambda do
24
- print "Breakpoint are not allowed" if @@verbose
25
- end
26
-
27
- Binding.class_eval do
28
- alias_method :old_pry, :pry
29
- define_method :pry, new_pry
30
- end
31
- end
32
- end
33
- end
34
-
35
- # -----------------------------------------------------------------------------------------
36
- # OCRA Supporting Functions
37
- # -----------------------------------------------------------------------------------------
38
- module OCRA
39
- # OCRA files run into a temp folder, where the script is unpacked.
40
- #
41
- # This function is useful to require a configuration file when the
42
- # script is running inside an OCRA temp folder, since the local folder
43
- # of the running command is not the filder where the exe file is hosted.
44
- #
45
- # More information:
46
- # * https://stackoverflow.com/questions/1937743/how-to-get-the-current-working-directorys-absolute-path-from-irb
47
- # * https://stackoverflow.com/questions/8577223/ruby-get-the-file-being-executed
48
- # * https://stackoverflow.com/questions/7399882/ruby-getting-path-from-pathfilename/7400057
49
- #
50
- def self.require_in_working_path(filename, path, show_path_info=false)
51
- puts '' if show_path_info
52
- path = File.expand_path File.dirname(path)
53
- #path = Dir.pwd
54
- puts "require_in_working_path.path:#{path}:." if show_path_info
55
- file = "#{path}/#{filename}"
56
- puts "require_in_working_path.file:#{file}:." if show_path_info
57
- require file
58
- end
59
- end # module OCRA
60
-
61
- # -----------------------------------------------------------------------------------------
62
- # DateTime Functions
63
- # -----------------------------------------------------------------------------------------
64
- module DateTime
65
- # -----------------------------------------------------------------------------------------
66
- # Encoding
67
- # -----------------------------------------------------------------------------------------
68
- module Encoding
69
- # Convierte un objeto date-time a un string con formato sql-datetime (yyyy-mm-dd hh:mm:ss).
70
- def self.datetime_to_sql(o)
71
- return o.strftime("%Y-%m-%d %H:%M:%S")
72
- end
73
- end # module Encode
74
-
75
- # -----------------------------------------------------------------------------------------
76
- # Miscelaneous
77
- # -----------------------------------------------------------------------------------------
78
- module Misc
79
- def self.datetime_values_check(year,month,day,hour,minute,second)
80
- if (year.to_i<1900 || year.to_i>=2100)
81
- return false
82
- end
83
-
84
- if (month.to_i<1 || month.to_i>12)
85
- return false
86
- end
87
-
88
- # TODO: Considerar la cantidad de dias de cada mes, y los anios biciestos. Buscar alguna funcion existente.
89
- if (day.to_i<1 || day.to_i>31)
90
- return false
91
- end
92
-
93
- if (hour.to_i<0 || hour.to_i>23)
94
- return false
95
- end
96
-
97
- if (minute.to_i<0 || minute.to_i>59)
98
- return false
99
- end
100
-
101
- if (second.to_i<0 || second.to_i>59)
102
- return false
103
- end
104
-
105
- return true
106
- end # datetime_values_check
107
- end # module Misc
108
- end # module DateTime
109
-
110
- # -----------------------------------------------------------------------------------------
111
- # Numeric Functions
112
- # -----------------------------------------------------------------------------------------
113
- module Number
114
- # -----------------------------------------------------------------------------------------
115
- # Encoding
116
- # -----------------------------------------------------------------------------------------
117
- module Encoding
118
- # Converts number to a string with a format like xx,xxx,xxx.xxxx
119
- # number: it may be int or float
120
- def self.format_with_separator(number)
121
- whole_part, decimal_part = number.to_s.split('.')
122
- [whole_part.gsub(/(\d)(?=\d{3}+$)/, '\1,'), decimal_part].compact.join('.')
123
- end
124
-
125
- # Convierte una cantidad de minutos a una leyenda legible por el usuario.
126
- # Ejemplo: "2 days, 5 hours"
127
- # Ejemplo: "4 hours, 30 minutes"
128
- # Ejemplo: "3 days, 4 hour"
129
- def self.encode_minutes(n)
130
- # TODO: validar que n sea un entero mayor a 0
131
- if (n<0)
132
- return "?"
133
- end
134
- if (n<60)
135
- return "#{n} minutes"
136
- elsif (n<24*60)
137
- return "#{(n/60).to_i} hours, #{n-60*(n/60).to_i} minutes"
138
- else
139
- return "#{(n/(24*60)).to_i} days, #{((n-24*60*(n/(24*60)).to_i)/60).to_i} hours"
140
- end
141
- end
142
- end # module Encode
143
- end # module Number
144
-
145
- # -----------------------------------------------------------------------------------------
146
- # String Functions
147
- # -----------------------------------------------------------------------------------------
148
- module Strings
149
-
150
- GUID_SIZE = 36
151
- MATCH_PASSWORD = /(?=.*[a-zA-Z])(?=.*[0-9]).{6,}/
152
- MATCH_GUID = /{?[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]}?/
153
- MATCH_FILENAME = /[\w\-\_\.]+/
154
- MATCH_EMAIL = /[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{1,25}/
155
- MATCH_DOMAIN = /(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,10}/
156
- MATCH_DATE_STANDARD = /\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])/
157
- MATCH_PHONE = /(?:\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/
158
-
159
- # Note: MATCH_URL gets the URL up to '?', but it doesn't retrieves the parameters.
160
- # Exmaple:
161
- # https://foo.com/bar?param1=value1&param2=value2 --> https://foo.com/bar?
162
- # https://foo.com/bar/?param1=value1&param2=value2 --> https://foo.com/bar/?
163
- MATCH_URL = /(https?:\/\/)?([\da-z\.-]+)([\.\:])([\da-z]{2,6})([\/[\da-z\.\-]+]*[\da-z])(\/)?(\?)?/i
164
-
165
- MATCH_LINKEDIN_COMPANY_URL = /(https?:\/\/)?(www\\.)?linkedin\.com\/company\//
166
- MATCH_FIXNUM = /[0-9]+/
167
- MATCH_CONTENT_SPINNING = /{[^}]+}/
168
- MATCH_SPINNED_TEXT = /code me/ # TODO: define this regex for the issue #1226
169
-
170
- # -----------------------------------------------------------------------------------------
171
- # Fuzzy String Comparsion Functions: How similar are 2 strings that are not exactly equal.
172
- # -----------------------------------------------------------------------------------------
173
- module SQL
174
- def self.string_to_sql_string(s)
175
- #return s.force_encoding("UTF-8").gsub("'", "''").to_s
176
- return s.gsub("'", "''").to_s
177
- end
178
- end
179
-
180
- # -----------------------------------------------------------------------------------------
181
- # Fuzzy String Comparsion Functions: How similar are 2 strings that are not exactly equal.
182
- # -----------------------------------------------------------------------------------------
183
- module Comparing
184
- # retorna 0 si los strings son iguales
185
- # https://stackoverflow.com/questions/16323571/measure-the-distance-between-two-strings-with-ruby
186
- def self.levenshtein_distance(s, t)
187
- s.downcase!
188
- t.downcase!
189
-
190
- m = s.length
191
- n = t.length
192
- return m if n == 0
193
- return n if m == 0
194
- d = Array.new(m+1) {Array.new(n+1)}
195
-
196
- (0..m).each {|i| d[i][0] = i}
197
- (0..n).each {|j| d[0][j] = j}
198
- (1..n).each do |j|
199
- (1..m).each do |i|
200
- d[i][j] = if s[i-1] == t[j-1] # adjust index into string
201
- d[i-1][j-1] # no operation required
202
- else
203
- [ d[i-1][j]+1, # deletion
204
- d[i][j-1]+1, # insertion
205
- d[i-1][j-1]+1, # substitution
206
- ].min
207
- end
208
- end
209
- end
210
- d[m][n]
211
- end
212
-
213
- # retorna la cantidad de palabras con mas de 3 caracteres que se encuentran en el parametro s
214
- def self.max_sardi_distance(s)
215
- s.downcase!
216
- s.gsub!(/-/,' ')
217
- ss = s.scan(/\b([a-z]+)\b/)
218
- n = 0
219
- ss.each { |x|
220
- x = x[0]
221
- if (x.size > 3) # para evitar keywords triviales como 'and'
222
- n += 1
223
- end
224
- }
225
- n
226
- end
227
-
228
- # retorna la cantidad de palabras con mas de 3 caracteres del parametro s que se encuentran en el parametro t
229
- def self.sardi_distance(s, t)
230
- s.downcase!
231
- t.downcase!
232
- s.gsub!(/-/,' ')
233
- t.gsub!(/-/,' ')
234
- max_distance = max_sardi_distance(s)
235
- ss = s.scan(/\b([a-z]+)\b/)
236
- tt = t.scan(/\b([a-z]+)\b/)
237
- n = 0
238
- ss.each { |x|
239
- x = x[0]
240
- if (x.size > 3) # para evitar keywords triviales como 'and'
241
- if ( tt.select { |y| y[0] == x }.size > 0 )
242
- n += 1
243
- end
244
- end
245
- }
246
- return max_distance - n
247
- end
248
- end # module Comparing
249
-
250
- # -----------------------------------------------------------------------------------------
251
- # Encoding: Make a string nice to be shown into an HTML string.
252
- # -----------------------------------------------------------------------------------------
253
- module Encoding
254
- # Then it makes it compatible with UTF-8.
255
- # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
256
- def self.encode_string(s)
257
- s.encode("UTF-8")
258
- end
259
-
260
- # Escape the string to be shown into an HTML screen.
261
- # Then it makes it compatible with UTF-8.
262
- # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
263
- def self.encode_html(s)
264
- encode_string(CGI.escapeHTML(s.to_s))
265
- end
266
-
267
- # Generates a description string from an exception object.
268
- # Eescapes the string to be shown into an HTML screen.
269
- # Makes it compatible with UTF-8.
270
- # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
271
- def self.encode_exception(e, include_backtrace=true)
272
- ret = encode_html(e.to_s)
273
- if (include_backtrace == true)
274
- e.backtrace.each { |s|
275
- ret += "<br/>" + encode_html(s)
276
- } # e.backtrace.each
277
- end # if
278
- ret
279
- end
280
-
281
- # Returns a string with a description of a period of time, to be shown in the screen.
282
- # period: it may be 'H', 'D', 'W', 'M', 'Y'
283
- # units: it is a positive integer
284
- def self.encode_period(period, units)
285
- s = "Last "
286
- s += units.to_i.to_s + " " if units.to_i > 1
287
- s += "Hours" if period.upcase == "H" && units.to_i != 1
288
- s += "Days" if period.upcase == "D" && units.to_i != 1
289
- s += "Weeks" if period.upcase == "W" && units.to_i != 1
290
- s += "Months" if period.upcase == "M" && units.to_i != 1
291
- s += "Years" if period.upcase == "Y" && units.to_i != 1
292
- s += "Hour" if period.upcase == "H" && units.to_i == 1
293
- s += "Day" if period.upcase == "D" && units.to_i == 1
294
- s += "Week" if period.upcase == "W" && units.to_i == 1
295
- s += "Month" if period.upcase == "M" && units.to_i == 1
296
- s += "Year" if period.upcase == "Y" && units.to_i == 1
297
- s
298
- end
299
-
300
- #
301
- def self.encode_guid(s)
302
- return s.gsub('{',"").gsub('}',"").downcase
303
- end
304
-
305
- #
306
- def self.encode_javascript(s)
307
- s.to_s.gsub("'", "\\\\'").gsub("\r", "' + String.fromCharCode(13) + '").gsub("\n", "' + String.fromCharCode(10) + '")
308
- end
309
-
310
- end # module Encoding
311
-
312
- # -----------------------------------------------------------------------------------------
313
- # DateTime
314
- # -----------------------------------------------------------------------------------------
315
- module DateTime
316
- # Check the string has the format yyyymmddhhmmss.
317
- # => Return true if success. Otherwise, return false.
318
- # => Year cannot be lower than 1900.
319
- # => Year cannot be higher or equal than 2100.
320
- def self.datetime_api_check(s)
321
- return false if (s.size!=14)
322
- year = s[0..3]
323
- month = s[4..5]
324
- day = s[6..7]
325
- hour = s[8..9]
326
- minute = s[10..11]
327
- second = s[12..13]
328
- BlackStack::DateTime::Misc::datetime_values_check(year,month,day,hour,minute,second)
329
- end # def datetime_api_check
330
-
331
- # Check the string has the format yyyy-mm-dd hh:mm:ss.
332
- # => Return true if success. Otherwise, return false.
333
- # => Year cannot be lower than 1900.
334
- # => Year cannot be higher or equal than 2100.
335
- def self.datetime_sql_check(s)
336
- return false if (s.size!=19)
337
- year = s[0..3]
338
- month = s[5..6]
339
- day = s[8..9]
340
- hour = s[11..12]
341
- minute = s[14..15]
342
- second = s[17..18]
343
- BlackStack::DateTime::Misc::datetime_values_check(year,month,day,hour,minute,second)
344
- end # def datetime_sql_check
345
-
346
- # Convierte un string con formato api-datatime (yyyymmddhhmmss) a un string con formato sql-datetime (yyyy-mm-dd hh:mm:ss).
347
- def self.datetime_api_to_sql(s)
348
- raise "Wrong Api DataTime Format." if (datetime_api_check(s)==false)
349
- year = s[0..3]
350
- month = s[4..5]
351
- day = s[6..7]
352
- hour = s[8..9]
353
- minute = s[10..11]
354
- second = s[12..13]
355
- ret = "#{year}-#{month}-#{day} #{hour}:#{minute}:#{second}"
356
- return ret
357
- end # def datetime_api_to_sql
358
-
359
- # Convierte un string con formato sql-datatime a un string con formato sql-datetime.
360
- def self.datetime_sql_to_api(s)
361
- raise "Wrong SQL DataTime Format." if (datetime_sql_check(s)==false)
362
- year = s[0..3]
363
- month = s[5..6]
364
- day = s[8..9]
365
- hour = s[11..12]
366
- minute = s[14..15]
367
- second = s[17..18]
368
- ret = "#{year}#{month}#{day}#{hour}#{minute}#{second}"
369
- return ret
370
- end # def datetime_sql_to_api
371
- end # module DateTime
372
-
373
-
374
- # -----------------------------------------------------------------------------------------
375
- # Spinning
376
- # -----------------------------------------------------------------------------------------
377
- module Spinning
378
- # Esta funcion retorna una variacion al azar del texto que se pasa.
379
- # Esta funcion se ocupa de dividir el texto en partes, para eviar el error "too big to product" que arroja la libraría.
380
- def self.random_spinning_variation(text)
381
- ret = text
382
-
383
- text.scan(MATCH_CONTENT_SPINNING).each { |s|
384
- a = ContentSpinning.new(s).spin
385
- rep = a[rand(a.size)]
386
- ret = ret.gsub(s, rep)
387
- a = nil
388
- }
389
-
390
- return ret
391
- end
392
-
393
- # retorna true si la sintaxis del texto spineado es correcta
394
- # caso contrario retorna false
395
- # no soporta spinnings anidados. ejemplo: {my|our|{a car of mine}}
396
- def self.valid_spinning_syntax?(s)
397
- # valido que exste
398
- n = 0
399
- s.split('').each { |c|
400
- n+=1 if c=='{'
401
- n-=1 if c=='}'
402
- if n!=0 && n!=1
403
- #raise "Closing spining char '}' with not previous opening spining char '{'." if n<0
404
- #raise "Opening spining char '{' inside another spining block." if n>1
405
- return false if n<0 # Closing spining char '}' with not previous opening spining char '{'.
406
- return false if n>1 # Opening spining char '{' inside another spining block.
407
- end
408
- }
409
-
410
- return false if n!=0
411
-
412
- # obtengo cada uno de los spinnings
413
- s.scan(MATCH_CONTENT_SPINNING).each { |x|
414
- a = x.split('|')
415
- raise "No variations delimited by '|' inside spinning block." if a.size <= 1
416
- }
417
-
418
- true
419
- end
420
-
421
- # returns true if the text is spinned.
422
- # otherwise, returns false.
423
- def self.spintax?(s)
424
- s.scan(MATCH_CONTENT_SPINNING).size > 0
425
- end
426
- end # module Spinning
427
-
428
-
429
- # -----------------------------------------------------------------------------------------
430
- # Miscelaneus
431
- # -----------------------------------------------------------------------------------------
432
- module Misc
433
- # make a Ruby string safe for a filesystem.
434
- # References:
435
- # => https://stackoverflow.com/questions/1939333/how-to-make-a-ruby-string-safe-for-a-filesystem
436
- # => http://devblog.muziboo.com/2008/06/17/attachment-fu-sanitize-filename-regex-and-unicode-gotcha/
437
- def self.sanitize_filename(filename)
438
- ret = filename.strip do |name|
439
- # NOTE: File.basename doesn't work right with Windows paths on Unix
440
- # get only the filename, not the whole path
441
- name.gsub!(/^.*(\\|\/)/, '')
442
-
443
- # Strip out the non-ascii character
444
- name.gsub!(/[^0-9A-Za-z.\-]/, '_')
445
- end
446
- return ret
447
- end
448
- end # module Misc
449
-
450
-
451
- # -----------------------------------------------------------------------------------------
452
- # Email Appending Functions
453
- # -----------------------------------------------------------------------------------------
454
- module Appending
455
- APPEND_PATTERN_FNAME_DOT_LNAME = 0
456
- APPEND_PATTERN_FNAME = 1
457
- APPEND_PATTERN_LNAME = 2
458
- APPEND_PATTERN_F_LNAME = 3
459
- APPEND_PATTERN_F_DOT_LNAME = 4
460
-
461
- #
462
- def self.name_pattern(pattern, fname, lname)
463
- if (pattern==APPEND_PATTERN_FNAME_DOT_LNAME)
464
- return "#{fname}.#{lname}"
465
- elsif (pattern==APPEND_PATTERN_FNAME)
466
- return "#{fname}"
467
- elsif (pattern==APPEND_PATTERN_LNAME)
468
- return "#{lname}"
469
- elsif (pattern==APPEND_PATTERN_F_LNAME)
470
- return "#{fname[0]}#{lname}"
471
- elsif (pattern==APPEND_PATTERN_F_DOT_LNAME)
472
- return "#{fname[0]}.#{lname}"
473
- else
474
- raise "getNamePattern: Unknown pattern code."
475
- end
476
- end
477
-
478
- #
479
- def self.get_email_variations(first_name, last_name, domain, is_a_big_company)
480
- variations = Array.new
481
- variations << first_name + "." + last_name + "@" + domain
482
- variations << first_name[0] + last_name + "@" + domain
483
- variations << first_name + "_" + last_name + "@" + domain
484
- variations << first_name[0] + "." + last_name + "@" + domain
485
- if (is_a_big_company == false)
486
- variations << last_name + "@" + domain
487
- variations << first_name + "@" + domain
488
- end
489
- #variations << first_name + "." + last_name + "@" + domain
490
- #variations << first_name + "_" + last_name + "@" + domain
491
- #variations << last_name + "." + first_name + "@" + domain
492
- #variations << last_name + "_" + first_name + "@" + domain
493
- #variations << first_name[0] + "." + last_name + "@" + domain
494
- #variations << first_name + "." + last_name[0] + "@" + domain
495
- #variations << last_name[0] + "." + first_name + "@" + domain
496
- #variations << last_name + "." + first_name[0] + "@" + domain
497
- #variations << first_name[0] + last_name + "@" + domain
498
- #variations << first_name + last_name[0] + "@" + domain
499
- #variations << last_name[0] + first_name + "@" + domain
500
- #variations << last_name + first_name[0] + "@" + domain
501
- #variations << first_name + "@" + domain
502
- #variations << last_name + "@" + domain
503
- return variations
504
- end
505
- end # module Appending
506
- end # module String
507
-
508
- # -----------------------------------------------------------------------------------------
509
- # Network
510
- # -----------------------------------------------------------------------------------------
511
- module Netting
512
- CALL_METHOD_GET = 'get'
513
- CALL_METHOD_POST = 'post'
514
- DEFAULT_SSL_VERIFY_MODE = OpenSSL::SSL::VERIFY_NONE
515
- SUCCESS = 'success'
516
-
517
- @@lockfiles = []
518
-
519
- @@max_api_call_channels = 0 # 0 means infinite
520
-
521
- def self.max_api_call_channels()
522
- @@max_api_call_channels
523
- end
524
-
525
- def self.lockfiles()
526
- @@lockfiles
527
- end
528
-
529
- def self.set(h)
530
- @@max_api_call_channels = h[:max_api_call_channels]
531
- @@lockfiles = []
532
-
533
- i = 0
534
- while i<@@max_api_call_channels
535
- @@lockfiles << File.open("./apicall.channel_#{i.to_s}.lock", "w")
536
- i+=1
537
- end
538
- end
539
-
540
-
541
- class ApiCallException < StandardError
542
- attr_accessor :description
543
-
544
- def initialize(s)
545
- self.description = s
546
- end
547
-
548
- def to_s
549
- self.description
550
- end
551
- end
552
-
553
- # New call_get
554
- def self.call_get(url, params = {}, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, support_redirections=true)
555
- uri = URI(url)
556
- uri.query = URI.encode_www_form(params)
557
- Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https', :verify_mode => ssl_verify_mode) do |http|
558
- req = Net::HTTP::Get.new uri
559
- #req.body = body if !body.nil?
560
- res = http.request req
561
- case res
562
- when Net::HTTPSuccess then res
563
- when Net::HTTPRedirection then BlackStack::Netting::call_get(URI(res['location']), params, false) if support_redirections
564
- else
565
- res.error!
566
- end
567
- end
568
- end
569
-
570
- # Call the API and return th result.
571
- #
572
- # Unlike `Net::HTTP::Post`, this method support complex json descriptors in order to submit complex data strucutres to access points.
573
- # For more information about support for complex data structures, reference to: https://github.com/leandrosardi/mysaas/issues/59
574
- #
575
- # url: valid internet address
576
- # params: hash of params to attach in the call
577
- # ssl_verify_mode: you can disabele SSL verification here.
578
- # max_channels: this method use lockfiles to prevent an excesive number of API calls from each datacenter. There is not allowed more simultaneous calls than max_channels.
579
- #
580
- # TODO: parameter support_redirections has been deprecated.
581
- #
582
- def self.call_post(url, params = {}, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, support_redirections=true)
583
- # issue: https://github.com/leandrosardi/mysaas/issues/59
584
- #
585
- # when ruby pushes hash of hashes (or hash of arrays), all values are converted into strings.
586
- # and arrays are mapped to the last element only.
587
- #
588
- # the solution is to convert each element of the hash into a string using `.to_json` method.
589
- #
590
- # reference: https://stackoverflow.com/questions/1667630/how-do-i-convert-a-string-object-into-a-hash-object
591
- #
592
- # iterate the keys of the hash
593
- #
594
- params.each { |key, value| params[key] = value.to_json }
595
-
596
- # do the call
597
- uri = URI(url)
598
- ret = Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https', :verify_mode => ssl_verify_mode) do |http|
599
- req = Net::HTTP::Post.new(uri)
600
- req['Content-Type'] = 'application/json'
601
- req.set_form_data(params)
602
- res = http.request req
603
- case res
604
- when Net::HTTPSuccess then res
605
- #when Net::HTTPRedirection then BlackStack::Netting::call_post(URI(res['location']), params, BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, false) if support_redirections
606
- else
607
- res.error!
608
- end
609
- end
610
- # return
611
- ret
612
- end
613
-
614
- #
615
- def self.api_call(url, params={}, method=BlackStack::Netting::CALL_METHOD_POST, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, max_retries=5)
616
- nTries = 0
617
- bSuccess = false
618
- parsed = nil
619
- sError = ""
620
- while (nTries < max_retries && bSuccess == false)
621
- begin
622
- nTries = nTries + 1
623
- uri = URI(url)
624
- res = BlackStack::Netting::call_post(uri, params, ssl_verify_mode) if method==BlackStack::Netting::CALL_METHOD_POST
625
- res = BlackStack::Netting::call_get(uri, params, ssl_verify_mode) if method==BlackStack::Netting::CALL_METHOD_GET
626
- parsed = JSON.parse(res.body)
627
- if (parsed['status']==BlackStack::Netting::SUCCESS)
628
- bSuccess = true
629
- else
630
- sError = "Status: #{parsed['status'].to_s}. Description: #{parsed['value'].to_s}."
631
- end
632
- rescue Errno::ECONNREFUSED => e
633
- sError = "Errno::ECONNREFUSED:" + e.to_console
634
- rescue => e2
635
- sError = "Exception:" + e2.to_console
636
- end
637
- end # while
638
-
639
- if (bSuccess==false)
640
- raise "#{sError}"
641
- end
642
- end # apicall
643
-
644
- # Download a file from an url to a local folder.
645
- # url: must be somedomain.net instead of somedomain.net/, otherwise, it will throw exception.
646
- # to: must be a valid path to a folder.
647
- def self.download(url, to)
648
- uri = URI(url)
649
- domain = uri.host.start_with?('www.') ? uri.host[4..-1] : uri.host
650
- path = uri.path
651
- filename = path.split("/").last
652
- Net::HTTP.start(domain) do |http|
653
- resp = http.get(path)
654
- open(to, "wb") do |file|
655
- file.write(resp.body)
656
- end
657
- end
658
- end
659
-
660
- # Return the extension of the last path into an URL.
661
- # Example: get_url_extension("http://connect.data.com/sitemap_index.xml?foo_param=foo_value") => ".xml"
662
- def self.get_url_extension(url)
663
- return File.extname(URI.parse(url).path.to_s)
664
- end
665
-
666
- # Removes the 'www.' from an URL.
667
- def self.get_host_without_www(url)
668
- url = "http://#{url}" if URI.parse(url).scheme.nil?
669
- host = URI.parse(url).host.downcase
670
- host.start_with?('www.') ? host[4..-1] : host
671
- end
672
-
673
- # Get the final URL if a web page is redirecting.
674
- def self.get_redirect(url)
675
- uri = URI.parse(url)
676
- protocol = uri.scheme
677
- host = uri.host.downcase
678
- res = Net::HTTP.get_response(uri)
679
- "#{protocol}://#{host}#{res['location']}"
680
- end
681
-
682
- # returns the age in days of the given file
683
- def self.file_age(filename)
684
- (Time.now - File.ctime(filename))/(24*3600)
685
- end
686
-
687
-
688
- # TODO: Is not guaranteed this function works with 100% of the redirect-urls. This problem requires analysis and development of a general purpose algorith
689
- # This function gets the final url from a redirect url.
690
- # Not all the redirect-urls works the same way.
691
- # Below are 3 examples. Each one works with 1 of the 2 strategies applied by this funcion.
692
- # => url = "https://www.google.com.ar/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CB0QFjAAahUKEwjCg8zMsNvGAhXMMj4KHWBfA50&url=https%3A%2F%2Fwww.linkedin.com%2Fpub%2Fdavid-bell%2F5%2F76a%2F12&ei=IGalVcLzFMzl-AHgvo3oCQ&usg=AFQjCNGMbF2vRIOWsRjF-bjjoG6Nl1wg_g&sig2=ZP6ZbZxpmTHw82rIP7YYew&bvm=bv.97653015,d.cWw"
693
- # => url = "https://www.google.com.ar/url?q=https://www.linkedin.com/pub/mark-greene/2/bb8/b59&sa=U&ved=0CDoQFjAIahUKEwiqivi5sdvGAhWJg5AKHSzkB5o&usg=AFQjCNGE09H9hf92mfvwPVnComssDjBBCw"
694
- # If the url is not a redirect-url, this function returns the same url.
695
- =begin
696
- def get_redirect(url)
697
- begin
698
- res = nil
699
- httpc = HTTPClient.new
700
- resp = httpc.get(url)
701
- res = resp.header['Location']
702
-
703
- if res.size == 0
704
- uri = URI.parse(url)
705
- uri_params = CGI.parse(uri.query)
706
- redirected_url = uri_params['url'][0]
707
-
708
- if ( redirected_url != nil )
709
- res = redirected_url
710
- else
711
- res = url
712
- end
713
- else
714
- res = res[0]
715
- end
716
- rescue
717
- res = url
718
- end
719
- return res
720
- end
721
- =end
722
- # returns a hash with the parametes in the url
723
- def self.params(url)
724
- # TODO: Corregir este parche:
725
- # => El codigo de abajo usa la URL de una busqueda en google. Esta url generara una excepcion cuando se intenta parsear sus parametros.
726
- # => Ejecutar las 2 lineas de abajo para verificar.
727
- # => url = "https://www.google.com/webhp#q=[lead+generation]+%22John%22+%22Greater+New+York+City+Area+*+Financial+Services%22+site:linkedin.com%2Fpub+-site:linkedin.com%2Fpub%2Fdir"
728
- # => p = CGI::parse(URI.parse(url).query)
729
- # => La linea de abajo hace un gsbub que hace que esta url siga funcionando como busqueda de google, y ademas se posible parsearla.
730
- url = url.gsub("webhp#q=", "webhp?q=")
731
-
732
- return CGI::parse(URI.parse(url).query)
733
- end
734
-
735
- # Add a parameter to the url. It doesn't validate if the param already exists.
736
- def self.add_param(url, param_name, param_value)
737
- uri = URI(url)
738
- params = URI.decode_www_form(uri.query || '')
739
-
740
- if (params.size==0)
741
- params << [param_name, param_value]
742
- uri.query = URI.encode_www_form(params)
743
- return uri.to_s
744
- else
745
- uri.query = URI.encode_www_form(params)
746
- return uri.to_s + "&" + param_name + "=" + param_value
747
- end
748
- end
749
-
750
- # Changes the value of a parameter in the url. It doesn't validate if the param already exists.
751
- def self.change_param(url, param_name, param_value)
752
- uri = URI(url)
753
- # params = URI.decode_www_form(uri.query || [])
754
- params = CGI.parse(uri.query)
755
- params["start"] = param_value
756
- uri.query = URI.encode_www_form(params)
757
- uri.to_s
758
- end
759
-
760
- # Change or add the value of a parameter in the url, depending if the parameter already exists or not.
761
- def self.set_param(url, param_name, param_value)
762
- params = BlackStack::Netting::params(url)
763
- if ( params.has_key?(param_name) == true )
764
- newurl = BlackStack::Netting::change_param(url, param_name, param_value)
765
- else
766
- newurl = BlackStack::Netting::add_param(url, param_name, param_value)
767
- end
768
- return newurl
769
- end
770
-
771
- # get the domain from any url
772
- def self.getDomainFromUrl(url)
773
- if (url !~ /^http:\/\//i && url !~ /^https:\/\//i)
774
- url = "http://#{url}"
775
- end
776
-
777
- if (URI.parse(url).host == nil)
778
- raise "Cannot get domain for #{url}"
779
- end
780
-
781
- if (url.to_s.length>0)
782
- return URI.parse(url).host.sub(/^www\./, '')
783
- else
784
- return nil
785
- end
786
- end
787
-
788
- def self.getDomainFromEmail(email)
789
- if email.email?
790
- return email.split("@").last
791
- else
792
- raise "getDomainFromEmail: Wrong email format."
793
- end
794
- end
795
-
796
- def self.getWhoisDomains(domain, allow_heuristic_to_avoid_hosting_companies=false)
797
- a = Array.new
798
- c = Whois::Client.new
799
- r = c.lookup(domain)
800
-
801
- res = r.to_s.scan(/Registrant Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
802
- if (res!=nil)
803
- a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
804
- end
805
-
806
- res = r.to_s.scan(/Admin Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
807
- if (res!=nil)
808
- a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
809
- end
810
-
811
- res = r.to_s.scan(/Tech Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
812
- if (res!=nil)
813
- a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
814
- end
815
-
816
- # remover duplicados
817
- a = a.uniq
818
-
819
- #
820
- if (allow_heuristic_to_avoid_hosting_companies==true)
821
- # TODO: develop this feature
822
- end
823
-
824
- return a
825
- end
826
-
827
- end # module Netting
828
-
829
- end # module BlackStack
1
+
2
+ module BlackStack
3
+
4
+ # -----------------------------------------------------------------------------------------
5
+ # PRY Supporting Functions
6
+ # -----------------------------------------------------------------------------------------
7
+ module Debugging
8
+ @@allow_breakpoints = false
9
+ @@verbose = false
10
+
11
+ # return true if breakpoints are allowed
12
+ def self.allow_breakpoints
13
+ @@allow_breakpoints
14
+ end
15
+
16
+ # set breakpoints allowed if the hash contains a key :allow_breakpoints with a value of true
17
+ def self.set(h)
18
+ @@allow_breakpoints = h[:allow_breakpoints] if h[:allow_breakpoints].is_a?(TrueClass)
19
+ @@verbose = h[:verbose] if h[:verbose].is_a?(TrueClass)
20
+
21
+ if !@@allow_breakpoints
22
+ # monkey patching the pry method to not break on breakpoints
23
+ new_pry = lambda do
24
+ print "Breakpoint are not allowed" if @@verbose
25
+ end
26
+
27
+ Binding.class_eval do
28
+ alias_method :old_pry, :pry
29
+ define_method :pry, new_pry
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ # -----------------------------------------------------------------------------------------
36
+ # OCRA Supporting Functions
37
+ # -----------------------------------------------------------------------------------------
38
+ module OCRA
39
+ # OCRA files run into a temp folder, where the script is unpacked.
40
+ #
41
+ # This function is useful to require a configuration file when the
42
+ # script is running inside an OCRA temp folder, since the local folder
43
+ # of the running command is not the filder where the exe file is hosted.
44
+ #
45
+ # More information:
46
+ # * https://stackoverflow.com/questions/1937743/how-to-get-the-current-working-directorys-absolute-path-from-irb
47
+ # * https://stackoverflow.com/questions/8577223/ruby-get-the-file-being-executed
48
+ # * https://stackoverflow.com/questions/7399882/ruby-getting-path-from-pathfilename/7400057
49
+ #
50
+ def self.require_in_working_path(filename, path, show_path_info=false)
51
+ puts '' if show_path_info
52
+ path = File.expand_path File.dirname(path)
53
+ #path = Dir.pwd
54
+ puts "require_in_working_path.path:#{path}:." if show_path_info
55
+ file = "#{path}/#{filename}"
56
+ puts "require_in_working_path.file:#{file}:." if show_path_info
57
+ require file
58
+ end
59
+ end # module OCRA
60
+
61
+ # -----------------------------------------------------------------------------------------
62
+ # DateTime Functions
63
+ # -----------------------------------------------------------------------------------------
64
+ module DateTime
65
+ # -----------------------------------------------------------------------------------------
66
+ # Encoding
67
+ # -----------------------------------------------------------------------------------------
68
+ module Encoding
69
+ # Convierte un objeto date-time a un string con formato sql-datetime (yyyy-mm-dd hh:mm:ss).
70
+ def self.datetime_to_sql(o)
71
+ return o.strftime("%Y-%m-%d %H:%M:%S")
72
+ end
73
+ end # module Encode
74
+
75
+ # -----------------------------------------------------------------------------------------
76
+ # Miscelaneous
77
+ # -----------------------------------------------------------------------------------------
78
+ module Misc
79
+ def self.datetime_values_check(year,month,day,hour,minute,second)
80
+ if (year.to_i<1900 || year.to_i>=2100)
81
+ return false
82
+ end
83
+
84
+ if (month.to_i<1 || month.to_i>12)
85
+ return false
86
+ end
87
+
88
+ # TODO: Considerar la cantidad de dias de cada mes, y los anios biciestos. Buscar alguna funcion existente.
89
+ if (day.to_i<1 || day.to_i>31)
90
+ return false
91
+ end
92
+
93
+ if (hour.to_i<0 || hour.to_i>23)
94
+ return false
95
+ end
96
+
97
+ if (minute.to_i<0 || minute.to_i>59)
98
+ return false
99
+ end
100
+
101
+ if (second.to_i<0 || second.to_i>59)
102
+ return false
103
+ end
104
+
105
+ return true
106
+ end # datetime_values_check
107
+ end # module Misc
108
+ end # module DateTime
109
+
110
+ # -----------------------------------------------------------------------------------------
111
+ # Numeric Functions
112
+ # -----------------------------------------------------------------------------------------
113
+ module Number
114
+ # -----------------------------------------------------------------------------------------
115
+ # Encoding
116
+ # -----------------------------------------------------------------------------------------
117
+ module Encoding
118
+ # Converts number to a string with a format like xx,xxx,xxx.xxxx
119
+ # number: it may be int or float
120
+ def self.format_with_separator(number)
121
+ whole_part, decimal_part = number.to_s.split('.')
122
+ [whole_part.gsub(/(\d)(?=\d{3}+$)/, '\1,'), decimal_part].compact.join('.')
123
+ end
124
+
125
+ # Convierte una cantidad de minutos a una leyenda legible por el usuario.
126
+ # Ejemplo: "2 days, 5 hours"
127
+ # Ejemplo: "4 hours, 30 minutes"
128
+ # Ejemplo: "3 days, 4 hour"
129
+ def self.encode_minutes(n)
130
+ # TODO: validar que n sea un entero mayor a 0
131
+ if (n<0)
132
+ return "?"
133
+ end
134
+ if (n<60)
135
+ return "#{n} minutes"
136
+ elsif (n<24*60)
137
+ return "#{(n/60).to_i} hours, #{n-60*(n/60).to_i} minutes"
138
+ else
139
+ return "#{(n/(24*60)).to_i} days, #{((n-24*60*(n/(24*60)).to_i)/60).to_i} hours"
140
+ end
141
+ end
142
+ end # module Encode
143
+ end # module Number
144
+
145
+ # -----------------------------------------------------------------------------------------
146
+ # String Functions
147
+ # -----------------------------------------------------------------------------------------
148
+ module Strings
149
+
150
+ GUID_SIZE = 36
151
+ MATCH_PASSWORD = /(?=.*[a-zA-Z])(?=.*[0-9]).{6,}/
152
+ MATCH_GUID = /{?[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]\-[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]}?/
153
+ MATCH_FILENAME = /[\w\-\_\.]+/
154
+ MATCH_EMAIL = /[A-Z0-9._%a-z\-]+@(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{1,25}/
155
+ MATCH_DOMAIN = /(?:[A-Z0-9a-z\-]+\.)+[A-Za-z]{2,10}/
156
+ MATCH_DATE_STANDARD = /\d{4}\-(0?[1-9]|1[012])\-(0?[1-9]|[12][0-9]|3[01])/
157
+ MATCH_PHONE = /(?:\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}/
158
+
159
+ # Note: MATCH_URL gets the URL up to '?', but it doesn't retrieves the parameters.
160
+ # Exmaple:
161
+ # https://foo.com/bar?param1=value1&param2=value2 --> https://foo.com/bar?
162
+ # https://foo.com/bar/?param1=value1&param2=value2 --> https://foo.com/bar/?
163
+ MATCH_URL = /(https?:\/\/)?([\da-z\.-]+)([\.\:])([\da-z]{2,6})([\/[\da-z\.\-]+]*[\da-z])(\/)?(\?)?/i
164
+
165
+ MATCH_LINKEDIN_COMPANY_URL = /(https?:\/\/)?(www\\.)?linkedin\.com\/company\//
166
+ MATCH_FIXNUM = /[0-9]+/
167
+ MATCH_CONTENT_SPINNING = /{[^}]+}/
168
+ MATCH_SPINNED_TEXT = /code me/ # TODO: define this regex for the issue #1226
169
+
170
+ # -----------------------------------------------------------------------------------------
171
+ # Fuzzy String Comparsion Functions: How similar are 2 strings that are not exactly equal.
172
+ # -----------------------------------------------------------------------------------------
173
+ module SQL
174
+ def self.string_to_sql_string(s)
175
+ #return s.force_encoding("UTF-8").gsub("'", "''").to_s
176
+ return s.gsub("'", "''").to_s
177
+ end
178
+ end
179
+
180
+ # -----------------------------------------------------------------------------------------
181
+ # Fuzzy String Comparsion Functions: How similar are 2 strings that are not exactly equal.
182
+ # -----------------------------------------------------------------------------------------
183
+ module Comparing
184
+ # retorna 0 si los strings son iguales
185
+ # https://stackoverflow.com/questions/16323571/measure-the-distance-between-two-strings-with-ruby
186
+ def self.levenshtein_distance(s, t)
187
+ s.downcase!
188
+ t.downcase!
189
+
190
+ m = s.length
191
+ n = t.length
192
+ return m if n == 0
193
+ return n if m == 0
194
+ d = Array.new(m+1) {Array.new(n+1)}
195
+
196
+ (0..m).each {|i| d[i][0] = i}
197
+ (0..n).each {|j| d[0][j] = j}
198
+ (1..n).each do |j|
199
+ (1..m).each do |i|
200
+ d[i][j] = if s[i-1] == t[j-1] # adjust index into string
201
+ d[i-1][j-1] # no operation required
202
+ else
203
+ [ d[i-1][j]+1, # deletion
204
+ d[i][j-1]+1, # insertion
205
+ d[i-1][j-1]+1, # substitution
206
+ ].min
207
+ end
208
+ end
209
+ end
210
+ d[m][n]
211
+ end
212
+
213
+ # retorna la cantidad de palabras con mas de 3 caracteres que se encuentran en el parametro s
214
+ def self.max_sardi_distance(s)
215
+ s.downcase!
216
+ s.gsub!(/-/,' ')
217
+ ss = s.scan(/\b([a-z]+)\b/)
218
+ n = 0
219
+ ss.each { |x|
220
+ x = x[0]
221
+ if (x.size > 3) # para evitar keywords triviales como 'and'
222
+ n += 1
223
+ end
224
+ }
225
+ n
226
+ end
227
+
228
+ # retorna la cantidad de palabras con mas de 3 caracteres del parametro s que se encuentran en el parametro t
229
+ def self.sardi_distance(s, t)
230
+ s.downcase!
231
+ t.downcase!
232
+ s.gsub!(/-/,' ')
233
+ t.gsub!(/-/,' ')
234
+ max_distance = max_sardi_distance(s)
235
+ ss = s.scan(/\b([a-z]+)\b/)
236
+ tt = t.scan(/\b([a-z]+)\b/)
237
+ n = 0
238
+ ss.each { |x|
239
+ x = x[0]
240
+ if (x.size > 3) # para evitar keywords triviales como 'and'
241
+ if ( tt.select { |y| y[0] == x }.size > 0 )
242
+ n += 1
243
+ end
244
+ end
245
+ }
246
+ return max_distance - n
247
+ end
248
+ end # module Comparing
249
+
250
+ # -----------------------------------------------------------------------------------------
251
+ # Encoding: Make a string nice to be shown into an HTML string.
252
+ # -----------------------------------------------------------------------------------------
253
+ module Encoding
254
+ # Then it makes it compatible with UTF-8.
255
+ # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
256
+ def self.encode_string(s)
257
+ s.encode("UTF-8")
258
+ end
259
+
260
+ # Escape the string to be shown into an HTML screen.
261
+ # Then it makes it compatible with UTF-8.
262
+ # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
263
+ def self.encode_html(s)
264
+ encode_string(CGI.escapeHTML(s.to_s))
265
+ end
266
+
267
+ # Generates a description string from an exception object.
268
+ # Eescapes the string to be shown into an HTML screen.
269
+ # Makes it compatible with UTF-8.
270
+ # More details here: https://bitbucket.org/leandro_sardi/blackstack/issues/961
271
+ def self.encode_exception(e, include_backtrace=true)
272
+ ret = encode_html(e.to_s)
273
+ if (include_backtrace == true)
274
+ e.backtrace.each { |s|
275
+ ret += "<br/>" + encode_html(s)
276
+ } # e.backtrace.each
277
+ end # if
278
+ ret
279
+ end
280
+
281
+ # Returns a string with a description of a period of time, to be shown in the screen.
282
+ # period: it may be 'H', 'D', 'W', 'M', 'Y'
283
+ # units: it is a positive integer
284
+ def self.encode_period(period, units)
285
+ s = "Last "
286
+ s += units.to_i.to_s + " " if units.to_i > 1
287
+ s += "Hours" if period.upcase == "H" && units.to_i != 1
288
+ s += "Days" if period.upcase == "D" && units.to_i != 1
289
+ s += "Weeks" if period.upcase == "W" && units.to_i != 1
290
+ s += "Months" if period.upcase == "M" && units.to_i != 1
291
+ s += "Years" if period.upcase == "Y" && units.to_i != 1
292
+ s += "Hour" if period.upcase == "H" && units.to_i == 1
293
+ s += "Day" if period.upcase == "D" && units.to_i == 1
294
+ s += "Week" if period.upcase == "W" && units.to_i == 1
295
+ s += "Month" if period.upcase == "M" && units.to_i == 1
296
+ s += "Year" if period.upcase == "Y" && units.to_i == 1
297
+ s
298
+ end
299
+
300
+ #
301
+ def self.encode_guid(s)
302
+ return s.gsub('{',"").gsub('}',"").downcase
303
+ end
304
+
305
+ #
306
+ def self.encode_javascript(s)
307
+ s.to_s.gsub("'", "\\\\'").gsub("\r", "' + String.fromCharCode(13) + '").gsub("\n", "' + String.fromCharCode(10) + '")
308
+ end
309
+
310
+ end # module Encoding
311
+
312
+ # -----------------------------------------------------------------------------------------
313
+ # DateTime
314
+ # -----------------------------------------------------------------------------------------
315
+ module DateTime
316
+ # Check the string has the format yyyymmddhhmmss.
317
+ # => Return true if success. Otherwise, return false.
318
+ # => Year cannot be lower than 1900.
319
+ # => Year cannot be higher or equal than 2100.
320
+ def self.datetime_api_check(s)
321
+ return false if (s.size!=14)
322
+ year = s[0..3]
323
+ month = s[4..5]
324
+ day = s[6..7]
325
+ hour = s[8..9]
326
+ minute = s[10..11]
327
+ second = s[12..13]
328
+ BlackStack::DateTime::Misc::datetime_values_check(year,month,day,hour,minute,second)
329
+ end # def datetime_api_check
330
+
331
+ # Check the string has the format yyyy-mm-dd hh:mm:ss.
332
+ # => Return true if success. Otherwise, return false.
333
+ # => Year cannot be lower than 1900.
334
+ # => Year cannot be higher or equal than 2100.
335
+ def self.datetime_sql_check(s)
336
+ return false if (s.size!=19)
337
+ year = s[0..3]
338
+ month = s[5..6]
339
+ day = s[8..9]
340
+ hour = s[11..12]
341
+ minute = s[14..15]
342
+ second = s[17..18]
343
+ BlackStack::DateTime::Misc::datetime_values_check(year,month,day,hour,minute,second)
344
+ end # def datetime_sql_check
345
+
346
+ # Convierte un string con formato api-datatime (yyyymmddhhmmss) a un string con formato sql-datetime (yyyy-mm-dd hh:mm:ss).
347
+ def self.datetime_api_to_sql(s)
348
+ raise "Wrong Api DataTime Format." if (datetime_api_check(s)==false)
349
+ year = s[0..3]
350
+ month = s[4..5]
351
+ day = s[6..7]
352
+ hour = s[8..9]
353
+ minute = s[10..11]
354
+ second = s[12..13]
355
+ ret = "#{year}-#{month}-#{day} #{hour}:#{minute}:#{second}"
356
+ return ret
357
+ end # def datetime_api_to_sql
358
+
359
+ # Convierte un string con formato sql-datatime a un string con formato sql-datetime.
360
+ def self.datetime_sql_to_api(s)
361
+ raise "Wrong SQL DataTime Format." if (datetime_sql_check(s)==false)
362
+ year = s[0..3]
363
+ month = s[5..6]
364
+ day = s[8..9]
365
+ hour = s[11..12]
366
+ minute = s[14..15]
367
+ second = s[17..18]
368
+ ret = "#{year}#{month}#{day}#{hour}#{minute}#{second}"
369
+ return ret
370
+ end # def datetime_sql_to_api
371
+ end # module DateTime
372
+
373
+
374
+ # -----------------------------------------------------------------------------------------
375
+ # Spinning
376
+ # -----------------------------------------------------------------------------------------
377
+ module Spinning
378
+ # Esta funcion retorna una variacion al azar del texto que se pasa.
379
+ # Esta funcion se ocupa de dividir el texto en partes, para eviar el error "too big to product" que arroja la libraría.
380
+ def self.random_spinning_variation(text)
381
+ ret = text
382
+
383
+ text.scan(MATCH_CONTENT_SPINNING).each { |s|
384
+ a = ContentSpinning.new(s).spin
385
+ rep = a[rand(a.size)]
386
+ ret = ret.gsub(s, rep)
387
+ a = nil
388
+ }
389
+
390
+ return ret
391
+ end
392
+
393
+ # retorna true si la sintaxis del texto spineado es correcta
394
+ # caso contrario retorna false
395
+ # no soporta spinnings anidados. ejemplo: {my|our|{a car of mine}}
396
+ def self.valid_spinning_syntax?(s)
397
+ # valido que exste
398
+ n = 0
399
+ s.split('').each { |c|
400
+ n+=1 if c=='{'
401
+ n-=1 if c=='}'
402
+ if n!=0 && n!=1
403
+ #raise "Closing spining char '}' with not previous opening spining char '{'." if n<0
404
+ #raise "Opening spining char '{' inside another spining block." if n>1
405
+ return false if n<0 # Closing spining char '}' with not previous opening spining char '{'.
406
+ return false if n>1 # Opening spining char '{' inside another spining block.
407
+ end
408
+ }
409
+
410
+ return false if n!=0
411
+
412
+ # obtengo cada uno de los spinnings
413
+ s.scan(MATCH_CONTENT_SPINNING).each { |x|
414
+ a = x.split('|')
415
+ raise "No variations delimited by '|' inside spinning block." if a.size <= 1
416
+ }
417
+
418
+ true
419
+ end
420
+
421
+ # returns true if the text is spinned.
422
+ # otherwise, returns false.
423
+ def self.spintax?(s)
424
+ s.scan(MATCH_CONTENT_SPINNING).size > 0
425
+ end
426
+ end # module Spinning
427
+
428
+
429
+ # -----------------------------------------------------------------------------------------
430
+ # Miscelaneus
431
+ # -----------------------------------------------------------------------------------------
432
+ module Misc
433
+ # make a Ruby string safe for a filesystem.
434
+ # References:
435
+ # => https://stackoverflow.com/questions/1939333/how-to-make-a-ruby-string-safe-for-a-filesystem
436
+ # => http://devblog.muziboo.com/2008/06/17/attachment-fu-sanitize-filename-regex-and-unicode-gotcha/
437
+ def self.sanitize_filename(filename)
438
+ ret = filename.strip do |name|
439
+ # NOTE: File.basename doesn't work right with Windows paths on Unix
440
+ # get only the filename, not the whole path
441
+ name.gsub!(/^.*(\\|\/)/, '')
442
+
443
+ # Strip out the non-ascii character
444
+ name.gsub!(/[^0-9A-Za-z.\-]/, '_')
445
+ end
446
+ return ret
447
+ end
448
+ end # module Misc
449
+
450
+
451
+ # -----------------------------------------------------------------------------------------
452
+ # Email Appending Functions
453
+ # -----------------------------------------------------------------------------------------
454
+ module Appending
455
+ APPEND_PATTERN_FNAME_DOT_LNAME = 0
456
+ APPEND_PATTERN_FNAME = 1
457
+ APPEND_PATTERN_LNAME = 2
458
+ APPEND_PATTERN_F_LNAME = 3
459
+ APPEND_PATTERN_F_DOT_LNAME = 4
460
+
461
+ #
462
+ def self.name_pattern(pattern, fname, lname)
463
+ if (pattern==APPEND_PATTERN_FNAME_DOT_LNAME)
464
+ return "#{fname}.#{lname}"
465
+ elsif (pattern==APPEND_PATTERN_FNAME)
466
+ return "#{fname}"
467
+ elsif (pattern==APPEND_PATTERN_LNAME)
468
+ return "#{lname}"
469
+ elsif (pattern==APPEND_PATTERN_F_LNAME)
470
+ return "#{fname[0]}#{lname}"
471
+ elsif (pattern==APPEND_PATTERN_F_DOT_LNAME)
472
+ return "#{fname[0]}.#{lname}"
473
+ else
474
+ raise "getNamePattern: Unknown pattern code."
475
+ end
476
+ end
477
+
478
+ #
479
+ def self.get_email_variations(first_name, last_name, domain, is_a_big_company)
480
+ variations = Array.new
481
+ variations << first_name + "." + last_name + "@" + domain
482
+ variations << first_name[0] + last_name + "@" + domain
483
+ variations << first_name + "_" + last_name + "@" + domain
484
+ variations << first_name[0] + "." + last_name + "@" + domain
485
+ if (is_a_big_company == false)
486
+ variations << last_name + "@" + domain
487
+ variations << first_name + "@" + domain
488
+ end
489
+ #variations << first_name + "." + last_name + "@" + domain
490
+ #variations << first_name + "_" + last_name + "@" + domain
491
+ #variations << last_name + "." + first_name + "@" + domain
492
+ #variations << last_name + "_" + first_name + "@" + domain
493
+ #variations << first_name[0] + "." + last_name + "@" + domain
494
+ #variations << first_name + "." + last_name[0] + "@" + domain
495
+ #variations << last_name[0] + "." + first_name + "@" + domain
496
+ #variations << last_name + "." + first_name[0] + "@" + domain
497
+ #variations << first_name[0] + last_name + "@" + domain
498
+ #variations << first_name + last_name[0] + "@" + domain
499
+ #variations << last_name[0] + first_name + "@" + domain
500
+ #variations << last_name + first_name[0] + "@" + domain
501
+ #variations << first_name + "@" + domain
502
+ #variations << last_name + "@" + domain
503
+ return variations
504
+ end
505
+ end # module Appending
506
+ end # module String
507
+
508
+ # -----------------------------------------------------------------------------------------
509
+ # Network
510
+ # -----------------------------------------------------------------------------------------
511
+ module Netting
512
+ CALL_METHOD_GET = 'get'
513
+ CALL_METHOD_POST = 'post'
514
+ DEFAULT_SSL_VERIFY_MODE = OpenSSL::SSL::VERIFY_NONE
515
+ SUCCESS = 'success'
516
+
517
+ @@lockfiles = []
518
+
519
+ @@max_api_call_channels = 0 # 0 means infinite
520
+
521
+ def self.max_api_call_channels()
522
+ @@max_api_call_channels
523
+ end
524
+
525
+ def self.lockfiles()
526
+ @@lockfiles
527
+ end
528
+
529
+ def self.set(h)
530
+ @@max_api_call_channels = h[:max_api_call_channels]
531
+ @@lockfiles = []
532
+
533
+ i = 0
534
+ while i<@@max_api_call_channels
535
+ @@lockfiles << File.open("./apicall.channel_#{i.to_s}.lock", "w")
536
+ i+=1
537
+ end
538
+ end
539
+
540
+
541
+ class ApiCallException < StandardError
542
+ attr_accessor :description
543
+
544
+ def initialize(s)
545
+ self.description = s
546
+ end
547
+
548
+ def to_s
549
+ self.description
550
+ end
551
+ end
552
+
553
+ # New call_get
554
+ def self.call_get(url, params = {}, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, support_redirections=true)
555
+ uri = URI(url)
556
+ uri.query = URI.encode_www_form(params)
557
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https', :verify_mode => ssl_verify_mode) do |http|
558
+ req = Net::HTTP::Get.new uri
559
+ #req.body = body if !body.nil?
560
+ res = http.request req
561
+ case res
562
+ when Net::HTTPSuccess then res
563
+ when Net::HTTPRedirection then BlackStack::Netting::call_get(URI(res['location']), params, false) if support_redirections
564
+ else
565
+ res.error!
566
+ end
567
+ end
568
+ end
569
+
570
+ # Call the API and return th result.
571
+ #
572
+ # Unlike `Net::HTTP::Post`, this method support complex json descriptors in order to submit complex data strucutres to access points.
573
+ # For more information about support for complex data structures, reference to: https://github.com/leandrosardi/mysaas/issues/59
574
+ #
575
+ # url: valid internet address
576
+ # body: hash of body to attach in the call
577
+ # ssl_verify_mode: you can disabele SSL verification here.
578
+ # max_channels: this method use lockfiles to prevent an excesive number of API calls from each datacenter. There is not allowed more simultaneous calls than max_channels.
579
+ #
580
+ # TODO: parameter support_redirections has been deprecated.
581
+ #
582
+ def self.call_post(url, body = {}, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, support_redirections=true)
583
+ # issue: https://github.com/leandrosardi/mysaas/issues/59
584
+ #
585
+ # when ruby pushes hash of hashes (or hash of arrays), all values are converted into strings.
586
+ # and arrays are mapped to the last element only.
587
+ #
588
+ # the solution is to convert each element of the hash into a string using `.to_json` method.
589
+ #
590
+ # references:
591
+ # - https://stackoverflow.com/questions/1667630/how-do-i-convert-a-string-object-into-a-hash-object
592
+ # - https://stackoverflow.com/questions/67572866/how-to-build-complex-json-to-post-to-a-web-service-with-rails-5-2-and-faraday-ge
593
+ #
594
+ # iterate the keys of the hash
595
+ #
596
+ params = {} # not needed for post calls to access points
597
+ path = URI::parse(url).path
598
+ domain = url.gsub(/#{Regexp.escape(path)}/, '')
599
+
600
+ conn = Faraday.new(domain, :ssl=>{:verify=>ssl_verify_mode!=OpenSSL::SSL::VERIFY_NONE})
601
+ ret = conn.post(path, params) do |req|
602
+ req.body = body.to_json
603
+ req.headers['Content-Type'] = 'application/json'
604
+ req.headers['Accept'] = 'application/json'
605
+ end
606
+ ret
607
+ end
608
+
609
+ # TODO: deprecated
610
+ def self.api_call(url, params={}, method=BlackStack::Netting::CALL_METHOD_POST, ssl_verify_mode=BlackStack::Netting::DEFAULT_SSL_VERIFY_MODE, max_retries=5)
611
+ nTries = 0
612
+ bSuccess = false
613
+ parsed = nil
614
+ sError = ""
615
+ while (nTries < max_retries && bSuccess == false)
616
+ begin
617
+ nTries = nTries + 1
618
+ uri = URI(url)
619
+ res = BlackStack::Netting::call_post(uri, params, ssl_verify_mode) if method==BlackStack::Netting::CALL_METHOD_POST
620
+ res = BlackStack::Netting::call_get(uri, params, ssl_verify_mode) if method==BlackStack::Netting::CALL_METHOD_GET
621
+ parsed = JSON.parse(res.body)
622
+ if (parsed['status']==BlackStack::Netting::SUCCESS)
623
+ bSuccess = true
624
+ else
625
+ sError = "Status: #{parsed['status'].to_s}. Description: #{parsed['value'].to_s}."
626
+ end
627
+ rescue Errno::ECONNREFUSED => e
628
+ sError = "Errno::ECONNREFUSED:" + e.to_console
629
+ rescue => e2
630
+ sError = "Exception:" + e2.to_console
631
+ end
632
+ end # while
633
+
634
+ if (bSuccess==false)
635
+ raise "#{sError}"
636
+ end
637
+ end # apicall
638
+
639
+ # Download a file from an url to a local folder.
640
+ # url: must be somedomain.net instead of somedomain.net/, otherwise, it will throw exception.
641
+ # to: must be a valid path to a folder.
642
+ def self.download(url, to)
643
+ uri = URI(url)
644
+ domain = uri.host.start_with?('www.') ? uri.host[4..-1] : uri.host
645
+ path = uri.path
646
+ filename = path.split("/").last
647
+ Net::HTTP.start(domain) do |http|
648
+ resp = http.get(path)
649
+ open(to, "wb") do |file|
650
+ file.write(resp.body)
651
+ end
652
+ end
653
+ end
654
+
655
+ # Return the extension of the last path into an URL.
656
+ # Example: get_url_extension("http://connect.data.com/sitemap_index.xml?foo_param=foo_value") => ".xml"
657
+ def self.get_url_extension(url)
658
+ return File.extname(URI.parse(url).path.to_s)
659
+ end
660
+
661
+ # Removes the 'www.' from an URL.
662
+ def self.get_host_without_www(url)
663
+ url = "http://#{url}" if URI.parse(url).scheme.nil?
664
+ host = URI.parse(url).host.downcase
665
+ host.start_with?('www.') ? host[4..-1] : host
666
+ end
667
+
668
+ # Get the final URL if a web page is redirecting.
669
+ def self.get_redirect(url)
670
+ uri = URI.parse(url)
671
+ protocol = uri.scheme
672
+ host = uri.host.downcase
673
+ res = Net::HTTP.get_response(uri)
674
+ "#{protocol}://#{host}#{res['location']}"
675
+ end
676
+
677
+ # returns the age in days of the given file
678
+ def self.file_age(filename)
679
+ (Time.now - File.ctime(filename))/(24*3600)
680
+ end
681
+
682
+
683
+ # TODO: Is not guaranteed this function works with 100% of the redirect-urls. This problem requires analysis and development of a general purpose algorith
684
+ # This function gets the final url from a redirect url.
685
+ # Not all the redirect-urls works the same way.
686
+ # Below are 3 examples. Each one works with 1 of the 2 strategies applied by this funcion.
687
+ # => url = "https://www.google.com.ar/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0CB0QFjAAahUKEwjCg8zMsNvGAhXMMj4KHWBfA50&url=https%3A%2F%2Fwww.linkedin.com%2Fpub%2Fdavid-bell%2F5%2F76a%2F12&ei=IGalVcLzFMzl-AHgvo3oCQ&usg=AFQjCNGMbF2vRIOWsRjF-bjjoG6Nl1wg_g&sig2=ZP6ZbZxpmTHw82rIP7YYew&bvm=bv.97653015,d.cWw"
688
+ # => url = "https://www.google.com.ar/url?q=https://www.linkedin.com/pub/mark-greene/2/bb8/b59&sa=U&ved=0CDoQFjAIahUKEwiqivi5sdvGAhWJg5AKHSzkB5o&usg=AFQjCNGE09H9hf92mfvwPVnComssDjBBCw"
689
+ # If the url is not a redirect-url, this function returns the same url.
690
+ =begin
691
+ def get_redirect(url)
692
+ begin
693
+ res = nil
694
+ httpc = HTTPClient.new
695
+ resp = httpc.get(url)
696
+ res = resp.header['Location']
697
+
698
+ if res.size == 0
699
+ uri = URI.parse(url)
700
+ uri_params = CGI.parse(uri.query)
701
+ redirected_url = uri_params['url'][0]
702
+
703
+ if ( redirected_url != nil )
704
+ res = redirected_url
705
+ else
706
+ res = url
707
+ end
708
+ else
709
+ res = res[0]
710
+ end
711
+ rescue
712
+ res = url
713
+ end
714
+ return res
715
+ end
716
+ =end
717
+ # returns a hash with the parametes in the url
718
+ def self.params(url)
719
+ # TODO: Corregir este parche:
720
+ # => El codigo de abajo usa la URL de una busqueda en google. Esta url generara una excepcion cuando se intenta parsear sus parametros.
721
+ # => Ejecutar las 2 lineas de abajo para verificar.
722
+ # => url = "https://www.google.com/webhp#q=[lead+generation]+%22John%22+%22Greater+New+York+City+Area+*+Financial+Services%22+site:linkedin.com%2Fpub+-site:linkedin.com%2Fpub%2Fdir"
723
+ # => p = CGI::parse(URI.parse(url).query)
724
+ # => La linea de abajo hace un gsbub que hace que esta url siga funcionando como busqueda de google, y ademas se posible parsearla.
725
+ url = url.gsub("webhp#q=", "webhp?q=")
726
+
727
+ return CGI::parse(URI.parse(url).query)
728
+ end
729
+
730
+ # Add a parameter to the url. It doesn't validate if the param already exists.
731
+ def self.add_param(url, param_name, param_value)
732
+ uri = URI(url)
733
+ params = URI.decode_www_form(uri.query || '')
734
+
735
+ if (params.size==0)
736
+ params << [param_name, param_value]
737
+ uri.query = URI.encode_www_form(params)
738
+ return uri.to_s
739
+ else
740
+ uri.query = URI.encode_www_form(params)
741
+ return uri.to_s + "&" + param_name + "=" + param_value
742
+ end
743
+ end
744
+
745
+ # Changes the value of a parameter in the url. It doesn't validate if the param already exists.
746
+ def self.change_param(url, param_name, param_value)
747
+ uri = URI(url)
748
+ # params = URI.decode_www_form(uri.query || [])
749
+ params = CGI.parse(uri.query)
750
+ params["start"] = param_value
751
+ uri.query = URI.encode_www_form(params)
752
+ uri.to_s
753
+ end
754
+
755
+ # Change or add the value of a parameter in the url, depending if the parameter already exists or not.
756
+ def self.set_param(url, param_name, param_value)
757
+ params = BlackStack::Netting::params(url)
758
+ if ( params.has_key?(param_name) == true )
759
+ newurl = BlackStack::Netting::change_param(url, param_name, param_value)
760
+ else
761
+ newurl = BlackStack::Netting::add_param(url, param_name, param_value)
762
+ end
763
+ return newurl
764
+ end
765
+
766
+ # get the domain from any url
767
+ def self.getDomainFromUrl(url)
768
+ if (url !~ /^http:\/\//i && url !~ /^https:\/\//i)
769
+ url = "http://#{url}"
770
+ end
771
+
772
+ if (URI.parse(url).host == nil)
773
+ raise "Cannot get domain for #{url}"
774
+ end
775
+
776
+ if (url.to_s.length>0)
777
+ return URI.parse(url).host.sub(/^www\./, '')
778
+ else
779
+ return nil
780
+ end
781
+ end
782
+
783
+ def self.getDomainFromEmail(email)
784
+ if email.email?
785
+ return email.split("@").last
786
+ else
787
+ raise "getDomainFromEmail: Wrong email format."
788
+ end
789
+ end
790
+
791
+ def self.getWhoisDomains(domain, allow_heuristic_to_avoid_hosting_companies=false)
792
+ a = Array.new
793
+ c = Whois::Client.new
794
+ r = c.lookup(domain)
795
+
796
+ res = r.to_s.scan(/Registrant Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
797
+ if (res!=nil)
798
+ a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
799
+ end
800
+
801
+ res = r.to_s.scan(/Admin Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
802
+ if (res!=nil)
803
+ a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
804
+ end
805
+
806
+ res = r.to_s.scan(/Tech Email: (#{BlackStack::Strings::MATCH_EMAIL})/).first
807
+ if (res!=nil)
808
+ a << BlackStack::Netting::getDomainFromEmail(res[0].downcase)
809
+ end
810
+
811
+ # remover duplicados
812
+ a = a.uniq
813
+
814
+ #
815
+ if (allow_heuristic_to_avoid_hosting_companies==true)
816
+ # TODO: develop this feature
817
+ end
818
+
819
+ return a
820
+ end
821
+
822
+ end # module Netting
823
+
824
+ end # module BlackStack