Coverage for MergeSummary.py: 0%

291 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-14 11:07 +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 import Decimal 

23import datetime 

24from bs4 import BeautifulSoup 

25 

26from HandHistoryConverter import HandHistoryConverter, FpdbParseError 

27import re 

28import logging 

29import MergeToFpdb, MergeStructures 

30from TourneySummary import TourneySummary 

31 

32# Merge HH Format 

33log = logging.getLogger("parser") 

34 

35 

36class MergeSummary(TourneySummary): 

37 limits = { 

38 "No Limit": "nl", 

39 "No Limit ": "nl", 

40 "Fixed Limit": "fl", 

41 "Limit": "fl", 

42 "Pot Limit": "pl", 

43 "Pot Limit ": "pl", 

44 "Half Pot Limit": "hp", 

45 } 

46 games = { # base, category 

47 "Holdem": ("hold", "holdem"), 

48 "Holdem Tournament": ("hold", "holdem"), 

49 "Omaha": ("hold", "omahahi"), 

50 "Omaha Tournament": ("hold", "omahahi"), 

51 "Omaha H/L8": ("hold", "omahahilo"), 

52 "2-7 Lowball": ("draw", "27_3draw"), 

53 "A-5 Lowball": ("draw", "a5_3draw"), 

54 "Badugi": ("draw", "badugi"), 

55 "5-Draw w/Joker": ("draw", "fivedraw"), 

56 "5-Draw": ("draw", "fivedraw"), 

57 "7-Stud": ("stud", "studhi"), 

58 "7-Stud H/L8": ("stud", "studhilo"), 

59 "5-Stud": ("stud", "5_studhi"), 

60 "Razz": ("stud", "razz"), 

61 } 

62 games_html = { 

63 "Texas Holdem": ("hold", "holdem"), 

64 "Omaha": ("hold", "omahahi"), 

65 "Omaha HiLo": ("hold", "omahahilo"), 

66 "2-7 Low Triple Draw": ("draw", "27_3draw"), 

67 "Badugi": ("draw", "badugi"), 

68 "Seven Card Stud": ("stud", "studhi"), 

69 "Seven Card Stud HiLo": ("stud", "studhilo"), 

70 "Five Card Stud": ("stud", "studhilo"), 

71 "Razz": ("stud", "razz"), 

72 } 

73 

74 mixes = {"HA": "ha", "RASH": "rash", "HO": "ho", "SHOE": "shoe", "HORSE": "horse", "HOSE": "hose", "HAR": "har"} 

75 

76 months = { 

77 "January": 1, 

78 "Jan": 1, 

79 "February": 2, 

80 "Feb": 2, 

81 "March": 3, 

82 "Mar": 3, 

83 "April": 4, 

84 "Apr": 4, 

85 "May": 5, 

86 "June": 6, 

87 "Jun": 6, 

88 "July": 7, 

89 "Jul": 7, 

90 "August": 8, 

91 "Aug": 8, 

92 "September": 9, 

93 "Sep": 9, 

94 "October": 10, 

95 "Oct": 10, 

96 "November": 11, 

97 "Nov": 11, 

98 "December": 12, 

99 "Dec": 12, 

100 } 

101 

102 substitutions = { 

103 "LEGAL_ISO": "USD|EUR|GBP|CAD|FPP", # legal ISO currency codes 

104 "LS": "\$|€|", # legal currency symbols 

105 } 

106 re_Identify = re.compile("<title>Online\sPoker\sTournament\sDetails\s\-\sCarbonPoker</title>") 

107 re_NotFound = re.compile("Tournament not found") 

108 re_GameTypeHH = re.compile( 

109 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?', 

110 re.MULTILINE, 

111 ) 

112 re_HandInfoHH = re.compile( 

113 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>[^|:]+)[|:]?.*>', 

114 re.MULTILINE, 

115 ) 

116 re_DateTimeHH = re.compile( 

117 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]+)", 

118 re.MULTILINE, 

119 ) 

120 re_turboHH = re.compile(r"Turbo\s\-\s") 

121 

122 re_HTMLName = re.compile("Name\s+?</th>\s+?<td>(?P<NAME>.+?)\s+?</td>") 

123 re_HTMLGameType = re.compile( 

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

125 ) 

126 re_HTMLBuyIn = re.compile("Buy In\s+?</th>\s+?<td>(?P<BUYIN>[0-9,.]+)\s+?</td>") 

127 re_HTMLFee = re.compile("Entry Fee\s+?</th>\s+?<td>(?P<FEE>[0-9,.]+)\s+?</td>") 

128 re_HTMLBounty = re.compile("Bounty\s+?</th>\s+?<td>(?P<KOBOUNTY>.+?)\s+?</td>") 

129 re_HTMLAddons = re.compile("Addons\s+?</th>\s+?<td>(?P<ADDON>.+?)\s+?</td>") 

130 re_HTMLRebuy = re.compile("Rebuys\s+?</th>\s+?<td>(?P<REBUY>.+?)\s+?</td>") 

131 re_HTMLTourNo = re.compile("Game ID</th>\s+?<td>(?P<TOURNO>[0-9]+)-1</td>") 

132 re_HTMLPlayer = re.compile( 

133 """<tr>(<td align="center">)?\s+?(?P<RANK>\d+)</td>\s+?<td>(?P<PNAME>.+?)</td>\s+?<td>(?P<WINNINGS>.+?)</td>\s+?</tr>""" 

134 ) 

135 # re_HTMLDetails = re.compile(u"""<p class="text">(?P<LABEL>.+?) : (?P<VALUE>.+?)</p>""") 

136 re_HTMLPrizepool = re.compile("""(Freeroll|Total) Prizepool\s+?</th>\s+?<td>(?P<PRIZEPOOL>[0-9,.]+)\s+?</td>""") 

137 re_HTMLStartTime = re.compile("Start Time\s+?</th>\s+?<td>(?P<STARTTIME>.+?)\s+?</td>") 

138 re_HTMLDateTime = re.compile( 

139 "\w+?\s+?(?P<D>\d+)\w+?\s+(?P<M>\w+)\s+(?P<Y>\d+),?\s+(?P<H>\d+):(?P<MIN>\d+):(?P<S>\d+)" 

140 ) 

141 

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

143 

144 codepage = ["utf-8"] 

145 

146 @staticmethod 

147 def getSplitRe(self, head): 

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

149 return re_SplitTourneys 

150 

151 def parseSummary(self): 

152 # id type of file and call correct function 

153 m = self.re_GameTypeHH.search(self.summaryText) 

154 if m: 

155 mg = m.groupdict() 

156 if " Tournament" == mg["TYPE"]: 

157 self.parseSummaryFromHH(mg) 

158 elif not self.re_NotFound.search(self.summaryText): 

159 self.parseSummaryFile() 

160 else: 

161 log.error(("The tournament was not found or is invalid")) 

162 raise FpdbParseError 

163 

164 def parseSummaryFromHH(self, mg): 

165 obj = getattr(MergeToFpdb, "Merge", None) 

166 hhc = obj(self.config, in_path=self.in_path, sitename=None, autostart=False) 

167 # update = False 

168 handsList = hhc.allHandsAsList() 

169 handsDict = {} 

170 Structures = MergeStructures.MergeStructures() 

171 for handText in handsList: 

172 m = self.re_HandInfoHH.search(handText) 

173 if m is None: 

174 tmp = self.summaryText[0:200] 

175 log.error(("MergeSummary.readHandInfo: '%s'") % tmp) 

176 continue 

177 tourNo = re.split("-", m.group("TDATA"))[0] 

178 hands = handsDict.get(tourNo) 

179 if hands is None: 

180 handsDict[tourNo] = [handText] 

181 else: 

182 hands.append(handText) 

183 for tourNo, hands in list(handsDict.items()): 

184 self.resetInfo() 

185 self.db.resetBulkCache() 

186 m = self.re_GameTypeHH.search(hands[0]) 

187 if m: 

188 mg = m.groupdict() 

189 

190 if "LIMIT" in mg: 

191 self.gametype["limitType"] = self.limits[mg["LIMIT"]] 

192 if "GAME" in mg: 

193 if mg["GAME"] == "HORSE": 

194 log.error(("MergeSummary.determineGameType: HORSE found, unsupported")) 

195 raise FpdbParseError 

196 # (self.info['base'], self.info['category']) = self.Multigametypes[m2.group('MULTIGAMETYPE')] 

197 else: 

198 self.gametype["category"] = self.games[mg["GAME"]][1] 

199 if "SEATS" in mg and mg["SEATS"] is not None: 

200 self.maxseats = int(mg["SEATS"]) 

201 

202 for handText in hands: 

203 m = self.re_HandInfoHH.search(handText) 

204 if m is None: 

205 tmp = self.summaryText[0:200] 

206 log.error(("MergeSummary.readHandInfo: '%s'") % tmp) 

207 continue 

208 # raise FpdbParseError 

209 # print 'DEBUG:', m.groupdict() 

210 

211 tourneyNameFull = m.group("TABLENAME").replace(" - ", " - ").strip() 

212 self.tourneyName = m.group("TABLENAME")[:40] 

213 self.tourNo = tourNo 

214 m1 = self.re_DateTimeHH.search(m.group("DATETIME")) 

215 if m1: 

216 mg = m1.groupdict() 

217 datetimestr = "%s/%s/%s %s:%s:%s" % (mg["Y"], mg["M"], mg["D"], mg["H"], mg["MIN"], mg["S"]) 

218 # tz = a.group('TZ') # just assume ET?? 

219 self.startTime = datetime.datetime.strptime( 

220 datetimestr, "%Y/%m/%d %H:%M:%S" 

221 ) # also timezone at end, e.g. " ET" 

222 else: 

223 self.startTime = datetime.datetime.strptime(m.group("DATETIME")[:14], "%Y%m%d%H%M%S") 

224 self.startTime = HandHistoryConverter.changeTimezone(self.startTime, "ET", "UTC") 

225 

226 if self.re_turboHH.match(tourneyNameFull): 

227 if self.maxseats == 6: 

228 tourneyNameFull += " (6-max)" 

229 

230 structure = Structures.lookupSnG(tourneyNameFull, self.startTime) 

231 if structure is None: 

232 log.error(("MergeSummary.determineGameType: No match in SnG_Structures")) 

233 continue 

234 raise FpdbParseError 

235 

236 self.buyin = int(100 * structure["buyIn"]) 

237 self.fee = int(100 * structure["fee"]) 

238 if "max" in structure: 

239 self.entries = structure["max"] 

240 else: 

241 self.entries = structure["seats"] 

242 self.buyinCurrency = structure["currency"] 

243 self.currency = structure["payoutCurrency"] 

244 self.maxseats = structure["seats"] 

245 if "speed" in structure: 

246 self.speed = structure["speed"] 

247 if "doubleOrNothing" in structure: 

248 self.isDoubleOrNothing = True 

249 if "bounty" in structure: 

250 self.isKO = True 

251 self.koBounty = int(100 * structure["bounty"]) 

252 

253 self.prizepool = sum(structure["payouts"]) 

254 payouts = len(structure["payouts"]) 

255 self.isSng = True 

256 

257 if structure["multi"]: 

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

259 continue 

260 

261 players, out, won = {}, [], [] 

262 for m in hhc.re_PlayerOut.finditer(handText): 

263 if m.group("PSEAT") is not None: 

264 out.append(m.group("PSEAT")) 

265 if out: 

266 for m in hhc.re_PlayerInfo.finditer(handText): 

267 players[m.group("SEAT")] = m.group("PNAME") 

268 if not players: 

269 continue 

270 for m in hhc.re_CollectPot.finditer(handText): 

271 won.append(m.group("PSEAT")) 

272 

273 if self.isDoubleOrNothing: 

274 if handText == hands[-1]: 

275 won = [w for w in list(players.keys()) if w not in out or w in won] 

276 out = [p for p in list(players.keys())] 

277 i = 0 

278 for n in out: 

279 winnings = 0 

280 if n in won: 

281 rank = 1 

282 winnings = int(100 * structure["payouts"][0]) 

283 else: 

284 rank = len(players) - i 

285 if rank <= payouts: 

286 winnings = int(100 * structure["payouts"][rank - 1]) 

287 i += 1 

288 self.addPlayer(rank, players[n], winnings, self.currency, None, None, None) 

289 self.insertOrUpdate() 

290 

291 def resetInfo(self): 

292 self.tourneyName = None 

293 self.tourneyTypeId = None 

294 self.tourneyId = None 

295 self.startTime = None 

296 self.endTime = None 

297 self.tourNo = None 

298 self.currency = None 

299 self.buyinCurrency = None 

300 self.buyin = 0 

301 self.fee = 0 

302 self.hero = None 

303 self.maxseats = 0 

304 self.entries = 0 

305 self.speed = "Normal" 

306 self.prizepool = 0 # Make it a dict in order to deal (eventually later) with non-money winnings : {'MONEY' : amount, 'OTHER' : Value ??} 

307 self.buyInChips = 0 

308 self.mixed = None 

309 self.isRebuy = False 

310 self.isAddOn = False 

311 self.isKO = False 

312 self.isMatrix = False 

313 self.isShootout = False 

314 self.isZoom = False 

315 self.matrixMatchId = ( 

316 None # For Matrix tourneys : 1-4 => match tables (traditionnal), 0 => Positional winnings info 

317 ) 

318 self.matrixIdProcessed = None 

319 self.subTourneyBuyin = None 

320 self.subTourneyFee = None 

321 self.rebuyChips = None 

322 self.addOnChips = None 

323 self.rebuyCost = 0 

324 self.addOnCost = 0 

325 self.totalRebuyCount = None 

326 self.totalAddOnCount = None 

327 self.koBounty = 0 

328 self.tourneyComment = None 

329 self.players = {} 

330 self.isSng = False 

331 self.isSatellite = False 

332 self.isDoubleOrNothing = False 

333 self.guarantee = None 

334 self.added = None 

335 self.addedCurrency = None 

336 self.gametype = {"category": None, "limitType": None, "mix": "none"} 

337 self.comment = None 

338 self.commentTs = None 

339 

340 # Collections indexed by player names 

341 self.playerIds = {} 

342 self.tourneysPlayersIds = {} 

343 self.ranks = {} 

344 self.winnings = {} 

345 self.winningsCurrency = {} 

346 self.rebuyCounts = {} 

347 self.addOnCounts = {} 

348 self.koCounts = {} 

349 

350 # currency symbol for this summary 

351 self.sym = None 

352 

353 def parseSummaryFile(self): 

354 self.buyinCurrency = "USD" 

355 soup = BeautifulSoup(self.summaryText) 

356 tables = soup.findAll("table") 

357 if len(tables) > 1: 

358 table1 = BeautifulSoup(str(tables[0])).findAll("tr") 

359 table2 = BeautifulSoup(str(tables[1])).findAll("tr") 

360 # FIXME: Searching every line for all regexes is pretty horrible 

361 # FIXME: Need to search for 'Status: Finished' 

362 # print self.in_path 

363 for p in table1: 

364 m = self.re_HTMLGameType.search(str(p)) 

365 if m: 

366 # print "DEBUG: re_HTMLGameType: '%s' '%s'" %(m.group('LIMIT'), m.group('GAME')) 

367 if m.group("GAME").strip() in self.mixes: 

368 self.gametype["category"] = self.mixes[m.group("GAME").strip()] 

369 else: 

370 self.gametype["category"] = self.games_html[m.group("GAME").strip()][1] 

371 self.gametype["limitType"] = self.limits[m.group("LIMIT").strip()] 

372 m = self.re_HTMLTourNo.search(str(p)) 

373 if m: 

374 # print "DEBUG: re_HTMLTourNo: '%s'" % m.group('TOURNO') 

375 self.tourNo = m.group("TOURNO").strip() 

376 m = self.re_HTMLName.search(str(p)) 

377 if m: 

378 # print "DEBUG: re_HTMLName: '%s'" % m.group('NAME') 

379 self.tourneyName = m.group("NAME").strip()[:40] 

380 if m.group("NAME").find("$") != -1: 

381 self.buyinCurrency = "USD" 

382 elif m.group("NAME").find("€") != -1: 

383 self.buyinCurrency = "EUR" 

384 m = self.re_HTMLPrizepool.search(str(p)) 

385 if m: 

386 # print "DEBUG: re_HTMLPrizepool: '%s'" % m.group('PRIZEPOOL') 

387 self.prizepool = int(self.convert_to_decimal(m.group("PRIZEPOOL").strip())) 

388 m = self.re_HTMLBuyIn.search(str(p)) 

389 if m: 

390 # print "DEBUG: re_HTMLBuyIn: '%s'" % m.group('BUYIN') 

391 self.buyin = int(100 * self.convert_to_decimal(m.group("BUYIN").strip())) 

392 if self.buyin == 0: 

393 self.buyinCurrency = "FREE" 

394 m = self.re_HTMLFee.search(str(p)) 

395 if m: 

396 # print "DEBUG: re_HTMLFee: '%s'" % m.group('FEE') 

397 self.fee = int(100 * self.convert_to_decimal(m.group("FEE").strip())) 

398 m = self.re_HTMLBounty.search(str(p)) 

399 if m: 

400 # print "DEBUG: re_HTMLBounty: '%s'" % m.group('KOBOUNTY') 

401 if m.group("KOBOUNTY").strip() != "0.00": 

402 self.isKO = True 

403 self.koBounty = int(100 * self.convert_to_decimal(m.group("KOBOUNTY").strip())) 

404 m = self.re_HTMLAddons.search(str(p)) 

405 if m: 

406 # print "DEBUG: re_HTMLAddons: '%s'" % m.group('ADDON') 

407 if m.group("ADDON").strip() != "0": 

408 self.isAddOn = True 

409 self.addOnCost = self.buyin 

410 m = self.re_HTMLRebuy.search(str(p)) 

411 if m: 

412 # print "DEBUG: re_HTMLRebuy: '%s'" % m.group('REBUY') 

413 if m.group("REBUY").strip() != "0": 

414 self.isRebuy = True 

415 self.rebuyCost = self.buyin 

416 m = self.re_HTMLStartTime.search(str(p)) 

417 if m: 

418 m2 = self.re_HTMLDateTime.search(m.group("STARTTIME")) 

419 if m2: 

420 month = self.months[m2.group("M")] 

421 datetimestr = "%s/%s/%s %s:%s:%s" % ( 

422 m2.group("Y"), 

423 month, 

424 m2.group("D"), 

425 m2.group("H"), 

426 m2.group("MIN"), 

427 m2.group("S"), 

428 ) 

429 self.startTime = datetime.datetime.strptime(datetimestr, "%Y/%m/%d %H:%M:%S") 

430 self.startTime = HandHistoryConverter.changeTimezone(self.startTime, "ET", "UTC") 

431 

432 self.currency = self.buyinCurrency 

433 for p in table2: 

434 m = self.re_HTMLPlayer.search(str(p)) 

435 if m: 

436 self.entries += 1 

437 # print "DEBUG: rank: %s pname: %s won: %s" %(m.group('RANK'), m.group('PNAME'), m.group('WINNINGS')) 

438 winnings = 0 

439 rebuyCount = None 

440 addOnCount = None 

441 koCount = None 

442 

443 rank = int(m.group("RANK")) 

444 name = m.group("PNAME") 

445 if m.group("WINNINGS") is not None: 

446 if m.group("WINNINGS").find("$") != -1: 

447 self.currency = "USD" 

448 elif m.group("WINNINGS").find("€") != -1: 

449 self.currency = "EUR" 

450 winnings = int(100 * self.convert_to_decimal(m.group("WINNINGS"))) 

451 self.addPlayer(rank, name, winnings, self.currency, rebuyCount, addOnCount, koCount) 

452 

453 if self.gametype["category"] is None: 

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

455 raise FpdbParseError 

456 

457 def convert_to_decimal(self, string): 

458 dec = self.clearMoneyString(string) 

459 dec = Decimal(dec) 

460 return dec