This bot runs on Heroku, and is essentially just a bridge between the Dota2 Web API (http://wiki.teamfortress.com/wiki/WebAPI) and Twitter.
This turns out to be a somewhat more complicated process than you might expect, becaues the Dota2 API is not necessarily the friendliest design for this use case. As a result, the basic flow of the bot is somewhat complicated. Minus some initialization, the core loop looks like this:
- Get a list of leagues that currently have live games.
- Get a list of matches for each league that has an active game (or recently had an active game).
- Compare the most recent game in each league with the last known game on record for that league.
- If it's different, load the match details for that specific match.
- Process the match details and generate a relevant tweet.
There is a bunch of added complexity around delaying the tweets so they align with stream delays, keeping track of the series status (the API only notes that a game is part of a series, but doesn't give you the current state of the series, eg 1-1 in a bo5, and other details. There are also some tricky bits around restarting the bot (which happens whenever I deploy a new version) without losing games that were in-the-pipeline for being tweeted. In-line comments are pretty decent at providing context.
The boxscore image generating system is a whole other situation. As of June 2014, Valve added real-time updates to GetLiveLeagueGames method. For each game, up-to-the-minute (sort of) scoreboard information is available, including per-player item loadouts, GPM/XMP, kill score, tower information, etc. There are two caveats:
- The data is not available historically. The request gives you the latest data available, but older snapshots are not available anywhere as far as I can see.
- The API servers seem to only get updates from the game servers every 60-120 seconds. So the data we're getting is "real time" in a certain sense, but pretty low temporal resolution.
To deal with these issues, there is a major new module,
lib/gamestate.js, which manages the details of tracking status-over-time of all open league games. The main
dota2results.js script calls GetLiveLeagueGames every 20 or so seconds and sends the resulting snapshots of each game over to a GameStateTracker object for processing. The GameStateTracker has three major functions:
- Ignore duplicate snapshots (we generally see the same snapshot 4-6 times before we get a genuinely new one)
- Generate total gold differential data. Historically this was very painful, but net work is now reported in the API, which is great.
- Detect towers falling. Each snapshot includes a bit mask that encodes tower states. Extract diffs in these masks to determine which towers fell and who to attribute them to, isolating these events into a single list of towers-that-fell.
- Detect barracks falling. Same method as above, although it seems to work a lot less well for reasons I have not yet figured out.
GetLiveLeagueGames has improved dramatically in the last year, but there are some big gaps in what is available:
- The game winner is not available in a formal way. We can detect lobbies closing because they stop getting returned by GetLiveLeagueGames and we could maybe infer the winner most of the time, but it's not reported directly.
- Kill data as reported by the final scoreboard doesn't seem to match kill data reported by GetMatchDetails.
- The time of the final snapshot is usually substantially lower than the time reported in GetMatchDetails.
- Games are organized by lobby_id, which is a transient id that is separate from the more enduring match_id, which makes matching lobby data back to match data a little squishy.
This data is accumulated over time. When the normal game-end detection system from the primary
dota2results.js logic identifies that a game has finished, it looks up GameStates that seem to match the GetMatchDetails information, relying on league_id and team_id. Merging the MatchDetails object with the accumulated GameState object gives us enough data to make a good visualization.
The visualization process is handled by
boxscores.js. Most of the work here is in the design, the actual code is a pretty straightforward translation of the target design into node-canvas style code that can generate an image. The
generate() function takes a MatchDetails object and a GameState object and combines them to create an appropriage image. This function draws on cached images of heroes. It doesn't have any major unusual dependencies, it's just a lot of detailed image work.
To run this locally, you'll need a valid Steam API key. If you want to post to Twitter, you'll need the relevant Twitter credentials (consumer key, consumer secret, access key, and access secret). In production mode, I strongly recommend providing a redis instance for caching across bot restarts, but the bot should run fine without one, especially in development mode. All configuration is found in
conf.sh.example, which you should use to configure your environment appropriately using
If you're interested in contributing, I can always use help keeping track of Twitter handles for teams. You can find the instructions for doing that in
- Auto-generate images to include in the tweets that provide a visual summary of the match, ala the end-game scoreboard.
- Now reads premier/professional/amateur status so long term whitelisting is less burdensom.
- Live-tweet significant moments in matches (ie big team fights, towers going down, etc); requires access to the real time DotaTV stream or other real-time data.