feed_into 0.2.9

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/feed_into.rb ADDED
@@ -0,0 +1,1433 @@
1
+ require_relative 'feed_into/version'
2
+ require_relative './modules/general.rb'
3
+
4
+ require 'nokogiri'
5
+ require 'net/http'
6
+ require 'time'
7
+ require 'tzinfo'
8
+ require 'active_support/core_ext/hash/indifferent_access'
9
+
10
+ #require 'active_support/core_ext/hash'
11
+ require 'cgi'
12
+ require 'json'
13
+ require 'uri'
14
+
15
+
16
+ module FeedInto
17
+ class Error < StandardError; end
18
+
19
+
20
+ class Single
21
+ SINGLE = {
22
+ format: {
23
+ title: {
24
+ symbol: {
25
+ video: '👾',
26
+ custom: '⚙️ ',
27
+ web: '🤖'
28
+ },
29
+ separator: '|',
30
+ more: '...',
31
+ length: 100,
32
+ str: '{{sym}} {{cmd_name__upcase}} ({{channel_name__upcase}}) {{separator}} {{title_item__titleize}}'
33
+ },
34
+ download: {
35
+ agent: ''
36
+ }
37
+ },
38
+ validation: {
39
+ allows: [
40
+ :format__title__symbol__video,
41
+ :format__title__symbol__custom,
42
+ :format__title__symbol__web,
43
+ :format__title__separator,
44
+ :format__title__more,
45
+ :format__title__length,
46
+ :format__title__str,
47
+ :format__download__agent,
48
+ ],
49
+ wildcards: [
50
+ :options__s3
51
+ ]
52
+ },
53
+ channels: [],
54
+ options: {}
55
+ }
56
+
57
+ attr_reader :settings
58
+ include General
59
+
60
+
61
+ def initialize( modules: nil, options: {}, silent: false )
62
+ mdl = modules.class.eql? String
63
+ chn = options.keys.include? :channels
64
+ mode = :not_found
65
+
66
+ if !mdl and !chn
67
+ puts 'No Channel found.'
68
+ else
69
+ mode = nil
70
+
71
+ if chn
72
+ mode = :options
73
+ transfer = Marshal.load( Marshal.dump( options[:channels] ) )
74
+ options.delete( :channels )
75
+ end
76
+
77
+ mdl ? mode = :folder : ''
78
+
79
+ @single = Marshal.load( Marshal.dump( SINGLE ) )
80
+ if options_update( options, @single, true )
81
+ @single[:channels].concat( crl_general_channels() )
82
+
83
+ chn ? @single[:channels].concat( transfer ) : ''
84
+ mdl ? @single[:channels].concat( load_modules( modules, silent ) ) : ''
85
+
86
+ @single = options_update( options, @single, false )
87
+ @settings = @single
88
+ else
89
+ end
90
+ end
91
+ end
92
+
93
+
94
+ def analyse( item: {}, trust_item: false )
95
+ def modul( type, channel, allow_methods, cmd, response, data, obj )
96
+ messages = []
97
+ if !channel[ type ].nil?
98
+ error = nil
99
+ execute = true
100
+ name = nil
101
+ case channel[ type ]
102
+ when :self
103
+ name = ( 'crl_' + channel[:name].to_s ).to_sym
104
+ !allow_methods.include?( name ) ? execute = false : ''
105
+ when :general
106
+ name = :crl_general
107
+ else
108
+ name = :crl_general
109
+ type = ( type.to_s + '_' + channel[ type ].to_s ).to_sym
110
+ end
111
+
112
+ if execute
113
+ data, messages = self
114
+ .method( name )
115
+ .call( type, cmd, channel, response, data, obj )
116
+ else
117
+ messages = [ "Modul: #{name} not found." ]
118
+ end
119
+ else
120
+ end
121
+
122
+ return data, messages
123
+ end
124
+
125
+
126
+ def formats( type, cmd, channel, response, data, obj )
127
+ channel[ type ].each do | format_ |
128
+ data = self
129
+ .method( 'crl_general' )
130
+ .call( format_, cmd, channel, response, data, obj )[ 0 ]
131
+ end
132
+ return data
133
+ end
134
+
135
+
136
+ def set_status( messages )
137
+ s = 'Download: Status '
138
+ status = nil
139
+
140
+ if messages.class.eql? Array
141
+ tmp = messages
142
+ .find { | a | a.start_with?( s ) }
143
+ .to_s
144
+ .gsub( s, '')
145
+ .to_i
146
+ tmp.nil? ? status = 0 : status = tmp
147
+ else
148
+ end
149
+
150
+ return status
151
+ end
152
+
153
+
154
+ result = {
155
+ cmd: nil,
156
+ result: nil,
157
+ messages: nil,
158
+ time: nil,
159
+ success: false,
160
+ status: nil
161
+ }
162
+
163
+ obj = Marshal.load( Marshal.dump( @single ) )
164
+
165
+ begin
166
+ start = Time.now.to_f
167
+ messages = []
168
+ status = nil
169
+ cmd = {}
170
+ data = nil
171
+
172
+ if trust_item
173
+ cmd, m0 = item[:cmd], item[:messages]
174
+ else
175
+ cmd, m0 = cmd( item, obj )
176
+ end
177
+
178
+ messages.concat( m0 )
179
+ if cmd[:valid]
180
+ channel = obj[:channels].find { | c | c[:name].eql? cmd[:channel] }
181
+ allow_channels = obj[:channels].map { | a | ( 'crl_' + a[:name].to_s ).to_sym }
182
+ allow_methods = self.methods.select { | a | allow_channels.include?( a.to_sym ) }
183
+
184
+ response, m1 = modul( :download, channel, allow_methods, cmd, response, nil, obj )
185
+ messages.concat( m1 )
186
+
187
+ data, m2 = modul( :mining, channel, allow_methods, cmd, response, nil, obj )
188
+ messages.concat( m2 )
189
+
190
+ data = formats( :pre, cmd, channel, response, data, obj )
191
+
192
+ data, m3 = modul( :transform, channel, allow_methods, cmd, response, data, obj )
193
+ messages.concat( m3 )
194
+
195
+ data = formats( :post, cmd, channel, response, data, obj )
196
+ result[:success] = true
197
+ end
198
+
199
+ result[:cmd] = cmd
200
+ result[:result] = data
201
+ result[:messages] = messages
202
+ result[:time] = Time.now.to_f - start
203
+
204
+ status = set_status( messages )
205
+ result[:status] = status
206
+
207
+ rescue => e
208
+ messages.push( "Begin/Rescue: #{e}, #{messages}, #{cmd}" )
209
+
210
+ result[:cmd] = cmd
211
+ result[:result] = data
212
+ result[:messages] = messages
213
+ result[:time] = Time.now.to_f - start
214
+
215
+ status = set_status( messages )
216
+ result[:status] = status
217
+ end
218
+
219
+ return result
220
+ end
221
+
222
+
223
+ def cmd( cmd, obj )
224
+ def validate( cmd )
225
+ check = {
226
+ validation: {
227
+ struct: false,
228
+ url: false,
229
+ channel: false,
230
+ },
231
+ struct: {
232
+ name: String,
233
+ url: String,
234
+ category: Symbol,
235
+ channel: Symbol
236
+ }
237
+ }
238
+
239
+ messages = []
240
+
241
+ check[:validation][:struct] = check[:struct]
242
+ .map { | k, v | cmd[ k ].class.eql? v }
243
+ .all?
244
+
245
+ !check[:validation][:struct] ? messages.push( 'Structure of cmd is not valid.' ) : ''
246
+
247
+ if cmd[:url] =~ URI::regexp
248
+ check[:validation][:url] = true
249
+ else
250
+ messages.push( "'#{cmd[:url]}' is not a valid URL.")
251
+ end
252
+
253
+ if !cmd[:channel].eql? :not_found
254
+ check[:validation][:channel] = true
255
+ else
256
+ messages.push( 'Channel not found' )
257
+ end
258
+
259
+ cmd[:valid] = check[:validation].map { | k, v | v }.all?
260
+
261
+ return cmd, messages
262
+ end
263
+
264
+
265
+ if [ Hash, ActiveSupport::HashWithIndifferentAccess ].include? cmd.class
266
+ !cmd.key?( :name ) ? cmd[:name] = 'Unknown' : ''
267
+ !cmd.key?( :category ) ? cmd[:category] = :unknown : ''
268
+ cmd[:category].class.eql?( String ) ? cmd[:category] = cmd[:category].to_s.downcase.to_sym : ''
269
+ else
270
+ if cmd.class.eql? String
271
+ cmd = {
272
+ name: 'Unknown',
273
+ url: cmd,
274
+ category: :unknown,
275
+ #channel: :not_found
276
+ }
277
+ else
278
+ cmd = {
279
+ name: 'Invalid',
280
+ url: cmd.to_s,
281
+ category: :invalid,
282
+ channel: :not_found
283
+ }
284
+ end
285
+ end
286
+
287
+ f = obj[:channels].find do | channel |
288
+ r = channel[:regexs]
289
+ .map { | ps | ps.map { | p | cmd[:url].match?( p ) }.all? }
290
+ .include?( true )
291
+ end
292
+
293
+ !f.nil? ? cmd[:channel] = f[:name] : cmd[:channel] = :not_found
294
+
295
+ valid, messages = validate( cmd )
296
+
297
+ return valid, messages
298
+ end
299
+
300
+
301
+ private
302
+
303
+
304
+ def load_modules( folder, silent )
305
+ mods = []
306
+ searchs = []
307
+
308
+ Dir[ folder + '*.*' ].each do | path |
309
+ # require_relative path
310
+ require path
311
+
312
+ search = open( path ).read.split( "\n" )
313
+ .find { | a | a.include?( 'module' ) }
314
+ .gsub('module ', '' )
315
+
316
+ searchs.push( search )
317
+ end
318
+
319
+ searchs.each do | search |
320
+ name = Module::const_get( search )
321
+ extend name
322
+ end
323
+
324
+ names = self.methods
325
+ .select { | a | a.to_s.start_with?( 'crl' ) }
326
+ .reject { | a | a.eql? ( ( 'crl_general' ).to_sym ) }
327
+
328
+ channels = []
329
+ names.each do | n |
330
+ mods.push( n.to_s.gsub( 'crl_', '' ) )
331
+ channel, messages = self
332
+ .method( n.to_sym )
333
+ .call( :settings, nil, nil, nil, nil, nil )
334
+ channels.push( channel )
335
+ end
336
+
337
+ !silent ? puts( "#{mods.length} Module#{mods.length > 2 ? 's' : ''} loaded (#{mods.join(', ')})" ) : ''
338
+ return channels
339
+ end
340
+
341
+
342
+ def options_update( options, obj, validation )
343
+ def str_difference( a, b )
344
+ a = a.to_s.downcase.split( '_' ).join( '' )
345
+ b = b.to_s.downcase.split( '_' ).join( '' )
346
+ longer = [ a.size, b.size ].max
347
+ same = a
348
+ .each_char
349
+ .zip( b.each_char )
350
+ .select { | a, b | a == b }
351
+ .size
352
+ ( longer - same ) / a.size.to_f
353
+ end
354
+
355
+
356
+ allows = obj[:validation][:allows]
357
+ wildcards = obj[:validation][:wildcards]
358
+
359
+ messages = []
360
+ insert = Marshal.load( Marshal.dump( obj ) )
361
+
362
+ options.keys.each do | key |
363
+ if allows.include?( key )
364
+
365
+ keys = key.to_s.split( '__' ).map { | a | a.to_sym }
366
+ case( keys.length )
367
+ when 1
368
+ insert[ keys[ 0 ] ] = options[ key ]
369
+ when 2
370
+ insert[ keys[ 0 ] ][ keys[ 1 ] ] = options[ key ]
371
+ when 3
372
+ insert[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ] = options[ key ]
373
+ when 4
374
+ insert[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ][ keys[ 3 ] ] = options[ key ]
375
+ end
376
+ else
377
+ standard = true
378
+ keys = key.to_s.split( '__' ).map { | a | a.to_sym }
379
+ case keys.length
380
+ when 1
381
+ inside = wildcards
382
+ .map { | a | a.to_s.split( '__' ).first.to_sym }
383
+ .include?( keys[ 0 ] )
384
+
385
+ if inside
386
+ message = "\"#{key}\" is a potential Wildcard key but has an invalid length. Use two additional keys (plus '__') to set your option."
387
+ messages.push( message )
388
+ standard = false
389
+ else
390
+ end
391
+ when 2..3
392
+ wildcard = [ keys[ 0 ].to_s, keys[ 1 ].to_s ].join( '__' ).to_sym
393
+ if wildcards.include?( wildcard )
394
+ if keys.length == 2
395
+ message = "\"#{key}\" is a Wildcard key but has an invalid length. Use an additional key (plus '__') to set your option."
396
+ messages.push( message )
397
+ standard = false
398
+ else
399
+ !insert.keys.include?( keys[ 0 ] ) ? insert[ keys[ 0 ] ] = {} : ''
400
+ !insert[ keys[ 0 ] ].keys.include?( keys[ 1 ] ) ? insert[ keys[ 0 ] ][ keys[ 1 ] ] = {} : ''
401
+ insert[ keys[ 0 ] ][ keys[ 1 ] ][ keys[ 2 ] ] = options[ key ]
402
+ standard = false
403
+ end
404
+ else
405
+ end
406
+ else
407
+ end
408
+
409
+ if standard
410
+ nearest = allows
411
+ .map { | word | { score: self.str_difference( key, word ), word: word } }
412
+ .min_by { | item | item[:score] }
413
+
414
+ if nearest.nil?
415
+ message = "\"#{key}\" is not a valid key."
416
+ else
417
+ message = "\"#{key}\" is not a valid key, did you mean \"<--similar-->\"?"
418
+ message = message.gsub( '<--similar-->', nearest[:word].to_s )
419
+ end
420
+
421
+ messages.push( message )
422
+ end
423
+ end
424
+ end
425
+
426
+ if messages.length != 0
427
+ messages.length == 1 ? puts( 'Error found:' ) : puts( 'Errors found:' )
428
+ messages.each { | m | puts( '- ' + m ) }
429
+ end
430
+ return validation ? messages.length == 0 : insert
431
+ end
432
+ end
433
+
434
+
435
+ class Group < Single
436
+ GROUP= {
437
+ meta: {
438
+ timestamp: nil
439
+ },
440
+ validation: {
441
+ allows: [
442
+ :sleep__range,
443
+ :sleep__codes,
444
+ :sleep__varieties,
445
+ :sleep__scores__ok__name,
446
+ :sleep__scores__ok__value,
447
+ :sleep__scores__user__name,
448
+ :sleep__scores__user__value,
449
+ :sleep__scores__server__name,
450
+ :sleep__scores__server__value,
451
+ :sleep__scores__other__name,
452
+ :sleep__scores__other__value,
453
+ :sleep__stages
454
+ ],
455
+ wildcards: []
456
+ },
457
+ sleep: {
458
+ range: 15,
459
+ codes: [
460
+ {
461
+ status: 0,
462
+ name: 'other',
463
+ add: :user
464
+ },
465
+ {
466
+ status: 100,
467
+ name: 'continue',
468
+ add: :server
469
+ },
470
+ {
471
+ status: 101,
472
+ name: 'switching_protocols',
473
+ add: :server
474
+ },
475
+ {
476
+ status: 200,
477
+ name: 'ok',
478
+ add: :ok
479
+ },
480
+ {
481
+ status: 201,
482
+ name: 'created',
483
+ add: :ok
484
+ },
485
+ {
486
+ status: 202,
487
+ name: 'accepted',
488
+ add: :ok
489
+ },
490
+ {
491
+ status: 203,
492
+ name: 'non_authoritative_information',
493
+ add: :ok
494
+ },
495
+ {
496
+ status: 204,
497
+ name: 'no_content',
498
+ add: :ok
499
+ },
500
+ {
501
+ status: 205,
502
+ name: 'reset_content',
503
+ add: :ok
504
+ },
505
+ {
506
+ status: 206,
507
+ name: 'partial_content',
508
+ add: :ok
509
+ },
510
+ {
511
+ status: 207,
512
+ name: 'multi_status',
513
+ add: :ok
514
+ },
515
+ {
516
+ status: 208,
517
+ name: 'already_reported',
518
+ add: :ok
519
+ },
520
+ {
521
+ status: 226,
522
+ name: 'im_used',
523
+ add: :ok
524
+ },
525
+ {
526
+ status: 300,
527
+ name: 'multiple_choices',
528
+ add: :user
529
+ },
530
+ {
531
+ status: 301,
532
+ name: 'moved_permanently',
533
+ add: :user
534
+ },
535
+ {
536
+ status: 302,
537
+ name: 'found',
538
+ add: :user
539
+ },
540
+ {
541
+ status: 303,
542
+ name: 'see_other',
543
+ add: :user
544
+ },
545
+ {
546
+ status: 304,
547
+ name: 'not_modified',
548
+ add: :user
549
+ },
550
+ {
551
+ status: 305,
552
+ name: 'use_proxy',
553
+ add: :user
554
+ },
555
+ {
556
+ status: 306,
557
+ name: 'switch_proxy',
558
+ add: :user
559
+ },
560
+ {
561
+ status: 307,
562
+ name: 'temporary_redirect',
563
+ add: :user
564
+ },
565
+ {
566
+ status: 308,
567
+ name: 'permanent_redirect',
568
+ add: :user
569
+ },
570
+ {
571
+ status: 400,
572
+ name: 'bad_request',
573
+ add: :user
574
+ },
575
+ {
576
+ status: 401,
577
+ name: 'unauthorized',
578
+ add: :server
579
+ },
580
+ {
581
+ status: 402,
582
+ name: 'payment_required',
583
+ add: :server
584
+ },
585
+ {
586
+ status: 403,
587
+ name: 'forbidden',
588
+ add: :server
589
+ },
590
+ {
591
+ status: 404,
592
+ name: 'not_found',
593
+ add: :user
594
+ },
595
+ {
596
+ status: 405,
597
+ name: 'method_not_allowed',
598
+ add: :server
599
+ },
600
+ {
601
+ status: 406,
602
+ name: 'not_acceptable',
603
+ add: :server
604
+ },
605
+ {
606
+ status: 407,
607
+ name: 'proxy_authentication_required',
608
+ add: :server
609
+ },
610
+ {
611
+ status: 408,
612
+ name: 'request_timeout',
613
+ add: :user
614
+ },
615
+ {
616
+ status: 409,
617
+ name: 'conflict',
618
+ add: :server
619
+ },
620
+ {
621
+ status: 410,
622
+ name: 'gone',
623
+ add: :user
624
+ },
625
+ {
626
+ status: 411,
627
+ name: 'length_required',
628
+ add: :user
629
+ },
630
+ {
631
+ status: 412,
632
+ name: 'precondition_failed',
633
+ add: :user
634
+ },
635
+ {
636
+ status: 413,
637
+ name: 'request_entity_too_large',
638
+ add: :user
639
+ },
640
+ {
641
+ status: 414,
642
+ name: 'request_uri_too_long',
643
+ add: :user
644
+ },
645
+ {
646
+ status: 415,
647
+ name: 'unsupported_media_type',
648
+ add: :server
649
+ },
650
+ {
651
+ status: 416,
652
+ name: 'requested_range_not_satisfiable',
653
+ add: :user
654
+ },
655
+ {
656
+ status: 417,
657
+ name: 'expectation_failed',
658
+ add: :user
659
+ },
660
+ {
661
+ status: 418,
662
+ name: 'im_a_teapot',
663
+ add: :server
664
+ },
665
+ {
666
+ status: 421,
667
+ name: 'misdirected_request',
668
+ add: :server
669
+ },
670
+ {
671
+ status: 422,
672
+ name: 'unprocessable_entity',
673
+ add: :server
674
+ },
675
+ {
676
+ status: 426,
677
+ name: 'upgrade_required',
678
+ add: :server
679
+ },
680
+ {
681
+ status: 428,
682
+ name: 'precondition_required',
683
+ add: :server
684
+ },
685
+ {
686
+ status: 423,
687
+ name: 'locked',
688
+ add: :server
689
+ },
690
+ {
691
+ status: 424,
692
+ name: 'failed_dependency',
693
+ add: :server
694
+ },
695
+ {
696
+ status: 429,
697
+ name: 'too_many_requests',
698
+ add: :server
699
+ },
700
+ {
701
+ status: 431,
702
+ name: 'request_header_fields_too_large',
703
+ add: :user
704
+ },
705
+ {
706
+ status: 451,
707
+ name: 'unavailable_for_legal_reasons',
708
+ add: :server
709
+ },
710
+ {
711
+ status: 500,
712
+ name: 'internal_server_error',
713
+ add: :server
714
+ },
715
+ {
716
+ status: 501,
717
+ name: 'not_implemented',
718
+ add: :server
719
+ },
720
+ {
721
+ status: 502,
722
+ name: 'bad_gateway',
723
+ add: :server
724
+ },
725
+ {
726
+ status: 503,
727
+ name: 'service_unavailable',
728
+ add: :server
729
+ },
730
+ {
731
+ status: 504,
732
+ name: 'gateway_timeout',
733
+ add: :server
734
+ },
735
+ {
736
+ status: 505,
737
+ name: 'http_version_not_supported',
738
+ add: :server
739
+ },
740
+ {
741
+ status: 506,
742
+ name: 'variant_also_negotiates',
743
+ add: :server
744
+ },
745
+ {
746
+ status: 507,
747
+ name: 'insufficient_storage',
748
+ add: :server
749
+ },
750
+ {
751
+ status: 508,
752
+ name: 'loop_detected',
753
+ add: :server
754
+ },
755
+ {
756
+ status: 510,
757
+ name: 'not_extended',
758
+ add: :server
759
+ },
760
+ {
761
+ status: 511,
762
+ name: 'network_authentication_required',
763
+ add: :server
764
+ }
765
+ ],
766
+ varieties: [
767
+ { variety: 1, sleep: 2 },
768
+ { variety: 2, sleep: 1 },
769
+ { variety: 3, sleep: 0.5 },
770
+ { variety: 4, sleep: 0.25 },
771
+ { variety: 5, sleep: 0.15 },
772
+ { variety: 6, sleep: 0.1 }
773
+ ],
774
+ scores: {
775
+ ok: {
776
+ name: 'Went through...',
777
+ value: 0
778
+ },
779
+ user: {
780
+ name: 'Wrong query, Data not found...',
781
+ value: 1
782
+ },
783
+ server: {
784
+ name: 'Wrong behavour, not patient enough...',
785
+ value: 3
786
+ },
787
+ other: {
788
+ name: 'Nil values and others errors...',
789
+ value: 0
790
+ }
791
+ },
792
+ stages: [
793
+ {
794
+ name: 'Default',
795
+ range: [ 0, 2 ],
796
+ skip: false,
797
+ sleep: 0
798
+ },
799
+ {
800
+ name: 'Low',
801
+ range: [ 3, 5 ],
802
+ skip: false,
803
+ sleep: 2
804
+ },
805
+ {
806
+ name: 'High',
807
+ range: [ 6, 8 ],
808
+ skip: false,
809
+ sleep: 5
810
+ },
811
+ {
812
+ name: 'Stop',
813
+ range: [ 9, 999 ],
814
+ skip: true
815
+ }
816
+ ]
817
+ },
818
+ options: {}
819
+ }
820
+
821
+
822
+ def initialize( modules: nil, group: {}, single: {}, silent: false )
823
+ mdl = modules.class.eql? String
824
+ chn = single.keys.include? :channels
825
+ mode = :not_found
826
+
827
+ if !mdl and !chn
828
+ puts 'No Channel found.'
829
+ else
830
+ if chn
831
+ # mode = :options
832
+ # transfer = Marshal.load( Marshal.dump( options[:channels] ) )
833
+ # options.delete( :channels )
834
+ end
835
+
836
+ @group = Marshal.load( Marshal.dump( GROUP ) )
837
+ @group[:meta][:timestamp] = Time.now.utc.to_s
838
+
839
+ if options_update( group, @group, true )
840
+ @single = Single.new( modules: modules, options: single, silent: silent )
841
+ @group = options_update( group, @group, false )
842
+ @analyse = nil
843
+ @merge = nil
844
+ else
845
+ end
846
+ end
847
+ end
848
+
849
+
850
+ def analyse( items: nil, silent: false )
851
+ def c_log( silent, r, score )
852
+ if !silent
853
+ print "(#{score[:sleep]}) "
854
+ if r[:success]
855
+ if r[:result][:items].length > 0
856
+ r[:success] ? print( r[:result][:items][ 0 ][:title] ) : ''
857
+ else
858
+ print r[:result][:meta][:title]
859
+ end
860
+ else
861
+ print r[:cmd][:name]
862
+ r[:messages].each { | m | puts( "- #{m}" ) }
863
+ end
864
+ puts
865
+ else
866
+ end
867
+ end
868
+
869
+
870
+ cmds, messages = cmds( items )
871
+
872
+ if cmds[:valid]
873
+ keys = cmds[:cmds]
874
+ .map { | a | a[:cmd][:channel] }
875
+ .to_set
876
+ .to_a
877
+
878
+ groups = keys.inject( {} ) do | hash, key |
879
+ hash[ key ] = cmds[:cmds].select { | cmd | cmd[:cmd][:channel].eql?( key ) }
880
+ hash
881
+ end
882
+
883
+ orders, struct = get_shuffle( groups )
884
+ results = struct.inject( {} ) do | item, k |
885
+ item[ k[ 0 ] ] = {}
886
+ item[ k[ 0 ] ][:responses] = k[ 1 ].clone
887
+ item[ k[ 0 ] ][:status] = k[ 1 ].clone
888
+ item
889
+ end
890
+
891
+ orders.each.with_index do | order, index |
892
+ start = Time.now.to_f
893
+ score = score( index, orders, results, @group, groups )
894
+ cmd = groups[ order[:category] ][ order[:index ] ]
895
+
896
+ if score[:skip]
897
+ !silent ? puts( ">> Skip: #{order}" ) : ''
898
+ results[ order[:category] ][:responses][ order[:index] ] = {
899
+ cmd: cmd[:cmd],
900
+ skip: true
901
+ }
902
+ else
903
+ sleep( score[:sleep] )
904
+ r = @single.analyse( item: cmd, trust_item: true )
905
+
906
+ !r[:status].class.eql? Integer ? tmp = 0 : tmp = r[:status]
907
+ results[ order[:category] ][:status][ order[:index] ] = tmp #r[:status]
908
+ results[ order[:category] ][:responses][ order[:index] ] = r
909
+ results[ order[:category] ][:responses][ order[:index] ][:skip] = false
910
+
911
+ results[ order[:category] ][:responses][ order[:index] ][:time] = Time.now.to_f - start
912
+ c_log( silent, r, score )
913
+ end
914
+
915
+ end
916
+ else
917
+ puts 'cmds not valid.'
918
+ messages.each { | m | puts( "- #{m}" ) }
919
+ end
920
+
921
+
922
+ items = results
923
+ .map { | k, v | v[:responses].map { | a | a } }
924
+ .flatten
925
+
926
+ categories = items
927
+ .map { | item | item[:cmd][:category] }
928
+ .to_set
929
+ .to_a
930
+
931
+ re_grouped = categories
932
+ .inject( {} ) { | group, category |
933
+ group[ category] = items.select { | a | a[:cmd][:category].eql? category }
934
+ group
935
+ }
936
+
937
+ @analyse = re_grouped
938
+ self
939
+ end
940
+
941
+
942
+ def merge
943
+ def valid?( item )
944
+ result = false
945
+
946
+ if [ Hash, ActiveSupport::HashWithIndifferentAccess ].include? item.class
947
+ one = [ :title, :time, :url ]
948
+ .map { | key | item.keys.include? key }
949
+ .all?
950
+
951
+ if one
952
+ if [ Hash, ActiveSupport::HashWithIndifferentAccess ].include? item[:time].class
953
+ if item[:time].keys.include? :stamp
954
+ result = true
955
+ end
956
+ end
957
+ end
958
+ end
959
+
960
+ return result
961
+ end
962
+
963
+
964
+ messages = []
965
+ result = @analyse.inject( {} ) do | categories, d |
966
+ all = d[ 1 ].inject( [] ) do | category, response |
967
+ if response[:skip]
968
+ else
969
+ if response[:success]
970
+ response[:result][:items].each do | item |
971
+ if valid?( item )
972
+ itm = {
973
+ title: item[:title],
974
+ timestamp: item[:time][:stamp],
975
+ url: item[:url]
976
+ }
977
+
978
+ category.push( itm )
979
+ else
980
+ messages.push( '- One or more key(s) in Item are not available.' )
981
+ end
982
+ end
983
+ end
984
+ end
985
+ category
986
+ end
987
+
988
+ all = all.sort_by { | a | -a[:timestamp] }
989
+ categories[ d[ 0 ].to_sym ] = all.clone
990
+ categories
991
+ end
992
+
993
+ messages.each { | message | puts( message ) }
994
+
995
+ @merge = result
996
+ self
997
+ end
998
+
999
+
1000
+ def to_h( type: nil )
1001
+ if @analyse.nil? and @merge.nil?
1002
+ puts 'No Data found, please use .analyse() before.'
1003
+ return nil
1004
+ else
1005
+ if type.nil?
1006
+ if @merge.nil?
1007
+ return @analyse
1008
+ else
1009
+ return @merge
1010
+ end
1011
+ else
1012
+ case type
1013
+ when :merge
1014
+ return @merge
1015
+ when :analyse
1016
+ return @analyse
1017
+ end
1018
+ end
1019
+ end
1020
+ end
1021
+
1022
+
1023
+ def to_rss( key: Symbol, silent: false )
1024
+ result = ''
1025
+
1026
+ if @merge.nil?
1027
+ !silent ? puts( 'Data is not merged in groups, use .merge() before.' ) : ''
1028
+ else
1029
+ if @merge.keys.include? key
1030
+ builder = Nokogiri::XML::Builder.new( encoding: 'utf-8' )
1031
+ builder.feed(
1032
+ xmlns: 'http://www.w3.org/2005/Atom',
1033
+ 'xmlns:dc': 'http://purl.org/dc/elements/1.1/'
1034
+ ) do | root |
1035
+ meta = {
1036
+ title: key.to_s,
1037
+ updated: Time.now.to_s,
1038
+ dcdate: Time.now.to_s
1039
+ }
1040
+
1041
+ root.author { | a | a.name { | aa | aa.text( '' ) } }
1042
+ root.id() { | a | a.text( '' ) }
1043
+ root.title() { | a | a.text( meta[:title] ) }
1044
+ root.updated() { | a | a.text( meta[:updated] ) }
1045
+ root['dc'].date { | a | a.text( meta[:dcdate] ) }
1046
+
1047
+ @merge[ key ]
1048
+ .each { | entry |
1049
+ root.entry() { | a |
1050
+ d = Time.at( entry[:timestamp] ).to_datetime.to_s
1051
+
1052
+ a.id() { | b | b.text( entry[:url] ) }
1053
+ a.link( rel: 'alternate', href: entry[:url] )
1054
+ a.title() { | b | b.text( entry[:title] ) }
1055
+ a.updated() { | b | b.text( d ) }
1056
+ a['dc'].date { | b | b.text( d ) }
1057
+ }
1058
+ }
1059
+ end
1060
+
1061
+ =begin
1062
+ rss = RSS::Maker.make( 'atom' ) do | maker |
1063
+ maker.channel.author = ''
1064
+ maker.channel.updated = Time.now.to_s
1065
+ maker.channel.about = ''
1066
+ maker.channel.title = key.to_s
1067
+
1068
+ @merge[ key ].each do | entry |
1069
+ maker.items.new_item do | item |
1070
+ item.link = entry[:url]
1071
+ item.title = entry[:title]
1072
+
1073
+ d = Time.at( entry[:timestamp] ).to_datetime.to_s
1074
+
1075
+ item.updated = d
1076
+ end
1077
+ end
1078
+ end
1079
+ =end
1080
+ else
1081
+ !silent ? puts( 'Key does not exist.' ) : ''
1082
+ end
1083
+ result = builder.to_xml.to_s #rss.to_s.gsub( '<link href="', '<link rel="alternate" href="' )
1084
+ end
1085
+
1086
+ return result
1087
+ end
1088
+
1089
+
1090
+ def to_rss_all( silent: false )
1091
+ results = {}
1092
+
1093
+ if @merge.nil?
1094
+ !silent ? puts( 'Data is not merged in groups, use .merge() before.' ) : ''
1095
+ else
1096
+ @merge.keys.each do | key |
1097
+ results[ key ] = to_rss( key: key, silent: false )
1098
+ end
1099
+ end
1100
+
1101
+ return results
1102
+ end
1103
+
1104
+
1105
+ def status( silent: false )
1106
+ def categories( analyse )
1107
+ results = {}
1108
+ analyse.keys.each do | key |
1109
+ a = {
1110
+ status: {
1111
+ success: nil,
1112
+ error: nil,
1113
+ skip: nil,
1114
+ total: nil
1115
+ },
1116
+ errors: nil,
1117
+ time: nil
1118
+ }
1119
+
1120
+ a[:status][:success] = analyse[ key ]
1121
+ .reject { | a | a[:skip] }
1122
+ .select { | a | a[:success].eql? true }
1123
+ .length
1124
+
1125
+ a[:status][:error] = analyse[ key ]
1126
+ .reject { | a | a[:skip] }
1127
+ .select { | a | a[:success].eql? false }
1128
+ .length
1129
+
1130
+ a[:status][:skip] = analyse[ key ]
1131
+ .select { | a | a[:skip] }
1132
+ .length
1133
+
1134
+ a[:status][:total] = [ :success, :error, :skip ]
1135
+ .map { | key | a[:status][ key ] }
1136
+ .sum
1137
+
1138
+ a[:errors] = analyse[ key ]
1139
+ .reject { | a | a[:skip] }
1140
+ .select { | a | a[:success].eql? false }
1141
+ .map { | a | { name: a[:cmd][:name], url: a[:cmd][:url] } }
1142
+
1143
+ a[:time] = analyse[ key ]
1144
+ .reject { | a | a[:skip] }
1145
+ .map { | a | a[:time] }
1146
+ .sum
1147
+ .round( 8 )
1148
+
1149
+ results[ key ] = a
1150
+ end
1151
+ results
1152
+ end
1153
+
1154
+
1155
+ def channels( analyse )
1156
+ results = {}
1157
+
1158
+ channels = analyse
1159
+ .map { | category |
1160
+ category[ 1 ]
1161
+ .map { | a | a[:cmd][:channel] }
1162
+ }
1163
+ .flatten
1164
+ .to_set
1165
+ .to_a
1166
+
1167
+ channels.each do | channel |
1168
+ a = {
1169
+ status: {
1170
+ success: nil,
1171
+ error: nil,
1172
+ skip: nil,
1173
+ total: nil
1174
+ },
1175
+ errors: nil,
1176
+ time: nil
1177
+ }
1178
+
1179
+ cmds = analyse
1180
+ .map { | category |
1181
+ category[ 1 ]
1182
+ .select { | a | a[:cmd][:channel].eql? channel }
1183
+ }
1184
+ .flatten
1185
+
1186
+ a[:status][:success] = cmds
1187
+ .reject { | a | a[:skip] }
1188
+ .select { | a | a[:success] }
1189
+ .length
1190
+
1191
+ a[:status][:error] = cmds
1192
+ .reject { | a | a[:skip] }
1193
+ .select { | a | !a[:success] }
1194
+ .length
1195
+
1196
+ a[:status][:skip] = cmds
1197
+ .select { | a | a[:skip] }
1198
+ .length
1199
+
1200
+ a[:status][:total] = [ :success, :error, :skip ]
1201
+ .map { | key | a[:status][ key ] }
1202
+ .sum
1203
+
1204
+ a[:time] = cmds
1205
+ .map { | a | a.keys.include?( :time ) ? a[:time] : 0 }
1206
+ .sum
1207
+ .round( 8 )
1208
+
1209
+ a[:errors] = cmds
1210
+ .reject { | a | a[:skip] }
1211
+ .select { | a | !a[:success] }
1212
+ .map { | a | { name: a[:cmd][:name], url: a[:cmd][:url] } }
1213
+
1214
+ results[ channel ] = a
1215
+ end
1216
+
1217
+ return results
1218
+ end
1219
+
1220
+
1221
+ def overview( channels )
1222
+ results = { time: {}, all: {} }
1223
+ results[:time][:now] = Time.now.utc.to_s
1224
+ z = channels
1225
+ .keys
1226
+ .map { | key | channels[ key ][:time] }
1227
+ .sum
1228
+ .round( 0 )
1229
+
1230
+ results[:time][:analyse] =
1231
+ Time.at( z ).utc.strftime( "%Mm %Ss" )
1232
+
1233
+ [ :success, :error, :skip, :total ].each do | key |
1234
+ results[:all][ key ] = channels
1235
+ .map { | k, v | v[:status][ key ] }
1236
+ .sum
1237
+ end
1238
+ return results
1239
+ end
1240
+
1241
+
1242
+ if @analyse.nil?
1243
+ !silent ? puts( 'Data is not analysed, use .analyse() before.' ) : ''
1244
+ else
1245
+
1246
+ messages = {
1247
+ overview: {},
1248
+ channels: {},
1249
+ categories: {}
1250
+ }
1251
+
1252
+ messages[:categories] = categories( @analyse )
1253
+ messages[:channels] = channels( @analyse )
1254
+ messages[:overview] = overview( messages[:channels] )
1255
+ end
1256
+
1257
+ return messages
1258
+ end
1259
+
1260
+
1261
+ private
1262
+
1263
+
1264
+ def cmds( cs )
1265
+ valid = {
1266
+ array: true,
1267
+ hash: true,
1268
+ }
1269
+
1270
+ result = {
1271
+ valid: false,
1272
+ cmds: []
1273
+ }
1274
+
1275
+ cmds = []
1276
+ messages = []
1277
+ if cs.class.eql? Array
1278
+ test = cs.map do | c |
1279
+ [ Hash, ActiveSupport::HashWithIndifferentAccess, String ].include? c.class
1280
+ end
1281
+
1282
+ if test.all?
1283
+ cmds = cs.map do | c |
1284
+ cmd, messages = @single.cmd( c, @single.settings )
1285
+ { cmd: cmd, messages: messages }
1286
+ end
1287
+ else
1288
+ valid[:hash] = false
1289
+ messages.push( 'cmds: Not all Items are Class "Hash"' )
1290
+ end
1291
+ else
1292
+ valid[:array] = false
1293
+ messages.push( 'cmds: Input is not Class "Array"' )
1294
+ end
1295
+
1296
+ result[:valid] = valid.map { | k, v | v }.all?
1297
+ result[:cmds].concat( cmds )
1298
+
1299
+ return result, messages
1300
+ end
1301
+
1302
+
1303
+ def get_shuffle( groups )
1304
+ def struct( groups )
1305
+ boilerplate = groups
1306
+ .inject( {} ) { | hash, k | hash[ k[ 0 ] ] = k[ 1 ].length; hash }
1307
+
1308
+ struct = boilerplate.inject( {} ) do | hash, key |
1309
+ hash[ key[ 0 ] ] = key[ 1 ].times.map { | a | nil }
1310
+ hash
1311
+ end
1312
+
1313
+ groups = boilerplate.inject( {} ) do | hash, key |
1314
+ hash[ key[ 0 ] ] = key[ 1 ].times.map { | a | "#{key[ 0 ]}--#{a}" }
1315
+ hash
1316
+ end
1317
+
1318
+ return groups, struct
1319
+ end
1320
+
1321
+
1322
+ def shuffle( struct, groups )
1323
+ results = []
1324
+ order = []
1325
+
1326
+ l = struct.map { | k, v | v.length }.sum
1327
+ for round in 0..l - 1
1328
+ selections = struct
1329
+ .filter_map { | k, v | k if v.length > 0 }
1330
+
1331
+ variety = selections.length
1332
+ if round == 0
1333
+ current = selections[ 0 ]
1334
+ else
1335
+ case selections.length
1336
+ when 0
1337
+ before = 0
1338
+ when 1
1339
+ before = 0
1340
+ when 2
1341
+ before = 1
1342
+ when 3
1343
+ before = 2
1344
+ when 4
1345
+ before = 3
1346
+ else
1347
+ before = 4
1348
+ end
1349
+
1350
+ selections = selections
1351
+ .reject { | s | order.last( before ).include?( s ) }
1352
+ end
1353
+
1354
+ current = selections[ rand( 0..selections.length - 1 ) ]
1355
+ r_index = rand( 0..struct[ current ].length - 1 )
1356
+
1357
+ index = struct[ current ][ r_index ].split( '--' ).last.to_i
1358
+
1359
+ channel = groups[ current ][ index ][:cmd][:channel]
1360
+
1361
+ result = { category: current, index: index, variety: variety, channel: channel }
1362
+
1363
+ results.push( result )
1364
+
1365
+ struct[ current ].delete_at( r_index )
1366
+ order.push( current )
1367
+ end
1368
+
1369
+ return results
1370
+ end
1371
+
1372
+ tmp, struct = struct( groups )
1373
+ orders = shuffle( tmp, groups )
1374
+ #orders.each { | order | puts( order.to_s ) }
1375
+
1376
+ return orders, struct
1377
+ end
1378
+
1379
+
1380
+ def score( index, orders, results, obj, groups )
1381
+ current = orders[ index ]
1382
+
1383
+ if index == 0
1384
+ test = []
1385
+ else
1386
+ test = orders
1387
+ .first( index )
1388
+ .select { | order | order[:channel].eql? current[:channel] }
1389
+
1390
+ test = test
1391
+ .map { | cmd |
1392
+ if results[ cmd[:category] ][:responses][ cmd[:index] ][:skip]
1393
+ :skip
1394
+ else
1395
+ results[ cmd[:category] ][:responses][ cmd[:index] ][:status]
1396
+ end
1397
+ }
1398
+ .select { | a | !a.eql?( :skip ) }
1399
+ .last( obj[:sleep][:range] )
1400
+ end
1401
+
1402
+ scores = test.map do | sr |
1403
+ if sr.nil?
1404
+ key = :other
1405
+ else
1406
+ tmp = obj[:sleep][:codes].find { | a | a[:status].eql?( sr ) }
1407
+ key = tmp[:add]
1408
+ end
1409
+ obj[:sleep][:scores][ key ][:value]
1410
+ end
1411
+
1412
+ score = scores.sum
1413
+ tmp = obj[:sleep][:stages].find do | a |
1414
+ case a[:range].length
1415
+ when 1
1416
+ score >= a[:range][ 0 ]
1417
+ when 2
1418
+ score >= a[:range][ 0 ] and score <= a[:range][ 1 ]
1419
+ end
1420
+ end
1421
+ result = Marshal.load( Marshal.dump( tmp ) )
1422
+
1423
+ found = obj[:sleep][:varieties]
1424
+ .find { | variety | variety[:variety].eql? current[:variety] }
1425
+
1426
+ tension = !found.nil? ? found[:sleep] : 0
1427
+ result[:sleep] = result[:sleep].to_i + tension.to_i
1428
+
1429
+ result[:score] = score
1430
+ return result
1431
+ end
1432
+ end
1433
+ end