Coverage for UnibetToFpdb.py: 0%

335 statements  

« prev     ^ index     » next       coverage.py v7.6.3, created at 2024-10-15 19:33 +0000

1#!/usr/bin/env python 

2# -*- coding: utf-8 -*- 

3# 

4# Copyright 2008-2011, Carl Gherardi 

5# 

6# This program is free software; you can redistribute it and/or modify 

7# it under the terms of the GNU General Public License as published by 

8# the Free Software Foundation; either version 2 of the License, or 

9# (at your option) any later version. 

10# 

11# This program is distributed in the hope that it will be useful, 

12# but WITHOUT ANY WARRANTY; without even the implied warranty of 

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14# GNU General Public License for more details. 

15# 

16# You should have received a copy of the GNU General Public License 

17# along with this program; if not, write to the Free Software 

18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

19######################################################################## 

20 

21# import L10n 

22# _ = L10n.get_translation() 

23 

24# TODO: straighten out discards for draw games 

25 

26from HandHistoryConverter import HandHistoryConverter, FpdbParseError, FpdbHandPartial 

27from decimal import Decimal 

28import re 

29import logging 

30import datetime 

31 

32# Unibet HH Format 

33log = logging.getLogger("parser") 

34 

35 

36class Unibet(HandHistoryConverter): 

37 # Class Variables 

38 

39 sitename = "Unibet" 

40 filetype = "text" 

41 codepage = ("utf8", "cp1252", "ISO-8859-1") 

42 siteId = 30 # Needs to match id entry in Sites database 

43 sym = { 

44 "USD": "\$", 

45 "CAD": "\$", 

46 "T$": "", 

47 "EUR": "\xe2\x82\xac", 

48 "GBP": "\£", 

49 "play": "", 

50 "INR": "\₹", 

51 "CNY": "\¥", 

52 } # ADD Euro, Sterling, etc HERE 

53 substitutions = { 

54 "LEGAL_ISO": "USD|EUR|GBP|CAD|FPP|SC|INR|CNY", # legal ISO currency codes 

55 "LS": "\$|\xe2\x82\xac|\u20ac|\£|\u20b9|\¥|", # legal currency symbols - Euro(cp1252, utf-8) 

56 "PLYR": r"\s?(?P<PNAME>.+?)", 

57 "CUR": "(\$|\xe2\x82\xac|\u20ac||\£|\u20b9|\¥|)", 

58 "BRKTS": r"(\(button\) |\(small blind\) |\(big blind\) |\(button blind\) |\(button\) \(small blind\) |\(small blind/button\) |\(button\) \(big blind\) )?", 

59 } 

60 

61 # translations from captured groups to fpdb info strings 

62 Lim_Blinds = { 

63 "0.04": ("0.01", "0.02"), 

64 "0.08": ("0.02", "0.04"), 

65 "0.10": ("0.02", "0.05"), 

66 "0.20": ("0.05", "0.10"), 

67 "0.40": ("0.10", "0.20"), 

68 "0.50": ("0.10", "0.25"), 

69 "1.00": ("0.25", "0.50"), 

70 "1": ("0.25", "0.50"), 

71 "2.00": ("0.50", "1.00"), 

72 "2": ("0.50", "1.00"), 

73 "4.00": ("1.00", "2.00"), 

74 "4": ("1.00", "2.00"), 

75 "6.00": ("1.00", "3.00"), 

76 "6": ("1.00", "3.00"), 

77 "8.00": ("2.00", "4.00"), 

78 "8": ("2.00", "4.00"), 

79 "10.00": ("2.00", "5.00"), 

80 "10": ("2.00", "5.00"), 

81 "16.00": ("4.00", "8.00"), 

82 "16": ("4.00", "8.00"), 

83 "20.00": ("5.00", "10.00"), 

84 "20": ("5.00", "10.00"), 

85 "30.00": ("10.00", "15.00"), 

86 "30": ("10.00", "15.00"), 

87 "40.00": ("10.00", "20.00"), 

88 "40": ("10.00", "20.00"), 

89 "50.00": ("10.00", "25.00"), 

90 "50": ("10.00", "25.00"), 

91 "60.00": ("15.00", "30.00"), 

92 "60": ("15.00", "30.00"), 

93 "80.00": ("20.00", "40.00"), 

94 "80": ("20.00", "40.00"), 

95 "100.00": ("25.00", "50.00"), 

96 "100": ("25.00", "50.00"), 

97 "150.00": ("50.00", "75.00"), 

98 "150": ("50.00", "75.00"), 

99 "200.00": ("50.00", "100.00"), 

100 "200": ("50.00", "100.00"), 

101 "400.00": ("100.00", "200.00"), 

102 "400": ("100.00", "200.00"), 

103 "500.00": ("100.00", "250.00"), 

104 "500": ("100.00", "250.00"), 

105 "600.00": ("150.00", "300.00"), 

106 "600": ("150.00", "300.00"), 

107 "800.00": ("200.00", "400.00"), 

108 "800": ("200.00", "400.00"), 

109 "1000.00": ("250.00", "500.00"), 

110 "1000": ("250.00", "500.00"), 

111 "2000.00": ("500.00", "1000.00"), 

112 "2000": ("500.00", "1000.00"), 

113 "4000.00": ("1000.00", "2000.00"), 

114 "4000": ("1000.00", "2000.00"), 

115 "10000.00": ("2500.00", "5000.00"), 

116 "10000": ("2500.00", "5000.00"), 

117 "20000.00": ("5000.00", "10000.00"), 

118 "20000": ("5000.00", "10000.00"), 

119 "40000.00": ("10000.00", "20000.00"), 

120 "40000": ("10000.00", "20000.00"), 

121 } 

122 

123 limits = {"No Limit": "nl", "Pot Limit": "pl", "Fixed Limit": "fl", "Limit": "fl"} 

124 games = { # base, category 

125 "Hold'em": ("hold", "holdem"), 

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

127 "Omaha Hi/Lo": ("hold", "omahahilo"), 

128 } 

129 currencies = {"€": "EUR", "$": "USD", "": "T$", "£": "GBP", "¥": "CNY", "₹": "INR"} 

130 

131 # Static regexes 

132 re_GameInfo = re.compile( 

133 """ 

134 Game\s\#(?P<HID>[0-9]+):\s+Table\s(?P<CURRENCY>€|$|£)[0-9]+\s(?P<LIMIT>PL|NL|FL)\s-\s(?P<SB>[.0-9]+)/(?P<BB>[.0-9]+)\s-\s(?P<GAME>Pot\sLimit\sOmaha|No\sLimit\sHold\'Em\sBanzai)\s-\s(?P<DATETIME>.*$) 

135 """ 

136 % substitutions, 

137 re.MULTILINE | re.VERBOSE, 

138 ) 

139 

140 re_PlayerInfo = re.compile( 

141 """ 

142 Seat\s(?P<SEAT>[0-9]+):\s(?P<PNAME>\w+)\s\((€|$|£)(?P<CASH>[,.0-9]+)\)""" 

143 % substitutions, 

144 re.MULTILINE | re.VERBOSE, 

145 ) 

146 

147 re_PlayerInfo2 = re.compile( 

148 """ 

149 (?P<SITOUT>\w+)\s\((€|$|£)[,.0-9]+\)\s\(sitting\sout\)""" 

150 % substitutions, 

151 re.MULTILINE | re.VERBOSE, 

152 ) 

153 

154 re_HandInfo = re.compile( 

155 """ 

156 (?P<TABLE>\sTable\s(€|$|£)[0-9]+\s(PL|NL|FL))""", 

157 re.MULTILINE | re.VERBOSE, 

158 ) 

159 

160 re_Identify = re.compile("Game\s\#\d+:\sTable\s(€|$|£)[0-9]+\s(PL|NL|FL)") 

161 re_SplitHands = re.compile("(?:\s?\n){2,}") 

162 re_TailSplitHands = re.compile("(\n\n\n+)") 

163 re_Button = re.compile("(?P<BUTTON>\w+)\shas\sthe\sbutton", re.MULTILINE) 

164 re_Board = re.compile(r"\[(?P<CARDS>.+)\]") 

165 re_Board2 = re.compile(r"\[(?P<C1>\S\S)\] \[(\S\S)?(?P<C2>\S\S) (?P<C3>\S\S)\]") 

166 re_DateTime1 = re.compile( 

167 """(?P<H>[0-9]+):(?P<MIN>[0-9]+):(?P<S>[0-9]+)\s(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})""", 

168 re.MULTILINE, 

169 ) 

170 re_DateTime2 = re.compile( 

171 """(?P<Y>[0-9]{4})\/(?P<M>[0-9]{2})\/(?P<D>[0-9]{2})[\- ]+(?P<H>[0-9]+):(?P<MIN>[0-9]+)""", re.MULTILINE 

172 ) 

173 # revised re including timezone (not currently used): 

174 # re_DateTime = re.compile("""(?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]+) \(?(?P<TZ>[A-Z0-9]+)""", re.MULTILINE) 

175 

176 # These used to be compiled per player, but regression tests say 

177 # we don't have to, and it makes life faster. 

178 re_PostSB = re.compile(r"%(PLYR)s:\sposts\ssmall\sblind\s%(CUR)s(?P<SB>[,.0-9]+)" % substitutions, re.MULTILINE) 

179 re_PostBB = re.compile(r"%(PLYR)s:\sposts\sbig\sblind\s%(CUR)s(?P<BB>[,.0-9]+)" % substitutions, re.MULTILINE) 

180 re_PostBUB = re.compile(r"%(PLYR)s:\sposts\sbutton\sblind\s%(CUR)s(?P<BUB>[,.0-9]+)" % substitutions, re.MULTILINE) 

181 re_Antes = re.compile(r"%(PLYR)s:\sposts\sthe\sant\s%(CUR)s(?P<ANTE>[,.0-9]+)" % substitutions, re.MULTILINE) 

182 re_BringIn = re.compile( 

183 r"%(PLYR)s:\sbrings[- ]in(\slow|)\sfo/%(CUR)s(?P<BRINGIN>[,.0-9]+)" % substitutions, re.MULTILINE 

184 ) 

185 re_PostBoth = re.compile( 

186 r"%(PLYR)s:\sposts\ssmall\s\&\sbig\sblinds\s%(CUR)s(?P<SBBB>[,.0-9]+)" % substitutions, re.MULTILINE 

187 ) 

188 re_PostStraddle = re.compile( 

189 r"%(PLYR)s:\sposts\sstraddle\s%(CUR)s(?P<STRADDLE>[,.0-9]+)" % substitutions, re.MULTILINE 

190 ) 

191 re_Action = re.compile( 

192 r""" 

193 %(PLYR)s:(?P<ATYPE>\sbets|\schecks|\sraises|\scalls|\sfolds|\sdiscards|\sstands\spat) 

194 (\s%(CUR)s(?P<BET>[,.\d]+))?(\sto\s%(CUR)s(?P<BETTO>[,.\d]+))?  

195 \s*(and\sis\sall.in)? 

196 (and\shas\sreached\sthe\s[%(CUR)s\d\.,]+\scap)? 

197 (\son|\scards?)? 

198 (\s\(disconnect\))? 

199 (\s\[(?P<CARDS>.+?)\])?\s*$""" 

200 % substitutions, 

201 re.MULTILINE | re.VERBOSE, 

202 ) 

203 re_ShowdownAction = re.compile(r"%s: shows \[(?P<CARDS>.*)\]" % substitutions["PLYR"], re.MULTILINE) 

204 re_sitsOut = re.compile("^%s sits out" % substitutions["PLYR"], re.MULTILINE) 

205 re_HeroCards = re.compile( 

206 r"Dealt\sto\s%(PLYR)s\s(?:\[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % substitutions, re.MULTILINE 

207 ) 

208 # re_ShownCards = re.compile("^Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(?P<SHOWED>showed|mucked) \[(?P<CARDS>.*)\]( and (lost|(won|collected) \(%(CUR)s(?P<POT>[.\d]+)\)) with (?P<STRING>.+?)(,\sand\s(won\s\(%(CUR)s[.\d]+\)|lost)\swith\s(?P<STRING2>.*))?)?$" % substitutions, re.MULTILINE) 

209 # re_CollectPot = re.compile(r"Seat (?P<SEAT>[0-9]+): %(PLYR)s %(BRKTS)s(collected|showed \[.*\] and (won|collected)) \(?%(CUR)s(?P<POT>[,.\d]+)\)?(, mucked| with.*|)" % substitutions, re.MULTILINE) 

210 re_CollectPot = re.compile( 

211 r"Seat (?P<SEAT>[0-9]+):\s%(PLYR)s:\sbet\s(€|$|£)(?P<BET>[,.\d]+)\sand\swon\s(€|$|£)[\.0-9]+\W\snet\sresult:\s(€|$|£)(?P<POT>[,.\d]+)" 

212 % substitutions, 

213 re.MULTILINE, 

214 ) 

215 # Vinsand88 cashed out the hand for $2.19 | Cash Out Fee $0.02 

216 re_CollectPot2 = re.compile( 

217 "%(PLYR)s (collected|cashed out the hand for) %(CUR)s(?P<POT>[,.\d]+)" % substitutions, re.MULTILINE 

218 ) 

219 re_CashedOut = re.compile(r"cashed\sout\sthe\shand") 

220 re_WinningRankOne = re.compile( 

221 "%(PLYR)s wins the tournament and receives %(CUR)s(?P<AMT>[,\.0-9]+) - congratulations!$" % substitutions, 

222 re.MULTILINE, 

223 ) 

224 re_WinningRankOther = re.compile( 

225 "%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place and received %(CUR)s(?P<AMT>[,.0-9]+)\.$" 

226 % substitutions, 

227 re.MULTILINE, 

228 ) 

229 re_RankOther = re.compile( 

230 "%(PLYR)s finished the tournament in (?P<RANK>[0-9]+)(st|nd|rd|th) place$" % substitutions, re.MULTILINE 

231 ) 

232 re_Cancelled = re.compile("Hand\scancelled", re.MULTILINE) 

233 re_Uncalled = re.compile("Uncalled\sbet\s\(%(CUR)s(?P<BET>[,.\d]+)\)\sreturned\sto" % substitutions, re.MULTILINE) 

234 # APTEM-89 wins the $0.27 bounty for eliminating Hero 

235 # ChazDazzle wins the 22000 bounty for eliminating berkovich609 

236 # JKuzja, vecenta split the $50 bounty for eliminating ODYSSES 

237 re_Bounty = re.compile( 

238 "%(PLYR)s (?P<SPLIT>split|wins) the %(CUR)s(?P<AMT>[,\.0-9]+) bounty for eliminating (?P<ELIMINATED>.+?)$" 

239 % substitutions, 

240 re.MULTILINE, 

241 ) 

242 # Amsterdam71 wins $19.90 for eliminating MuKoJla and their own bounty increases by $19.89 to $155.32 

243 # Amsterdam71 wins $4.60 for splitting the elimination of Frimble11 and their own bounty increases by $4.59 to $41.32 

244 # Amsterdam71 wins the tournament and receives $230.36 - congratulations! 

245 re_Progressive = re.compile( 

246 """ 

247 %(PLYR)s\swins\s%(CUR)s(?P<AMT>[,\.0-9]+)\s 

248 for\s(splitting\sthe\selimination\sof|eliminating)\s(?P<ELIMINATED>.+?)\s 

249 and\stheir\sown\sbounty\sincreases\sby\s%(CUR)s(?P<INCREASE>[\.0-9]+)\sto\s%(CUR)s(?P<ENDAMT>[\.0-9]+)$""" 

250 % substitutions, 

251 re.MULTILINE | re.VERBOSE, 

252 ) 

253 re_Rake = re.compile( 

254 """ 

255 Total\spot\s%(CUR)s(?P<POT>[,\.0-9]+)(.+?)?\s\|\sRake\s%(CUR)s(?P<RAKE>[,\.0-9]+)""" 

256 % substitutions, 

257 re.MULTILINE | re.VERBOSE, 

258 ) 

259 

260 re_STP = re.compile( 

261 """ 

262 STP\sadded:\s%(CUR)s(?P<AMOUNT>[,\.0-9]+)""" 

263 % substitutions, 

264 re.MULTILINE | re.VERBOSE, 

265 ) 

266 

267 def compilePlayerRegexs(self, hand): 

268 players = set([player[1] for player in hand.players]) 

269 if not players <= self.compiledPlayers: # x <= y means 'x is subset of y' 

270 self.compiledPlayers = players 

271 player_re = "(?P<PNAME>" + "|".join(map(re.escape, players)) + ")" 

272 subst = { 

273 "PLYR": player_re, 

274 "BRKTS": r"(\(button\) |\(small\sblind\) |\(big\blind\) |\(button\) \(small\sblind\) |\(button\) \(big\sblind\) )?", 

275 "CUR": "(\$|\xe2\x82\xac|\u20ac||\£|)", 

276 } 

277 

278 self.re_HeroCards = re.compile( 

279 r"Dealt\sto\s%(PLYR)s(?: \[(?P<OLDCARDS>.+?)\])?( \[(?P<NEWCARDS>.+?)\])" % subst, re.MULTILINE 

280 ) 

281 self.re_ShownCards = re.compile( 

282 "Seat\s(?P<SEAT>[0-9]+):\s%(PLYR)s\s%(BRKTS)s(?P<SHOWED>showed|mucked)\s\[(?P<CARDS>.*)\](\sand\s(lost|(won|collected)\s \(%(CUR)s(?P<POT>[,\.\d]+)\))\swith\s(?P<STRING>.+?)(,\sand\s(won\s\(%(CUR)s[\.\d]+\)|lost)\swith\s(?P<STRING2>.*))?)?$" 

283 % subst, 

284 re.MULTILINE, 

285 ) 

286 

287 def readSupportedGames(self): 

288 return [ 

289 ["ring", "hold", "nl"], 

290 ["ring", "hold", "pl"], 

291 ["ring", "hold", "fl"], 

292 ["ring", "hold", "pn"], 

293 ["ring", "stud", "fl"], 

294 ["ring", "draw", "fl"], 

295 ["ring", "draw", "pl"], 

296 ["ring", "draw", "nl"], 

297 ["tour", "hold", "nl"], 

298 ["tour", "hold", "pl"], 

299 ["tour", "hold", "fl"], 

300 ["tour", "hold", "pn"], 

301 ["tour", "stud", "fl"], 

302 ["tour", "draw", "fl"], 

303 ["tour", "draw", "pl"], 

304 ["tour", "draw", "nl"], 

305 ] 

306 

307 def determineGameType(self, handText): 

308 info = {} 

309 m = self.re_GameInfo.search(handText) 

310 if not m: 

311 tmp = handText[0:200] 

312 log.error(("UnibetToFpdb.determineGameType: '%s'") % tmp) 

313 raise FpdbParseError 

314 

315 mg = m.groupdict() 

316 if "LIMIT" in mg: 

317 # print(mg['LIMIT']) 

318 if mg["LIMIT"] == "NL": 

319 info["limitType"] = self.limits["No Limit"] 

320 elif mg["LIMIT"] == "PL": 

321 info["limitType"] = self.limits["Pot Limit"] 

322 

323 # info['limitType'] = self.limits[mg['LIMIT']] 

324 if "GAME" in mg: 

325 print(mg["GAME"]) 

326 if mg["GAME"] == "No Limit Hold'Em Banzai": 

327 info["base"] = "hold" 

328 info["category"] = "holdem" 

329 info["type"] = "ring" 

330 info["split"] = False 

331 elif mg["GAME"] == "Pot Limit Omaha": 

332 info["base"] = "hold" 

333 info["category"] = "omahahi" 

334 info["type"] = "ring" 

335 info["split"] = False 

336 # (info['base'], info['category']) = self.games[mg['GAME']] 

337 if "SB" in mg and mg["SB"] is not None: 

338 info["sb"] = mg["SB"] 

339 if "BB" in mg and mg["BB"] is not None: 

340 info["bb"] = mg["BB"] 

341 if "BUB" in mg and mg["BUB"] is not None: 

342 info["sb"] = "0" 

343 info["bb"] = mg["BUB"] 

344 if "CURRENCY1" in mg and mg["CURRENCY1"] is not None: 

345 info["currency"] = self.currencies[mg["CURRENCY1"]] 

346 elif "CURRENCY" in mg: 

347 info["currency"] = self.currencies[mg["CURRENCY"]] 

348 

349 # if 'Zoom' in mg['TITLE'] or 'Rush' in mg['TITLE']: 

350 # info['fast'] = True 

351 # else: 

352 # info['fast'] = False 

353 # if 'Home' in mg['TITLE']: 

354 # info['homeGame'] = True 

355 # else: 

356 # info['homeGame'] = False 

357 # if 'CAP' in mg and mg['CAP'] is not None: 

358 # info['buyinType'] = 'cap' 

359 # else: 

360 # info['buyinType'] = 'regular' 

361 # if 'SPLIT' in mg and mg['SPLIT'] == 'Split': 

362 # info['split'] = True 

363 # else: 

364 # info['split'] = False 

365 # if 'SITE' in mg: 

366 # if mg['SITE'] == 'PokerMaster': 

367 # self.sitename = "PokerMaster" 

368 # self.siteId = 25 

369 # m1 = self.re_HandInfo.search(handText,re.DOTALL) 

370 # if m1 and '_5Cards_' in m1.group('TABLE'): 

371 # info['category'] = '5_omahahi' 

372 # elif mg['SITE'] == 'Run It Once Poker': 

373 # self.sitename = "Run It Once Poker" 

374 # self.siteId = 26 

375 # elif mg['SITE'] == 'BetOnline': 

376 # self.sitename = 'BetOnline' 

377 # self.siteId = 19 

378 # elif mg['SITE'] == 'PokerBros': 

379 # self.sitename = 'PokerBros' 

380 # self.siteId = 29 

381 

382 # if 'TOURNO' in mg and mg['TOURNO'] is None: 

383 # info['type'] = 'ring' 

384 # else: 

385 # info['type'] = 'tour' 

386 # if 'ZOOM' in mg['TOUR']: 

387 # info['fast'] = True 

388 

389 if info.get("currency") in ("T$", None) and info["type"] == "ring": 

390 info["currency"] = "play" 

391 

392 if info["limitType"] == "fl" and info["bb"] is not None: 

393 if info["type"] == "ring": 

394 try: 

395 info["sb"] = self.Lim_Blinds[mg["BB"]][0] 

396 info["bb"] = self.Lim_Blinds[mg["BB"]][1] 

397 except KeyError: 

398 tmp = handText[0:200] 

399 log.error( 

400 ("UnibetToFpdb.determineGameType: Lim_Blinds has no lookup for '%s' - '%s'") % (mg["BB"], tmp) 

401 ) 

402 raise FpdbParseError 

403 else: 

404 info["sb"] = str((Decimal(mg["SB"]) / 2).quantize(Decimal("0.01"))) 

405 info["bb"] = str(Decimal(mg["SB"]).quantize(Decimal("0.01"))) 

406 log.info(("UnibetToFpdb.determineGameType: '%s'") % info) 

407 return info 

408 

409 def readHandInfo(self, hand): 

410 # First check if partial 

411 if hand.handText.count("*** Summary ***") != 1: 

412 raise FpdbHandPartial(("Hand is not cleanly split into pre and post Summary")) 

413 

414 info = {} 

415 m = self.re_HandInfo.search(hand.handText, re.DOTALL) 

416 m2 = self.re_GameInfo.search(hand.handText) 

417 if m is None or m2 is None: 

418 tmp = hand.handText[0:200] 

419 log.error(("UnibetToFpdb.readHandInfo: '%s'") % tmp) 

420 raise FpdbParseError 

421 

422 info.update(m.groupdict()) 

423 info.update(m2.groupdict()) 

424 

425 log.debug("readHandInfo: %s" % info) 

426 for key in info: 

427 if key == "DATETIME": 

428 # 2008/11/12 10:00:48 CET [2008/11/12 4:00:48 ET] # (both dates are parsed so ET date overrides the other) 

429 # 2008/08/17 - 01:14:43 (ET) 

430 # 2008/09/07 06:23:14 ET 

431 datetimestr = "00:00:00 2000/01/01" # default used if time not found 

432 if self.siteId == 26: 

433 m2 = self.re_DateTime2.finditer(info[key]) 

434 

435 else: 

436 m1 = self.re_DateTime1.finditer(info[key]) 

437 for a in m1: 

438 datetimestr1 = str(a.group("H")) + ":" + str(a.group("MIN")) + ":" + str(a.group("S")) 

439 datetimestr2 = str(a.group("Y")) + "/" + str(a.group("M")) + "/" + str(a.group("D")) 

440 datetimestr = datetimestr2 + " " + datetimestr1 

441 print("datetimestr", datetimestr) 

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

443 # print (" tz = ", tz, " datetime =", datetimestr) 

444 hand.startTime = datetime.datetime.strptime( 

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

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

447 # hand.startTime = HandHistoryConverter.changeTimezone(hand.startTime, "ET", "UTC") 

448 

449 if key == "HID": 

450 hand.handid = info[key] 

451 if key == "TOURNO": 

452 hand.tourNo = info[key] 

453 if key == "BUYIN": 

454 if hand.tourNo is not None: 

455 print("DEBUG: info['BUYIN']: %s" % info["BUYIN"]) 

456 print("DEBUG: info['BIAMT']: %s" % info["BIAMT"]) 

457 print("DEBUG: info['BIRAKE']: %s" % info["BIRAKE"]) 

458 print("DEBUG: info['BOUNTY']: %s" % info["BOUNTY"]) 

459 if info[key].strip() == "Freeroll": 

460 hand.buyin = 0 

461 hand.fee = 0 

462 hand.buyinCurrency = "FREE" 

463 elif info[key].strip() == "": 

464 hand.buyin = 0 

465 hand.fee = 0 

466 hand.buyinCurrency = "NA" 

467 else: 

468 if info[key].find("$") != -1: 

469 hand.buyinCurrency = "USD" 

470 elif info[key].find("£") != -1: 

471 hand.buyinCurrency = "GBP" 

472 elif info[key].find("€") != -1: 

473 hand.buyinCurrency = "EUR" 

474 elif info[key].find("₹") != -1: 

475 hand.buyinCurrency = "INR" 

476 elif info[key].find("¥") != -1: 

477 hand.buyinCurrency = "CNY" 

478 elif info[key].find("FPP") != -1: 

479 hand.buyinCurrency = "PSFP" 

480 elif info[key].find("SC") != -1: 

481 hand.buyinCurrency = "PSFP" 

482 elif re.match("^[0-9+]*$", info[key].strip()): 

483 hand.buyinCurrency = "play" 

484 else: 

485 # FIXME: handle other currencies, play money 

486 log.error( 

487 ("UnibetToFpdb.readHandInfo: Failed to detect currency.") 

488 + " Hand ID: %s: '%s'" % (hand.handid, info[key]) 

489 ) 

490 raise FpdbParseError 

491 

492 info["BIAMT"] = info["BIAMT"].strip("$€£FPPSC₹") 

493 

494 if hand.buyinCurrency != "PSFP": 

495 if info["BOUNTY"] is not None: 

496 # There is a bounty, Which means we need to switch BOUNTY and BIRAKE values 

497 tmp = info["BOUNTY"] 

498 info["BOUNTY"] = info["BIRAKE"] 

499 info["BIRAKE"] = tmp 

500 info["BOUNTY"] = info["BOUNTY"].strip("$€£₹") # Strip here where it isn't 'None' 

501 hand.koBounty = int(100 * Decimal(info["BOUNTY"])) 

502 hand.isKO = True 

503 else: 

504 hand.isKO = False 

505 

506 info["BIRAKE"] = info["BIRAKE"].strip("$€£₹") 

507 

508 hand.buyin = int(100 * Decimal(info["BIAMT"])) + hand.koBounty 

509 hand.fee = int(100 * Decimal(info["BIRAKE"])) 

510 else: 

511 hand.buyin = int(100 * Decimal(info["BIAMT"])) 

512 hand.fee = 0 

513 if "Zoom" in info["TITLE"] or "Rush" in info["TITLE"]: 

514 hand.isFast = True 

515 else: 

516 hand.isFast = False 

517 if "Home" in info["TITLE"]: 

518 hand.isHomeGame = True 

519 else: 

520 hand.isHomeGame = False 

521 if key == "LEVEL": 

522 hand.level = info[key] 

523 if key == "SHOOTOUT" and info[key] is not None: 

524 hand.isShootout = True 

525 if key == "TABLE": 

526 hand.tablename = info[key] 

527 # if info['TOURNO'] is not None and info['HIVETABLE'] is not None: 

528 # hand.tablename = info['HIVETABLE'] 

529 # elif hand.tourNo != None and len(tablesplit)>1: 

530 # hand.tablename = tablesplit[1] 

531 # else: 

532 # hand.tablename = info[key] 

533 if key == "BUTTON": 

534 hand.buttonpos = info[key] 

535 if key == "MAX" and info[key] is not None: 

536 hand.maxseats = int(info[key]) 

537 log.info("readHandInfo.hand: %s" % hand) 

538 if self.re_Cancelled.search(hand.handText): 

539 raise FpdbHandPartial(("Hand '%s' was cancelled.") % hand.handid) 

540 

541 def readButton(self, hand): 

542 pre, post = hand.handText.split("*** Summary ***") 

543 m = self.re_Button.search(hand.handText) 

544 m2 = self.re_PlayerInfo.finditer(pre) 

545 if m: 

546 for b in m2: 

547 if b.group("PNAME") == m.group("BUTTON"): 

548 hand.buttonpos = int(b.group("SEAT")) 

549 log.info("readHandInfo.readbutton: %s" % int(b.group("SEAT"))) 

550 else: 

551 log.info("readButton: " + ("not found")) 

552 

553 def readPlayerStacks(self, hand): 

554 pre, post = hand.handText.split("*** Summary ***") 

555 m = self.re_PlayerInfo.finditer(pre) 

556 m2 = self.re_PlayerInfo2.finditer(pre) 

557 for b in m2: 

558 for a in m: 

559 if a.group("PNAME") == b.group("SITOUT"): 

560 hand.addPlayer( 

561 int(a.group("SEAT")), 

562 a.group("PNAME"), 

563 self.clearMoneyString(a.group("CASH")), 

564 None, 

565 int(a.group("SEAT")), 

566 # self.clearMoneyString(a.group('BOUNTY')) 

567 ) 

568 log.info( 

569 "readPlayerStacks: '%s' '%s' '%s' '%s' '%s'" % int(a.group("SEAT")), 

570 a.group("PNAME"), 

571 self.clearMoneyString(a.group("CASH")), 

572 None, 

573 int(a.group("SEAT")), 

574 ) 

575 break 

576 elif a.group("PNAME") != b.group("SITOUT"): 

577 hand.addPlayer( 

578 int(a.group("SEAT")), 

579 a.group("PNAME"), 

580 self.clearMoneyString(a.group("CASH")), 

581 None, 

582 ) 

583 log.info( 

584 "readPlayerStacks: '%s' '%s' '%s' '%s' '%s'" % int(a.group("SEAT")), 

585 a.group("PNAME"), 

586 self.clearMoneyString(a.group("CASH")), 

587 None, 

588 ) 

589 

590 def markStreets(self, hand): 

591 # There is no marker between deal and draw in Stars single draw games 

592 # this upsets the accounting, incorrectly sets handsPlayers.cardxx and 

593 # in consequence the mucked-display is incorrect. 

594 # Attempt to fix by inserting a DRAW marker into the hand text attribute 

595 # PREFLOP = ** Dealing down cards ** 

596 # This re fails if, say, river is missing; then we don't get the ** that starts the river. 

597 m = re.search( 

598 r"\*\*\*\sHole\scards\s\*\*\*(?P<PREFLOP>(.+(?P<FLOPET>\[\S\S\]))?.+(?=\*\*\*\sFlop\s\*\*\*)|.+)" 

599 r"(\*\*\*\sFlop\s\*\*\*(?P<FLOP>(\[\S\S\s])?\[(\S\S?)?\S\S\S\S\].+(?=\*\*\*\sTurn\s\*\*\*)|.+))?" 

600 r"(\*\*\*\sTurn\s\*\*\*\s\[\S\S \S\S \S\S\](?P<TURN>\[\S\S\].+(?=\*\*\*\sRiver\s\*\*\*)|.+))?" 

601 r"(\*\*\*\sRiver\s\*\*\*\s\[\S\S \S\S \S\S\]?\s\[?\S\S\]\s(?P<RIVER>\[\S\S\].+))?", 

602 hand.handText, 

603 re.DOTALL, 

604 ) 

605 hand.addStreets(m) 

606 log.info("markStreets.handaddstreets: %s" % hand) 

607 

608 def readCommunityCards(self, hand, street): # street has been matched by markStreets, so exists in this hand 

609 m = self.re_Board.search(hand.streets[street]) 

610 if m: 

611 hand.setCommunityCards(street, m.group("CARDS").split(" ")) 

612 log.info("readCommunityCards.setCommunityCards:' %s' " % street) 

613 else: 

614 log.error("readCommunityCards.setCommunityCards: none") 

615 

616 def readAntes(self, hand): 

617 log.debug(("reading antes")) 

618 m = self.re_Antes.finditer(hand.handText) 

619 for player in m: 

620 logging.info("hand.addAnte(%s,%s)" % (player.group("PNAME"), player.group("ANTE"))) 

621 hand.addAnte(player.group("PNAME"), self.clearMoneyString(player.group("ANTE"))) 

622 

623 def readBringIn(self, hand): 

624 m = self.re_BringIn.search(hand.handText, re.DOTALL) 

625 if m: 

626 logging.info("readBringIn: %s for %s" % (m.group("PNAME"), m.group("BRINGIN"))) 

627 hand.addBringIn(m.group("PNAME"), self.clearMoneyString(m.group("BRINGIN"))) 

628 

629 def readBlinds(self, hand): 

630 liveBlind = True 

631 for a in self.re_PostSB.finditer(hand.handText): 

632 if liveBlind: 

633 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SB"))) 

634 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("SB")))) 

635 liveBlind = False 

636 else: 

637 names = [p[1] for p in hand.players] 

638 if "Big Blind" in names or "Small Blind" in names or "Dealer" in names: 

639 hand.addBlind(a.group("PNAME"), "small blind", self.clearMoneyString(a.group("SB"))) 

640 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("SB")))) 

641 else: 

642 # Post dead blinds as ante 

643 hand.addBlind(a.group("PNAME"), "secondsb", self.clearMoneyString(a.group("SB"))) 

644 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("SB")))) 

645 for a in self.re_PostBB.finditer(hand.handText): 

646 hand.addBlind(a.group("PNAME"), "big blind", self.clearMoneyString(a.group("BB"))) 

647 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("BB")))) 

648 for a in self.re_PostBoth.finditer(hand.handText): 

649 hand.addBlind(a.group("PNAME"), "both", self.clearMoneyString(a.group("SBBB"))) 

650 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("SBBB")))) 

651 

652 for a in self.re_PostStraddle.finditer(hand.handText): 

653 hand.addBlind(a.group("PNAME"), "straddle", self.clearMoneyString(a.group("STRADDLE"))) 

654 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("STRADDLE")))) 

655 for a in self.re_PostBUB.finditer(hand.handText): 

656 hand.addBlind(a.group("PNAME"), "button blind", self.clearMoneyString(a.group("BUB"))) 

657 logging.info("readBlinds: '%s' for '%s'" % (a.group("PNAME"), self.clearMoneyString(a.group("BUB")))) 

658 

659 def readHoleCards(self, hand): 

660 # streets PREFLOP, PREDRAW, and THIRD are special cases beacause 

661 # we need to grab hero's cards 

662 for street in ("PREFLOP", "DEAL"): 

663 if street in hand.streets.keys(): 

664 print(street) 

665 m = self.re_HeroCards.finditer(hand.streets[street]) 

666 print(m) 

667 for found in m: 

668 # if m == None: 

669 # hand.involved = False 

670 # else: 

671 hand.hero = found.group("PNAME") 

672 logging.info("readHoleCards: '%s'" % (found.group("PNAME"))) 

673 if "cards" not in found.group("NEWCARDS"): 

674 newcards = found.group("NEWCARDS").split(" ") 

675 hand.addHoleCards(street, hand.hero, closed=newcards, shown=False, mucked=False, dealt=True) 

676 

677 def readAction(self, hand, street): 

678 if hand.gametype["split"] and street in hand.communityStreets: 

679 s = street + "2" 

680 else: 

681 s = street 

682 if not hand.streets[s]: 

683 return 

684 m = self.re_Action.finditer(hand.streets[s]) 

685 for action in m: 

686 acts = action.groupdict() 

687 log.error("DEBUG: %s acts: %s" % (street, acts)) 

688 if action.group("ATYPE") == " folds": 

689 hand.addFold(street, action.group("PNAME")) 

690 elif action.group("ATYPE") == " checks": 

691 hand.addCheck(street, action.group("PNAME")) 

692 elif action.group("ATYPE") == " calls": 

693 hand.addCall(street, action.group("PNAME"), self.clearMoneyString(action.group("BET"))) 

694 elif action.group("ATYPE") == " raises": 

695 if action.group("BETTO") is not None: 

696 hand.addRaiseTo(street, action.group("PNAME"), self.clearMoneyString(action.group("BETTO"))) 

697 elif action.group("BET") is not None: 

698 hand.addCallandRaise(street, action.group("PNAME"), self.clearMoneyString(action.group("BET"))) 

699 elif action.group("ATYPE") == " bets": 

700 hand.addBet(street, action.group("PNAME"), self.clearMoneyString(action.group("BET"))) 

701 elif action.group("ATYPE") == " discards": 

702 hand.addDiscard(street, action.group("PNAME"), action.group("BET"), action.group("CARDS")) 

703 elif action.group("ATYPE") == " stands pat": 

704 hand.addStandsPat(street, action.group("PNAME"), action.group("CARDS")) 

705 else: 

706 log.info( 

707 ("DEBUG:") 

708 + " " 

709 + ("Unimplemented %s: '%s' '%s'") % ("readAction", action.group("PNAME"), action.group("ATYPE")) 

710 ) 

711 

712 def readShowdownActions(self, hand): 

713 for shows in self.re_ShowdownAction.finditer(hand.handText): 

714 cards = shows.group("CARDS").split(" ") 

715 logging.debug("hand.readShowdownActions('%s','%s')" % cards, shows.group("PNAME")) 

716 hand.addShownCards(cards, shows.group("PNAME")) 

717 logging.info("hand.readShowdownActions('%s','%s')" % cards, shows.group("PNAME")) 

718 

719 def readTourneyResults(self, hand): 

720 """Reads knockout bounties and add them to the koCounts dict""" 

721 pass 

722 

723 def readCollectPot(self, hand): 

724 hand.setUncalledBets(True) 

725 for m in self.re_CollectPot.finditer(hand.handText): 

726 hand.addCollectPot(player=m.group("PNAME"), pot=str(Decimal((m.group("POT"))))) 

727 logging.info("readCollectPot: '%s' for '%s'" % (m.group("PNAME"), str(Decimal((m.group("POT")))))) 

728 

729 def readShownCards(self, hand): 

730 pass 

731 

732 @staticmethod 

733 def getTableTitleRe(type, table_name=None, tournament=None, table_number=None): 

734 pass