betforker 1.0.1

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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +90 -0
  7. data/README.md +53 -0
  8. data/Rakefile +12 -0
  9. data/betforker.gemspec +23 -0
  10. data/bin/betforker +64 -0
  11. data/config.yml +14 -0
  12. data/lib/betforker.rb +80 -0
  13. data/lib/betforker/bookmakers/__to_change__/betfair.rb +112 -0
  14. data/lib/betforker/bookmakers/__to_change__/parimatch.rb +137 -0
  15. data/lib/betforker/bookmakers/__to_change__/sbobet.rb +90 -0
  16. data/lib/betforker/bookmakers/__to_change__/winlinebet.rb +122 -0
  17. data/lib/betforker/bookmakers/marathon.rb +121 -0
  18. data/lib/betforker/bookmakers/williamhill.rb +115 -0
  19. data/lib/betforker/comparer.rb +176 -0
  20. data/lib/betforker/config.rb +201 -0
  21. data/lib/betforker/downloader.rb +128 -0
  22. data/lib/betforker/event.rb +74 -0
  23. data/lib/betforker/fork.rb +29 -0
  24. data/lib/betforker/names_winlinebet.rb +1092 -0
  25. data/lib/betforker/parsed_page.rb +13 -0
  26. data/lib/betforker/version.rb +3 -0
  27. data/spec/betforker_spec.rb +39 -0
  28. data/spec/bookmakers/__to_change__/betfair_test.rb +40 -0
  29. data/spec/bookmakers/__to_change__/parimatch_test.rb +41 -0
  30. data/spec/bookmakers/__to_change__/sbobet_test.rb +40 -0
  31. data/spec/bookmakers/__to_change__/winlinebet_test.rb +39 -0
  32. data/spec/bookmakers/marathon_spec.rb +84 -0
  33. data/spec/bookmakers/williamhill_spec.rb +81 -0
  34. data/spec/comparer_spec.rb +162 -0
  35. data/spec/config_spec.rb +155 -0
  36. data/spec/downloader_spec.rb +88 -0
  37. data/spec/event_spec.rb +94 -0
  38. data/spec/features/without_downloads_spec.rb +138 -0
  39. data/spec/fork_spec.rb +21 -0
  40. data/spec/spec_helper.rb +199 -0
  41. data/spec/support/html/betfair/betfair1.html +3453 -0
  42. data/spec/support/html/betfair/betfair2.html +3996 -0
  43. data/spec/support/html/betfair/betfair3.html +6566 -0
  44. data/spec/support/html/betfair/betfair4.html +3373 -0
  45. data/spec/support/html/betfair/bf_live.htm +5905 -0
  46. data/spec/support/html/fake/fake.html +15759 -0
  47. data/spec/support/html/marathon/first.html +2717 -0
  48. data/spec/support/html/marathon/live_page.html +7226 -0
  49. data/spec/support/html/marathon/second.html +3500 -0
  50. data/spec/support/html/marathon/with_forks.html +2525 -0
  51. data/spec/support/html/parimatch/parimatch1.html +588 -0
  52. data/spec/support/html/parimatch/parimatch2.html +589 -0
  53. data/spec/support/html/parimatch/parimatch3.html +581 -0
  54. data/spec/support/html/parimatch/parimatch4.html +641 -0
  55. data/spec/support/html/parimatch/pm_live.html +1049 -0
  56. data/spec/support/html/sbobet/sb1.htm +8 -0
  57. data/spec/support/html/sbobet/sb2.htm +8 -0
  58. data/spec/support/html/sbobet/sb3.htm +8 -0
  59. data/spec/support/html/sbobet/sb4.htm +8 -0
  60. data/spec/support/html/sbobet/sbobet_live.htm +8 -0
  61. data/spec/support/html/williamhill/first.html +13273 -0
  62. data/spec/support/html/williamhill/live_page.html +42735 -0
  63. data/spec/support/html/williamhill/second.html +15759 -0
  64. data/spec/support/html/williamhill/with_forks.html +13473 -0
  65. data/spec/support/html/williamhill/without_forks.html +14016 -0
  66. data/spec/support/html/winlinebet/win1.htm +23797 -0
  67. data/spec/support/html/winlinebet/win2.htm +23797 -0
  68. data/spec/support/html/winlinebet/win3.htm +23797 -0
  69. data/spec/support/html/winlinebet/win4.htm +23797 -0
  70. data/spec/support/html/winlinebet/win_live.htm +23815 -0
  71. metadata +211 -0
@@ -0,0 +1,115 @@
1
+ module Betforker
2
+ module Bookmakers
3
+ module WilliamHill
4
+ module_function
5
+
6
+ def parse_live_page(html, sport)
7
+ nok = Nokogiri::HTML(html)
8
+ links = Hash.new
9
+ nok.css('#ip_sport_24_types .CentrePad a').each do |link|
10
+ next unless link['href']
11
+ href = link['href']
12
+ players = link.text
13
+ links[href] = concatenated_names players
14
+ end
15
+ links
16
+ end
17
+
18
+ def parse_event(event, sport)
19
+ result = Betforker::ParsedPage.new bookie: 'WilliamHill'
20
+ html = extract_html_from(event)
21
+ return event if html.nil?
22
+ nok = Nokogiri::HTML(html)
23
+ nok.css('script').remove
24
+ nok.css('#primaryCollectionContainer .marketHolderExpanded .tableData').each do |market|
25
+ market.css('thead div').remove
26
+ title = market.css('thead span').text
27
+ next if title =~ /Total|Point|Deuce|Score/
28
+ target_filler(market, title, 'home', result)
29
+ target_filler(market, title, 'away', result)
30
+ end
31
+ result.home_player[:name] ||= 'HomePlayer'
32
+ result.away_player[:name] ||= 'AwayPlayer'
33
+ event.parsed_webpages << result
34
+ end
35
+
36
+ def extract_html_from(event)
37
+ arr = event.webpages.values_at 'williamhill'
38
+ arr.first
39
+ end
40
+
41
+ def concatenated_names(string)
42
+ w = []
43
+ if string.include?(' v ')
44
+ string.split(' v ').each do |pl|
45
+ w += second_names_finder(pl)
46
+ end
47
+ else
48
+ w += second_names_finder(string)
49
+ end
50
+ result = ""
51
+ w.sort.each {|wh| result << wh}
52
+ result
53
+ end
54
+
55
+ def second_names_finder(names)
56
+ w = []
57
+ names.gsub!('-', ' ')
58
+ if names.include? '/'
59
+ names.split('/').each {|n| w << n.scan(/\w+/)[-1]}
60
+ elsif names.include?('Doubles')
61
+ w << names.scan(/\w+/)[-2]
62
+ else
63
+ w << names.scan(/\w+/)[-1]
64
+ end
65
+ w
66
+ end
67
+
68
+ def target_filler(market, title, home_away, result)
69
+ result = result
70
+ case home_away
71
+ when 'home'
72
+ priceholder = '.eventpriceholder-left'
73
+ pricecoeff = 'div.eventpriceup'
74
+ player = result.home_player
75
+ when 'away'
76
+ priceholder = '.eventpriceholder-right'
77
+ pricecoeff = 'div.eventpricedown'
78
+ player = result.away_player
79
+ end
80
+ chunk = market.css("tbody #{priceholder}").each do |pl|
81
+ name = pl.css('div.eventselection').text.strip
82
+ coeff = pl.css('div.eventprice').text.strip.to_f
83
+ coeff = pl.css(pricecoeff).text.strip.to_f if coeff == 0.0
84
+ unless coeff == 0.0
85
+ player[:name] ||= concatenated_names name
86
+ if title.include? 'Match Betting'
87
+ player[:match] = coeff
88
+ elsif title.include? ' Set - Game '
89
+ player[:game] ||= {}
90
+ player[:game].merge!({title.scan(/\w+/)[-1] => coeff})
91
+ elsif title.include? ' Set Betting Live'
92
+ player[:set] ||= {}
93
+ player[:set].merge!({title.scan(/\w+/)[0].to_i.to_s => coeff})
94
+ end
95
+ end
96
+ end
97
+ result
98
+ end
99
+
100
+ def set_cookies
101
+ domain = Betforker::WILLIAMHILL_CHANGABLE.split('//').last.gsub('sports', '')
102
+ [{
103
+ name: 'cust_lang',
104
+ value: 'en-gb',
105
+ attr: { domain: domain }
106
+ },
107
+ {
108
+ name: 'cust_prefs',
109
+ value: 'en|DECIMAL|form|TYPE|PRICE|||0|SB|0|0||0|en|0|TIME|TYPE|0|1|A|0||0|0||TYPE||-|0',
110
+ attr: { domain: domain }
111
+ }]
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,176 @@
1
+ module Betforker
2
+ module Comparer
3
+ UNREAL_PERCENT = 25
4
+ FAKE_PERCENT = -3.5
5
+
6
+ module_function
7
+
8
+ def compare(first, second)
9
+ return [] if first.bookie == second.bookie
10
+ forks = []
11
+ check_sorting first, second
12
+ return forks unless same_players? first, second
13
+
14
+ filtering, break_now = if $config[:filtering]
15
+ [ true, is_a_break?(init_score(first, second)) ]
16
+ else
17
+ [ false, true ]
18
+ end
19
+
20
+ forks << market_processing(:match, first, second, break_now)
21
+ forks << market_processing(:set, first, second, break_now)
22
+ forks << market_processing(:game, first, second)
23
+
24
+ forks = score_analyzer forks.flatten, filtering
25
+
26
+ forks.map do |f|
27
+ Fork.new f
28
+ end
29
+ end
30
+
31
+ def same_players?(first, second)
32
+ first.home_player[:name] == second.home_player[:name] &&
33
+ first.away_player[:name] == second.away_player[:name]
34
+ end
35
+
36
+ def change_names(object)
37
+ object.home_player, object.away_player =
38
+ object.away_player, object.home_player
39
+ end
40
+
41
+ def check_sorting(first, second)
42
+ [first, second].each do |k|
43
+ if k.home_player[:name] > k.away_player[:name]
44
+ change_names k
45
+ end
46
+ end
47
+ end
48
+
49
+ def init_score(first, second)
50
+ if first.score.empty? then second.score else first.score end
51
+ end
52
+
53
+ def fork_template(first, second)
54
+ {
55
+ bookmakers: "#{first.bookie} - #{second.bookie}",
56
+ players: "#{first.home_player[:name]} VS #{first.away_player[:name]}",
57
+ score: init_score(first, second)
58
+ }
59
+ end
60
+
61
+ def market_processing(market, first, second, break_now = true)
62
+ result = []
63
+ if first.home_player.key?(market) and second.home_player.key?(market) and break_now
64
+ respond = case market
65
+ when :match then match_win first, second
66
+ when :set then game_or_set_win :set, first, second
67
+ when :game then game_or_set_win :game, first, second
68
+ end
69
+ unless respond.empty?
70
+ if respond.instance_of? Array
71
+ respond.each do |m|
72
+ result << fork_template(first, second).merge(m)
73
+ end
74
+ else
75
+ result << fork_template(first, second).merge(respond)
76
+ end
77
+ end
78
+ end
79
+ result
80
+ end
81
+
82
+ def calculate(x, y)
83
+ return FAKE_PERCENT if x == 0.0 or y == 0.0
84
+ x_bet = 100.0
85
+ sum_of_win = x * x_bet
86
+ y_bet = sum_of_win / y
87
+ sum_of_bet = x_bet + y_bet
88
+ profit = sum_of_win - sum_of_bet
89
+ percent = (profit / sum_of_bet) * 100
90
+ percent = FAKE_PERCENT if percent >= UNREAL_PERCENT
91
+ percent
92
+ end
93
+
94
+ def match_win(first, second)
95
+ respond = {}
96
+ percent_straight = calculate(first.home_player[:match], second.away_player[:match])
97
+ percent_reverse = calculate(first.away_player[:match], second.home_player[:match])
98
+ percent = percent_straight > percent_reverse ? percent_straight : percent_reverse
99
+ if percent > $config[:min_percent]
100
+ respond[:what] = 'match'
101
+ respond[:percent] = percent.round(2).to_s
102
+ end
103
+ respond
104
+ end
105
+
106
+ def game_or_set_win(param, first, second)
107
+ respond = []
108
+ percent_straight = {}
109
+ percent_reverse = {}
110
+ first.home_player[param].each do |first_key, first_val|
111
+ second.away_player[param].each do |second_key, second_val|
112
+ if first_key == second_key
113
+ percent_straight[first_key] = calculate(first_val, second_val)
114
+ end
115
+ end
116
+ end
117
+
118
+ first.away_player[param].each do |first_key, first_val|
119
+ second.home_player[param].each do |second_key, second_val|
120
+ if first_key == second_key
121
+ percent_reverse[first_key] = calculate(first_val, second_val)
122
+ end
123
+ end
124
+ end
125
+
126
+ percent_straight.each do |key_str,val_str|
127
+ percent_reverse.each do |key_rev,val_rev|
128
+ if key_str == key_rev
129
+ percent = val_str > val_rev ? val_str : val_rev
130
+ if percent > $config[:min_percent]
131
+ respond << { what: (param.to_s + key_str), percent: percent.round(2).to_s }
132
+ end
133
+ end
134
+ end
135
+ end
136
+ respond
137
+ end
138
+
139
+ def is_a_break?(score)
140
+ g1, g2, s1, s2 = score_parser(score)
141
+ end_of_set = ((s1 + s2) == 12 and s1 != s2) || ((s1 == 6 and (s1 - s2) > 1) || (s2 == 6 and (s2 - s1) > 1))
142
+ if (g1 + g2) == 0 and ((s1 + s2) == 0 or (s1 + s2).odd? or end_of_set)
143
+ return true
144
+ else
145
+ return false
146
+ end
147
+ end
148
+
149
+ def score_analyzer(forks, filtering)
150
+ return forks if forks.empty? or not filtering
151
+ forks.each do |f|
152
+ next unless f[:what].include?('game')
153
+ g1, g2, s1, s2 = score_parser(f[:score])
154
+ game_in_fork = f[:what].scan(/\d+/)[0].to_i
155
+ not_a_time = game_in_fork > (s1 + s2 + 1) ? false : true
156
+ forks.delete(f) if not_a_time
157
+ end
158
+ forks
159
+ end
160
+
161
+ def score_parser(score)
162
+ s = score.split(/\(|\)|,/)
163
+ if s.size == 1 or score[0] == ' '
164
+ games = '0:0'
165
+ sets = s[0].strip
166
+ else
167
+ games = s[0].strip
168
+ sets = s[-1].strip
169
+ end
170
+ g1, g2 = games.scan(/\d+/)
171
+ s1, s2 = sets.scan(/\d+/)
172
+ return g1.to_i, g2.to_i, s1.to_i, s2.to_i
173
+ end
174
+
175
+ end
176
+ end
@@ -0,0 +1,201 @@
1
+ module Betforker
2
+ module Config
3
+ module_function
4
+
5
+ PERSONAL_CONFIG = "#{ENV['HOME']}/.betforker.conf.yml"
6
+
7
+ def config_initialization!
8
+ template= YAML.load File.open(File.dirname(__FILE__) + '/../../config.yml', 'r')
9
+ write_personal_config(template) unless File.exist? PERSONAL_CONFIG
10
+ personal= YAML.load File.open(PERSONAL_CONFIG, 'r')
11
+ $config = check_personal_configuration template, personal
12
+ end
13
+
14
+ def changable_addresses
15
+ config_initialization!
16
+ [$config[:marathon_changable],
17
+ $config[:williamhill_changable]]
18
+ end
19
+
20
+ def check_personal_configuration(template, personal)
21
+ same_values = []
22
+ 2.times do
23
+ if template.keys == personal.keys
24
+ template.values.each_with_index do |tempval, index|
25
+ same_values << same_typeofs(tempval.class, personal.values[index].class)
26
+ end
27
+ if same_values.include? false
28
+ new = {}
29
+ template.each do |key, value|
30
+ new[key] = if same_typeofs(value.class, personal[key].class)
31
+ personal[key]
32
+ else
33
+ value
34
+ end
35
+ end
36
+ personal = new
37
+ write_personal_config(personal)
38
+ end
39
+ break
40
+ else
41
+ new = {}
42
+ template.each do |key, value|
43
+ new[key] = if personal.key?(key) then personal[key] else value end
44
+ end
45
+ personal = new
46
+ write_personal_config(personal)
47
+ end
48
+ end
49
+ personal
50
+ end
51
+
52
+ def same_typeofs(a, b)
53
+ (a == b) or (a == TrueClass and b == FalseClass) or
54
+ (a == FalseClass and b == TrueClass)
55
+ end
56
+
57
+ def write_personal_config(config)
58
+ File.open(PERSONAL_CONFIG, 'w') do |file|
59
+ yaml = YAML.dump config
60
+ file.write yaml
61
+ end
62
+ end
63
+
64
+ def update
65
+ print_current_config
66
+ change = true_or_false ask('Будешь что-нибудь менять? (y/N) ')
67
+ while change
68
+ new_values = manual_enter
69
+ update_config new_values
70
+ print_current_config
71
+ change = true_or_false ask('Что нибудь еще? (y/N) ')
72
+ end
73
+ end
74
+
75
+ def update_config(values)
76
+ values.each do |key, value|
77
+ $config[key] = value
78
+ end
79
+ write_personal_config $config
80
+ end
81
+
82
+ def print_current_config
83
+ puts 'Текущая конфигурация:'
84
+ $config.each do |key, value|
85
+ puts " #{translate key} => #{value}"
86
+ end
87
+ end
88
+
89
+ def manual_enter
90
+ result = {}
91
+ $config.each_with_index do |value, index|
92
+ puts " #{index} - #{translate value.first}"
93
+ end
94
+ puts " #{$config.size} - поменять все"
95
+ entry = ask('Выбери то, что хочешь изменить (число): ', Integer) { |q| q.in = 0..$config.size }
96
+ result.merge case entry
97
+ when 0 then set_marathon_changable
98
+ when 1 then set_williamhill_changable
99
+ when 2 then set_min_percent
100
+ when 3 then set_filtering
101
+ when 4 then set_sound_notification
102
+ when 5 then set_sport
103
+ when 6 then set_bookmakers
104
+ when 7 then set_log
105
+ when 8 then set_time_of_notification
106
+ when 9 then set_download_timeout
107
+ when 10 then set_phantomjs_logger
108
+ else all_fields
109
+ end
110
+ end
111
+
112
+ #!!!!!!!!!!!!!!! ^ and V should be synchronized !!!!!!!!!!!!!!
113
+ def all_fields
114
+ config = {}
115
+ config.merge! set_marathon_changable
116
+ config.merge! set_williamhill_changable
117
+ config.merge! set_min_percent
118
+ config.merge! set_filtering
119
+ config.merge! set_sound_notification
120
+ config.merge! set_sport
121
+ config.merge! set_bookmakers
122
+ config.merge! set_log
123
+ config.merge! set_time_of_notification
124
+ config.merge! set_download_timeout
125
+ config.merge! set_phantomjs_logger
126
+ config
127
+ end
128
+
129
+ def translate(config_key)
130
+ case config_key
131
+ when :marathon_changable then 'адрес marathon'
132
+ when :williamhill_changable then 'адрес williamhill'
133
+ when :download_timeout then 'download timeout'
134
+ when :min_percent then 'минимальный процент вилки'
135
+ when :time_of_notification then 'время показа найденной вилки в секундах'
136
+ when :filtering then 'показ только тех вилок, что можно поставить'
137
+ when :sound_notification then 'звук сообщений'
138
+ when :log then 'показ событий в терминале'
139
+ when :phantomjs_logger then 'phantomjs logs'
140
+ when :sport then 'виды спорта'
141
+ when :bookmakers then 'список букмекеров'
142
+ else config_key
143
+ end
144
+ end
145
+
146
+ def set_marathon_changable
147
+ address = ask("Впиши новый адрес формата https://www.mirrorofmarathon.com > ")
148
+ { marathon_changable: address }
149
+ end
150
+
151
+ def set_williamhill_changable
152
+ address = ask("Впиши новый адрес формата http://sports.mirrorofwilliamhill.com > ")
153
+ { williamhill_changable: address }
154
+ end
155
+
156
+ def set_download_timeout
157
+ timeout = ask("downloader timeout > ", Integer)
158
+ { download_timeout: timeout }
159
+ end
160
+
161
+ def set_min_percent
162
+ percent = ask("минимальный процент (число) > ", Float) do |q|
163
+ q.in = Betforker::Comparer::FAKE_PERCENT...Betforker::Comparer::UNREAL_PERCENT
164
+ end
165
+ { min_percent: percent }
166
+ end
167
+
168
+ def set_time_of_notification
169
+ time = ask("время показа вилки в секундах > ", Integer) { |q| q.in = 1..120 }
170
+ { time_of_notification: time }
171
+ end
172
+
173
+ def set_filtering
174
+ { filtering: true_or_false(ask("показывать вилки ТОЛЬКО в перерывах (y/N) > ")) }
175
+ end
176
+
177
+ def set_sound_notification
178
+ { sound_notification: true_or_false(ask("включить звук (y/N) > ")) }
179
+ end
180
+
181
+ def set_log
182
+ { log: true_or_false(ask("показывать в терминале, что происходит (y/N) > ")) }
183
+ end
184
+
185
+ def set_phantomjs_logger
186
+ { phantomjs_logger: true_or_false(ask("показывать логи phantomjs (y/N) > ")) }
187
+ end
188
+
189
+ def set_sport
190
+ { sport: 'tennis' }
191
+ end
192
+
193
+ def set_bookmakers
194
+ { bookmakers: ['Marathon', 'WilliamHill'] }
195
+ end
196
+
197
+ def true_or_false(answer)
198
+ if answer =~ /^y/i then true else false end
199
+ end
200
+ end
201
+ end