Coverage for MergeSummary.py: 0%

289 statements  

« 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 -*- 

3 

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. 

17 

18 

19#import L10n 

20#_ = L10n.get_translation() 

21 

22from decimal_wrapper import Decimal 

23import datetime 

24from bs4 import BeautifulSoup 

25 

26from Exceptions import FpdbParseError 

27from HandHistoryConverter import * 

28import MergeToFpdb, MergeStructures 

29from TourneySummary import * 

30 

31 

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 } 

61 

62 mixes = { 

63 'HA' : 'ha', 

64 'RASH' : 'rash', 

65 'HO' : 'ho', 

66 'SHOE' : 'shoe', 

67 'HORSE' : 'horse', 

68 'HOSE' : 'hose', 

69 'HAR' : 'har' 

70 } 

71 

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} 

76 

77 

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') 

88 

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+)") 

102 

103 re_Ticket = re.compile(u""" / Ticket (?P<VALUE>[0-9.]+)&euro;""") 

104 

105 codepage = ["utf-8"] 

106 

107 @staticmethod 

108 def getSplitRe(self, head): 

109 re_SplitTourneys = re.compile("PokerStars Tournament ") 

110 return re_SplitTourneys 

111 

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 

124 

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() 

150 

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']) 

162 

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() 

171 

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") 

184 

185 if self.re_turboHH.match(tourneyNameFull): 

186 if self.maxseats==6: 

187 tourneyNameFull += ' (6-max)' 

188 

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 

194 

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']) 

211 

212 self.prizepool = sum(structure['payouts']) 

213 payouts = len(structure['payouts']) 

214 self.isSng = True 

215 

216 if structure['multi']: 

217 log.error(("MergeSummary.determineGameType: Muli-table SnG found, unsupported")) 

218 continue 

219 

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')) 

230 

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() 

248 

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 

295 

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 = {} 

305 

306 # currency symbol for this summary 

307 self.sym = None 

308 

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") 

380 

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 

391 

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) 

401 

402 if self.gametype['category'] is None: 

403 log.error(("MergeSummary.parseSummaryFile: Could not parse summary file")) 

404 raise FpdbParseError 

405 

406 def convert_to_decimal(self, string): 

407 dec = self.clearMoneyString(string) 

408 dec = Decimal(dec) 

409 return dec 

410