dailyrep 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,77 @@
1
+ require 'open-uri'
2
+ require 'nokogiri'
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ require_relative 'Notificator'
7
+ require_relative 'IndexWriter'
8
+ require_relative 'Dbops'
9
+ require_relative 'Trackable'
10
+
11
+ module DailyRep
12
+ class IBrowser
13
+ include Dbops
14
+ include Trackable
15
+ attr_reader :entity
16
+
17
+ @@notificators = Notificator.get_clients
18
+ @@index_writer = IndexWriter.new
19
+ @@app_cycle_methods = ['process', 'write_to_db', 'web_reload', 'notify']
20
+
21
+ def initialize *inputs
22
+ @entity = self.class.to_s.downcase.split('::').last
23
+ end
24
+
25
+ def push_note note
26
+ @@notificators.each { |client|
27
+ client.push_note(note)
28
+ }
29
+ # @@client.push_note(nil, @@note_title, note)
30
+ end
31
+
32
+ def set_html entity, values, push=0
33
+ @@index_writer.set_html entity, values, push
34
+ end
35
+
36
+ def set_html_with_diff entity, scope, diff, reset=false
37
+ @@index_writer.set_html_with_diff entity, scope, diff, reset
38
+ end
39
+
40
+ def self.write_html
41
+ @@index_writer.write_html
42
+ end
43
+
44
+
45
+ def get_source_http source, val_scope
46
+ page = Nokogiri::HTML(open(source.to_s))
47
+ page.css(val_scope)
48
+ end
49
+
50
+ def get_source_json source
51
+ uri = URI(source)
52
+ out_json = Net::HTTP.get(uri)
53
+ JSON.parse(out_json)
54
+ end
55
+
56
+ def method_missing(meth, *args, &block)
57
+ if @@app_cycle_methods.include?(meth.to_s)
58
+ begin
59
+ self.log_msg meth.to_s
60
+ block.call
61
+ rescue Exception => e
62
+ log_error e
63
+ end
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ def run
70
+ self.process
71
+ self.write_to_db if Configer.is_phase_enabled?('write_to_db')
72
+ self.notify if Configer.is_phase_enabled?('notify')
73
+ self.web_reload if Configer.is_phase_enabled?('web_reload')
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,55 @@
1
+ module DailyRep
2
+ class IndexWriter
3
+
4
+ def initialize(index_path=Configer.index_path)
5
+ @out_html_path = index_path.to_s
6
+ @out_html = Nokogiri::HTML(File.open(@out_html_path))
7
+ end
8
+
9
+ def write_html
10
+ @out_html.css("#refresh")[0].content = Time.now
11
+ File.open(@out_html_path , 'w') { |file| file.write(@out_html.to_html) }
12
+ end
13
+
14
+ def set_html entity, params, notif=0
15
+ entity = entity.downcase
16
+ params.each do |key, val|
17
+ @out_html.css("##{entity}_#{key}")[0].content = val
18
+ end
19
+ @out_html.css("##{entity}_refresh")[0].content = Time.now
20
+ @out_html.css("##{entity}_notif")[0].content = Time.now if notif == 1
21
+ end
22
+
23
+ def set_html_with_diff entity, scope, diff, reset=false
24
+ entity = entity.downcase
25
+ target_div = @out_html.css(scope + " .list-group .list-group-item")
26
+ new_root_node = Nokogiri::XML::Node.new('div', @out_html)
27
+ new_root_node['class'] = 'list-group'
28
+
29
+
30
+ diff.each do |diff_el|
31
+ target_div.each { |node|
32
+ if diff_el[0] == '+' && node['href'].include?(diff_el[2].first[0]) then
33
+ target_div.delete(node)
34
+ end
35
+ }
36
+ end
37
+
38
+ new_root_node.add_child(target_div.unlink) if not reset
39
+
40
+ diff.each do |diff_el|
41
+ if diff_el[0] == '-' then
42
+ nodeel = Nokogiri::XML::Node.new('a', @out_html)
43
+ nodeel['href'] = diff_el[2].first[0]
44
+ nodeel['class'] = 'list-group-item'
45
+ nodeel.content = diff_el[2].first[1]
46
+ new_root_node.add_child(nodeel)
47
+ end
48
+ end
49
+
50
+ @out_html.at_css(scope + " .list-group").replace(new_root_node)
51
+
52
+ @out_html.css("##{entity}_refresh")[0].content = Time.now
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,135 @@
1
+
2
+ class String
3
+ def to_bool
4
+ return true if self == true || self =~ (/(true|t|yes|y|1)$/i)
5
+ return false if self == false || self.blank? || self =~ (/(false|f|no|n|0)$/i)
6
+ raise ArgumentError.new("invalid value for Boolean: \"#{self}\"")
7
+ end
8
+ end
9
+
10
+ ActiveRecord::Base.establish_connection(
11
+ :adapter => 'sqlite3',
12
+ :database => DATABASE,
13
+ :pool => 5,
14
+ :timeout => 5000
15
+ )
16
+
17
+ def generate_database
18
+ ActiveRecord::Schema.define(version: 201502161646) do
19
+ create_table "Histories", force: false do |t|
20
+ t.text "entity"
21
+ t.text "parameter"
22
+ t.text "val"
23
+ t.datetime "created_at"
24
+ t.datetime "notified"
25
+ end
26
+ end
27
+ end
28
+
29
+ tables = ActiveRecord::Base.connection.execute(
30
+ "SELECT name FROM sqlite_master WHERE type='table' and name = 'Histories'; "
31
+ )
32
+
33
+ if tables.empty?
34
+ generate_database
35
+ else
36
+ generate_database unless tables[0]["name"] == 'Histories'
37
+ end
38
+
39
+
40
+ class History < ActiveRecord::Base
41
+ end
42
+
43
+ module DailyRep
44
+
45
+ START_TIME = Time.now
46
+
47
+ #constants
48
+ P_GO_FILE_PATH = 'GoFilePath'
49
+ P_PUSH_CLIENTS = 'Push clients'
50
+ P_PUSH_NOTE_TITLE = 'Push title'
51
+ P_RAISE_ERRORS = 'Raise errors'
52
+
53
+ PA_ENTITIES = 'Entities'
54
+ PA_APP_CYCLE = 'app_cycle'
55
+ P_INDEX_PATH = 'Index path'
56
+ P_NOTIF_DELTA = 'notification delta'
57
+ P_ENABLED = 'enabled'
58
+ P_TIME_FRAME = 'time frame'
59
+ P_SOURCE = 'source'
60
+ P_SEARCH_STRS = 'searchStr'
61
+
62
+
63
+
64
+
65
+
66
+ class Configer
67
+ def self.set_index_file file
68
+ @@config_json[P_INDEX_PATH] = file
69
+ end
70
+
71
+ File.open(CONFIG_FILE) {|file| @@config_json = JSON.parse(file.read) }
72
+
73
+ def self.index_path
74
+ @@config_json[P_INDEX_PATH]
75
+ end
76
+
77
+ def self.push_note_title
78
+ @@config_json[P_PUSH_NOTE_TITLE]
79
+ end
80
+
81
+ def self.raise_error?
82
+ @@config_json[P_RAISE_ERRORS].to_bool
83
+ end
84
+
85
+ def self.exctract_entity_params entity, parameter_name
86
+ @@config_json[PA_ENTITIES][entity][parameter_name]
87
+ end
88
+
89
+ def self.is_phase_enabled? phase
90
+ @@config_json[PA_APP_CYCLE][phase].to_bool
91
+ end
92
+ #+
93
+ def self.is_entity_enabled? entity
94
+ time_arr = @@config_json[PA_ENTITIES][entity][P_TIME_FRAME].split('..').map { |el|
95
+ el.to_i
96
+ }
97
+ (time_arr[0]..time_arr[1]).include?(Time.now.hour) &&
98
+ @@config_json[PA_ENTITIES][entity][P_ENABLED].to_bool
99
+ end
100
+ #+
101
+ def self.enabled_entities
102
+ @@config_json[PA_ENTITIES].keys.map { |entity|
103
+ entity.downcase.capitalize if is_entity_enabled?(entity)
104
+ }
105
+ end
106
+ #+
107
+ def self.get_client_tokens
108
+ @@config_json[P_PUSH_CLIENTS].values
109
+ end
110
+
111
+ # def self.include_search_strs? entity
112
+ # result = false
113
+ # result = true if @@config_json[PA_ENTITIES][entity][P_SEARCH_STRS]
114
+ # return result
115
+ # end
116
+
117
+ def self.get_search_strs entity
118
+ @@config_json[PA_ENTITIES][entity][P_SEARCH_STRS]
119
+ end
120
+
121
+ def self.method_missing(meth, *args, &block)
122
+ method_inputs = meth.to_s.split('_')
123
+ out_param = ''
124
+ @@config_json[PA_ENTITIES].keys.each { |key|
125
+ if method_inputs[0] == key.downcase
126
+ out_param = self.exctract_entity_params method_inputs[0], method_inputs[1]
127
+ end
128
+ }
129
+ super if not @@config_json[PA_ENTITIES].keys.include?(method_inputs[0])
130
+ return out_param
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,23 @@
1
+ require 'washbullet'
2
+ module DailyRep
3
+
4
+ class Notificator
5
+ def self.get_clients
6
+ result = []
7
+ result = Configer.get_client_tokens.map { |token|
8
+ new token
9
+ }
10
+ end
11
+
12
+ def initialize(push_token, note_title=Configer.push_note_title)
13
+ @client = Washbullet::Client.new(push_token)
14
+ @note_title = note_title
15
+ end
16
+
17
+ def push_note note
18
+ @client.push_note(nil, @note_title, note)
19
+ end
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,14 @@
1
+ module DailyRep
2
+ module Trackable
3
+ def log_msg entity, add_param='', phase
4
+ p "#{entity} (#{add_param}), stage: #{phase} at #{Time.now}"
5
+ end
6
+
7
+ def log_error e
8
+ # p " ERROR!: #{e.message}"
9
+ # p " ERROR_MSG: #{e.backtrace.inspect}"
10
+ raise
11
+ # if Configer.raise_error?
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,83 @@
1
+ require_relative '../Dbops'
2
+
3
+ module DailyRep
4
+ module Entities
5
+
6
+
7
+ class Doujob < IBrowser
8
+ attr_reader :diff
9
+ include Dbops
10
+
11
+ def self.create
12
+ result = []
13
+ result = Configer.get_search_strs('doujob').map { |str|
14
+ new str
15
+ }
16
+ end
17
+
18
+ def initialize search, source=Configer.doujob_source
19
+ super
20
+ @search=search.gsub(' ','+')
21
+ @dou_search_path = source + @search
22
+ @vacancy_scope = 'div#vacancyListId li'
23
+ @parameter = 'title-href'
24
+ @diff =[]
25
+ end
26
+
27
+ def log_msg phase
28
+ super @entity, @search, phase
29
+ end
30
+
31
+ #overwriting
32
+ def process
33
+ super do
34
+ out_set = get_source_http @dou_search_path, @vacancy_scope
35
+ @out_vac = out_set.map do |a|
36
+ [a.css('.title .vt').attribute("href").text, "#{a.css('.company').text} : #{a.css('.title .vt').text}"]
37
+ end
38
+ out_hash = @out_vac.map { |el|
39
+ {el[0] => el[1]}
40
+ }
41
+ hist_hash = read_hist_hash
42
+ @diff = HashDiff.diff(out_hash, hist_hash)
43
+ end
44
+ end
45
+
46
+ #overwriting
47
+ def notify
48
+ super do
49
+ @diff.each { |lv1|
50
+ push_note("New vacancy at DOU: #{lv1[2]} - #{lv1[3]} ") if lv1[0] == "-"
51
+ push_note("DOU vacancy has gone: #{lv1[2]} - #{lv1[3]} ") if lv1[0] == "+"
52
+ }
53
+ end
54
+ end
55
+ #overwriting
56
+ def write_to_db
57
+ super do
58
+ @out_vac.each { |vac|
59
+ write_hist @entity, @parameter, "#{vac[0]}=>#{vac[1]}"
60
+ }
61
+ end
62
+ end
63
+ #overwriting
64
+ def web_reload
65
+ super do
66
+ set_html_with_diff @entity, '#doujobs', @diff
67
+ end
68
+ end
69
+
70
+ private
71
+ def read_hist_hash
72
+ maxtime = History.where(entity: @entity, parameter: @parameter).where("created_at > ?", Time.now - 6.hours).maximum("created_at")
73
+ maxtime||= Time.now
74
+ maxtime = maxtime.to_datetime
75
+ History.select("val").where(entity: @entity, parameter: @parameter).where("created_at > ?", maxtime - 1.minute).map { |row|
76
+ el_ar = row.val.split('=>')
77
+ {el_ar[0] => el_ar[1]}
78
+ }
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,87 @@
1
+ #encoding: utf-8
2
+ require 'active_support/core_ext'
3
+ module DailyRep
4
+ module Entities
5
+ class Kinozal < IBrowser
6
+ attr_reader :out_hash_clean, :to_notification
7
+
8
+ def self.create
9
+ result = []
10
+ result = Configer.get_search_strs('kinozal').map { |str|
11
+ new str
12
+ }
13
+ end
14
+
15
+ def initialize movie, source=Configer.kinozal_source, filters=Configer.kinozal_filters
16
+ super
17
+ @movie = movie
18
+ @search_path = source+@movie.gsub(' ','+') + filters
19
+ @scope = 'td.nam a'
20
+ @dirty_words = ['тизер', 'трейлер']
21
+ @out_hash_clean = {}
22
+ end
23
+
24
+ def log_msg phase
25
+ super @entity, @movie, phase
26
+ end
27
+
28
+ def process
29
+ super do
30
+ out_set = get_source_http @search_path, @scope
31
+ out_hash_dirty = {}
32
+ out_set.each { |el|
33
+ out_hash_dirty.merge!({ el['href'] => el.text.split(' / ')})
34
+ }
35
+ out_hash_dirty.each { |key, val|
36
+ exclude = 0
37
+ @dirty_words.each { |word|
38
+ if val[0].mb_chars.downcase.include?(word) then
39
+ exclude = 1
40
+ break
41
+ end
42
+ }
43
+ @out_hash_clean.merge!({key => val}) if exclude == 0
44
+ }
45
+ end
46
+ end
47
+
48
+ def write_to_db
49
+ super do
50
+ @to_notification = {}
51
+ @out_hash_clean.each { |key, val|
52
+ id = key.split('id=')[1]
53
+ if !check_row_exists(@entity, @movie, id) then
54
+ write_hist(@entity, @movie, id, 1)
55
+ @to_notification.merge!({key => [val[0], val.last]})
56
+ end
57
+ }
58
+ end
59
+ end
60
+
61
+ def notify
62
+ super do
63
+ @to_notification.each { |key, val|
64
+ note = "Kinozal
65
+ Movie appears: #{val[0]}
66
+ Quality: #{val.last}
67
+ Link: #{key}"
68
+ push_note note
69
+ }
70
+ end
71
+ end
72
+
73
+
74
+ def web_reload
75
+ super do
76
+ i = 0
77
+ diff = @to_notification.map { |key, val|
78
+ i = i+1
79
+ ['-', i , {'http://kinozal.tv/'+ key => val.join(' : ')} ]
80
+ }
81
+ set_html_with_diff @entity, '#kinozal', diff, true
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+ end