dailyrep 1.0.0
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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/README.md +17 -0
- data/Rakefile +8 -0
- data/bin/dailyrep +42 -0
- data/config/.gitkeep +0 -0
- data/config/config_template.json +57 -0
- data/config/index_template.html +189 -0
- data/dailyrep.gemspec +27 -0
- data/lib/dailyrep/AppContainer.rb +25 -0
- data/lib/dailyrep/Changable.rb +20 -0
- data/lib/dailyrep/Dbops.rb +28 -0
- data/lib/dailyrep/IBrowser.rb +77 -0
- data/lib/dailyrep/IndexWriter.rb +55 -0
- data/lib/dailyrep/Loadconfig.rb +135 -0
- data/lib/dailyrep/Notificator.rb +23 -0
- data/lib/dailyrep/Trackable.rb +14 -0
- data/lib/dailyrep/entities/Doujob.rb +83 -0
- data/lib/dailyrep/entities/Kinozal.rb +87 -0
- data/lib/dailyrep/entities/Micex.rb +66 -0
- data/lib/dailyrep/entities/Minfin.rb +69 -0
- data/lib/dailyrep/entities/Yanoil.rb +67 -0
- data/lib/dailyrep/version.rb +3 -0
- data/test/dailyrep/Doujob_test.rb +22 -0
- data/test/dailyrep/IBrowser_test.rb +46 -0
- data/test/dailyrep/Kinozal_test.rb +23 -0
- data/test/dailyrep/Micex_test.rb +27 -0
- data/test/dailyrep/Minfin_test.rb +23 -0
- data/test/dailyrep/Yanoil_test.rb +21 -0
- data/test/test_helper.rb +37 -0
- metadata +176 -0
@@ -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
|