-
Notifications
You must be signed in to change notification settings - Fork 15
/
kicktippbb.py
274 lines (221 loc) · 9.96 KB
/
kicktippbb.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
"""KickTipp BetBot
Automated kicktipp.de bet placement.
Places bets to the upcomming matchday.
Unless specified by parameter it places the bets on all prediction games of the account.
Usage:
kicktippbb.py [ --get-login-token ]
kicktippbb.py [ --list-predictors ]
kicktippbb.py [--use-login-token <token> ] [--dry-run] [--override-bets] [--deadline <duration>] [--predictor <value>] [--matchday <value>] [COMMUNITY]...
Options:
COMMUNITY Name of the prediction game community to place bets on,
one or more names can be specified.
If no community name is given all available communities will be considered.
--get-login-token Just login and print the login token string
for later use with '--use-login-token' option
--use-login-token <token> Perform bets without interactive login, use login token instead.
--override-bets Override already placed bets.
--deadline <duration> Only place bets on matches that start in <duration> from now.
The duration format is <number><unit[m,h,d]>, e.g. 10m,5h or 1d
--list-predictors Display a list of predictors available to be used with '--predictor' option
--predictor <value> A specific predictor name to be used during calculation
--dry-run Dont place any bet just print out predicitons
--matchday <value> Choose a specific matchday in the range of 1 to 34 to place bets on
"""
import sys
import datetime
import getpass
import re
from docopt import docopt
from robobrowser import RoboBrowser
import predictors.base
from helper.deadline import is_before_dealine, timedelta_tostring
from helper.match import Match
URL_BASE = 'http://www.kicktipp.de'
URL_LOGIN = URL_BASE + '/info/profil/login'
DEADLINE_REGEX = re.compile('([1-9][0-9]*)(m|h|d)')
def login(browser: RoboBrowser):
"""Log into the user account by asking for username and password.
If login succeded the login cookie token is returned
"""
while True:
username, password = get_credentials()
perform_login(browser, username, password)
if not logged_in(browser):
print("Email or password incorrect. Please try again.\n")
else:
return browser.session.cookies['login']
def perform_login(browser: RoboBrowser, username: str, password: str):
"""
Open the log in page then fill out the form and submit
"""
browser.open(URL_LOGIN)
form = browser.get_form()
form['kennung'] = username
form['passwort'] = password
browser.submit_form(form)
def get_credentials():
"""
Ask the user for the credentials.
"""
username = input("Username: ")
password = getpass.getpass(prompt='Password: ')
return username, password
def logged_in(browser: RoboBrowser):
"""
Returns true if we are still on the login page
"""
login_div = browser.find('div', content="Login")
return True if not login_div else False
def get_table_rows(soup):
"""
Get all table rows from the first tbody element found in soup parameter
"""
tbody = soup.find('tbody')
return [tr.find_all('td') for tr in tbody.find_all('tr')]
def parse_match_rows(browser: RoboBrowser, community, matchday = None):
"""Fetch latest odds for each match
Returns a list of tuples (heimtipp,gasttipp, match)
"""
browser.open(get_tippabgabe_url(community, matchday))
content = get_kicktipp_content(browser)
rows = get_table_rows(content)
matchtuple = list()
lastmatch = None
for row in rows:
heimtipp = row[3].find(
'input', id=lambda x: x and x.endswith('_heimTipp'))
gasttipp = row[3].find(
'input', id=lambda x: x and x.endswith('_gastTipp'))
try:
odds=[odd.replace(" ","") for odd in row[4].get_text().split("/")]
match = Match(row[1].get_text(), row[2].get_text(), row[0].get_text(
), odds[0], odds[1], odds[2])
except:
print("Error: Not enough data, maybe there are no rates yet.")
sys.exit()
if not match.match_date:
match.match_date = lastmatch.match_date
lastmatch = match
matchtuple.append((heimtipp, gasttipp, match))
return matchtuple
def get_tippabgabe_url(community, matchday = None):
tippabgabeurl = URL_BASE + '/' + community + '/tippabgabe'
if matchday is None:
return tippabgabeurl
else:
matchday = int(matchday)
if matchday < 1 or matchday > 34:
raise IndexError("The matchday '{}' is not valid, use only 1 to 34!".format(matchday))
return tippabgabeurl + '?&spieltagIndex={matchday}'.format(matchday=matchday)
def get_kicktipp_content(browser: RoboBrowser):
"""
Get the content view area from the kicktipp page.
"""
content = browser.find_all(id='kicktipp-content')
if content[0]:
return content[0]
return None
def get_communities(browser: RoboBrowser, desired_communities: list):
"""
Get a list of all communities of the user
"""
browser.open(URL_BASE + '/info/profil/meinetipprunden')
content = get_kicktipp_content(browser)
links = content.find_all('a')
def gethreftext(link): return link.get('href').replace("/", "")
def is_community(link):
hreftext = gethreftext(link)
if hreftext == link.get_text():
return True
else:
linkdiv = link.find('div', {'class': "menu-title-mit-tippglocke"})
return linkdiv and linkdiv.get_text() == hreftext
community_list = [gethreftext(link)
for link in links if is_community(link)]
if len(desired_communities) > 0:
return intersection(community_list, desired_communities)
return community_list
def intersection(a, b):
i = [x for x in a if x in b]
return i
def place_bets(browser: RoboBrowser, communities: list, predictor, override=False, deadline=None, dryrun=False, matchday=None):
"""Place bets on all given communities."""
for com in communities:
print("Community: {0}".format(com))
matches = parse_match_rows(browser, com, matchday)
submitform = browser.get_form()
for field_hometeam, field_roadteam, match in matches:
if not field_hometeam or not field_roadteam:
print("{0} - no bets possible".format(match))
continue
input_hometeam_value = submitform[field_hometeam.attrs['name']].value
input_roadteam_value = submitform[field_roadteam.attrs['name']].value
if not override and (input_hometeam_value or input_roadteam_value):
print("{0} - skipped, already placed {1}:{2}".format(match,
input_hometeam_value, input_roadteam_value))
continue
if deadline is not None:
if not is_before_dealine(deadline, match.match_date):
time_to_match = match.match_date - datetime.datetime.now()
print("{0} - not betting yet, due in {1}".format(match,
timedelta_tostring(time_to_match)))
continue
homebet, roadbet = predictor.predict(match)
print("{0} - betting {1}:{2}".format(match, homebet, roadbet))
submitform[field_hometeam.attrs['name']] = str(homebet)
submitform[field_roadteam.attrs['name']] = str(roadbet)
if not dryrun:
browser.submit_form(submitform, submit='submitbutton')
else:
print("INFO: Dry run, no bets were placed")
def validate_arguments(arguments):
if arguments['--deadline']:
deadline_value = arguments['--deadline']
if not re.match(DEADLINE_REGEX, deadline_value):
exit("Invalid deadline value ({}), use <Number><Unit>, Unit=[m,h,d]".format(
deadline_value))
def choose_predictor(predictor_param, predictors):
if(predictor_param):
if(predictor_param in predictors):
predictor = predictors[predictor_param]()
else:
exit('Unknown predictor: {}'.format(predictor_param))
else:
# Just get the first predictor in the dict and instanciate it
predictor = next(iter(predictors.values()))()
print("Using predictor: "+type(predictor).__name__)
return predictor
def main(arguments):
browser = RoboBrowser(parser="html5lib")
validate_arguments(arguments)
predictors_ = predictors.base.get_predictors()
# Log in to kicktipp and print out the login cookie value
if arguments['--get-login-token']:
token = login(browser)
print(token)
exit(0)
# Just list the predictors at hand and exit
if arguments['--list-predictors']:
[print(key) for key in predictors_.keys()]
exit(0)
# Use login token pass by argument or let the caller log in right here
if arguments['--use-login-token']:
token = arguments['--use-login-token']
else:
token = login(browser)
communities = arguments['COMMUNITY']
# Just use the token for all interactions with the website
browser.session.cookies['login'] = token
# Which communities are considered, fail if no were found
communities = get_communities(browser, communities)
if(len(communities) == 0):
exit("No community found!?")
# Which prediction method is used
predictor_param = arguments['--predictor'] if '--predictor' in arguments else None
predictor = choose_predictor(predictor_param, predictors_)
# Place bets
place_bets(browser, communities, predictor,
override=arguments['--override-bets'], deadline=arguments['--deadline'], dryrun=arguments['--dry-run'], matchday=arguments['--matchday'])
if __name__ == '__main__':
arguments = docopt(__doc__, version='KickTipp BetBot 1.0')
main(arguments)