feed_into 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
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