Parse hand histories from online poker sites in JavaScript & TypeScript.
Add @poker-apprentice/hand-history-parser
as a dependency.
- yarn:
yarn add @poker-apprentice/hand-history-parser
- npm:
npm install @poker-apprentice/hand-history-parser --save
This promise-based function can be used to parse hand histories from any support poker site. To use it, simply pass the contents of an individual hand history along with the filename.
// Assumes `hand` is a string containing the hand history file contents & `filename`
// is a string containing the hand history filename.
const handHistory = parseHand({ hand, filename })
.then((handHistory) => console.log(handHistory))
.catch((err) => console.error(err));
If preferred, an async/await implementation can be used instead.
// Assumes `hand` is a string containing the hand history file contents & `filename`
// is a string containing the hand history filename.
try {
const handHistory = await parseHand({ hand, filename });
console.log(handHistory);
} catch (err) {
console.error(err);
}
The value returned is represented as a HandHistory
type object. For example:
{
"info": {
"bettingStructure": "no limit",
"blinds": ["0.1", "0.25"],
"currency": "USD",
"handNumber": "4290322948",
"isFastFold": false,
"tableNumber": "26728545",
"site": "bovada",
"tableSize": 6,
"timestamp": new Date("2022-05-26T14:16:24.000Z"),
"variant": "holdem",
},
"players": [
{
"chipStack": "25",
"isAnonymous": false,
"isHero": true,
"name": "Big Blind",
"position": "BB",
"seatNumber": 1,
},
{
"chipStack": "24.4",
"isAnonymous": true,
"isHero": false,
"name": "UTG",
"position": "UTG",
"seatNumber": 2,
},
{
"chipStack": "7.5",
"isAnonymous": true,
"isHero": false,
"name": "UTG+1",
"position": "UTG+1",
"seatNumber": 3,
},
{
"chipStack": "33.77",
"isAnonymous": true,
"isHero": false,
"name": "UTG+2",
"position": "UTG+2",
"seatNumber": 4,
},
{
"chipStack": "26.87",
"isAnonymous": true,
"isHero": false,
"name": "Dealer",
"position": "BTN",
"seatNumber": 5,
},
{
"chipStack": "23.3",
"isAnonymous": true,
"isHero": false,
"name": "Small Blind",
"position": "SB",
"seatNumber": 6,
},
],
"actions": [
{
"type": "post",
"postType": "blind",
"amount": "0.1",
"playerName": "Small Blind",
},
{
"type": "post",
"postType": "blind",
"amount": "0.25",
"playerName": "Big Blind",
},
{
"type": "deal-hand",
"cards": ["Th", "Qh"],
"playerName": "Big Blind",
},
{
"type": "deal-hand",
"cards": ["9s", "Ks"],
"playerName": "UTG",
},
{
"type": "deal-hand",
"cards": ["6h", "Ad"],
"playerName": "UTG+1",
},
{
"type": "deal-hand",
"cards": ["Jd", "2s"],
"playerName": "UTG+2",
},
{
"type": "deal-hand",
"cards": ["Tc", "8d"],
"playerName": "Dealer",
},
{
"type": "deal-hand",
"cards": ["3d", "8c"],
"playerName": "Small Blind",
},
{
"type": "call",
"amount": "0.25",
"isAllIn": false,
"playerName": "UTG",
},
{
"type": "call",
"amount": "0.25",
"isAllIn": false,
"playerName": "UTG+1",
},
{
"type": "fold",
"playerName": "UTG+2",
},
{
"type": "fold",
"playerName": "Dealer",
},
{
"type": "fold",
"playerName": "Small Blind",
},
{
"type": "check",
"playerName": "Big Blind",
},
{
"type": "deal-board",
"cards": ["Qs", "Ts", "4s"],
"street": "flop",
},
{
"type": "bet",
"amount": "0.5",
"isAllIn": false,
"playerName": "Big Blind",
},
{
"type": "call",
"amount": "0.5",
"isAllIn": false,
"playerName": "UTG",
},
{
"type": "fold",
"playerName": "UTG+1",
},
{
"type": "deal-board",
"cards": ["Jc"],
"street": "turn",
},
{
"type": "bet",
"amount": "1",
"isAllIn": false,
"playerName": "Big Blind",
},
{
"type": "call",
"amount": "1",
"isAllIn": false,
"playerName": "UTG",
},
{
"type": "deal-board",
"cards": ["5d"],
"street": "river",
},
{
"type": "check",
"playerName": "Big Blind",
},
{
"type": "bet",
"amount": "2.75",
"isAllIn": false,
"playerName": "UTG",
},
{
"type": "call",
"amount": "2.75",
"isAllIn": false,
"playerName": "Big Blind",
},
{
"type": "showdown",
"handStrength": 5,
"playerName": "UTG",
},
{
"type": "showdown",
"handStrength": 2,
"playerName": "Big Blind",
},
{
"type": "award-pot",
"amount": "8.89",
"isSidePot": false,
"playerName": "UTG",
},
],
}
This function can be used to determine the support poker site on which a hand was played. To use it, simply pass the contents of an individual hand history.
// assumes `hand` is a string containing the hand history file contents
const site = parseSite(hand);
console.log(site);
The value returned is represented as a Site
string union type.
This package currently supports the following poker sites & networks:
Site | Network | Cash Games | Tournaments | Hold'em | Omaha | Omaha-8 | Stud | Currencies |
---|---|---|---|---|---|---|---|---|
Bodog | Ignition | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | USD |
Bovada | Ignition | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | USD |
Ignition | Ignition | ✅ | ✅ | ✅ | ✅ | ✅ | N/A | USD |
The parser is built in a way that it is relatively straightforward to extend with new poker sites. The main thing that is missing for this to happen is a combination of sample hand histories to implement against & time.
If you are a developer that is interested in contributing, see the contributing section below.
If you are an online poker player that has hand histories that can be used as a reference, please email them to the author.
If you'd like to implement a new poker site, fix a bug, improve the documentation, or anything else to better this library, pull requests are welcomed!
The architecture is as straightforward as possible, with the most complex part involving an understanding of the ANTLR language processor. The approach is as follows:
- Clone the repository:
git clone git@github.com:poker-apprentice/hand-history-parser.git
- Install dependencies:
yarn install
- Add a
.g4
grammar file undergrammar/[PokerNetwork].g4
. It is recommended that this file be parsed by different types of lines that appear within the file. These lines can typically be broken down into one of: game metadata, player metadata, & player actions. These three types of information comprise the data that must be returned by a new hand history parser. - Build all ANTLR grammar files:
yarn build:grammar
- Add a visitor for your poker site under
networks/[pokernetwork]/[PokerNetwork]HandHistoryVisitor.ts
. The visitor is responsible for returning a value representing the current node that is being parsed bym ANTLR. Ideally each line being parsed can return an object representing one of the three types of metadata outlined above. However, it may be necessary to utilize a custom return type that may or may not wrap the sharedAction
,Player
, andGameInfo
types depending on the poker site's hand history structure. - Add a parser for your poker site under
networks/[pokernetwork]/parseHand.ts
. This function is intended to delegate to the Visitor class implemented above, massaging the return value into aHandHistory
object as a return value. - Add an if-condition for your poker site within
parseSite.ts
. - Include tests for common & uncommon parsing scenarios that demonstrate the
parseHand
andparseSite
functions are working as intended. Common & uncommon scenarios include for theparseHand
function include:- Multiple variants (e.g.: Hold'em, Omaha, Omaha-8, etc.),
- Multiple betting structures (i.e.: limit, no-limit, pot-limit, spread-limit, cap-limit),
- Multiple currencies,
- Cash & tournament games,
- Chopped pots & side pots,
- Antes & bounties, and
- Player disconnections & timeouts.
If you are interested in contributing, but you are stuck or lost at any point in your efforts, please reach out for help!