Coverage for MergeSummary.py: 0%
289 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-28 16:41 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-28 16:41 +0000
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
4#Copyright 2008-2011 Carl Gherardi
5#This program is free software: you can redistribute it and/or modify
6#it under the terms of the GNU Affero General Public License as published by
7#the Free Software Foundation, version 3 of the License.
8#
9#This program is distributed in the hope that it will be useful,
10#but WITHOUT ANY WARRANTY; without even the implied warranty of
11#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12#GNU General Public License for more details.
13#
14#You should have received a copy of the GNU Affero General Public License
15#along with this program. If not, see <http://www.gnu.org/licenses/>.
16#In the "official" distribution you can find the license in agpl-3.0.txt.
19#import L10n
20#_ = L10n.get_translation()
22from decimal_wrapper import Decimal
23import datetime
24from bs4 import BeautifulSoup
26from Exceptions import FpdbParseError
27from HandHistoryConverter import *
28import MergeToFpdb, MergeStructures
29from TourneySummary import *
32class MergeSummary(TourneySummary):
33 limits = { 'No Limit':'nl', 'No Limit ':'nl', 'Fixed Limit':'fl', 'Limit':'fl', 'Pot Limit':'pl', 'Pot Limit ':'pl', 'Half Pot Limit':'hp'}
34 games = { # base, category
35 'Holdem' : ('hold','holdem'),
36 'Holdem Tournament' : ('hold','holdem'),
37 'Omaha' : ('hold','omahahi'),
38 'Omaha Tournament' : ('hold','omahahi'),
39 'Omaha H/L8' : ('hold','omahahilo'),
40 '2-7 Lowball' : ('draw','27_3draw'),
41 'A-5 Lowball' : ('draw','a5_3draw'),
42 'Badugi' : ('draw','badugi'),
43 '5-Draw w/Joker' : ('draw','fivedraw'),
44 '5-Draw' : ('draw','fivedraw'),
45 '7-Stud' : ('stud','studhi'),
46 '7-Stud H/L8' : ('stud','studhilo'),
47 '5-Stud' : ('stud','5_studhi'),
48 'Razz' : ('stud','razz')
49 }
50 games_html = {
51 'Texas Holdem' : ('hold','holdem'),
52 'Omaha' : ('hold','omahahi'),
53 'Omaha HiLo' : ('hold','omahahilo'),
54 '2-7 Low Triple Draw' : ('draw','27_3draw'),
55 'Badugi' : ('draw','badugi'),
56 'Seven Card Stud' : ('stud','studhi'),
57 'Seven Card Stud HiLo' : ('stud','studhilo'),
58 'Five Card Stud' : ('stud','studhilo'),
59 'Razz' : ('stud','razz')
60 }
62 mixes = {
63 'HA' : 'ha',
64 'RASH' : 'rash',
65 'HO' : 'ho',
66 'SHOE' : 'shoe',
67 'HORSE' : 'horse',
68 'HOSE' : 'hose',
69 'HAR' : 'har'
70 }
72 months = { 'January':1, 'Jan':1, 'February':2, 'Feb':2, 'March':3, 'Mar':3,
73 'April':4, 'Apr':4, 'May':5, 'May':5, 'June':6, 'Jun':6,
74 'July':7, 'Jul':7, 'August':8, 'Aug':8, 'September':9, 'Sep':9,
75 'October':10, 'Oct':10, 'November':11, 'Nov':11, 'December':12, 'Dec':12}
78 substitutions = {
79 'LEGAL_ISO' : "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes
80 'LS' : u"\$|€|" # legal currency symbols
81 }
82 re_Identify = re.compile(u"<title>Online\sPoker\sTournament\sDetails\s\-\sCarbonPoker</title>")
83 re_NotFound = re.compile(u"Tournament not found")
84 re_GameTypeHH = re.compile(r'<description type="(?P<GAME>Holdem|Omaha|Omaha|Omaha\sH/L8|2\-7\sLowball|A\-5\sLowball|Badugi|5\-Draw\sw/Joker|5\-Draw|7\-Stud|7\-Stud\sH/L8|5\-Stud|Razz|HORSE|RASH|HA|HO|SHOE|HOSE|HAR)(?P<TYPE>\sTournament)?" stakes="(?P<LIMIT>[a-zA-Z ]+)(\s\(?\$?(?P<SB>[.0-9]+)?/?\$?(?P<BB>[.0-9]+)?(?P<blah>.*)\)?)?"(\sversion="\d+")?/>\s?', re.MULTILINE)
85 re_HandInfoHH = re.compile(r'<game id="(?P<HID1>[0-9]+)-(?P<HID2>[0-9]+)" starttime="(?P<DATETIME>.+?)" numholecards="[0-9]+" gametype="[0-9]+" (multigametype="(?P<MULTIGAMETYPE>\d+)" )?(seats="(?P<SEATS>[0-9]+)" )?realmoney="(?P<REALMONEY>(true|false))" data="[0-9]+[|:](?P<TABLENAME>[^|:]+)[|:](?P<TDATA>[^|:]+)[|:]?.*>', re.MULTILINE)
86 re_DateTimeHH = re.compile(r'(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)', re.MULTILINE)
87 re_turboHH = re.compile(r'Turbo\s\-\s')
89 re_HTMLName = re.compile("Name\s+?</th>\s+?<td>(?P<NAME>.+?)\s+?</td>")
90 re_HTMLGameType = re.compile("""Game Type\s+?</th>\s+?<td>(?P<LIMIT>Fixed Limit|No Limit|Pot Limit) (?P<GAME>Texas\sHoldem|Omaha|Omaha\sHiLo|2\-7\sLow\sTriple\sDraw|Badugi|Seven\sCard\sStud|Seven\sCard\sStud\sHiLo|Five\sCard\sStud|Razz|HORSE|HA|HO)\s+?</td>""")
91 re_HTMLBuyIn = re.compile("Buy In\s+?</th>\s+?<td>(?P<BUYIN>[0-9,.]+)\s+?</td>")
92 re_HTMLFee = re.compile("Entry Fee\s+?</th>\s+?<td>(?P<FEE>[0-9,.]+)\s+?</td>")
93 re_HTMLBounty = re.compile("Bounty\s+?</th>\s+?<td>(?P<KOBOUNTY>.+?)\s+?</td>")
94 re_HTMLAddons = re.compile("Addons\s+?</th>\s+?<td>(?P<ADDON>.+?)\s+?</td>")
95 re_HTMLRebuy = re.compile("Rebuys\s+?</th>\s+?<td>(?P<REBUY>.+?)\s+?</td>")
96 re_HTMLTourNo = re.compile("Game ID</th>\s+?<td>(?P<TOURNO>[0-9]+)-1</td>")
97 re_HTMLPlayer = re.compile(u"""<tr>(<td align="center">)?\s+?(?P<RANK>\d+)</td>\s+?<td>(?P<PNAME>.+?)</td>\s+?<td>(?P<WINNINGS>.+?)</td>\s+?</tr>""")
98 #re_HTMLDetails = re.compile(u"""<p class="text">(?P<LABEL>.+?) : (?P<VALUE>.+?)</p>""")
99 re_HTMLPrizepool = re.compile(u"""(Freeroll|Total) Prizepool\s+?</th>\s+?<td>(?P<PRIZEPOOL>[0-9,.]+)\s+?</td>""")
100 re_HTMLStartTime = re.compile("Start Time\s+?</th>\s+?<td>(?P<STARTTIME>.+?)\s+?</td>")
101 re_HTMLDateTime = re.compile("\w+?\s+?(?P<D>\d+)\w+?\s+(?P<M>\w+)\s+(?P<Y>\d+),?\s+(?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+)")
103 re_Ticket = re.compile(u""" / Ticket (?P<VALUE>[0-9.]+)€""")
105 codepage = ["utf-8"]
107 @staticmethod
108 def getSplitRe(self, head):
109 re_SplitTourneys = re.compile("PokerStars Tournament ")
110 return re_SplitTourneys
112 def parseSummary(self):
113 # id type of file and call correct function
114 m = self.re_GameTypeHH.search(self.summaryText)
115 if m:
116 mg = m.groupdict()
117 if ' Tournament' == mg['TYPE']:
118 self.parseSummaryFromHH(mg)
119 elif not self.re_NotFound.search(self.summaryText):
120 self.parseSummaryFile()
121 else:
122 log.error(("The tournament was not found or is invalid"))
123 raise FpdbParseError
125 def parseSummaryFromHH(self, mg):
126 obj = getattr(MergeToFpdb, "Merge", None)
127 hhc = obj(self.config, in_path = self.in_path, sitename = None, autostart = False)
128 update = False
129 handsList = hhc.allHandsAsList()
130 handsDict = {}
131 Structures = MergeStructures.MergeStructures()
132 for handText in handsList:
133 m = self.re_HandInfoHH.search(handText)
134 if m is None:
135 tmp = self.summaryText[0:200]
136 log.error(("MergeSummary.readHandInfo: '%s'") % tmp)
137 continue
138 tourNo = re.split('-', m.group('TDATA'))[0]
139 hands = handsDict.get(tourNo)
140 if hands is None:
141 handsDict[tourNo] = [handText]
142 else:
143 hands.append(handText)
144 for tourNo, hands in list(handsDict.items()):
145 self.resetInfo()
146 self.db.resetBulkCache()
147 m = self.re_GameTypeHH.search(hands[0])
148 if m:
149 mg = m.groupdict()
151 if 'LIMIT' in mg:
152 self.gametype['limitType'] = self.limits[mg['LIMIT']]
153 if 'GAME' in mg:
154 if mg['GAME'] == "HORSE":
155 log.error(("MergeSummary.determineGameType: HORSE found, unsupported"))
156 raise FpdbParseError
157 #(self.info['base'], self.info['category']) = self.Multigametypes[m2.group('MULTIGAMETYPE')]
158 else:
159 self.gametype['category'] = self.games[mg['GAME']][1]
160 if 'SEATS' in mg and mg['SEATS'] is not None:
161 self.maxseats = int(mg['SEATS'])
163 for handText in hands:
164 m = self.re_HandInfoHH.search(handText)
165 if m is None:
166 tmp = self.summaryText[0:200]
167 log.error(("MergeSummary.readHandInfo: '%s'") % tmp)
168 continue
169 #raise FpdbParseError
170 #print 'DEBUG:', m.groupdict()
172 tourneyNameFull = m.group('TABLENAME').replace(' - ', ' - ').strip()
173 self.tourneyName = m.group('TABLENAME')[:40]
174 self.tourNo = tourNo
175 m1 = self.re_DateTimeHH.search(m.group('DATETIME'))
176 if m1:
177 mg = m1.groupdict()
178 datetimestr = "%s/%s/%s %s:%s:%s" % (mg['Y'], mg['M'],mg['D'],mg['H'],mg['MIN'],mg['S'])
179 #tz = a.group('TZ') # just assume ET??
180 self.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") # also timezone at end, e.g. " ET"
181 else:
182 self.startTime = datetime.datetime.strptime(m.group('DATETIME')[:14],'%Y%m%d%H%M%S')
183 self.startTime = HandHistoryConverter.changeTimezone(self.startTime, "ET", "UTC")
185 if self.re_turboHH.match(tourneyNameFull):
186 if self.maxseats==6:
187 tourneyNameFull += ' (6-max)'
189 structure = Structures.lookupSnG(tourneyNameFull, self.startTime)
190 if structure is None:
191 log.error(("MergeSummary.determineGameType: No match in SnG_Structures"))
192 continue
193 raise FpdbParseError
195 self.buyin = int(100*structure['buyIn'])
196 self.fee = int(100*structure['fee'])
197 if 'max' in structure:
198 self.entries = structure['max']
199 else:
200 self.entries = structure['seats']
201 self.buyinCurrency = structure['currency']
202 self.currency = structure['payoutCurrency']
203 self.maxseats = structure['seats']
204 if 'speed' in structure:
205 self.speed = structure['speed']
206 if 'doubleOrNothing' in structure:
207 self.isDoubleOrNothing = True
208 if 'bounty' in structure:
209 self.isKO = True
210 self.koBounty = int(100*structure['bounty'])
212 self.prizepool = sum(structure['payouts'])
213 payouts = len(structure['payouts'])
214 self.isSng = True
216 if structure['multi']:
217 log.error(("MergeSummary.determineGameType: Muli-table SnG found, unsupported"))
218 continue
220 players, out, won = {}, [], []
221 for m in hhc.re_PlayerOut.finditer(handText):
222 if m.group('PSEAT') != None:
223 out.append(m.group('PSEAT'))
224 if out:
225 for m in hhc.re_PlayerInfo.finditer(handText):
226 players[m.group('SEAT')] = m.group('PNAME')
227 if not players: continue
228 for m in hhc.re_CollectPot.finditer(handText):
229 won.append(m.group('PSEAT'))
231 if self.isDoubleOrNothing:
232 if handText==hands[-1]:
233 won = [w for w in list(players.keys()) if w not in out or w in won]
234 out = [p for p in list(players.keys())]
235 i = 0
236 for n in out:
237 winnings = 0
238 if n in won:
239 rank = 1
240 winnings = int(100*structure['payouts'][0])
241 else:
242 rank = len(players) - i
243 if rank <= payouts:
244 winnings = int(100*structure['payouts'][rank-1])
245 i += 1
246 self.addPlayer(rank, players[n], winnings, self.currency, None, None, None)
247 self.insertOrUpdate()
249 def resetInfo(self):
250 self.tourneyName = None
251 self.tourneyTypeId = None
252 self.tourneyId = None
253 self.startTime = None
254 self.endTime = None
255 self.tourNo = None
256 self.currency = None
257 self.buyinCurrency = None
258 self.buyin = 0
259 self.fee = 0
260 self.hero = None
261 self.maxseats = 0
262 self.entries = 0
263 self.speed = "Normal"
264 self.prizepool = 0 # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??}
265 self.buyInChips = 0
266 self.mixed = None
267 self.isRebuy = False
268 self.isAddOn = False
269 self.isKO = False
270 self.isMatrix = False
271 self.isShootout = False
272 self.isZoom = False
273 self.matrixMatchId = None # For Matrix tourneys : 1-4 => match tables (traditionnal), 0 => Positional winnings info
274 self.matrixIdProcessed = None
275 self.subTourneyBuyin = None
276 self.subTourneyFee = None
277 self.rebuyChips = None
278 self.addOnChips = None
279 self.rebuyCost = 0
280 self.addOnCost = 0
281 self.totalRebuyCount = None
282 self.totalAddOnCount = None
283 self.koBounty = 0
284 self.tourneyComment = None
285 self.players = {}
286 self.isSng = False
287 self.isSatellite = False
288 self.isDoubleOrNothing = False
289 self.guarantee = None
290 self.added = None
291 self.addedCurrency = None
292 self.gametype = {'category':None, 'limitType':None, 'mix':'none'}
293 self.comment = None
294 self.commentTs = None
296 # Collections indexed by player names
297 self.playerIds = {}
298 self.tourneysPlayersIds = {}
299 self.ranks = {}
300 self.winnings = {}
301 self.winningsCurrency = {}
302 self.rebuyCounts = {}
303 self.addOnCounts = {}
304 self.koCounts = {}
306 # currency symbol for this summary
307 self.sym = None
309 def parseSummaryFile(self):
310 self.buyinCurrency = "USD"
311 soup = BeautifulSoup(self.summaryText)
312 tables = soup.findAll('table')
313 if len(tables)>1:
314 table1 = BeautifulSoup(str(tables[0])).findAll('tr')
315 table2 = BeautifulSoup(str(tables[1])).findAll('tr')
316 # FIXME: Searching every line for all regexes is pretty horrible
317 # FIXME: Need to search for 'Status: Finished'
318 #print self.in_path
319 for p in table1:
320 m = self.re_HTMLGameType.search(str(p))
321 if m:
322 #print "DEBUG: re_HTMLGameType: '%s' '%s'" %(m.group('LIMIT'), m.group('GAME'))
323 if m.group('GAME').strip() in self.mixes:
324 self.gametype['category'] = self.mixes[m.group('GAME').strip()]
325 else:
326 self.gametype['category'] = self.games_html[m.group('GAME').strip()][1]
327 self.gametype['limitType'] = self.limits[m.group('LIMIT').strip()]
328 m = self.re_HTMLTourNo.search(str(p))
329 if m:
330 #print "DEBUG: re_HTMLTourNo: '%s'" % m.group('TOURNO')
331 self.tourNo = m.group('TOURNO').strip()
332 m = self.re_HTMLName.search(str(p))
333 if m:
334 #print "DEBUG: re_HTMLName: '%s'" % m.group('NAME')
335 self.tourneyName = m.group('NAME').strip()[:40]
336 if m.group('NAME').find("$")!=-1:
337 self.buyinCurrency="USD"
338 elif m.group('NAME').find(u"€")!=-1:
339 self.buyinCurrency="EUR"
340 m = self.re_HTMLPrizepool.search(str(p))
341 if m:
342 #print "DEBUG: re_HTMLPrizepool: '%s'" % m.group('PRIZEPOOL')
343 self.prizepool = int(self.convert_to_decimal(m.group('PRIZEPOOL').strip()))
344 m = self.re_HTMLBuyIn.search(str(p))
345 if m:
346 #print "DEBUG: re_HTMLBuyIn: '%s'" % m.group('BUYIN')
347 self.buyin = int(100*self.convert_to_decimal(m.group('BUYIN').strip()))
348 if self.buyin==0:
349 self.buyinCurrency="FREE"
350 m = self.re_HTMLFee.search(str(p))
351 if m:
352 #print "DEBUG: re_HTMLFee: '%s'" % m.group('FEE')
353 self.fee = int(100*self.convert_to_decimal(m.group('FEE').strip()))
354 m = self.re_HTMLBounty.search(str(p))
355 if m:
356 #print "DEBUG: re_HTMLBounty: '%s'" % m.group('KOBOUNTY')
357 if m.group('KOBOUNTY').strip() != '0.00':
358 self.isKO = True
359 self.koBounty = int(100*self.convert_to_decimal(m.group('KOBOUNTY').strip()))
360 m = self.re_HTMLAddons.search(str(p))
361 if m:
362 #print "DEBUG: re_HTMLAddons: '%s'" % m.group('ADDON')
363 if m.group('ADDON').strip() != '0':
364 self.isAddOn = True
365 self.addOnCost = self.buyin
366 m = self.re_HTMLRebuy.search(str(p))
367 if m:
368 #print "DEBUG: re_HTMLRebuy: '%s'" % m.group('REBUY')
369 if m.group('REBUY').strip() != '0':
370 self.isRebuy = True
371 self.rebuyCost = self.buyin
372 m = self.re_HTMLStartTime.search(str(p))
373 if m:
374 m2 = self.re_HTMLDateTime.search(m.group('STARTTIME'))
375 if m2:
376 month = self.months[m2.group('M')]
377 datetimestr = "%s/%s/%s %s:%s:%s" % (m2.group('Y'), month,m2.group('D'),m2.group('H'),m2.group('MIN'),m2.group('S'))
378 self.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S")
379 self.startTime = HandHistoryConverter.changeTimezone(self.startTime, "ET", "UTC")
381 self.currency = self.buyinCurrency
382 for p in table2:
383 m = self.re_HTMLPlayer.search(str(p))
384 if m:
385 self.entries += 1
386 #print "DEBUG: rank: %s pname: %s won: %s" %(m.group('RANK'), m.group('PNAME'), m.group('WINNINGS'))
387 winnings = 0
388 rebuyCount = None
389 addOnCount = None
390 koCount = None
392 rank = int(m.group('RANK'))
393 name = m.group('PNAME')
394 if m.group('WINNINGS') != None:
395 if m.group('WINNINGS').find("$")!=-1:
396 self.currency="USD"
397 elif m.group('WINNINGS').find(u"€")!=-1:
398 self.currency="EUR"
399 winnings = int(100*self.convert_to_decimal(m.group('WINNINGS')))
400 self.addPlayer(rank, name, winnings, self.currency, rebuyCount, addOnCount, koCount)
402 if self.gametype['category'] is None:
403 log.error(("MergeSummary.parseSummaryFile: Could not parse summary file"))
404 raise FpdbParseError
406 def convert_to_decimal(self, string):
407 dec = self.clearMoneyString(string)
408 dec = Decimal(dec)
409 return dec