├── requirements.txt ├── setup.cfg ├── dota2api ├── src │ ├── __init__.py │ ├── urls.py │ ├── exceptions.py │ ├── response.py │ └── parse.py ├── ref │ ├── __init__.py │ ├── lobbies.json │ ├── leaver.json │ ├── modes.json │ ├── regions.json │ └── abilities.json └── __init__.py ├── tests ├── src │ ├── __init__.py │ └── reponse_test.py ├── __init__.py ├── exceptions_test.py ├── utils.py ├── api_test.py ├── urls_test.py └── ref │ └── single_match_result.json ├── MANIFEST.in ├── .travis.yml ├── .coveragerc ├── docs ├── source │ ├── reference.rst │ ├── contribute.rst │ ├── installation.rst │ ├── index.rst │ ├── tutorial.rst │ ├── conf.py │ └── responses.rst └── make.bat ├── .gitignore ├── setup.py ├── README.rst └── LICENSE /requirements.txt: -------------------------------------------------------------------------------- 1 | requests -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | release = register sdist upload 3 | 4 | -------------------------------------------------------------------------------- /dota2api/src/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- -------------------------------------------------------------------------------- /dota2api/ref/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /tests/src/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst 2 | include *.py 3 | include *.txt 4 | include LICENSE 5 | 6 | recursive-include dota2api/ref *.json 7 | recursive-include dota2api *.py 8 | prune build 9 | prune dist 10 | prune docs 11 | prune *.egg-info -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.3" 6 | - "3.4" 7 | - "3.5" 8 | - "pypy" 9 | - "pypy3" 10 | 11 | # whitelist 12 | branches: 13 | only: 14 | - master 15 | 16 | # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 17 | install: pip install -r requirements.txt 18 | 19 | # command to run tests, e.g. python setup.py test 20 | script: nosetests tests 21 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # .coveragerc to control coverage.py 2 | [run] 3 | branch = True 4 | 5 | omit = 6 | /opt/* 7 | */site-packages/* 8 | dota2api/ref/* 9 | dota2api/src/__init__.py 10 | tests/* 11 | setup.py 12 | 13 | [report] 14 | # Regexes for lines to exclude from consideration 15 | exclude_lines = 16 | # Don't complain if non-runnable code isn't run: 17 | if 0: 18 | if __name__ == .__main__.: 19 | 20 | [html] 21 | directory = cover -------------------------------------------------------------------------------- /docs/source/reference.rst: -------------------------------------------------------------------------------- 1 | ################# 2 | Library Reference 3 | ################# 4 | 5 | This section covers the ``dota2api`` package, the ``parse`` module and the ``exceptions`` used. 6 | 7 | 8 | *** 9 | API 10 | *** 11 | 12 | .. automodule:: dota2api 13 | :members: 14 | 15 | 16 | ****** 17 | Parser 18 | ****** 19 | 20 | .. automodule:: dota2api.src.parse 21 | :members: 22 | 23 | 24 | ********** 25 | Exceptions 26 | ********** 27 | 28 | .. automodule:: dota2api.src.exceptions 29 | :members: -------------------------------------------------------------------------------- /docs/source/contribute.rst: -------------------------------------------------------------------------------- 1 | ############ 2 | Contributing 3 | ############ 4 | 5 | This section provides help for people who wish to contribute to the project. 6 | 7 | We are open to most change requests, the only request is that every piece of functionality is accompanied by a test! 8 | 9 | 10 | ************* 11 | Documentation 12 | ************* 13 | 14 | Documentation improvements are always welcome, I'm hoping this will be a useful guide to the API as most information online is out of date. 15 | 16 | 17 | *********** 18 | Bug reports 19 | *********** 20 | 21 | Forks or bug reports are welcome! If you spot any errors in the code or documentation please open an issue on `GitHub`_. 22 | 23 | .. _`GitHub`: https://github.com/joshuaduffy/dota2api/issues -------------------------------------------------------------------------------- /dota2api/ref/lobbies.json: -------------------------------------------------------------------------------- 1 | { 2 | "lobbies": [ 3 | { 4 | "id": -1, 5 | "name": "Invalid" 6 | }, 7 | { 8 | "id": 0, 9 | "name": "Public matchmaking" 10 | }, 11 | { 12 | "id": 1, 13 | "name": "Practice" 14 | }, 15 | { 16 | "id": 2, 17 | "name": "Tournament" 18 | }, 19 | { 20 | "id": 3, 21 | "name": "Tutorial" 22 | }, 23 | { 24 | "id": 4, 25 | "name": "Co-op with bots" 26 | }, 27 | { 28 | "id": 5, 29 | "name": "Team match" 30 | }, 31 | { 32 | "id": 6, 33 | "name": "Solo Queue" 34 | }, 35 | { 36 | "id": 7, 37 | "name": "Ranked" 38 | }, 39 | { 40 | "id": 8, 41 | "name": "Solo Mid 1 vs 1" 42 | } 43 | ] 44 | } -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | ############ 2 | Installation 3 | ############ 4 | 5 | This section covers installation of the library. 6 | 7 | .. tip:: 8 | 9 | Work in a virtual environment! 10 | 11 | *** 12 | Pip 13 | *** 14 | 15 | Installing via `pip`_ is the recommended method: 16 | 17 | .. code-block:: bash 18 | 19 | $ pip install dota2api 20 | 21 | 22 | ***************** 23 | Build from source 24 | ***************** 25 | 26 | You can also download the latest version of the code from the `repository`_ and install: 27 | 28 | .. code-block:: bash 29 | 30 | $ git clone https://github.com/joshuaduffy/dota2api/ && cd dota2api/ 31 | $ python setup.py install 32 | 33 | 34 | .. target-notes:: 35 | .. _`pip`: http://www.pip-installer.org/ 36 | .. _`repository`: https://github.com/joshuaduffy/dota2api -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | /build/* 12 | /docs/build/* 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .coverage 39 | .cache 40 | cover/ 41 | 42 | # Translations 43 | *.mo 44 | *.pot 45 | 46 | # Django stuff: 47 | *.log 48 | 49 | # PyBuilder 50 | target/ 51 | 52 | # Mine 53 | key.tmp 54 | /.idea/* 55 | 56 | -------------------------------------------------------------------------------- /dota2api/ref/leaver.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 0, 4 | "name": "NONE", 5 | "description": "finished match, no abandon" 6 | }, 7 | { 8 | "id": 1, 9 | "name": "DISCONNECTED", 10 | "description": "player DC, no abandon" 11 | }, 12 | { 13 | "id": 2, 14 | "name": "DISCONNECTED_TOO_LONG", 15 | "description": "player DC > 5min, abandon" 16 | }, 17 | { 18 | "id": 3, 19 | "name": "ABANDONED", 20 | "description": "player dc, clicked leave, abandon" 21 | }, 22 | { 23 | "id": 4, 24 | "name": "AFK", 25 | "description": "player AFK, abandon" 26 | }, 27 | { 28 | "id": 5, 29 | "name": "NEVER_CONNECTED", 30 | "description": "never connected, no abandon" 31 | }, 32 | { 33 | "id": 6, 34 | "name": "NEVER_CONNECTED_TOO_LONG", 35 | "description": "too long to connect, no abandon" 36 | } 37 | ] -------------------------------------------------------------------------------- /tests/exceptions_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | 5 | from dota2api.src import exceptions 6 | 7 | 8 | class APIErrorTest(unittest.TestCase): 9 | def setUp(self): 10 | import dota2api 11 | self.api = dota2api.Initialise() 12 | 13 | def wrong_account_id(self): 14 | self.assertRaises(exceptions.APIError(), self.api.get_match_history(account_id=1)) 15 | 16 | 17 | class APIAuthenticationErrorTest(unittest.TestCase): 18 | def setUp(self): 19 | import dota2api 20 | self.api = dota2api.Initialise("d") 21 | 22 | def wrong_account_id(self): 23 | self.assertRaises(exceptions.APIAuthenticationError(), self.api.get_match_details()) 24 | 25 | 26 | class APITimeoutErrorTest(unittest.TestCase): 27 | def setUp(self): 28 | import dota2api 29 | self.api = dota2api.Initialise() 30 | 31 | def too_many_requests(self): 32 | assert True # Can I do this? 33 | -------------------------------------------------------------------------------- /dota2api/src/urls.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """The base URL and the API calls are defined in this file""" 4 | 5 | BASE_URL = "http://api.steampowered.com/" 6 | GET_MATCH_HISTORY = "IDOTA2Match_570/GetMatchHistory/v001/" 7 | GET_MATCH_HISTORY_BY_SEQ_NUM = "IDOTA2Match_570/GetMatchHistoryBySequenceNum/v0001/" 8 | GET_MATCH_DETAILS = "IDOTA2Match_570/GetMatchDetails/v001/" 9 | GET_LEAGUE_LISTING = "IDOTA2Match_570/GetLeagueListing/v0001/" 10 | GET_LIVE_LEAGUE_GAMES = "IDOTA2Match_570/GetLiveLeagueGames/v0001/" 11 | GET_TEAM_INFO_BY_TEAM_ID = "IDOTA2Match_570/GetTeamInfoByTeamID/v001/" 12 | GET_PLAYER_SUMMARIES = "ISteamUser/GetPlayerSummaries/v0002/" 13 | GET_HEROES = "IEconDOTA2_570/GetHeroes/v0001/" 14 | GET_GAME_ITEMS = "IEconDOTA2_570/GetGameItems/v0001/" 15 | GET_TOURNAMENT_PRIZE_POOL = "IEconDOTA2_570/GetTournamentPrizePool/v1/" 16 | GET_TOP_LIVE_GAME="IDOTA2Match_570/GetTopLiveGame/v1/" 17 | BASE_ITEMS_IMAGES_URL = 'http://cdn.dota2.com/apps/dota2/images/items/' 18 | BASE_HERO_IMAGES_URL = 'http://cdn.dota2.com/apps/dota2/images/heroes/' 19 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. dota2api documentation master file, created by 2 | sphinx-quickstart on Thu Oct 30 16:27:41 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ######################## 7 | Dota 2 API Documentation 8 | ######################## 9 | 10 | Welcome to the dota2api documentation. This Python library is an unofficial wrapper for the `Dota 2 API`_ from `Valve Software`_. 11 | The repository can be found on `GitHub`_. 12 | 13 | 14 | ******** 15 | Contents 16 | ******** 17 | 18 | .. toctree:: 19 | :maxdepth: 2 20 | 21 | installation 22 | tutorial 23 | responses 24 | reference 25 | contribute 26 | 27 | 28 | ****************** 29 | Indices and tables 30 | ****************** 31 | 32 | * :ref:`genindex` 33 | * :ref:`modindex` 34 | * :ref:`search` 35 | 36 | 37 | ********** 38 | References 39 | ********** 40 | 41 | .. target-notes:: 42 | .. _`Dota 2 API`: https://wiki.teamfortress.com/wiki/WebAPI#Dota_2 43 | .. _`Valve Software`: http://www.valvesoftware.com/ 44 | .. _`GitHub`: https://github.com/joshuaduffy/dota2api 45 | -------------------------------------------------------------------------------- /dota2api/src/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Not many exceptions exist due to server side validation on the parameters""" 4 | 5 | 6 | class BaseError(Exception): 7 | pass 8 | 9 | 10 | class APIError(BaseError): 11 | """ 12 | Raised when the API response is an error, or the status does not equal one 13 | """ 14 | 15 | def __init__(self, msg): 16 | self.msg = msg 17 | 18 | def __str__(self): 19 | return repr(self.msg) 20 | 21 | 22 | class APIAuthenticationError(BaseError): 23 | """ 24 | Raised when the API key supplied is invalid 25 | 26 | :param api_key: (str) key used will be displayed upon error 27 | """ 28 | 29 | def __init__(self, api_key=None): 30 | self.msg = "The following API Key is invalid: {0}".format(api_key) 31 | 32 | def __str__(self): 33 | return repr(self.msg) 34 | 35 | 36 | class APITimeoutError(BaseError): 37 | """ 38 | Raised when too many requests are been made or the server is busy 39 | """ 40 | 41 | def __init__(self, ): 42 | self.msg = "HTTP 503: Please try again later." 43 | 44 | def __str__(self): 45 | return repr(self.msg) 46 | -------------------------------------------------------------------------------- /dota2api/src/response.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Response template, this is used so we can pass the response as an object""" 4 | 5 | import json 6 | from .parse import * 7 | from .exceptions import * 8 | 9 | 10 | class Dota2Dict(dict): 11 | pass 12 | 13 | 14 | def build(req, url, raw_mode=False): 15 | req_resp = req.json() 16 | if 'result' in req_resp: 17 | if 'error' in req_resp['result']: 18 | raise APIError(req_resp['result']['error']) 19 | if 'status' in req_resp['result']: 20 | if not (1 == req_resp['result']['status'] == 200): 21 | try: 22 | raise APIError(req_resp['result']['statusDetail']) 23 | except KeyError: 24 | pass 25 | resp = Dota2Dict(req_resp['result']) 26 | elif 'response' in req_resp: 27 | resp = Dota2Dict(req_resp['response']) 28 | else: 29 | resp = Dota2Dict(req_resp) 30 | 31 | if not raw_mode: 32 | try: 33 | if 'players' in resp: 34 | resp = hero_id(resp) 35 | resp = item_id(resp) 36 | resp = lobby_type(resp) 37 | resp = game_mode(resp) 38 | resp = cluster(resp) 39 | resp = leaver(resp) 40 | except KeyError: 41 | pass # Only do the above for matches 42 | 43 | if 'items' in resp: 44 | parse_items_images_urls(resp) 45 | 46 | if 'heroes' in resp: 47 | parse_heroes_images(resp) 48 | 49 | resp.url = url 50 | resp.json = json.dumps(resp, ensure_ascii=False) 51 | 52 | return resp 53 | -------------------------------------------------------------------------------- /dota2api/ref/modes.json: -------------------------------------------------------------------------------- 1 | { 2 | "modes": [ 3 | { 4 | "id": 0, 5 | "name": "Unknown" 6 | }, 7 | { 8 | "id": 1, 9 | "name": "All Pick" 10 | }, 11 | { 12 | "id": 2, 13 | "name": "Captains Mode" 14 | }, 15 | { 16 | "id": 3, 17 | "name": "Random Draft" 18 | }, 19 | { 20 | "id": 4, 21 | "name": "Single Draft" 22 | }, 23 | { 24 | "id": 5, 25 | "name": "All Random" 26 | }, 27 | { 28 | "id": 6, 29 | "name": "?? INTRO/DEATH ??" 30 | }, 31 | { 32 | "id": 7, 33 | "name": "The Diretide" 34 | }, 35 | { 36 | "id": 8, 37 | "name": "Reverse Captains Mode" 38 | }, 39 | { 40 | "id": 9, 41 | "name": "Greeviling" 42 | }, 43 | { 44 | "id": 10, 45 | "name": "Tutorial" 46 | }, 47 | { 48 | "id": 11, 49 | "name": "Mid Only" 50 | }, 51 | { 52 | "id": 12, 53 | "name": "Least Played" 54 | }, 55 | { 56 | "id": 13, 57 | "name": "New Player Pool" 58 | }, 59 | { 60 | "id": 14, 61 | "name": "Compendium Matchmaking" 62 | }, 63 | { 64 | "id": 15, 65 | "name": "Custom" 66 | }, 67 | { 68 | "id": 16, 69 | "name": "Captains Draft" 70 | }, 71 | { 72 | "id": 17, 73 | "name": "Balanced Draft" 74 | }, 75 | { 76 | "id": 18, 77 | "name": "Ability Draft" 78 | }, 79 | { 80 | "id": 19, 81 | "name": "?? Event ??" 82 | }, 83 | { 84 | "id": 20, 85 | "name": "All Random Death Match" 86 | }, 87 | { 88 | "id": 21, 89 | "name": "1 vs 1 Solo Mid" 90 | }, 91 | { 92 | "id": 22, 93 | "name": "Ranked All Pick" 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Setup script""" 5 | 6 | from setuptools import setup 7 | import re 8 | 9 | 10 | with open('dota2api/__init__.py', 'r') as fd: 11 | version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 12 | fd.read(), 13 | re.MULTILINE).group(1) 14 | 15 | with open('dota2api/__init__.py', 'r') as fd: 16 | author = re.search(r'^__author__\s*=\s*[\'"]([^\'"]*)[\'"]', 17 | fd.read(), 18 | re.MULTILINE).group(1) 19 | 20 | with open('dota2api/__init__.py', 'r') as fd: 21 | licence = re.search(r'^__licence__\s*=\s*[\'"]([^\'"]*)[\'"]', 22 | fd.read(), 23 | re.MULTILINE).group(1) 24 | 25 | setup( 26 | name="dota2api", 27 | version=version, 28 | author=author, 29 | author_email="mail@joshuaduffy.org", 30 | url="https://github.com/joshuaduffy/dota2api", 31 | description="Dota 2 API wrapper and parser in Python", 32 | license=licence, 33 | keywords="dota2 dota api dota2api parser", 34 | packages=['dota2api', 'dota2api.src', 'dota2api.ref'], 35 | package_data={'dota2api.ref': ['abilities.json', 36 | 'heroes.json', 37 | 'leaver.json', 38 | 'items.json', 39 | 'lobbies.json', 40 | 'modes.json', 41 | 'regions.json']}, 42 | install_requires=['requests'], 43 | classifiers=[ 44 | "Intended Audience :: Developers", 45 | "Topic :: Software Development :: Libraries :: Python Modules", 46 | "Development Status :: 5 - Production/Stable", 47 | "License :: OSI Approved :: GNU General Public License (GPL)", 48 | "Programming Language :: Python", 49 | "Programming Language :: Python :: 2.6", 50 | "Programming Language :: Python :: 2.7", 51 | "Programming Language :: Python :: 3.2", 52 | "Programming Language :: Python :: 3.3", 53 | "Programming Language :: Python :: 3.4", 54 | "Programming Language :: Python :: 3.5", 55 | ] 56 | ) 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | dota2api: wrapper and parser 2 | ============================ 3 | 4 | .. image:: https://badges.gitter.im/Join%20Chat.svg 5 | :alt: Join the chat at https://gitter.im/joshuaduffy/dota2api 6 | :target: https://gitter.im/joshuaduffy/dota2api?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge 7 | 8 | .. image:: https://travis-ci.org/joshuaduffy/dota2api.svg 9 | :target: https://travis-ci.org/joshuaduffy/dota2api 10 | .. image:: https://readthedocs.org/projects/dota2api/badge/?version=latest 11 | :target: https://readthedocs.org/projects/dota2api/?badge=latest 12 | 13 | Wrapper and parser in Python created for interacting and getting data easily from Valve's Dota 2 API. It supports Python versions ``2.6 to 2.7+``, ``3.2 to 3.5+`` along with ``PyPy/PyPy3`` 14 | 15 | This library parses some ID's into the dictionary keys like ``hero_name`` and so on. See ``src.parse`` for details. 16 | 17 | This also comes with a growing set of tests and some documentation for the API itself. 18 | 19 | Look how easy it is... 20 | 21 | .. code-block:: python 22 | 23 | >>> import dota2api 24 | >>> api = dota2api.Initialise("API_KEY") 25 | >>> hist = api.get_match_history(account_id=41231571) 26 | >>> match = api.get_match_details(match_id=1000193456) 27 | >>> match['radiant_win'] 28 | False 29 | 30 | You can even store your API key as an environment variable instead of passing it through ``Initialise()`` to save some finger work. 31 | 32 | .. code-block:: bash 33 | 34 | $ export D2_API_KEY=83247983248793298732 35 | 36 | 37 | Install 38 | ------- 39 | 40 | Install via pip... 41 | 42 | .. code-block:: bash 43 | 44 | $ pip install dota2api 45 | 46 | 47 | Or the old fashioned way... 48 | 49 | .. code-block:: bash 50 | 51 | $ git clone https://github.com/joshuaduffy/dota2api.git 52 | $ cd dota2api 53 | $ python setup.py install 54 | 55 | 56 | Documentation 57 | ------------- 58 | Documentation is available at http://dota2api.readthedocs.org/ 59 | 60 | 61 | Supported API calls 62 | ------------------- 63 | - get_match_history 64 | - get_match_history_by_seq_num 65 | - get_match_details 66 | - get_player_summaries 67 | - get_league_listing 68 | - get_live_league_games 69 | - get_team_info_by_team_id 70 | - get_heroes 71 | - get_tournament_prize_pool 72 | - get_game_items 73 | - get_top_live_games 74 | 75 | 76 | Unsupported 77 | ----------- 78 | - EconomySchema 79 | 80 | Run the tests 81 | ------------- 82 | 83 | Using nose and nose-cov: 84 | 85 | .. code-block:: bash 86 | 87 | $ nosetests --with-cov --cov-report html dota2api tests 88 | 89 | To install them do the following: 90 | 91 | .. code-block:: bash 92 | 93 | $ pip install nose nose-cov 94 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import os 4 | 5 | DEFAULT_MATCHES_SIZE = 100 6 | LANGUAGE_PAR = 'language=en_us' 7 | 8 | if not os.environ.get('D2_API_KEY'): 9 | exit("Please set D2_API_KEY environment variable.") 10 | else: 11 | STEAM_ID_PAR = 'key=' + os.environ.get('D2_API_KEY') 12 | 13 | 14 | 15 | def convert_to_64_bit(number): 16 | min64b = 76561197960265728 17 | if number < min64b: 18 | return number + min64b 19 | return number 20 | 21 | 22 | def request_pars(*args): 23 | return '?' + '&'.join(args) 24 | 25 | 26 | class RequestMock(object): 27 | def __init__(self, url_matcher=None): 28 | self.status_code = 666 29 | self.url_matcher = url_matcher 30 | self.called = False 31 | self.json_result = None 32 | 33 | def configure_success(self): 34 | self.status_code = 200 35 | return self 36 | 37 | def configure_authentication_error(self): 38 | self.status_code = 403 39 | return self 40 | 41 | def configure_timeout_error(self): 42 | self.status_code = 503 43 | return self 44 | 45 | def json(self): 46 | if self.json_result: 47 | return self.json_result 48 | return {'result': {}} 49 | 50 | def configure_single_match_result(self): 51 | abs_dir = os.path.abspath(os.path.dirname(__file__)) 52 | join = os.path.join(abs_dir, "ref", "single_match_result.json") 53 | with open(join) as match_json: 54 | self.json_result = json.load(match_json) 55 | return self 56 | 57 | def __call__(self, url): 58 | if self.url_matcher: 59 | self.url_matcher.compare(url) 60 | self.called = True 61 | return self 62 | 63 | def assert_called(self): 64 | if not self.called: 65 | raise AssertionError("The url was not called") 66 | 67 | 68 | class UrlMatcher(object): 69 | def __init__(self, base_url, *args): 70 | self.args = args 71 | self.base_url = base_url 72 | 73 | def compare(self, url): 74 | if type(url) != str: 75 | raise AssertionError(str(url) + ' should be a string') 76 | 77 | if not url.startswith(self.base_url): 78 | raise AssertionError(url + ' does not start with ' + self.base_url) 79 | 80 | all_args = str(url).split('?')[1] 81 | split_args = all_args.split("&") 82 | 83 | for arg in self.args: 84 | if arg in split_args: 85 | split_args.remove(arg) 86 | else: 87 | raise AssertionError('The parameter ' + arg + ' is not in the url ' + url) 88 | 89 | if split_args: 90 | raise AssertionError("Args left: " + str(split_args)) 91 | return True 92 | -------------------------------------------------------------------------------- /docs/source/tutorial.rst: -------------------------------------------------------------------------------- 1 | ######## 2 | Tutorial 3 | ######## 4 | 5 | This section covers basic usage of the library. 6 | 7 | ****************** 8 | Getting an API Key 9 | ****************** 10 | 11 | Get one from `Valve`_. 12 | 13 | ******************************* 14 | D2_API_KEY environment variable 15 | ******************************* 16 | 17 | You can set the ``D2_API_KEY`` environment variable to save entering it all the time. 18 | 19 | For example, in Linux: 20 | 21 | .. code-block:: bash 22 | 23 | $ export D2_API_KEY=83247983248793298732 24 | 25 | ************ 26 | Initialising 27 | ************ 28 | 29 | If you've set the API Key as an environment variable, initialise the module like so: 30 | 31 | .. code-block:: python 32 | 33 | >>> import dota2api 34 | >>> api = dota2api.Initialise() 35 | 36 | If not you'll need to pass it into the constructor: 37 | 38 | .. code-block:: python 39 | 40 | >>> import dota2api 41 | >>> api = dota2api.Initialise("45735474375437457457") 42 | 43 | 44 | Official DOTA2 web API would response identifiers for records like heroes, items, lobby type, game mode, etc. By default, this dota2api would translate most dota2 identifiers into human readable strings. 45 | But you can disable our translation by enabling raw mode: 46 | 47 | .. code-block:: python 48 | 49 | >>> import dota2api 50 | >>> api = dota2api.Initialise("45735474375437457457", raw_mode=True) 51 | 52 | By default, you'll get {"hero_name": "axe"} for axe but when raw_mode is on, it will be replaced by {"hero_id", 2}. 53 | 54 | ********* 55 | API calls 56 | ********* 57 | 58 | The functions are mapped to API calls: 59 | 60 | .. code-block:: python 61 | 62 | >>> match = api.get_match_details(match_id=1000193456) 63 | 64 | The responses are then returned in a ``dict``: 65 | 66 | .. code-block:: python 67 | 68 | >>> match['radiant_win'] 69 | False 70 | 71 | Parameters can be used to filter the results. They're all listed in the :doc:`Library Reference ` 72 | 73 | ***************** 74 | Get match history 75 | ***************** 76 | 77 | You can use the ``account_id`` parameter to filter the results for a specific user. 78 | 79 | .. code-block:: python 80 | 81 | >>> hist = api.get_match_history(account_id=76482434) 82 | 83 | ***************** 84 | Get match details 85 | ***************** 86 | 87 | .. code-block:: python 88 | 89 | >>> match = api.get_match_details(match_id=1000193456) 90 | 91 | *************** 92 | Other API calls 93 | *************** 94 | 95 | Listed in the :doc:`Library Reference ` 96 | 97 | ********** 98 | Exceptions 99 | ********** 100 | 101 | ``APIError`` will be raised if an error message is returned by the API. 102 | 103 | ``APITimeoutError`` will be raised you're making too many requests or the API itself is down. 104 | 105 | ``APIAuthenticationError`` will be raised if you're using an invalid API key. 106 | 107 | 108 | .. _`Valve`: https://steamcommunity.com/dev/apikey -------------------------------------------------------------------------------- /tests/api_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import json 4 | import unittest 5 | 6 | import os 7 | 8 | import dota2api 9 | from dota2api.src.exceptions import * 10 | from dota2api.src.response import Dota2Dict 11 | from .utils import RequestMock 12 | 13 | 14 | class APITest(unittest.TestCase): 15 | def setUp(self): 16 | self.api = dota2api.Initialise() 17 | 18 | def no_environment_variable_set_test(self): 19 | # Pass the API key as a positional argument 20 | self.api_key = os.environ['D2_API_KEY'] 21 | api = dota2api.Initialise(self.api_key) 22 | match = api.get_match_details(match_id=988604774) 23 | # Do we get a match back 24 | mock = RequestMock() 25 | stored_match = mock.configure_single_match_result().json_result 26 | # Check the response is the same as the stored one 27 | self.assertEqual(stored_match['result']['match_id'], match['match_id']) 28 | # Check it is our custom dict type 29 | self.assertEqual(type(Dota2Dict()), type(match)) 30 | 31 | def json_loads_test(self): 32 | # Test json function loads json 33 | match = self.api.get_match_details(match_id=988604774) 34 | try: 35 | json.loads(match.json) 36 | except ValueError: 37 | self.fail("JSON does not load!") 38 | 39 | def wrong_api_key_test(self): 40 | # Test the wrong API key 41 | try: 42 | dota2api.Initialise("sdfsdfsdf").get_match_history() 43 | except APIAuthenticationError: 44 | assert True 45 | 46 | def update_heroes_test(self): 47 | self.api.update_heroes() 48 | try: 49 | self.api.get_match_details(988604774) 50 | except: 51 | self.fail("JSON Heroes update failed!") 52 | 53 | def update_game_items_test(self): 54 | self.api.update_game_items() 55 | try: 56 | self.api.get_match_details(988604774) 57 | except: 58 | self.fail("JSON Items update failed!") 59 | 60 | def test_parse_heroes_urls(self): 61 | heroes = self.api.get_heroes() 62 | 63 | try: 64 | anti_mage = filter(lambda h: h['name'] == 'npc_dota_hero_antimage', heroes['heroes'])[0] 65 | self.assertEqual('http://cdn.dota2.com/apps/dota2/images/heroes/antimage_full.png', anti_mage['url_full_portrait']) 66 | self.assertEqual('http://cdn.dota2.com/apps/dota2/images/heroes/antimage_sb.png', anti_mage['url_small_portrait']) 67 | self.assertEqual('http://cdn.dota2.com/apps/dota2/images/heroes/antimage_lg.png', anti_mage['url_large_portrait']) 68 | self.assertEqual('http://cdn.dota2.com/apps/dota2/images/heroes/antimage_vert.jpg', anti_mage['url_vertical_portrait']) 69 | 70 | except TypeError: 71 | anti_mage = filter(lambda h: h['name'] == 'npc_dota_hero_antimage', heroes['heroes']) 72 | 73 | def test_parse_items_urls(self): 74 | items = self.api.get_game_items() 75 | 76 | try: 77 | blink_dagger = filter(lambda i: i['name'] == 'item_blink', items['items'])[0] 78 | self.assertEqual('http://cdn.dota2.com/apps/dota2/images/items/blink_lg.png', blink_dagger['url_image']) 79 | 80 | except TypeError: 81 | blink_dagger = filter(lambda i: i['name'] == 'item_blink', items['items']) 82 | 83 | def test_raw_mode(self): 84 | # Pass the API key as a positional argument 85 | self.api_key = os.environ['D2_API_KEY'] 86 | api = dota2api.Initialise(self.api_key, raw_mode=True) 87 | match = api.get_match_details(match_id=988604774) 88 | # Do we get a match back 89 | mock = RequestMock() 90 | stored_match = mock.configure_single_match_result().json_result 91 | 92 | # hero_id should not be parsed into hero_name 93 | self.assertEqual('hero_name' in match['players'][0], False) 94 | self.assertEqual(stored_match['result']['players'][0]['hero_id'], match['players'][0]['hero_id']) 95 | 96 | # Check it is our custom dict type 97 | self.assertEqual(type(Dota2Dict()), type(match)) 98 | -------------------------------------------------------------------------------- /dota2api/ref/regions.json: -------------------------------------------------------------------------------- 1 | { 2 | "regions": [ 3 | { 4 | "id": 0, 5 | "name": "Unknow" 6 | }, 7 | { 8 | "id": 111, 9 | "name": "US West" 10 | }, 11 | { 12 | "id": 112, 13 | "name": "US West" 14 | }, 15 | { 16 | "id": 113, 17 | "name": "US West" 18 | }, 19 | { 20 | "id": 114, 21 | "name": "US West" 22 | }, 23 | { 24 | "id": 121, 25 | "name": "US East" 26 | }, 27 | { 28 | "id": 122, 29 | "name": "US East" 30 | }, 31 | { 32 | "id": 123, 33 | "name": "US East" 34 | }, 35 | { 36 | "id": 124, 37 | "name": "US East" 38 | }, 39 | { 40 | "id": 131, 41 | "name": "Europe West" 42 | }, 43 | { 44 | "id": 132, 45 | "name": "Europe West" 46 | }, 47 | { 48 | "id": 133, 49 | "name": "Europe West" 50 | }, 51 | { 52 | "id": 134, 53 | "name": "Europe West" 54 | }, 55 | { 56 | "id": 135, 57 | "name": "Europe West" 58 | }, 59 | { 60 | "id": 136, 61 | "name": "Europe West" 62 | }, 63 | { 64 | "id": 137, 65 | "name": "Europe West" 66 | }, 67 | { 68 | "id": 138, 69 | "name": "Europe West" 70 | }, 71 | { 72 | "id": 142, 73 | "name": "South Korea" 74 | }, 75 | { 76 | "id": 143, 77 | "name": "South Korea" 78 | }, 79 | { 80 | "id": 144, 81 | "name": "Japan" 82 | }, 83 | { 84 | "id": 145, 85 | "name": "Japan" 86 | }, 87 | { 88 | "id": 151, 89 | "name": "Southeast Asia" 90 | }, 91 | { 92 | "id": 152, 93 | "name": "Southeast Asia" 94 | }, 95 | { 96 | "id": 153, 97 | "name": "Southeast Asia" 98 | }, 99 | { 100 | "id": 154, 101 | "name": "Southeast Asia" 102 | }, 103 | { 104 | "id": 155, 105 | "name": "Southeast Asia" 106 | }, 107 | { 108 | "id": 156, 109 | "name": "Southeast Asia" 110 | }, 111 | { 112 | "id": 161, 113 | "name": "China" 114 | }, 115 | { 116 | "id": 163, 117 | "name": "China" 118 | }, 119 | { 120 | "id": 171, 121 | "name": "Australia" 122 | }, 123 | { 124 | "id": 181, 125 | "name": "Russia" 126 | }, 127 | { 128 | "id": 182, 129 | "name": "Russia" 130 | }, 131 | { 132 | "id": 183, 133 | "name": "Russia" 134 | }, 135 | { 136 | "id": 184, 137 | "name": "Russia" 138 | }, 139 | { 140 | "id": 185, 141 | "name": "Russia" 142 | }, 143 | { 144 | "id": 186, 145 | "name": "Russia" 146 | }, 147 | { 148 | "id": 187, 149 | "name": "Russia" 150 | }, 151 | { 152 | "id": 188, 153 | "name": "Russia" 154 | }, 155 | { 156 | "id": 191, 157 | "name": "Europe East" 158 | }, 159 | { 160 | "id": 192, 161 | "name": "Europe East" 162 | }, 163 | { 164 | "id": 200, 165 | "name": "South America" 166 | }, 167 | { 168 | "id": 202, 169 | "name": "South America" 170 | }, 171 | { 172 | "id": 203, 173 | "name": "South America" 174 | }, 175 | { 176 | "id": 204, 177 | "name": "South America" 178 | }, 179 | { 180 | "id": 211, 181 | "name": "South Africa" 182 | }, 183 | { 184 | "id": 212, 185 | "name": "South Africa" 186 | }, 187 | { 188 | "id": 213, 189 | "name": "South America" 190 | }, 191 | { 192 | "id": 221, 193 | "name": "China" 194 | }, 195 | { 196 | "id": 222, 197 | "name": "China" 198 | }, 199 | { 200 | "id": 223, 201 | "name": "China" 202 | }, 203 | { 204 | "id": 224, 205 | "name": "China" 206 | }, 207 | { 208 | "id": 225, 209 | "name": "China" 210 | }, 211 | { 212 | "id": 231, 213 | "name": "China" 214 | }, 215 | { 216 | "id": 241, 217 | "name": "Chile" 218 | }, 219 | { 220 | "id": 242, 221 | "name": "Chile" 222 | }, 223 | { 224 | "id": 251, 225 | "name": "Peru" 226 | }, 227 | { 228 | "id": 261, 229 | "name": "India" 230 | } 231 | ] 232 | } 233 | -------------------------------------------------------------------------------- /dota2api/src/parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Parse some of the values from the API, all can be found in the ``response`` returned""" 4 | 5 | import json 6 | import os 7 | from .urls import BASE_ITEMS_IMAGES_URL, BASE_HERO_IMAGES_URL 8 | 9 | try: 10 | from itertools import izip as zip 11 | except ImportError: 12 | pass 13 | 14 | 15 | def hero_id(response): 16 | """ 17 | Parse the lobby, will be available as ``hero_name`` 18 | """ 19 | for player in response['players']: 20 | for hero in heroes['heroes']: 21 | if hero['id'] == player['hero_id']: 22 | player['hero_name'] = hero['localized_name'] 23 | 24 | return response 25 | 26 | 27 | def leaver(response): 28 | """ 29 | Parse the lobby, will be available as ``hero_name`` 30 | """ 31 | for player in response['players']: 32 | for leaver in leavers: 33 | if leaver['id'] == player['leaver_status']: 34 | player['leaver_status_name'] = leaver['name'] 35 | player['leaver_status_description'] = leaver['description'] 36 | 37 | return response 38 | 39 | 40 | def item_id(response): 41 | """ 42 | Parse the item ids, will be available as ``item_0_name``, ``item_1_name``, 43 | ``item_2_name`` and so on 44 | """ 45 | dict_keys = ['item_0', 'item_1', 'item_2', 46 | 'item_3', 'item_4', 'item_5'] 47 | new_keys = ['item_0_name', 'item_1_name', 'item_2_name', 48 | 'item_3_name', 'item_4_name', 'item_5_name'] 49 | 50 | for player in response['players']: 51 | for key, new_key in zip(dict_keys, new_keys): 52 | for item in items['items']: 53 | if item['id'] == player[key]: 54 | player[new_key] = item['localized_name'] 55 | 56 | return response 57 | 58 | 59 | def lobby_type(response): 60 | """ 61 | Parse the lobby, will be available as ``lobby_type`` 62 | """ 63 | for lobby in lobbies['lobbies']: 64 | if lobby['id'] == response['lobby_type']: 65 | response['lobby_name'] = lobby['name'] 66 | 67 | return response 68 | 69 | 70 | def game_mode(response): 71 | """ 72 | Parse the lobby, will be available as ``game_mode_name`` 73 | """ 74 | for mode in modes['modes']: 75 | if mode['id'] == response['game_mode']: 76 | response['game_mode_name'] = mode['name'] 77 | 78 | return response 79 | 80 | 81 | def cluster(response): 82 | """ 83 | Parse the lobby, will be available as ``cluster_name`` 84 | """ 85 | for reg in regions['regions']: 86 | if reg['id'] == response['cluster']: 87 | response['cluster_name'] = reg['name'] 88 | 89 | return response 90 | 91 | 92 | def load_json_file(file_name): 93 | inp_file = os.path.abspath(os.path.join( 94 | os.path.dirname(os.path.abspath(__file__)), "..", 95 | "ref", 96 | file_name)) 97 | return inp_file 98 | 99 | 100 | def parse_items_images_urls(resp): 101 | for item in resp['items']: 102 | item['url_image'] = BASE_ITEMS_IMAGES_URL + item['name'].replace('item_', '') + '_lg.png' 103 | 104 | 105 | def parse_heroes_images(resp): 106 | for hero in resp['heroes']: 107 | base_images_url = BASE_HERO_IMAGES_URL + hero['name'].replace('npc_dota_hero_', '') 108 | 109 | hero['url_small_portrait'] = base_images_url + '_sb.png' 110 | hero['url_large_portrait'] = base_images_url + '_lg.png' 111 | hero['url_full_portrait'] = base_images_url + '_full.png' 112 | hero['url_vertical_portrait'] = base_images_url + '_vert.jpg' 113 | 114 | # Load the files into memory as a response 115 | with open(load_json_file("heroes.json")) as heroes_json: 116 | heroes = json.load(heroes_json) 117 | with open(load_json_file("items.json")) as items_json: 118 | items = json.load(items_json) 119 | with open(load_json_file("abilities.json")) as abilities_json: 120 | abilities = json.load(abilities_json) 121 | with open(load_json_file("lobbies.json")) as lobbies_json: 122 | lobbies = json.load(lobbies_json) 123 | with open(load_json_file("modes.json")) as modes_json: 124 | modes = json.load(modes_json) 125 | with open(load_json_file("regions.json")) as regions_json: 126 | regions = json.load(regions_json) 127 | with open(load_json_file("leaver.json")) as leaver_json: 128 | leavers = json.load(leaver_json) 129 | -------------------------------------------------------------------------------- /tests/src/reponse_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import requests 4 | 5 | from dota2api.src import response 6 | from dota2api.src.exceptions import * 7 | from dota2api.src.urls import * 8 | from tests.utils import * 9 | 10 | PLAYER_ID=3420585 11 | 12 | 13 | class TestBuildDota2Dict(unittest.TestCase): 14 | def setUp(self): 15 | self.executor = requests.get 16 | 17 | def test_get_match_history_with_no_param(self): 18 | url = BASE_URL + GET_MATCH_HISTORY + request_pars(LANGUAGE_PAR, 'account_id=None', STEAM_ID_PAR, 19 | 'format=json') 20 | request = self.executor(url) 21 | 22 | self.assertEqual(request.status_code, 200) 23 | 24 | dota2dict = response.build(request, url) 25 | 26 | self.assertEqual(len(dota2dict), 5) 27 | self.assertEqual(len(dota2dict['matches']), DEFAULT_MATCHES_SIZE) 28 | 29 | def test_get_match_history_with_limited_matches(self): 30 | url = BASE_URL + GET_MATCH_HISTORY + request_pars(LANGUAGE_PAR, 'account_id=None', STEAM_ID_PAR, 31 | 'format=json', 'matches_requested=1') 32 | request = self.executor(url) 33 | 34 | self.assertEqual(request.status_code, 200) 35 | dota2dict = response.build(request, url) 36 | self.assertEqual(len(dota2dict['matches']), 1) 37 | 38 | def test_get_match_history_from_only_one_player(self): 39 | url = BASE_URL + GET_MATCH_HISTORY + request_pars(LANGUAGE_PAR, 'account_id=%d' % PLAYER_ID, STEAM_ID_PAR, 40 | 'format=json', 'matches_requested=10') 41 | request = self.executor(url) 42 | 43 | self.assertEqual(request.status_code, 200) 44 | 45 | dota2dict = response.build(request, url) 46 | 47 | self.assertEqual(len(dota2dict['matches']), 10) 48 | for match in dota2dict['matches']: 49 | player_is_in_match = bool([p for p in match['players'] if p['account_id'] == PLAYER_ID]) 50 | self.assertTrue(player_is_in_match, 'Player was not in a match from the result') 51 | 52 | def test_request_match_detail_on_a_non_existent_match(self): 53 | url = BASE_URL + GET_MATCH_DETAILS + request_pars(LANGUAGE_PAR, STEAM_ID_PAR, 'format=json', 'match_id=1') 54 | request = self.executor(url) 55 | 56 | self.assertEqual(request.status_code, 200) 57 | self.assertRaises(APIError, response.build, request, url) 58 | 59 | def test_request_match_detail_with_no_match_id(self): 60 | url = BASE_URL + GET_MATCH_DETAILS + request_pars(LANGUAGE_PAR, STEAM_ID_PAR, 'format=json', 'match_id=None') 61 | request = self.executor(url) 62 | 63 | self.assertEqual(request.status_code, 200) 64 | self.assertRaises(APIError, response.build, request, url) 65 | 66 | def test_parse_hero_names_in_response(self): 67 | build = response.build(RequestMock().configure_single_match_result(), 'SomeUrl') 68 | 69 | self.assertEqual(build['players'][0]['hero_name'], "Nature's Prophet") 70 | self.assertEqual(build['players'][1]['hero_name'], "Naga Siren") 71 | self.assertEqual(build['players'][2]['hero_name'], "Death Prophet") 72 | self.assertEqual(build['players'][3]['hero_name'], "Weaver") 73 | self.assertEqual(build['players'][4]['hero_name'], "Undying") 74 | self.assertEqual(build['players'][5]['hero_name'], "Ember Spirit") 75 | self.assertEqual(build['players'][6]['hero_name'], "Pudge") 76 | self.assertEqual(build['players'][7]['hero_name'], "Meepo") 77 | self.assertEqual(build['players'][8]['hero_name'], "Lich") 78 | self.assertEqual(build['players'][9]['hero_name'], "Kunkka") 79 | 80 | def test_parse_items_names_in_response(self): 81 | build = response.build(RequestMock().configure_single_match_result(), 'SomeUrl') 82 | 83 | self.assertEqual(build['players'][0]['item_0_name'], "Phase Boots") 84 | self.assertEqual(build['players'][0]['item_1_name'], "Shadow Blade") 85 | self.assertEqual(build['players'][0]['item_2_name'], "Dagon") 86 | self.assertEqual(build['players'][0]['item_3_name'], "Hand of Midas") 87 | self.assertEqual(build['players'][0]['item_4_name'], "Town Portal Scroll") 88 | 89 | def test_parse_lobby_name_in_response(self): 90 | build = response.build(RequestMock().configure_single_match_result(), 'SomeUrl') 91 | 92 | self.assertEqual(build['lobby_name'], "Public matchmaking") 93 | 94 | def test_parse_lobby_name_in_response(self): 95 | build = response.build(RequestMock().configure_single_match_result(), 'SomeUrl') 96 | 97 | self.assertEqual(build['game_mode_name'], "All pick") 98 | 99 | def test_parse_lobby_name_in_response(self): 100 | build = response.build(RequestMock().configure_single_match_result(), 'SomeUrl') 101 | 102 | self.assertEqual(build['cluster_name'], "Europe West") 103 | 104 | def test_parse_leaver_status(self): 105 | build = response.build(RequestMock().configure_single_match_result(), 'SomeUrl') 106 | 107 | self.assertEqual(build['players'][0]['leaver_status'], 0) 108 | self.assertEqual(build['players'][0]['leaver_status_name'], "NONE") 109 | self.assertEqual(build['players'][0]['leaver_status_description'], "finished match, no abandon") 110 | -------------------------------------------------------------------------------- /tests/urls_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import unittest 4 | 5 | from dota2api import Initialise 6 | from dota2api.src import exceptions 7 | from dota2api.src.urls import * 8 | from tests.utils import * 9 | 10 | 11 | class UrlsMatchTests(unittest.TestCase): 12 | def setUp(self): 13 | self.api = Initialise(logging=True) 14 | 15 | def test_api_authentication_error(self): 16 | self.api.executor = RequestMock().configure_authentication_error() 17 | self.assertRaises(exceptions.APIAuthenticationError, self.api.get_match_history) 18 | 19 | self.api.executor.assert_called() 20 | 21 | def test_api_timeout_error(self): 22 | self.api.executor = RequestMock().configure_timeout_error() 23 | self.assertRaises(exceptions.APITimeoutError, self.api.get_match_history) 24 | 25 | self.api.executor.assert_called() 26 | 27 | def test_get_match_history_with_no_param(self): 28 | matcher = UrlMatcher(BASE_URL + GET_MATCH_HISTORY, 29 | LANGUAGE_PAR, 30 | 'account_id=None', 31 | STEAM_ID_PAR, 32 | 'format=json') 33 | 34 | self.api.executor = RequestMock(matcher).configure_success() 35 | self.api.get_match_history() 36 | self.api.executor.assert_called() 37 | 38 | def test_get_match_history_with_limited_matches(self): 39 | matcher = UrlMatcher(BASE_URL + GET_MATCH_HISTORY, LANGUAGE_PAR, 'account_id=None', STEAM_ID_PAR, 40 | 'format=json', 'matches_requested=1') 41 | 42 | self.api.executor = RequestMock(matcher).configure_success() 43 | self.api.get_match_history(matches_requested=1) 44 | self.api.executor.assert_called() 45 | 46 | def test_get_match_history_from_only_one_player(self): 47 | matcher = UrlMatcher(BASE_URL + GET_MATCH_HISTORY, LANGUAGE_PAR, 'account_id=88585077', 48 | STEAM_ID_PAR, 'format=json', 'matches_requested=10') 49 | 50 | self.api.executor = RequestMock(matcher).configure_success() 51 | self.api.get_match_history(account_id=88585077, matches_requested=10) 52 | self.api.executor.assert_called() 53 | 54 | def test_get_match_history_by_seq_num(self): 55 | matcher = UrlMatcher(BASE_URL + GET_MATCH_HISTORY_BY_SEQ_NUM, 56 | LANGUAGE_PAR, 57 | 'start_at_match_seq_num=988604774', 58 | STEAM_ID_PAR, 59 | 'format=json') 60 | 61 | self.api.executor = RequestMock(matcher).configure_success() 62 | self.api.get_match_history_by_seq_num(start_at_match_seq_num=988604774) 63 | self.api.executor.assert_called() 64 | 65 | def test_get_match_details_test(self): 66 | matcher = UrlMatcher(BASE_URL + GET_MATCH_DETAILS, LANGUAGE_PAR, STEAM_ID_PAR, 67 | 'match_id=988604774', 'format=json') 68 | 69 | self.api.executor = RequestMock(matcher).configure_success() 70 | self.api.get_match_details(match_id=988604774) 71 | self.api.executor.assert_called() 72 | 73 | def test_get_league_list(self): 74 | matcher = UrlMatcher(BASE_URL + GET_LEAGUE_LISTING, LANGUAGE_PAR, STEAM_ID_PAR, 'format=json') 75 | 76 | self.api.executor = RequestMock(matcher).configure_success() 77 | self.api.get_league_listing() 78 | self.api.executor.assert_called() 79 | 80 | def test_get_live_league_games(self): 81 | matcher = UrlMatcher(BASE_URL + GET_LIVE_LEAGUE_GAMES, LANGUAGE_PAR, STEAM_ID_PAR, 'format=json') 82 | 83 | self.api.executor = RequestMock(matcher).configure_success() 84 | self.api.get_live_league_games() 85 | self.api.executor.assert_called() 86 | 87 | def test_get_team_info_by_team_id(self): 88 | matcher = UrlMatcher(BASE_URL + GET_TEAM_INFO_BY_TEAM_ID, LANGUAGE_PAR, STEAM_ID_PAR, 'start_at_team_id=None', 89 | 'format=json') 90 | 91 | self.api.executor = RequestMock(matcher).configure_success() 92 | self.api.get_team_info_by_team_id() 93 | self.api.executor.assert_called() 94 | 95 | def test_get_team_info_by_team_id_with_parameter(self): 96 | matcher = UrlMatcher(BASE_URL + GET_TEAM_INFO_BY_TEAM_ID, LANGUAGE_PAR, STEAM_ID_PAR, 97 | 'start_at_team_id=123', 'format=json') 98 | 99 | self.api.executor = RequestMock(matcher).configure_success() 100 | self.api.get_team_info_by_team_id(123) 101 | self.api.executor.assert_called() 102 | 103 | def test_get_player_summaries(self): 104 | matcher = UrlMatcher(BASE_URL + GET_PLAYER_SUMMARIES, LANGUAGE_PAR, STEAM_ID_PAR, 105 | 'steamids=%5B76561198049003839%5D', 'format=json') 106 | 107 | self.api.executor = RequestMock(matcher).configure_success() 108 | account_id = 88738111 109 | self.api.get_player_summaries(convert_to_64_bit(account_id)) 110 | self.api.executor.assert_called() 111 | 112 | def test_get_heroes(self): 113 | matcher = UrlMatcher(BASE_URL + GET_HEROES, STEAM_ID_PAR, LANGUAGE_PAR, 'format=json') 114 | 115 | self.api.executor = RequestMock(matcher).configure_success() 116 | self.api.get_heroes() 117 | self.api.executor.assert_called() 118 | 119 | def test_get_game_items(self): 120 | matcher = UrlMatcher(BASE_URL + GET_GAME_ITEMS, STEAM_ID_PAR, LANGUAGE_PAR, 'format=json') 121 | 122 | self.api.executor = RequestMock(matcher).configure_success() 123 | self.api.get_game_items() 124 | self.api.executor.assert_called() 125 | 126 | def test_get_tournament_prize_pool(self): 127 | matcher = UrlMatcher(BASE_URL + GET_TOURNAMENT_PRIZE_POOL, STEAM_ID_PAR, LANGUAGE_PAR, 128 | 'leagueid=1', 'format=json') 129 | 130 | self.api.executor = RequestMock(matcher).configure_success() 131 | self.api.get_tournament_prize_pool(1) 132 | self.api.executor.assert_called() 133 | 134 | def test_player_summaries_converts_steam_ids_32b_automatically(self): 135 | matcher = UrlMatcher(BASE_URL + GET_PLAYER_SUMMARIES, STEAM_ID_PAR, LANGUAGE_PAR, 136 | 'steamids=%5B76561197960266049%5D', 'format=json') 137 | 138 | self.api.executor = RequestMock(matcher).configure_success() 139 | self.api.get_player_summaries(321) 140 | self.api.executor.assert_called() 141 | 142 | def test_get_top_live_games(self): 143 | matcher = UrlMatcher(BASE_URL + GET_TOP_LIVE_GAME, STEAM_ID_PAR, LANGUAGE_PAR, 'partner=', 'format=json') 144 | 145 | self.api.executor = RequestMock(matcher).configure_success() 146 | self.api.get_top_live_games() 147 | self.api.executor.assert_called() 148 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\dota2api.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\dota2api.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # dota2api documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Oct 30 16:27:41 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath('../')) 22 | 23 | # -- General configuration ------------------------------------------------ 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be 29 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 30 | # ones. 31 | extensions = [ 32 | 'sphinx.ext.autodoc', 33 | 'sphinx.ext.intersphinx', 34 | 'sphinx.ext.todo', 35 | 'sphinx.ext.ifconfig', 36 | 'sphinx.ext.viewcode', 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ['_templates'] 41 | 42 | # The suffix of source filenames. 43 | source_suffix = ['.txt', '.rst'] 44 | 45 | # The encoding of source files. 46 | #source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = 'index' 50 | 51 | # General information about the project. 52 | project = u'dota2api' 53 | copyright = u'2014, Joshua Duffy' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '0.0.1' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '1' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | #language = None 67 | 68 | # There are two options for replacing |today|: either, you set today to some 69 | # non-false value, then it is used: 70 | #today = '' 71 | # Else, today_fmt is used as the format for a strftime call. 72 | #today_fmt = '%B %d, %Y' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | exclude_patterns = [] 77 | 78 | # The reST default role (used for this markup: `text`) to use for all 79 | # documents. 80 | #default_role = None 81 | 82 | # If true, '()' will be appended to :func: etc. cross-reference text. 83 | #add_function_parentheses = True 84 | 85 | # If true, the current module name will be prepended to all description 86 | # unit titles (such as .. function::). 87 | #add_module_names = True 88 | 89 | # If true, sectionauthor and moduleauthor directives will be shown in the 90 | # output. They are ignored by default. 91 | #show_authors = False 92 | 93 | # The name of the Pygments (syntax highlighting) style to use. 94 | pygments_style = 'sphinx' 95 | 96 | # A list of ignored prefixes for module index sorting. 97 | #modindex_common_prefix = [] 98 | 99 | # If true, keep warnings as "system message" paragraphs in the built documents. 100 | #keep_warnings = False 101 | 102 | 103 | # -- Options for HTML output ---------------------------------------------- 104 | 105 | # The theme to use for HTML and HTML Help pages. See the documentation for 106 | # a list of builtin themes. 107 | html_theme = 'default' 108 | 109 | # Theme options are theme-specific and customize the look and feel of a theme 110 | # further. For a list of options available for each theme, see the 111 | # documentation. 112 | #html_theme_options = {} 113 | 114 | # Add any paths that contain custom themes here, relative to this directory. 115 | #html_theme_path = [] 116 | 117 | # The name for this set of Sphinx documents. If None, it defaults to 118 | # " v documentation". 119 | #html_title = None 120 | 121 | # A shorter title for the navigation bar. Default is the same as html_title. 122 | #html_short_title = None 123 | 124 | # The name of an image file (relative to this directory) to place at the top 125 | # of the sidebar. 126 | #html_logo = None 127 | 128 | # The name of an image file (within the static path) to use as favicon of the 129 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 130 | # pixels large. 131 | #html_favicon = None 132 | 133 | # Add any paths that contain custom static files (such as style sheets) here, 134 | # relative to this directory. They are copied after the builtin static files, 135 | # so a file named "default.css" will overwrite the builtin "default.css". 136 | html_static_path = ['_static'] 137 | 138 | # Add any extra paths that contain custom files (such as robots.txt or 139 | # .htaccess) here, relative to this directory. These files are copied 140 | # directly to the root of the documentation. 141 | #html_extra_path = [] 142 | 143 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 144 | # using the given strftime format. 145 | #html_last_updated_fmt = '%b %d, %Y' 146 | 147 | # If true, SmartyPants will be used to convert quotes and dashes to 148 | # typographically correct entities. 149 | #html_use_smartypants = True 150 | 151 | # Custom sidebar templates, maps document names to template names. 152 | #html_sidebars = {} 153 | 154 | # Additional templates that should be rendered to pages, maps page names to 155 | # template names. 156 | #html_additional_pages = {} 157 | 158 | # If false, no module index is generated. 159 | #html_domain_indices = True 160 | 161 | # If false, no index is generated. 162 | #html_use_index = True 163 | 164 | # If true, the index is split into individual pages for each letter. 165 | #html_split_index = False 166 | 167 | # If true, links to the reST sources are added to the pages. 168 | #html_show_sourcelink = True 169 | 170 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 171 | #html_show_sphinx = True 172 | 173 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 174 | #html_show_copyright = True 175 | 176 | # If true, an OpenSearch description file will be output, and all pages will 177 | # contain a tag referring to it. The value of this option must be the 178 | # base URL from which the finished HTML is served. 179 | #html_use_opensearch = '' 180 | 181 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 182 | #html_file_suffix = None 183 | 184 | # Output file base name for HTML help builder. 185 | htmlhelp_basename = 'dota2apidoc' 186 | 187 | 188 | # -- Options for LaTeX output --------------------------------------------- 189 | 190 | latex_elements = { 191 | # The paper size ('letterpaper' or 'a4paper'). 192 | #'papersize': 'letterpaper', 193 | 194 | # The font size ('10pt', '11pt' or '12pt'). 195 | #'pointsize': '10pt', 196 | 197 | # Additional stuff for the LaTeX preamble. 198 | #'preamble': '', 199 | } 200 | 201 | # Grouping the document tree into LaTeX files. List of tuples 202 | # (source start file, target name, title, 203 | # author, documentclass [howto, manual, or own class]). 204 | latex_documents = [ 205 | ('index', 'dota2api.tex', u'dota2api Documentation', 206 | u'Joshua Duffy', 'manual'), 207 | ] 208 | 209 | # The name of an image file (relative to this directory) to place at the top of 210 | # the title page. 211 | #latex_logo = None 212 | 213 | # For "manual" documents, if this is true, then toplevel headings are parts, 214 | # not chapters. 215 | #latex_use_parts = False 216 | 217 | # If true, show page references after internal links. 218 | #latex_show_pagerefs = False 219 | 220 | # If true, show URL addresses after external links. 221 | #latex_show_urls = False 222 | 223 | # Documents to append as an appendix to all manuals. 224 | #latex_appendices = [] 225 | 226 | # If false, no module index is generated. 227 | #latex_domain_indices = True 228 | 229 | 230 | # -- Options for manual page output --------------------------------------- 231 | 232 | # One entry per manual page. List of tuples 233 | # (source start file, name, description, authors, manual section). 234 | man_pages = [ 235 | ('index', 'dota2api', u'dota2api Documentation', 236 | [u'Joshua Duffy'], 1) 237 | ] 238 | 239 | # If true, show URL addresses after external links. 240 | #man_show_urls = False 241 | 242 | 243 | # -- Options for Texinfo output ------------------------------------------- 244 | 245 | # Grouping the document tree into Texinfo files. List of tuples 246 | # (source start file, target name, title, author, 247 | # dir menu entry, description, category) 248 | texinfo_documents = [ 249 | ('index', 'dota2api', u'dota2api Documentation', 250 | u'Joshua Duffy', 'dota2api', 'One line description of project.', 251 | 'Miscellaneous'), 252 | ] 253 | 254 | # Documents to append as an appendix to all manuals. 255 | #texinfo_appendices = [] 256 | 257 | # If false, no module index is generated. 258 | #texinfo_domain_indices = True 259 | 260 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 261 | #texinfo_show_urls = 'footnote' 262 | 263 | # If true, do not generate a @detailmenu in the "Top" node's menu. 264 | #texinfo_no_detailmenu = False 265 | 266 | 267 | # Example configuration for intersphinx: refer to the Python standard library. 268 | intersphinx_mapping = {'http://docs.python.org/': None} 269 | -------------------------------------------------------------------------------- /dota2api/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """Dota 2 API wrapper and parser in Python""" 4 | 5 | __author__ = "Joshua Duffy, Evaldo Bratti" 6 | __date__ = "29/10/2014" 7 | __version__ = "1.3.3" 8 | __licence__ = "GPL" 9 | 10 | import json 11 | import collections 12 | 13 | try: 14 | from urllib import urlencode 15 | except ImportError: 16 | from urllib.parse import urlencode 17 | 18 | import os 19 | import requests 20 | 21 | from .src import urls, exceptions, response, parse 22 | 23 | 24 | class Initialise(object): 25 | """When calling this you need to provide the ``api_key`` 26 | You can also specify a ``language`` 27 | 28 | :param api_key: (str) string with the ``api key`` 29 | :param logging: (bool, optional) set this to True for logging output 30 | :param raw_mode: (bool, optional) get the raw data from dota2 API without parsing it into human-readable string 31 | """ 32 | 33 | def __init__(self, api_key=None, executor=None, language=None, logging=None, raw_mode=None): 34 | if api_key: 35 | self.api_key = api_key 36 | elif 'D2_API_KEY' in os.environ: 37 | self.api_key = os.environ['D2_API_KEY'] 38 | else: 39 | raise exceptions.APIAuthenticationError() 40 | 41 | if not language: 42 | self.language = "en_us" 43 | else: 44 | self.language = language 45 | 46 | if not executor: 47 | self.executor = requests.get 48 | else: 49 | self.executor = executor 50 | 51 | if logging: 52 | self.logger = _setup_logger() 53 | else: 54 | self.logger = None 55 | 56 | if raw_mode: 57 | self.raw_mode = True 58 | else: 59 | self.raw_mode = False 60 | 61 | self.__format = "json" 62 | 63 | def set_raw_mode(self, raw_mode): 64 | self.raw_mode = raw_mode 65 | 66 | def get_match_history(self, account_id=None, **kwargs): 67 | """Returns a dictionary containing a list of the most recent Dota matches 68 | 69 | :param account_id: (int, optional) 70 | :param hero_id: (int, optional) 71 | :param game_mode: (int, optional) see ``ref/modes.json`` 72 | :param skill: (int, optional) see ``ref/skill.json`` 73 | :param min_players: (int, optional) only return matches with minimum 74 | amount of players 75 | :param league_id: (int, optional) for ids use ``get_league_listing()`` 76 | :param start_at_match_id: (int, optional) start at matches equal to or 77 | older than this match id 78 | :param matches_requested: (int, optional) defaults to ``100`` 79 | :param tournament_games_only: (str, optional) limit results to 80 | tournament matches only 81 | :return: dictionary of matches, see :doc:`responses ` 82 | """ 83 | if 'account_id' not in kwargs: 84 | kwargs['account_id'] = account_id 85 | url = self.__build_url(urls.GET_MATCH_HISTORY, **kwargs) 86 | req = self.executor(url) 87 | if self.logger: 88 | self.logger.info('URL: {0}'.format(url)) 89 | if not self.__check_http_err(req.status_code): 90 | return response.build(req, url, self.raw_mode) 91 | 92 | def get_match_history_by_seq_num(self, start_at_match_seq_num=None, **kwargs): 93 | """Returns a dictionary containing a list of Dota matches in the order they were recorded 94 | 95 | :param start_at_match_seq_num: (int, optional) start at matches equal to or 96 | older than this match id 97 | :param matches_requested: (int, optional) defaults to ``100`` 98 | :return: dictionary of matches, see :doc:`responses ` 99 | """ 100 | if 'start_at_match_seq_num' not in kwargs: 101 | kwargs['start_at_match_seq_num'] = start_at_match_seq_num 102 | url = self.__build_url(urls.GET_MATCH_HISTORY_BY_SEQ_NUM, **kwargs) 103 | req = self.executor(url) 104 | if self.logger: 105 | self.logger.info('URL: {0}'.format(url)) 106 | if not self.__check_http_err(req.status_code): 107 | return response.build(req, url, self.raw_mode) 108 | 109 | def get_match_details(self, match_id=None, **kwargs): 110 | """Returns a dictionary containing the details for a Dota 2 match 111 | 112 | :param match_id: (int, optional) 113 | :return: dictionary of matches, see :doc:`responses ` 114 | """ 115 | if 'match_id' not in kwargs: 116 | kwargs['match_id'] = match_id 117 | url = self.__build_url(urls.GET_MATCH_DETAILS, **kwargs) 118 | req = self.executor(url) 119 | if self.logger: 120 | self.logger.info('URL: {0}'.format(url)) 121 | if not self.__check_http_err(req.status_code): 122 | return response.build(req, url, self.raw_mode) 123 | 124 | def get_league_listing(self): 125 | """Returns a dictionary containing a list of all ticketed leagues 126 | 127 | :return: dictionary of ticketed leagues, see :doc:`responses ` 128 | """ 129 | url = self.__build_url(urls.GET_LEAGUE_LISTING) 130 | req = self.executor(url) 131 | if self.logger: 132 | self.logger.info('URL: {0}'.format(url)) 133 | if not self.__check_http_err(req.status_code): 134 | return response.build(req, url, self.raw_mode) 135 | 136 | def get_live_league_games(self): 137 | """Returns a dictionary containing a list of ticked games in progress 138 | 139 | :return: dictionary of live games, see :doc:`responses ` 140 | """ 141 | url = self.__build_url(urls.GET_LIVE_LEAGUE_GAMES) 142 | req = self.executor(url) 143 | if self.logger: 144 | self.logger.info('URL: {0}'.format(url)) 145 | if not self.__check_http_err(req.status_code): 146 | return response.build(req, url, self.raw_mode) 147 | 148 | def get_team_info_by_team_id(self, start_at_team_id=None, **kwargs): 149 | """Returns a dictionary containing a in-game teams 150 | 151 | :param start_at_team_id: (int, optional) 152 | :param teams_requested: (int, optional) 153 | :return: dictionary of teams, see :doc:`responses ` 154 | """ 155 | if 'start_at_team_id' not in kwargs: 156 | kwargs['start_at_team_id'] = start_at_team_id 157 | url = self.__build_url(urls.GET_TEAM_INFO_BY_TEAM_ID, **kwargs) 158 | req = self.executor(url) 159 | if self.logger: 160 | self.logger.info('URL: {0}'.format(url)) 161 | if not self.__check_http_err(req.status_code): 162 | return response.build(req, url, self.raw_mode) 163 | 164 | def get_player_summaries(self, steamids=None, **kwargs): 165 | """Returns a dictionary containing a player summaries 166 | 167 | :param steamids: (list) list of ``32-bit`` or ``64-bit`` steam ids, notice 168 | that api will convert if ``32-bit`` are given 169 | :return: dictionary of player summaries, see :doc:`responses ` 170 | """ 171 | if not isinstance(steamids, collections.Iterable): 172 | steamids = [steamids] 173 | 174 | base64_ids = list(map(convert_to_64_bit, filter(lambda x: x is not None, steamids))) 175 | 176 | if 'steamids' not in kwargs: 177 | kwargs['steamids'] = base64_ids 178 | url = self.__build_url(urls.GET_PLAYER_SUMMARIES, **kwargs) 179 | req = self.executor(url) 180 | if self.logger: 181 | self.logger.info('URL: {0}'.format(url)) 182 | if not self.__check_http_err(req.status_code): 183 | return response.build(req, url, self.raw_mode) 184 | 185 | def get_heroes(self, **kwargs): 186 | """Returns a dictionary of in-game heroes, used to parse ids into localised names 187 | 188 | :return: dictionary of heroes, see :doc:`responses ` 189 | """ 190 | url = self.__build_url(urls.GET_HEROES, language=self.language, **kwargs) 191 | req = self.executor(url) 192 | if self.logger: 193 | self.logger.info('URL: {0}'.format(url)) 194 | if not self.__check_http_err(req.status_code): 195 | return response.build(req, url, self.raw_mode) 196 | 197 | def get_game_items(self, **kwargs): 198 | """Returns a dictionary of in-game items, used to parse ids into localised names 199 | 200 | :return: dictionary of items, see :doc:`responses ` 201 | """ 202 | url = self.__build_url(urls.GET_GAME_ITEMS, language=self.language, **kwargs) 203 | req = self.executor(url) 204 | if self.logger: 205 | self.logger.info('URL: {0}'.format(url)) 206 | if not self.__check_http_err(req.status_code): 207 | return response.build(req, url, self.raw_mode) 208 | 209 | def get_tournament_prize_pool(self, leagueid=None, **kwargs): 210 | """Returns a dictionary that includes community funded tournament prize pools 211 | 212 | :param leagueid: (int, optional) 213 | :return: dictionary of prize pools, see :doc:`responses ` 214 | """ 215 | if 'leagueid' not in kwargs: 216 | kwargs['leagueid'] = leagueid 217 | url = self.__build_url(urls.GET_TOURNAMENT_PRIZE_POOL, **kwargs) 218 | req = self.executor(url) 219 | if self.logger: 220 | self.logger.info('URL: {0}'.format(url)) 221 | if not self.__check_http_err(req.status_code): 222 | return response.build(req, url, self.raw_mode) 223 | 224 | def get_top_live_games(self, partner='', **kwargs): 225 | """Returns a dictionary that includes top MMR live games 226 | 227 | :param partner: (int, optional) 228 | :return: dictionary of prize pools, see :doc:`responses ` 229 | """ 230 | if 'partner' not in kwargs: 231 | kwargs['partner'] = partner 232 | url = self.__build_url(urls.GET_TOP_LIVE_GAME, **kwargs) 233 | req = self.executor(url) 234 | if self.logger: 235 | self.logger.info('URL: {0}'.format(url)) 236 | if not self.__check_http_err(req.status_code): 237 | return response.build(req, url, self.raw_mode) 238 | 239 | def update_game_items(self): 240 | """ 241 | Update the item reference data via the API 242 | """ 243 | _save_dict_to_file(self.get_game_items(), "items.json") 244 | 245 | def update_heroes(self): 246 | """ 247 | Update the hero reference data via the API 248 | """ 249 | _save_dict_to_file(self.get_heroes(), "heroes.json") 250 | 251 | def __build_url(self, api_call, **kwargs): 252 | """Builds the api query""" 253 | kwargs['key'] = self.api_key 254 | if 'language' not in kwargs: 255 | kwargs['language'] = self.language 256 | if 'format' not in kwargs: 257 | kwargs['format'] = self.__format 258 | api_query = urlencode(kwargs) 259 | 260 | return "{0}{1}?{2}".format(urls.BASE_URL, 261 | api_call, 262 | api_query) 263 | 264 | def __check_http_err(self, status_code): 265 | """Raises an exception if we get a http error""" 266 | if status_code == 403: 267 | raise exceptions.APIAuthenticationError(self.api_key) 268 | elif status_code == 503: 269 | raise exceptions.APITimeoutError() 270 | else: 271 | return False 272 | 273 | 274 | def convert_to_64_bit(number): 275 | min64b = 76561197960265728 276 | if number < min64b: 277 | return number + min64b 278 | return number 279 | 280 | 281 | def _setup_logger(): 282 | import logging 283 | logging.basicConfig(level=logging.NOTSET) # Will log all 284 | return logging.getLogger(__name__) 285 | 286 | 287 | def _save_dict_to_file(json_dict, file_name): 288 | out_file = open(parse.load_json_file(file_name), "w") 289 | json.dump(json_dict, out_file, indent=4) 290 | out_file.close() 291 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | -------------------------------------------------------------------------------- /docs/source/responses.rst: -------------------------------------------------------------------------------- 1 | ######### 2 | Responses 3 | ######### 4 | 5 | This section describes the dictionary structure of each response. 6 | 7 | Every response has a number of attributes you can use. For example: 8 | 9 | .. code-block:: python 10 | 11 | >>> match = api.get_match_details(match_id=1000193456) 12 | 13 | The following will return the URL constructed by the library: 14 | 15 | .. code-block:: python 16 | 17 | >>> match.url 18 | 19 | The following will return the response as raw json: 20 | 21 | .. code-block:: python 22 | 23 | >>> match.json 24 | 25 | ******************* 26 | get_match_history() 27 | ******************* 28 | Returns a dictionary with a list of ``players`` within. 29 | 30 | ``match::lobby_type`` -- see :ref:`lobby_type`. 31 | 32 | ``player::player_slot`` -- see :ref:`player_slot`. 33 | 34 | .. code-block:: text 35 | 36 | { 37 | num_results - Number of matches within a single response 38 | total_results - Total number of matches for this query 39 | results_remaining - Number of matches remaining to be retrieved with subsequent API calls 40 | [matches] - List of matches for this response 41 | { 42 | match_id - Unique match ID 43 | match_seq_num - Number indicating position in which this match was recorded 44 | start_time - Unix timestamp of beginning of match 45 | lobby_type - See lobby_type table 46 | [player] - List of players in the match 47 | { 48 | account_id - Unique account ID 49 | player_slot - Player's position within the team 50 | hero_id - Unique hero ID 51 | } 52 | } 53 | } 54 | 55 | .. _player_slot: 56 | 57 | player_slot 58 | ============= 59 | 60 | The player slot is an 8-bit representation of the player's team and the slot (0-4) within the team. 61 | 62 | .. code-block:: text 63 | 64 | ┌─────────────── Team (false if Radiant, true if Dire). 65 | │ ┌─┬─┬─┬─────── Not used. 66 | │ │ │ │ │ ┌─┬─┬─ The position of a player within their team (0-4). 67 | │ │ │ │ │ │ │ │ 68 | 0 0 0 0 0 0 0 0 69 | 70 | ****************************** 71 | get_match_history_by_seq_num() 72 | ****************************** 73 | 74 | Returns a dictionary with a list of ``matches`` within. See :ref:`get_match_details()` for structure of matches. 75 | 76 | .. code-block:: text 77 | 78 | { 79 | status 80 | 1 - Success 81 | 8 - Matches_requested must be greater than 0 82 | statusDetail - Message explaining a status that is not equal to 1 83 | [matches] - See get_match_details() 84 | } 85 | 86 | .. _get_match_details(): 87 | 88 | ******************* 89 | get_match_details() 90 | ******************* 91 | 92 | Returns a ``match`` dictionary with ``players``. 93 | 94 | For dynamic values such as kills or gold, if the match is live, then the value is current as of 95 | the API call. For matches that have finished, these values are simply the value at the end of the 96 | match for the player. 97 | 98 | ``lobby_type`` -- see :ref:`lobby_type`. 99 | 100 | ``game_mode`` and ``game_mode_name`` -- see :ref:`game_mode` 101 | 102 | .. code-block:: text 103 | 104 | { 105 | season - Season the game was played in 106 | radiant_win - Win status of game (True for Radiant win, False for Dire win) 107 | duration - Elapsed match time in seconds 108 | start_time - Unix timestamp for beginning of match 109 | match_id - Unique match ID 110 | match_seq_num - Number indicating position in which this match was recorded 111 | tower_status_radiant - Status of Radiant towers 112 | tower_status_dire - Status of Dire towers 113 | barracks_status_radiant - Status of Radiant barracks 114 | barracks_status_dire - Status of Dire barracks 115 | cluster - The server cluster the match was played on, used in retrieving replays 116 | cluster_name - The region the match was played on 117 | first_blood_time - Time elapsed in seconds since first blood of the match 118 | lobby_type - See lobby_type table 119 | lobby_name - See lobby_type table 120 | human_players - Number of human players in the match 121 | leagueid - Unique league ID 122 | positive_votes - Number of positive/thumbs up votes 123 | negative_votes - Number of negative/thumbs down votes 124 | game_mode - See game_mode table 125 | game_mode_name - See game_mode table 126 | radiant_captain - Account ID for Radiant captain 127 | dire_captain - Account ID for Dire captain 128 | [pick_bans] 129 | { 130 | { 131 | hero_id - Unique hero ID 132 | is_pick - True if hero was picked, False if hero was banned 133 | order - Order of pick or ban in overall pick/ban sequence 134 | team - See team_id table. 135 | 136 | } 137 | } 138 | [players] 139 | { 140 | account_id - Unique account ID 141 | player_slot - Player's position within the team 142 | hero_id - Unique hero ID 143 | hero_name - Hero's name 144 | item_# - Item ID for item in slot # (0-5) 145 | item_#_name - Item name for item in slot # (0-5) 146 | kills - Number of kills by player 147 | deaths - Number of player deaths 148 | assists - Number of player assists 149 | leaver_status - Connection/leaving status of player 150 | gold - Gold held by player 151 | last_hits - Number of last hits by player (creep score) 152 | denies - Number of denies 153 | gold_per_min - Average gold per minute 154 | xp_per_min - Average XP per minute 155 | gold_spent - Total amount of gold spent 156 | hero_damage - Amount of hero damage dealt by player 157 | tower_damage - Amount of tower damage dealt by player 158 | hero_healing - Amount of healing done by player 159 | level - Level of player's hero 160 | [ability_upgrades] - Order of abilities chosen by player 161 | { 162 | ability - Ability chosen 163 | time - Time in seconds since match start when ability was upgraded 164 | level - Level of player at time of upgrading 165 | } 166 | 167 | [additional_units] - Only available if the player has a additional unit 168 | { 169 | unitname - Name of unit 170 | item_# - ID of item in slot # (0-5) 171 | } 172 | } 173 | // These fields are only available for matches with teams // 174 | [radiant_team] 175 | { 176 | team_name - Team name for Radiant 177 | team_logo - Team logo for Radiant 178 | team_complete - ? 179 | } 180 | [dire_team] 181 | { 182 | team_name - Team name for Dire 183 | team_logo - Team logo for Dire 184 | team_team_complete - ? 185 | } 186 | } 187 | 188 | 189 | 190 | ******************** 191 | get_league_listing() 192 | ******************** 193 | 194 | Returns a dictionary with a list of ``leagues`` within; can be viewed with DotaTV. 195 | 196 | .. code-block:: text 197 | 198 | { 199 | [leagues] 200 | { 201 | description - Description of the league 202 | itemdef - ID for an item associated with the tournament 203 | leagueid - Unique league ID 204 | name - Name of the league 205 | tournament_url - League website information 206 | } 207 | } 208 | 209 | 210 | *********************** 211 | get_live_league_games() 212 | *********************** 213 | 214 | Returns a dictionary with a list of league ``games`` within. 215 | 216 | ``league_tier`` -- see :ref:`league_tier`. 217 | 218 | ``tower_state`` -- see :ref:`towers_and_barracks`. 219 | 220 | ``series_type`` -- see :ref:`series_type`. 221 | 222 | ``player::team`` -- see :ref:`team_id`. 223 | 224 | .. code-block:: text 225 | 226 | { 227 | [games] 228 | { 229 | league_id - ID for the league in which the match is being played 230 | league_tier - Level of play in this game 231 | league_series_id - ? 232 | [players] - list of all players in the match 233 | { 234 | account_id - Unique account ID 235 | name - In-game display name 236 | hero_id - Unique hero ID 237 | team - Team the player is on 238 | } 239 | series_id - ID for the game series 240 | series_type - Type of tournament series 241 | stage_name - ? 242 | game_number - Game number of the series 243 | radiant_series_wins - Number of wins by Radiant during the series 244 | dire_series_wins - Number of wins by Dire during the series 245 | tower_state - State of *all* towers in the match 246 | spectators - Number of spectators watching 247 | lobby_id - ID for the match's lobby 248 | stream_delay_s - Delay in seconds for streaming to spectators 249 | 250 | // These fields are only available for matches with teams // 251 | [radiant_team] 252 | { 253 | team_name - Team name for Radiant 254 | team_logo - Team logo for Radiant 255 | team_complete - ? 256 | } 257 | [dire_team] 258 | { 259 | team_name - Team name for Dire 260 | team_logo - Team logo for Dire 261 | team_team_complete - ? 262 | } 263 | } 264 | } 265 | 266 | ************************** 267 | get_team_info_by_team_id() 268 | ************************** 269 | 270 | Returns a dictionary with a list of ``teams`` within. 271 | 272 | .. code-block:: text 273 | 274 | { 275 | status - 1 if success, non-1 otherwise 276 | [teams] 277 | { 278 | admin_account_id - Account ID for team admin 279 | calibration_games_remaining - ? 280 | country_code - ISO 3166-1 country code 281 | games_played - Number of games played by team with current team members 282 | league_id_# - League IDs in which the team has played 283 | logo - UGC ID for the team logo 284 | logo_sponsor - UGC ID for the team sponsor logo 285 | name - Team's name 286 | player_#_account_id - Account ID for player # (0-5) 287 | tag - Team's tag 288 | team_id - Unique team ID 289 | time_created - Unix timestamp of team creation 290 | url - Team-provided URL 291 | } 292 | } 293 | 294 | 295 | ********************** 296 | get_player_summaries() 297 | ********************** 298 | 299 | Returns a dictionary with a list of ``players`` within. 300 | 301 | .. code-block:: text 302 | 303 | { 304 | [player] 305 | { 306 | avatar - 32x32 avatar image 307 | avatarfull - 184x184 avatar image 308 | avatarmedium - 64x64 avatar image 309 | communityvisibilitystate - See table below. 310 | lastlogoff - Unix timestamp since last time logged out of steam 311 | personaname - Equivalent of Steam username 312 | personastate - See table below. 313 | personastateflags - ? 314 | primaryclanid - 64-bit unique clan identifier 315 | profilestate - ? 316 | profileurl - Steam profile URL 317 | realname - User's given name 318 | steamid - Unique Steam ID 319 | timecreated - Unix timestamp of profile creation time 320 | } 321 | } 322 | 323 | communityvisibilitystate 324 | ======================== 325 | 326 | .. csv-table:: 327 | :header: "Value", "Description" 328 | 329 | 1, Private 330 | 2, Friends only 331 | 3, Friends of friends 332 | 4, Users only 333 | 5, Public 334 | 335 | personastate 336 | ============ 337 | 338 | .. csv-table:: 339 | :header: "Value", "Description" 340 | 341 | 0, Offline 342 | 1, Online 343 | 2, Busy 344 | 3, Away 345 | 4, Snooze 346 | 5, Looking to trade 347 | 6, Looking to play 348 | 349 | ************ 350 | get_heroes() 351 | ************ 352 | 353 | .. code-block:: text 354 | 355 | { 356 | count - Number of results 357 | status - HTTP status code 358 | [heroes] 359 | { 360 | id - Unique hero ID 361 | name - Hero's name 362 | localized_name - Localized version of hero's name 363 | url_full_portrait - URL to full-size hero portrait (256x144) 364 | url_large_portrait - URL to large hero portrait (205x115) 365 | url_small_portrait - URL to small hero portrait (59x33) 366 | url_vertical_portrait - URL to vertical hero portrait (235x272) 367 | } 368 | } 369 | 370 | **************** 371 | get_game_items() 372 | **************** 373 | 374 | .. code-block:: text 375 | 376 | { 377 | count - Number of results 378 | status - HTTP status respose 379 | [items] 380 | { 381 | id - Unique item ID 382 | name - Item's name 383 | cost - Item's gold cost in game, 0 if recipe 384 | localized_name - Item's localized name 385 | recipe - True if item is a recipe item, false otherwise 386 | secret_shop - True if item is bought at the secret shop, false otherwise 387 | side_shop - True if item is bought at the side shop, false otherwise 388 | } 389 | } 390 | 391 | *************************** 392 | get_tournament_prize_pool() 393 | *************************** 394 | 395 | .. code-block:: text 396 | 397 | { 398 | league_id - Unique league ID 399 | prizepool - Current prize pool if the league includes a community-funded pool, otherwise 0 400 | status - HTTP status code 401 | } 402 | 403 | .. _towers_and_barracks: 404 | 405 | *************************** 406 | Towers and Barracks 407 | *************************** 408 | 409 | Combined status 410 | =============== 411 | 412 | The overall match tower and barracks status uses 32 bits for representation and should be interpreted as follows: 413 | 414 | .. code-block:: text 415 | 416 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬───────────────────────────────────────────── Not used. 417 | │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────────── Dire Ancient Top 418 | │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────────────────────────── Dire Ancient Bottom 419 | │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────────── Dire Bottom Tier 3 420 | │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────────────────────── Dire Bottom Tier 2 421 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────────── Dire Bottom Tier 1 422 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────────────────── Dire Middle Tier 3 423 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────────── Dire Middle Tier 2 424 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────────────── Dire Middle Tier 1 425 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────────── Dire Top Tier 3 426 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────────── Dire Top Tier 2 427 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────────── Dire Top Tier 1 428 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────────── Radiant Ancient Top 429 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────────── Radiant Ancient Bottom 430 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────────── Radiant Bottom Tier 3 431 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────── Radiant Bottom Tier 2 432 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────────── Radiant Bottom Tier 1 433 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────── Radiant Middle Tier 3 434 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────── Radiant Middle Tier 2 435 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────── Radiant Middle Tier 1 436 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───── Radiant Top Tier 3 437 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─── Radiant Top Tier 2 438 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─ Radiant Top Tier 1 439 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 440 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 441 | 442 | Single team tower status 443 | ======================== 444 | 445 | The tower status for a single team uses 16 bits for representation and should be interpreted as follows: 446 | 447 | .. code-block:: text 448 | 449 | ┌─┬─┬─┬─┬─────────────────────── Not used. 450 | │ │ │ │ │ ┌───────────────────── Ancient Bottom 451 | │ │ │ │ │ │ ┌─────────────────── Ancient Top 452 | │ │ │ │ │ │ │ ┌───────────────── Bottom Tier 3 453 | │ │ │ │ │ │ │ │ ┌─────────────── Bottom Tier 2 454 | │ │ │ │ │ │ │ │ │ ┌───────────── Bottom Tier 1 455 | │ │ │ │ │ │ │ │ │ │ ┌─────────── Middle Tier 3 456 | │ │ │ │ │ │ │ │ │ │ │ ┌───────── Middle Tier 2 457 | │ │ │ │ │ │ │ │ │ │ │ │ ┌─────── Middle Tier 1 458 | │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───── Top Tier 3 459 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─── Top Tier 2 460 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─ Top Tier 1 461 | │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 462 | 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 463 | 464 | Single team barracks status 465 | =========================== 466 | 467 | The barracks status uses 8 bits for representation and should be interpreted as follows: 468 | 469 | .. code-block:: text 470 | 471 | ┌─┬───────────── Not used. 472 | │ │ ┌─────────── Bottom Ranged 473 | │ │ │ ┌───────── Bottom Melee 474 | │ │ │ │ ┌─────── Middle Ranged 475 | │ │ │ │ │ ┌───── Middle Melee 476 | │ │ │ │ │ │ ┌─── Top Ranged 477 | │ │ │ │ │ │ │ ┌─ Top Melee 478 | │ │ │ │ │ │ │ │ 479 | 0 0 0 0 0 0 0 0 480 | 481 | .. _status_code_mappings: 482 | 483 | *************************** 484 | Status code mappings 485 | *************************** 486 | 487 | These tables outline various codes/status in responses and their meaning. 488 | 489 | See ``dota2api.parse`` for various parsing utilities. 490 | 491 | .. _series_type: 492 | 493 | series_type 494 | =========== 495 | .. csv-table:: 496 | :header: "Value", "Description" 497 | 498 | 0, Non-series 499 | 1, Best of 3 500 | 2, Best of 5 501 | 502 | league_tier 503 | =========== 504 | .. csv-table:: 505 | :header: "Value", "Description" 506 | 507 | 1, Amateur 508 | 2, Professional 509 | 3, Premier 510 | 511 | .. _game_mode: 512 | 513 | game_mode 514 | ========= 515 | .. csv-table:: 516 | :header: "Value", "Description" 517 | 518 | 0, Unknown 519 | 1, All Pick 520 | 2, Captain's Mode 521 | 3, Random Draft 522 | 4, Single Draft 523 | 5, All Random 524 | 6, Intro 525 | 7, Diretide 526 | 8, Reverse Captain's Mode 527 | 9, The Greeviling 528 | 10, Tutorial 529 | 11, Mid Only 530 | 12, Least Played 531 | 13, New Player Pool 532 | 14, Compendium Matchmaking 533 | 15, Custom 534 | 16, Captains Draft 535 | 17, Balanced Draft 536 | 18, Ability Draft 537 | 19, Event (?) 538 | 20, All Random Death Match 539 | 21, Solo Mid 1 vs 1 540 | 22, Ranked All Pick 541 | 542 | .. _lobby_type: 543 | 544 | lobby_type 545 | ========== 546 | .. csv-table:: 547 | :header: "Status", "Description" 548 | 549 | -1, Invalid 550 | 0, Public matchmaking 551 | 1, Practice 552 | 2, Tournament 553 | 3, Tutorial 554 | 4, Co-op with AI 555 | 5, Team match 556 | 6, Solo queue 557 | 7, Ranked matchmaking 558 | 8, Solo Mid 1 vs 1 559 | 560 | .. _leaver_status: 561 | 562 | leaver_status 563 | ============= 564 | .. csv-table:: 565 | :header: "ID", "Value", "Description" 566 | 567 | 0, "NONE", "finished match, no abandon" 568 | 1, "DISCONNECTED", "player DC, no abandon" 569 | 2, "DISCONNCECTED_TOO_LONG", "player DC > 5min, abandon" 570 | 3, "ABANDONED", "player dc, clicked leave, abandon" 571 | 4, "AFK", "player AFK, abandon" 572 | 5, "NEVER_CONNECTED", "never connected, no abandon" 573 | 6, "NEVER_CONNECTED_TOO_LONG", "too long to connect, no abandon" 574 | 575 | .. _team_id: 576 | 577 | team_id 578 | ======= 579 | .. csv-table:: 580 | :header: "Value", "Description" 581 | 582 | 0, Radiant 583 | 1, Dire 584 | 2, Broadcaster 585 | 3+, Unassigned (?) 586 | 587 | 588 | **************** 589 | get_top_live_games() 590 | **************** 591 | 592 | Returns a dictionary that includes top MMR live games 593 | 594 | .. code-block:: text 595 | 596 | { 597 | [game_list] 598 | { 599 | activate_time - 600 | deactivate_time - 601 | server_steam_id - 602 | lobby_id - 603 | league_id - League ID (Available for league matches) 604 | lobby_type - See lobby_type table 605 | game_time - Current in-game time (in seconds) 606 | delay - Delay in seconds for spectators 607 | spectators - Number of spectators in-game 608 | game_mode - See game_mode table 609 | average_mmr - Average MMR of players in the game 610 | team_name_radiant - Radiant team name (Available for matches with teams) 611 | team_name_dire - Dire team name (Available for matches with teams) 612 | sort_score - 613 | last_update_time - 614 | radiant_lead - Gold lead for Radiant (negative if Dire leads) 615 | radiant_score - Radiant kill score 616 | dire_score - Dire kill score 617 | building_state - 618 | [players] 619 | { 620 | account_id - Player's 32-bit Steam ID 621 | hero_id - Player's hero ID 622 | } 623 | 624 | } 625 | } 626 | -------------------------------------------------------------------------------- /tests/ref/single_match_result.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": { 3 | "players": [ 4 | { 5 | "account_id": 129704923, 6 | "player_slot": 0, 7 | "hero_id": 53, 8 | "item_0": 50, 9 | "item_1": 152, 10 | "item_2": 204, 11 | "item_3": 65, 12 | "item_4": 46, 13 | "item_5": 0, 14 | "kills": 9, 15 | "deaths": 8, 16 | "assists": 8, 17 | "leaver_status": 0, 18 | "gold": 314, 19 | "last_hits": 137, 20 | "denies": 1, 21 | "gold_per_min": 526, 22 | "xp_per_min": 591, 23 | "gold_spent": 14820, 24 | "hero_damage": 15504, 25 | "tower_damage": 216, 26 | "hero_healing": 0, 27 | "level": 19, 28 | "ability_upgrades": [ 29 | { 30 | "ability": 5247, 31 | "time": 196, 32 | "level": 1 33 | }, 34 | { 35 | "ability": 5246, 36 | "time": 333, 37 | "level": 2 38 | }, 39 | { 40 | "ability": 5247, 41 | "time": 382, 42 | "level": 3 43 | }, 44 | { 45 | "ability": 5246, 46 | "time": 459, 47 | "level": 4 48 | }, 49 | { 50 | "ability": 5247, 51 | "time": 558, 52 | "level": 5 53 | }, 54 | { 55 | "ability": 5248, 56 | "time": 684, 57 | "level": 6 58 | }, 59 | { 60 | "ability": 5246, 61 | "time": 763, 62 | "level": 7 63 | }, 64 | { 65 | "ability": 5245, 66 | "time": 903, 67 | "level": 8 68 | }, 69 | { 70 | "ability": 5246, 71 | "time": 1022, 72 | "level": 9 73 | }, 74 | { 75 | "ability": 5247, 76 | "time": 1176, 77 | "level": 10 78 | }, 79 | { 80 | "ability": 5248, 81 | "time": 1232, 82 | "level": 11 83 | }, 84 | { 85 | "ability": 5245, 86 | "time": 1384, 87 | "level": 12 88 | }, 89 | { 90 | "ability": 5245, 91 | "time": 1517, 92 | "level": 13 93 | }, 94 | { 95 | "ability": 5245, 96 | "time": 1662, 97 | "level": 14 98 | }, 99 | { 100 | "ability": 5002, 101 | "time": 1714, 102 | "level": 15 103 | }, 104 | { 105 | "ability": 5248, 106 | "time": 1800, 107 | "level": 16 108 | }, 109 | { 110 | "ability": 5002, 111 | "time": 2033, 112 | "level": 17 113 | }, 114 | { 115 | "ability": 5002, 116 | "time": 2062, 117 | "level": 18 118 | }, 119 | { 120 | "ability": 5002, 121 | "time": 2165, 122 | "level": 19 123 | } 124 | ] 125 | 126 | }, 127 | { 128 | "account_id": 120656094, 129 | "player_slot": 1, 130 | "hero_id": 89, 131 | "item_0": 29, 132 | "item_1": 92, 133 | "item_2": 42, 134 | "item_3": 36, 135 | "item_4": 46, 136 | "item_5": 0, 137 | "kills": 1, 138 | "deaths": 14, 139 | "assists": 6, 140 | "leaver_status": 0, 141 | "gold": 1705, 142 | "last_hits": 25, 143 | "denies": 3, 144 | "gold_per_min": 213, 145 | "xp_per_min": 232, 146 | "gold_spent": 4235, 147 | "hero_damage": 2461, 148 | "tower_damage": 52, 149 | "hero_healing": 0, 150 | "level": 11, 151 | "ability_upgrades": [ 152 | { 153 | "ability": 5468, 154 | "time": 244, 155 | "level": 1 156 | }, 157 | { 158 | "ability": 5469, 159 | "time": 339, 160 | "level": 2 161 | }, 162 | { 163 | "ability": 5468, 164 | "time": 451, 165 | "level": 3 166 | }, 167 | { 168 | "ability": 5469, 169 | "time": 589, 170 | "level": 4 171 | }, 172 | { 173 | "ability": 5469, 174 | "time": 728, 175 | "level": 5 176 | }, 177 | { 178 | "ability": 5467, 179 | "time": 1116, 180 | "level": 6 181 | }, 182 | { 183 | "ability": 5470, 184 | "time": 1317, 185 | "level": 7 186 | }, 187 | { 188 | "ability": 5468, 189 | "time": 1532, 190 | "level": 8 191 | }, 192 | { 193 | "ability": 5469, 194 | "time": 1874, 195 | "level": 9 196 | }, 197 | { 198 | "ability": 5468, 199 | "time": 2004, 200 | "level": 10 201 | }, 202 | { 203 | "ability": 5470, 204 | "time": 2004, 205 | "level": 11 206 | } 207 | ] 208 | 209 | }, 210 | { 211 | "account_id": 200136262, 212 | "player_slot": 2, 213 | "hero_id": 43, 214 | "item_0": 36, 215 | "item_1": 50, 216 | "item_2": 100, 217 | "item_3": 185, 218 | "item_4": 77, 219 | "item_5": 41, 220 | "kills": 5, 221 | "deaths": 13, 222 | "assists": 6, 223 | "leaver_status": 0, 224 | "gold": 758, 225 | "last_hits": 94, 226 | "denies": 3, 227 | "gold_per_min": 339, 228 | "xp_per_min": 459, 229 | "gold_spent": 8520, 230 | "hero_damage": 15598, 231 | "tower_damage": 456, 232 | "hero_healing": 0, 233 | "level": 17, 234 | "ability_upgrades": [ 235 | { 236 | "ability": 5090, 237 | "time": 183, 238 | "level": 1 239 | }, 240 | { 241 | "ability": 5092, 242 | "time": 314, 243 | "level": 2 244 | }, 245 | { 246 | "ability": 5090, 247 | "time": 363, 248 | "level": 3 249 | }, 250 | { 251 | "ability": 5091, 252 | "time": 407, 253 | "level": 4 254 | }, 255 | { 256 | "ability": 5090, 257 | "time": 480, 258 | "level": 5 259 | }, 260 | { 261 | "ability": 5092, 262 | "time": 553, 263 | "level": 6 264 | }, 265 | { 266 | "ability": 5090, 267 | "time": 647, 268 | "level": 7 269 | }, 270 | { 271 | "ability": 5092, 272 | "time": 762, 273 | "level": 8 274 | }, 275 | { 276 | "ability": 5093, 277 | "time": 1020, 278 | "level": 9 279 | }, 280 | { 281 | "ability": 5092, 282 | "time": 1146, 283 | "level": 10 284 | }, 285 | { 286 | "ability": 5093, 287 | "time": 1162, 288 | "level": 11 289 | }, 290 | { 291 | "ability": 5091, 292 | "time": 1475, 293 | "level": 12 294 | }, 295 | { 296 | "ability": 5091, 297 | "time": 1582, 298 | "level": 13 299 | }, 300 | { 301 | "ability": 5091, 302 | "time": 1722, 303 | "level": 14 304 | }, 305 | { 306 | "ability": 5002, 307 | "time": 1968, 308 | "level": 15 309 | }, 310 | { 311 | "ability": 5093, 312 | "time": 2052, 313 | "level": 16 314 | }, 315 | { 316 | "ability": 5002, 317 | "time": 2084, 318 | "level": 17 319 | } 320 | ] 321 | 322 | }, 323 | { 324 | "account_id": 4294967295, 325 | "player_slot": 3, 326 | "hero_id": 63, 327 | "item_0": 63, 328 | "item_1": 212, 329 | "item_2": 34, 330 | "item_3": 123, 331 | "item_4": 51, 332 | "item_5": 0, 333 | "kills": 3, 334 | "deaths": 3, 335 | "assists": 8, 336 | "leaver_status": 0, 337 | "gold": 774, 338 | "last_hits": 134, 339 | "denies": 4, 340 | "gold_per_min": 369, 341 | "xp_per_min": 522, 342 | "gold_spent": 11025, 343 | "hero_damage": 9271, 344 | "tower_damage": 423, 345 | "hero_healing": 0, 346 | "level": 18, 347 | "ability_upgrades": [ 348 | { 349 | "ability": 5290, 350 | "time": 185, 351 | "level": 1 352 | }, 353 | { 354 | "ability": 5002, 355 | "time": 309, 356 | "level": 2 357 | }, 358 | { 359 | "ability": 5290, 360 | "time": 358, 361 | "level": 3 362 | }, 363 | { 364 | "ability": 5291, 365 | "time": 412, 366 | "level": 4 367 | }, 368 | { 369 | "ability": 5290, 370 | "time": 480, 371 | "level": 5 372 | }, 373 | { 374 | "ability": 5292, 375 | "time": 564, 376 | "level": 6 377 | }, 378 | { 379 | "ability": 5290, 380 | "time": 653, 381 | "level": 7 382 | }, 383 | { 384 | "ability": 5289, 385 | "time": 767, 386 | "level": 8 387 | }, 388 | { 389 | "ability": 5291, 390 | "time": 928, 391 | "level": 9 392 | }, 393 | { 394 | "ability": 5289, 395 | "time": 990, 396 | "level": 10 397 | }, 398 | { 399 | "ability": 5292, 400 | "time": 1052, 401 | "level": 11 402 | }, 403 | { 404 | "ability": 5289, 405 | "time": 1312, 406 | "level": 12 407 | }, 408 | { 409 | "ability": 5291, 410 | "time": 1328, 411 | "level": 13 412 | }, 413 | { 414 | "ability": 5289, 415 | "time": 1473, 416 | "level": 14 417 | }, 418 | { 419 | "ability": 5002, 420 | "time": 1585, 421 | "level": 15 422 | }, 423 | { 424 | "ability": 5292, 425 | "time": 1875, 426 | "level": 16 427 | }, 428 | { 429 | "ability": 5002, 430 | "time": 1910, 431 | "level": 17 432 | }, 433 | { 434 | "ability": 5002, 435 | "time": 2058, 436 | "level": 18 437 | } 438 | ] 439 | 440 | }, 441 | { 442 | "account_id": 4294967295, 443 | "player_slot": 4, 444 | "hero_id": 85, 445 | "item_0": 180, 446 | "item_1": 90, 447 | "item_2": 182, 448 | "item_3": 4, 449 | "item_4": 19, 450 | "item_5": 46, 451 | "kills": 5, 452 | "deaths": 6, 453 | "assists": 7, 454 | "leaver_status": 0, 455 | "gold": 1017, 456 | "last_hits": 31, 457 | "denies": 0, 458 | "gold_per_min": 274, 459 | "xp_per_min": 327, 460 | "gold_spent": 7445, 461 | "hero_damage": 6591, 462 | "tower_damage": 80, 463 | "hero_healing": 74, 464 | "level": 14, 465 | "ability_upgrades": [ 466 | { 467 | "ability": 5442, 468 | "time": 288, 469 | "level": 1 470 | }, 471 | { 472 | "ability": 5444, 473 | "time": 310, 474 | "level": 2 475 | }, 476 | { 477 | "ability": 5442, 478 | "time": 391, 479 | "level": 3 480 | }, 481 | { 482 | "ability": 5444, 483 | "time": 540, 484 | "level": 4 485 | }, 486 | { 487 | "ability": 5442, 488 | "time": 689, 489 | "level": 5 490 | }, 491 | { 492 | "ability": 5447, 493 | "time": 757, 494 | "level": 6 495 | }, 496 | { 497 | "ability": 5444, 498 | "time": 837, 499 | "level": 7 500 | }, 501 | { 502 | "ability": 5444, 503 | "time": 903, 504 | "level": 8 505 | }, 506 | { 507 | "ability": 5442, 508 | "time": 1242, 509 | "level": 9 510 | }, 511 | { 512 | "ability": 5443, 513 | "time": 1384, 514 | "level": 10 515 | }, 516 | { 517 | "ability": 5447, 518 | "time": 1463, 519 | "level": 11 520 | }, 521 | { 522 | "ability": 5443, 523 | "time": 1892, 524 | "level": 12 525 | }, 526 | { 527 | "ability": 5443, 528 | "time": 1967, 529 | "level": 13 530 | }, 531 | { 532 | "ability": 5443, 533 | "time": 2215, 534 | "level": 14 535 | } 536 | ] 537 | 538 | }, 539 | { 540 | "account_id": 199626952, 541 | "player_slot": 128, 542 | "hero_id": 106, 543 | "item_0": 50, 544 | "item_1": 149, 545 | "item_2": 71, 546 | "item_3": 145, 547 | "item_4": 46, 548 | "item_5": 145, 549 | "kills": 13, 550 | "deaths": 2, 551 | "assists": 10, 552 | "leaver_status": 0, 553 | "gold": 3481, 554 | "last_hits": 150, 555 | "denies": 6, 556 | "gold_per_min": 546, 557 | "xp_per_min": 474, 558 | "gold_spent": 15850, 559 | "hero_damage": 14663, 560 | "tower_damage": 907, 561 | "hero_healing": 0, 562 | "level": 17, 563 | "ability_upgrades": [ 564 | { 565 | "ability": 5603, 566 | "time": 267, 567 | "level": 1 568 | }, 569 | { 570 | "ability": 5605, 571 | "time": 295, 572 | "level": 2 573 | }, 574 | { 575 | "ability": 5605, 576 | "time": 331, 577 | "level": 3 578 | }, 579 | { 580 | "ability": 5603, 581 | "time": 366, 582 | "level": 4 583 | }, 584 | { 585 | "ability": 5605, 586 | "time": 439, 587 | "level": 5 588 | }, 589 | { 590 | "ability": 5606, 591 | "time": 516, 592 | "level": 6 593 | }, 594 | { 595 | "ability": 5605, 596 | "time": 637, 597 | "level": 7 598 | }, 599 | { 600 | "ability": 5604, 601 | "time": 708, 602 | "level": 8 603 | }, 604 | { 605 | "ability": 5604, 606 | "time": 1019, 607 | "level": 9 608 | }, 609 | { 610 | "ability": 5604, 611 | "time": 1104, 612 | "level": 10 613 | }, 614 | { 615 | "ability": 5606, 616 | "time": 1147, 617 | "level": 11 618 | }, 619 | { 620 | "ability": 5604, 621 | "time": 1477, 622 | "level": 12 623 | }, 624 | { 625 | "ability": 5603, 626 | "time": 1532, 627 | "level": 13 628 | }, 629 | { 630 | "ability": 5603, 631 | "time": 1739, 632 | "level": 14 633 | }, 634 | { 635 | "ability": 5002, 636 | "time": 1794, 637 | "level": 15 638 | }, 639 | { 640 | "ability": 5606, 641 | "time": 2031, 642 | "level": 16 643 | }, 644 | { 645 | "ability": 5002, 646 | "time": 2227, 647 | "level": 17 648 | } 649 | ] 650 | 651 | }, 652 | { 653 | "account_id": 4294967295, 654 | "player_slot": 129, 655 | "hero_id": 14, 656 | "item_0": 214, 657 | "item_1": 92, 658 | "item_2": 1, 659 | "item_3": 102, 660 | "item_4": 46, 661 | "item_5": 41, 662 | "kills": 8, 663 | "deaths": 5, 664 | "assists": 1, 665 | "leaver_status": 0, 666 | "gold": 1932, 667 | "last_hits": 46, 668 | "denies": 2, 669 | "gold_per_min": 322, 670 | "xp_per_min": 283, 671 | "gold_spent": 8145, 672 | "hero_damage": 8093, 673 | "tower_damage": 0, 674 | "hero_healing": 1003, 675 | "level": 13, 676 | "ability_upgrades": [ 677 | { 678 | "ability": 5076, 679 | "time": 177, 680 | "level": 1 681 | }, 682 | { 683 | "ability": 5075, 684 | "time": 331, 685 | "level": 2 686 | }, 687 | { 688 | "ability": 5075, 689 | "time": 378, 690 | "level": 3 691 | }, 692 | { 693 | "ability": 5076, 694 | "time": 436, 695 | "level": 4 696 | }, 697 | { 698 | "ability": 5075, 699 | "time": 515, 700 | "level": 5 701 | }, 702 | { 703 | "ability": 5077, 704 | "time": 628, 705 | "level": 6 706 | }, 707 | { 708 | "ability": 5075, 709 | "time": 816, 710 | "level": 7 711 | }, 712 | { 713 | "ability": 5076, 714 | "time": 885, 715 | "level": 8 716 | }, 717 | { 718 | "ability": 5076, 719 | "time": 1432, 720 | "level": 9 721 | }, 722 | { 723 | "ability": 5074, 724 | "time": 1628, 725 | "level": 10 726 | }, 727 | { 728 | "ability": 5077, 729 | "time": 1797, 730 | "level": 11 731 | }, 732 | { 733 | "ability": 5074, 734 | "time": 2082, 735 | "level": 12 736 | } 737 | ] 738 | 739 | }, 740 | { 741 | "account_id": 4294967295, 742 | "player_slot": 130, 743 | "hero_id": 82, 744 | "item_0": 63, 745 | "item_1": 108, 746 | "item_2": 1, 747 | "item_3": 147, 748 | "item_4": 30, 749 | "item_5": 96, 750 | "kills": 22, 751 | "deaths": 4, 752 | "assists": 10, 753 | "leaver_status": 0, 754 | "gold": 7285, 755 | "last_hits": 315, 756 | "denies": 0, 757 | "gold_per_min": 929, 758 | "xp_per_min": 973, 759 | "gold_spent": 22425, 760 | "hero_damage": 25746, 761 | "tower_damage": 11287, 762 | "hero_healing": 0, 763 | "level": 25, 764 | "ability_upgrades": [ 765 | { 766 | "ability": 5431, 767 | "time": 275, 768 | "level": 1 769 | }, 770 | { 771 | "ability": 5433, 772 | "time": 502, 773 | "level": 2 774 | }, 775 | { 776 | "ability": 5431, 777 | "time": 515, 778 | "level": 3 779 | }, 780 | { 781 | "ability": 5430, 782 | "time": 575, 783 | "level": 4 784 | }, 785 | { 786 | "ability": 5431, 787 | "time": 591, 788 | "level": 5 789 | }, 790 | { 791 | "ability": 5432, 792 | "time": 688, 793 | "level": 6 794 | }, 795 | { 796 | "ability": 5431, 797 | "time": 696, 798 | "level": 7 799 | }, 800 | { 801 | "ability": 5430, 802 | "time": 809, 803 | "level": 8 804 | }, 805 | { 806 | "ability": 5432, 807 | "time": 946, 808 | "level": 9 809 | }, 810 | { 811 | "ability": 5433, 812 | "time": 998, 813 | "level": 10 814 | }, 815 | { 816 | "ability": 5432, 817 | "time": 1031, 818 | "level": 11 819 | }, 820 | { 821 | "ability": 5432, 822 | "time": 1164, 823 | "level": 12 824 | }, 825 | { 826 | "ability": 5430, 827 | "time": 1222, 828 | "level": 13 829 | }, 830 | { 831 | "ability": 5430, 832 | "time": 1285, 833 | "level": 14 834 | }, 835 | { 836 | "ability": 5002, 837 | "time": 1347, 838 | "level": 15 839 | }, 840 | { 841 | "ability": 5002, 842 | "time": 1425, 843 | "level": 16 844 | }, 845 | { 846 | "ability": 5433, 847 | "time": 1485, 848 | "level": 17 849 | }, 850 | { 851 | "ability": 5002, 852 | "time": 1508, 853 | "level": 18 854 | }, 855 | { 856 | "ability": 5002, 857 | "time": 1602, 858 | "level": 19 859 | }, 860 | { 861 | "ability": 5002, 862 | "time": 1657, 863 | "level": 20 864 | }, 865 | { 866 | "ability": 5002, 867 | "time": 1657, 868 | "level": 21 869 | }, 870 | { 871 | "ability": 5002, 872 | "time": 1710, 873 | "level": 22 874 | }, 875 | { 876 | "ability": 5002, 877 | "time": 1770, 878 | "level": 23 879 | }, 880 | { 881 | "ability": 5002, 882 | "time": 1824, 883 | "level": 24 884 | }, 885 | { 886 | "ability": 5002, 887 | "time": 1870, 888 | "level": 25 889 | } 890 | ] 891 | , 892 | "additional_units": [ 893 | { 894 | "unitname": "", 895 | "item_0": 63, 896 | "item_1": 108, 897 | "item_2": 1, 898 | "item_3": 147, 899 | "item_4": 30, 900 | "item_5": 96 901 | } 902 | ] 903 | 904 | }, 905 | { 906 | "account_id": 114232365, 907 | "player_slot": 131, 908 | "hero_id": 31, 909 | "item_0": 60, 910 | "item_1": 0, 911 | "item_2": 79, 912 | "item_3": 40, 913 | "item_4": 214, 914 | "item_5": 0, 915 | "kills": 0, 916 | "deaths": 6, 917 | "assists": 9, 918 | "leaver_status": 0, 919 | "gold": 1426, 920 | "last_hits": 25, 921 | "denies": 4, 922 | "gold_per_min": 238, 923 | "xp_per_min": 349, 924 | "gold_spent": 6225, 925 | "hero_damage": 6644, 926 | "tower_damage": 291, 927 | "hero_healing": 813, 928 | "level": 14, 929 | "ability_upgrades": [ 930 | { 931 | "ability": 5136, 932 | "time": 202, 933 | "level": 1 934 | }, 935 | { 936 | "ability": 5134, 937 | "time": 311, 938 | "level": 2 939 | }, 940 | { 941 | "ability": 5136, 942 | "time": 394, 943 | "level": 3 944 | }, 945 | { 946 | "ability": 5134, 947 | "time": 492, 948 | "level": 4 949 | }, 950 | { 951 | "ability": 5134, 952 | "time": 615, 953 | "level": 5 954 | }, 955 | { 956 | "ability": 5137, 957 | "time": 738, 958 | "level": 6 959 | }, 960 | { 961 | "ability": 5134, 962 | "time": 861, 963 | "level": 7 964 | }, 965 | { 966 | "ability": 5135, 967 | "time": 1004, 968 | "level": 8 969 | }, 970 | { 971 | "ability": 5135, 972 | "time": 1306, 973 | "level": 9 974 | }, 975 | { 976 | "ability": 5136, 977 | "time": 1473, 978 | "level": 10 979 | }, 980 | { 981 | "ability": 5137, 982 | "time": 1672, 983 | "level": 11 984 | }, 985 | { 986 | "ability": 5136, 987 | "time": 1740, 988 | "level": 12 989 | }, 990 | { 991 | "ability": 5135, 992 | "time": 1847, 993 | "level": 13 994 | }, 995 | { 996 | "ability": 5135, 997 | "time": 2044, 998 | "level": 14 999 | } 1000 | ] 1001 | 1002 | }, 1003 | { 1004 | "account_id": 79642916, 1005 | "player_slot": 132, 1006 | "hero_id": 23, 1007 | "item_0": 50, 1008 | "item_1": 141, 1009 | "item_2": 40, 1010 | "item_3": 149, 1011 | "item_4": 56, 1012 | "item_5": 0, 1013 | "kills": 1, 1014 | "deaths": 7, 1015 | "assists": 3, 1016 | "leaver_status": 0, 1017 | "gold": 96, 1018 | "last_hits": 162, 1019 | "denies": 18, 1020 | "gold_per_min": 417, 1021 | "xp_per_min": 324, 1022 | "gold_spent": 11695, 1023 | "hero_damage": 6091, 1024 | "tower_damage": 1349, 1025 | "hero_healing": 0, 1026 | "level": 14, 1027 | "ability_upgrades": [ 1028 | { 1029 | "ability": 5032, 1030 | "time": 169, 1031 | "level": 1 1032 | }, 1033 | { 1034 | "ability": 5031, 1035 | "time": 316, 1036 | "level": 2 1037 | }, 1038 | { 1039 | "ability": 5032, 1040 | "time": 372, 1041 | "level": 3 1042 | }, 1043 | { 1044 | "ability": 5033, 1045 | "time": 459, 1046 | "level": 4 1047 | }, 1048 | { 1049 | "ability": 5032, 1050 | "time": 563, 1051 | "level": 5 1052 | }, 1053 | { 1054 | "ability": 5035, 1055 | "time": 703, 1056 | "level": 6 1057 | }, 1058 | { 1059 | "ability": 5032, 1060 | "time": 819, 1061 | "level": 7 1062 | }, 1063 | { 1064 | "ability": 5033, 1065 | "time": 909, 1066 | "level": 8 1067 | }, 1068 | { 1069 | "ability": 5031, 1070 | "time": 1055, 1071 | "level": 9 1072 | }, 1073 | { 1074 | "ability": 5031, 1075 | "time": 1275, 1076 | "level": 10 1077 | }, 1078 | { 1079 | "ability": 5035, 1080 | "time": 1462, 1081 | "level": 11 1082 | }, 1083 | { 1084 | "ability": 5031, 1085 | "time": 1738, 1086 | "level": 12 1087 | }, 1088 | { 1089 | "ability": 5033, 1090 | "time": 1820, 1091 | "level": 13 1092 | }, 1093 | { 1094 | "ability": 5033, 1095 | "time": 2135, 1096 | "level": 14 1097 | } 1098 | ] 1099 | 1100 | } 1101 | ] 1102 | , 1103 | "radiant_win": false, 1104 | "duration": 1999, 1105 | "start_time": 1414497931, 1106 | "match_id": 988604774, 1107 | "match_seq_num": 884656009, 1108 | "tower_status_radiant": 0, 1109 | "tower_status_dire": 1974, 1110 | "barracks_status_radiant": 0, 1111 | "barracks_status_dire": 63, 1112 | "cluster": 134, 1113 | "first_blood_time": 104, 1114 | "lobby_type": 0, 1115 | "human_players": 10, 1116 | "leagueid": 0, 1117 | "positive_votes": 3, 1118 | "negative_votes": 0, 1119 | "game_mode": 1 1120 | } 1121 | } 1122 | -------------------------------------------------------------------------------- /dota2api/ref/abilities.json: -------------------------------------------------------------------------------- 1 | { 2 | "abilities": [ 3 | { 4 | "name": "centaur_khan_war_stomp", 5 | "id": "5295" 6 | }, 7 | { 8 | "name": "necronomicon_warrior_last_will", 9 | "id": "5200" 10 | }, 11 | { 12 | "name": "enigma_demonic_conversion", 13 | "id": "5147" 14 | }, 15 | { 16 | "name": "beastmaster_boar_poison", 17 | "id": "5171" 18 | }, 19 | { 20 | "name": "juggernaut_blade_fury", 21 | "id": "5028" 22 | }, 23 | { 24 | "name": "witch_doctor_voodoo_restoration", 25 | "id": "5139" 26 | }, 27 | { 28 | "name": "bloodseeker_blood_bath", 29 | "id": "5016" 30 | }, 31 | { 32 | "name": "backdoor_protection_in_base", 33 | "id": "5351" 34 | }, 35 | { 36 | "name": "broodmother_spawn_spiderite", 37 | "id": "5283" 38 | }, 39 | { 40 | "name": "spirit_breaker_charge_of_darkness", 41 | "id": "5353" 42 | }, 43 | { 44 | "name": "chaos_knight_reality_rift", 45 | "id": "5427" 46 | }, 47 | { 48 | "name": "lina_laguna_blade", 49 | "id": "5043" 50 | }, 51 | { 52 | "name": "riki_backstab", 53 | "id": "5144" 54 | }, 55 | { 56 | "name": "forest_troll_high_priest_heal", 57 | "id": "5318" 58 | }, 59 | { 60 | "name": "warlock_upheaval", 61 | "id": "5164" 62 | }, 63 | { 64 | "name": "ogre_magi_frost_armor", 65 | "id": "5304" 66 | }, 67 | { 68 | "name": "stats", 69 | "id": "5002" 70 | }, 71 | { 72 | "name": "dazzle_poison_touch", 73 | "id": "5233" 74 | }, 75 | { 76 | "name": "templar_assassin_self_trap", 77 | "id": "5199" 78 | }, 79 | { 80 | "name": "silencer_glaives_of_wisdom", 81 | "id": "5378" 82 | }, 83 | { 84 | "name": "warlock_rain_of_chaos", 85 | "id": "5165" 86 | }, 87 | { 88 | "name": "sven_warcry", 89 | "id": "5096" 90 | }, 91 | { 92 | "name": "templar_assassin_meld", 93 | "id": "5195" 94 | }, 95 | { 96 | "name": "enchantress_impetus", 97 | "id": "5270" 98 | }, 99 | { 100 | "name": "earthshaker_enchant_totem", 101 | "id": "5024" 102 | }, 103 | { 104 | "name": "lone_druid_synergy", 105 | "id": "5414" 106 | }, 107 | { 108 | "name": "invoker_forge_spirit", 109 | "id": "5387" 110 | }, 111 | { 112 | "name": "chaos_knight_phantasm", 113 | "id": "5429" 114 | }, 115 | { 116 | "name": "satyr_hellcaller_unholy_aura", 117 | "id": "5317" 118 | }, 119 | { 120 | "name": "shadow_shaman_shackles", 121 | "id": "5080" 122 | }, 123 | { 124 | "name": "ogre_magi_bloodlust", 125 | "id": "5440" 126 | }, 127 | { 128 | "name": "shadow_demon_shadow_poison_release", 129 | "id": "5424" 130 | }, 131 | { 132 | "name": "blue_dragonspawn_sorcerer_evasion", 133 | "id": "5325" 134 | }, 135 | { 136 | "name": "antimage_mana_break", 137 | "id": "5003" 138 | }, 139 | { 140 | "name": "spirit_breaker_empowering_haste", 141 | "id": "5354" 142 | }, 143 | { 144 | "name": "satyr_hellcaller_shockwave", 145 | "id": "5316" 146 | }, 147 | { 148 | "name": "morphling_morph_agi", 149 | "id": "5055" 150 | }, 151 | { 152 | "name": "bounty_hunter_track", 153 | "id": "5288" 154 | }, 155 | { 156 | "name": "bloodseeker_bloodrage", 157 | "id": "5015" 158 | }, 159 | { 160 | "name": "obsidian_destroyer_astral_imprisonment", 161 | "id": "5392" 162 | }, 163 | { 164 | "name": "lich_dark_ritual", 165 | "id": "5136" 166 | }, 167 | { 168 | "name": "antimage_spell_shield", 169 | "id": "5005" 170 | }, 171 | { 172 | "name": "enchantress_natures_attendants", 173 | "id": "5269" 174 | }, 175 | { 176 | "name": "broodmother_incapacitating_bite", 177 | "id": "5281" 178 | }, 179 | { 180 | "name": "undying_flesh_golem", 181 | "id": "5447" 182 | }, 183 | { 184 | "name": "lone_druid_rabid", 185 | "id": "5413" 186 | }, 187 | { 188 | "name": "slardar_sprint", 189 | "id": "5114" 190 | }, 191 | { 192 | "name": "sandking_caustic_finale", 193 | "id": "5104" 194 | }, 195 | { 196 | "name": "nevermore_dark_lord", 197 | "id": "5063" 198 | }, 199 | { 200 | "name": "lycan_summon_wolves_invisibility", 201 | "id": "5500" 202 | }, 203 | { 204 | "name": "life_stealer_infest", 205 | "id": "5252" 206 | }, 207 | { 208 | "name": "lycan_feral_impulse", 209 | "id": "5397" 210 | }, 211 | { 212 | "name": "ursa_overpower", 213 | "id": "5358" 214 | }, 215 | { 216 | "name": "enraged_wildkin_toughness_aura", 217 | "id": "5313" 218 | }, 219 | { 220 | "name": "pudge_flesh_heap", 221 | "id": "5074" 222 | }, 223 | { 224 | "name": "witch_doctor_maledict", 225 | "id": "5140" 226 | }, 227 | { 228 | "name": "vengefulspirit_nether_swap", 229 | "id": "5125" 230 | }, 231 | { 232 | "name": "weaver_shukuchi", 233 | "id": "5290" 234 | }, 235 | { 236 | "name": "weaver_the_swarm", 237 | "id": "5289" 238 | }, 239 | { 240 | "name": "alchemist_acid_spray", 241 | "id": "5365" 242 | }, 243 | { 244 | "name": "phantom_assassin_stifling_dagger", 245 | "id": "5190" 246 | }, 247 | { 248 | "name": "sandking_epicenter", 249 | "id": "5105" 250 | }, 251 | { 252 | "name": "tornado_tempest", 253 | "id": "5310" 254 | }, 255 | { 256 | "name": "jakiro_liquid_fire", 257 | "id": "5299" 258 | }, 259 | { 260 | "name": "chen_hand_of_god", 261 | "id": "5331" 262 | }, 263 | { 264 | "name": "batrider_flaming_lasso", 265 | "id": "5323" 266 | }, 267 | { 268 | "name": "lone_druid_true_form", 269 | "id": "5416" 270 | }, 271 | { 272 | "name": "spirit_breaker_nether_strike", 273 | "id": "5356" 274 | }, 275 | { 276 | "name": "spectre_haunt", 277 | "id": "5337" 278 | }, 279 | { 280 | "name": "alchemist_chemical_rage", 281 | "id": "5369" 282 | }, 283 | { 284 | "name": "silencer_last_word", 285 | "id": "5379" 286 | }, 287 | { 288 | "name": "tinker_rearm", 289 | "id": "5153" 290 | }, 291 | { 292 | "name": "rattletrap_rocket_flare", 293 | "id": "5239" 294 | }, 295 | { 296 | "name": "omniknight_guardian_angel", 297 | "id": "5266" 298 | }, 299 | { 300 | "name": "leshrac_diabolic_edict", 301 | "id": "5242" 302 | }, 303 | { 304 | "name": "tiny_toss", 305 | "id": "5107" 306 | }, 307 | { 308 | "name": "razor_static_link", 309 | "id": "5083" 310 | }, 311 | { 312 | "name": "invoker_ice_wall", 313 | "id": "5389" 314 | }, 315 | { 316 | "name": "brewmaster_drunken_haze", 317 | "id": "5401" 318 | }, 319 | { 320 | "name": "batrider_flamebreak", 321 | "id": "5321" 322 | }, 323 | { 324 | "name": "dark_seer_ion_shell", 325 | "id": "5256" 326 | }, 327 | { 328 | "name": "queenofpain_scream_of_pain", 329 | "id": "5175" 330 | }, 331 | { 332 | "name": "phantom_assassin_blur", 333 | "id": "5192" 334 | }, 335 | { 336 | "name": "phantom_lancer_juxtapose", 337 | "id": "5067" 338 | }, 339 | { 340 | "name": "tidehunter_anchor_smash", 341 | "id": "5120" 342 | }, 343 | { 344 | "name": "axe_battle_hunger", 345 | "id": "5008" 346 | }, 347 | { 348 | "name": "pugna_life_drain", 349 | "id": "5189" 350 | }, 351 | { 352 | "name": "batrider_firefly", 353 | "id": "5322" 354 | }, 355 | { 356 | "name": "beastmaster_primal_roar", 357 | "id": "5177" 358 | }, 359 | { 360 | "name": "shadow_shaman_mass_serpent_ward", 361 | "id": "5081" 362 | }, 363 | { 364 | "name": "treant_leech_seed", 365 | "id": "5435" 366 | }, 367 | { 368 | "name": "chen_holy_persuasion", 369 | "id": "5330" 370 | }, 371 | { 372 | "name": "courier_burst", 373 | "id": "5210" 374 | }, 375 | { 376 | "name": "luna_lucent_beam", 377 | "id": "5222" 378 | }, 379 | { 380 | "name": "morphling_morph_replicate", 381 | "id": "5058" 382 | }, 383 | { 384 | "name": "venomancer_poison_sting", 385 | "id": "5179" 386 | }, 387 | { 388 | "name": "roshan_bash", 389 | "id": "5214" 390 | }, 391 | { 392 | "name": "tinker_march_of_the_machines", 393 | "id": "5152" 394 | }, 395 | { 396 | "name": "lone_druid_spirit_bear_demolish", 397 | "id": "5420" 398 | }, 399 | { 400 | "name": "mirana_leap", 401 | "id": "5050" 402 | }, 403 | { 404 | "name": "brewmaster_storm_wind_walk", 405 | "id": "5410" 406 | }, 407 | { 408 | "name": "skeleton_king_reincarnation", 409 | "id": "5089" 410 | }, 411 | { 412 | "name": "beastmaster_call_of_the_wild", 413 | "id": "5169" 414 | }, 415 | { 416 | "name": "riki_permanent_invisibility", 417 | "id": "5145" 418 | }, 419 | { 420 | "name": "courier_shield", 421 | "id": "5209" 422 | }, 423 | { 424 | "name": "clinkz_searing_arrows", 425 | "id": "5260" 426 | }, 427 | { 428 | "name": "puck_illusory_orb", 429 | "id": "5069" 430 | }, 431 | { 432 | "name": "forged_spirit_melting_strike", 433 | "id": "5388" 434 | }, 435 | { 436 | "name": "viper_viper_strike", 437 | "id": "5221" 438 | }, 439 | { 440 | "name": "furion_wrath_of_nature", 441 | "id": "5248" 442 | }, 443 | { 444 | "name": "silencer_curse_of_the_silent", 445 | "id": "5377" 446 | }, 447 | { 448 | "name": "lich_frost_nova", 449 | "id": "5134" 450 | }, 451 | { 452 | "name": "doom_bringer_doom", 453 | "id": "5342" 454 | }, 455 | { 456 | "name": "warlock_golem_flaming_fists", 457 | "id": "5166" 458 | }, 459 | { 460 | "name": "invoker_quas", 461 | "id": "5370" 462 | }, 463 | { 464 | "name": "invoker_invoke", 465 | "id": "5375" 466 | }, 467 | { 468 | "name": "dazzle_weave", 469 | "id": "5236" 470 | }, 471 | { 472 | "name": "courier_go_to_secretshop", 473 | "id": "5492" 474 | }, 475 | { 476 | "name": "meepo_geostrike", 477 | "id": "5432" 478 | }, 479 | { 480 | "name": "ogre_magi_multicast", 481 | "id": "5441" 482 | }, 483 | { 484 | "name": "crystal_maiden_frostbite", 485 | "id": "5127" 486 | }, 487 | { 488 | "name": "invoker_deafening_blast", 489 | "id": "5390" 490 | }, 491 | { 492 | "name": "crystal_maiden_freezing_field", 493 | "id": "5129" 494 | }, 495 | { 496 | "name": "lina_light_strike_array", 497 | "id": "5041" 498 | }, 499 | { 500 | "name": "dragon_knight_breathe_fire", 501 | "id": "5226" 502 | }, 503 | { 504 | "name": "earthshaker_fissure", 505 | "id": "5023" 506 | }, 507 | { 508 | "name": "witch_doctor_paralyzing_cask", 509 | "id": "5138" 510 | }, 511 | { 512 | "name": "centaur_khan_endurance_aura", 513 | "id": "5294" 514 | }, 515 | { 516 | "name": "gyrocopter_homing_missile", 517 | "id": "5362" 518 | }, 519 | { 520 | "name": "death_prophet_silence", 521 | "id": "5091" 522 | }, 523 | { 524 | "name": "beastmaster_wild_axes", 525 | "id": "5168" 526 | }, 527 | { 528 | "name": "doom_bringer_lvl_death", 529 | "id": "5341" 530 | }, 531 | { 532 | "name": "queenofpain_shadow_strike", 533 | "id": "5173" 534 | }, 535 | { 536 | "name": "tiny_avalanche", 537 | "id": "5106" 538 | }, 539 | { 540 | "name": "invoker_exort", 541 | "id": "5372" 542 | }, 543 | { 544 | "name": "lone_druid_true_form_battle_cry", 545 | "id": "5417" 546 | }, 547 | { 548 | "name": "tiny_craggy_exterior", 549 | "id": "5108" 550 | }, 551 | { 552 | "name": "antimage_mana_void", 553 | "id": "5006" 554 | }, 555 | { 556 | "name": "riki_smoke_screen", 557 | "id": "5142" 558 | }, 559 | { 560 | "name": "roshan_spell_block", 561 | "id": "5213" 562 | }, 563 | { 564 | "name": "juggernaut_healing_ward", 565 | "id": "5029" 566 | }, 567 | { 568 | "name": "ancient_apparition_ice_blast_release", 569 | "id": "5349" 570 | }, 571 | { 572 | "name": "obsidian_destroyer_arcane_orb", 573 | "id": "5391" 574 | }, 575 | { 576 | "name": "earthshaker_aftershock", 577 | "id": "5025" 578 | }, 579 | { 580 | "name": "satyr_trickster_purge", 581 | "id": "5314" 582 | }, 583 | { 584 | "name": "slardar_amplify_damage", 585 | "id": "5117" 586 | }, 587 | { 588 | "name": "dazzle_shallow_grave", 589 | "id": "5234" 590 | }, 591 | { 592 | "name": "tiny_grow", 593 | "id": "5109" 594 | }, 595 | { 596 | "name": "shadow_demon_demonic_purge", 597 | "id": "5425" 598 | }, 599 | { 600 | "name": "enigma_midnight_pulse", 601 | "id": "5148" 602 | }, 603 | { 604 | "name": "treant_living_armor", 605 | "id": "5436" 606 | }, 607 | { 608 | "name": "spectre_desolate", 609 | "id": "5335" 610 | }, 611 | { 612 | "name": "weaver_time_lapse", 613 | "id": "5292" 614 | }, 615 | { 616 | "name": "ancient_apparition_chilling_touch", 617 | "id": "5347" 618 | }, 619 | { 620 | "name": "brewmaster_storm_cyclone", 621 | "id": "5409" 622 | }, 623 | { 624 | "name": "vengefulspirit_command_aura", 625 | "id": "5123" 626 | }, 627 | { 628 | "name": "broodmother_spawn_spiderlings", 629 | "id": "5279" 630 | }, 631 | { 632 | "name": "bane_fiends_grip", 633 | "id": "5013" 634 | }, 635 | { 636 | "name": "gyrocopter_flak_cannon", 637 | "id": "5363" 638 | }, 639 | { 640 | "name": "sniper_take_aim", 641 | "id": "5156" 642 | }, 643 | { 644 | "name": "sven_great_cleave", 645 | "id": "5095" 646 | }, 647 | { 648 | "name": "death_prophet_exorcism", 649 | "id": "5093" 650 | }, 651 | { 652 | "name": "mirana_arrow", 653 | "id": "5048" 654 | }, 655 | { 656 | "name": "bounty_hunter_jinada", 657 | "id": "5286" 658 | }, 659 | { 660 | "name": "pudge_rot", 661 | "id": "5076" 662 | }, 663 | { 664 | "name": "chen_test_of_faith", 665 | "id": "5329" 666 | }, 667 | { 668 | "name": "jakiro_ice_path", 669 | "id": "5298" 670 | }, 671 | { 672 | "name": "enraged_wildkin_tornado", 673 | "id": "5312" 674 | }, 675 | { 676 | "name": "pugna_decrepify", 677 | "id": "5187" 678 | }, 679 | { 680 | "name": "dark_troll_warlord_raise_dead", 681 | "id": "5306" 682 | }, 683 | { 684 | "name": "drow_ranger_trueshot", 685 | "id": "5021" 686 | }, 687 | { 688 | "name": "vengefulspirit_wave_of_terror", 689 | "id": "5124" 690 | }, 691 | { 692 | "name": "bounty_hunter_wind_walk", 693 | "id": "5287" 694 | }, 695 | { 696 | "name": "nevermore_requiem", 697 | "id": "5064" 698 | }, 699 | { 700 | "name": "courier_transfer_items", 701 | "id": "5206" 702 | }, 703 | { 704 | "name": "viper_poison_attack", 705 | "id": "5218" 706 | }, 707 | { 708 | "name": "brewmaster_fire_permanent_immolation", 709 | "id": "5411" 710 | }, 711 | { 712 | "name": "alchemist_unstable_concoction", 713 | "id": "5366" 714 | }, 715 | { 716 | "name": "undying_soul_rip", 717 | "id": "5443" 718 | }, 719 | { 720 | "name": "roshan_devotion", 721 | "id": "5217" 722 | }, 723 | { 724 | "name": "zuus_thundergods_wrath", 725 | "id": "5113" 726 | }, 727 | { 728 | "name": "broodmother_spin_web", 729 | "id": "5280" 730 | }, 731 | { 732 | "name": "jakiro_dual_breath", 733 | "id": "5297" 734 | }, 735 | { 736 | "name": "luna_moon_glaive", 737 | "id": "5223" 738 | }, 739 | { 740 | "name": "roshan_slam", 741 | "id": "5215" 742 | }, 743 | { 744 | "name": "queenofpain_sonic_wave", 745 | "id": "5176" 746 | }, 747 | { 748 | "name": "sniper_assassinate", 749 | "id": "5157" 750 | }, 751 | { 752 | "name": "invoker_tornado", 753 | "id": "5382" 754 | }, 755 | { 756 | "name": "dark_troll_warlord_ensnare", 757 | "id": "5305" 758 | }, 759 | { 760 | "name": "courier_return_stash_items", 761 | "id": "5207" 762 | }, 763 | { 764 | "name": "batrider_sticky_napalm", 765 | "id": "5320" 766 | }, 767 | { 768 | "name": "ancient_apparition_ice_vortex", 769 | "id": "5346" 770 | }, 771 | { 772 | "name": "windrunner_shackleshot", 773 | "id": "5130" 774 | }, 775 | { 776 | "name": "lion_voodoo", 777 | "id": "5045" 778 | }, 779 | { 780 | "name": "undying_decay", 781 | "id": "5442" 782 | }, 783 | { 784 | "name": "tinker_heat_seeking_missile", 785 | "id": "5151" 786 | }, 787 | { 788 | "name": "lion_mana_drain", 789 | "id": "5046" 790 | }, 791 | { 792 | "name": "lone_druid_spirit_bear", 793 | "id": "5412" 794 | }, 795 | { 796 | "name": "shadow_shaman_voodoo", 797 | "id": "5079" 798 | }, 799 | { 800 | "name": "brewmaster_thunder_clap", 801 | "id": "5400" 802 | }, 803 | { 804 | "name": "meepo_divided_we_stand", 805 | "id": "5433" 806 | }, 807 | { 808 | "name": "big_thunder_lizard_frenzy", 809 | "id": "5333" 810 | }, 811 | { 812 | "name": "luna_eclipse", 813 | "id": "5225" 814 | }, 815 | { 816 | "name": "sandking_sand_storm", 817 | "id": "5103" 818 | }, 819 | { 820 | "name": "giant_wolf_critical_strike", 821 | "id": "5307" 822 | }, 823 | { 824 | "name": "brewmaster_storm_dispel_magic", 825 | "id": "5408" 826 | }, 827 | { 828 | "name": "big_thunder_lizard_slam", 829 | "id": "5332" 830 | }, 831 | { 832 | "name": "forest_troll_high_priest_mana_aura", 833 | "id": "5491" 834 | }, 835 | { 836 | "name": "invoker_wex", 837 | "id": "5371" 838 | }, 839 | { 840 | "name": "clinkz_wind_walk", 841 | "id": "5261" 842 | }, 843 | { 844 | "name": "lone_druid_true_form", 845 | "id": "5415" 846 | }, 847 | { 848 | "name": "undying_tombstone_zombie_deathstrike", 849 | "id": "5446" 850 | }, 851 | { 852 | "name": "queenofpain_blink", 853 | "id": "5174" 854 | }, 855 | { 856 | "name": "roshan_illusion_protection", 857 | "id": "5216" 858 | }, 859 | { 860 | "name": "necronomicon_archer_aoe", 861 | "id": "5204" 862 | }, 863 | { 864 | "name": "witch_doctor_death_ward", 865 | "id": "5141" 866 | }, 867 | { 868 | "name": "enchantress_untouchable", 869 | "id": "5267" 870 | }, 871 | { 872 | "name": "night_stalker_void", 873 | "id": "5275" 874 | }, 875 | { 876 | "name": "faceless_void_time_lock", 877 | "id": "5184" 878 | }, 879 | { 880 | "name": "faceless_void_backtrack", 881 | "id": "5183" 882 | }, 883 | { 884 | "name": "polar_furbolg_ursa_warrior_thunder_clap", 885 | "id": "5302" 886 | }, 887 | { 888 | "name": "slardar_bash", 889 | "id": "5116" 890 | }, 891 | { 892 | "name": "omniknight_repel", 893 | "id": "5264" 894 | }, 895 | { 896 | "name": "huskar_life_break", 897 | "id": "5274" 898 | }, 899 | { 900 | "name": "spirit_breaker_greater_bash", 901 | "id": "5355" 902 | }, 903 | { 904 | "name": "invoker_ghost_walk", 905 | "id": "5381" 906 | }, 907 | { 908 | "name": "tidehunter_gush", 909 | "id": "5118" 910 | }, 911 | { 912 | "name": "tidehunter_ravage", 913 | "id": "5121" 914 | }, 915 | { 916 | "name": "lycan_summon_wolves", 917 | "id": "5395" 918 | }, 919 | { 920 | "name": "drow_ranger_frost_arrows", 921 | "id": "5019" 922 | }, 923 | { 924 | "name": "rattletrap_power_cogs", 925 | "id": "5238" 926 | }, 927 | { 928 | "name": "chen_penitence", 929 | "id": "5328" 930 | }, 931 | { 932 | "name": "skeleton_king_critical_strike", 933 | "id": "5088" 934 | }, 935 | { 936 | "name": "dazzle_shadow_wave", 937 | "id": "5235" 938 | }, 939 | { 940 | "name": "sniper_headshot", 941 | "id": "5155" 942 | }, 943 | { 944 | "name": "life_stealer_consume", 945 | "id": "5253" 946 | }, 947 | { 948 | "name": "kunkka_x_marks_the_spot", 949 | "id": "5033" 950 | }, 951 | { 952 | "name": "furion_teleportation", 953 | "id": "5246" 954 | }, 955 | { 956 | "name": "brewmaster_drunken_brawler", 957 | "id": "5402" 958 | }, 959 | { 960 | "name": "morphling_waveform", 961 | "id": "5052" 962 | }, 963 | { 964 | "name": "morphling_replicate", 965 | "id": "5057" 966 | }, 967 | { 968 | "name": "storm_spirit_static_remnant", 969 | "id": "5098" 970 | }, 971 | { 972 | "name": "drow_ranger_marksmanship", 973 | "id": "5022" 974 | }, 975 | { 976 | "name": "night_stalker_darkness", 977 | "id": "5278" 978 | }, 979 | { 980 | "name": "windrunner_powershot", 981 | "id": "5131" 982 | }, 983 | { 984 | "name": "venomancer_plague_ward", 985 | "id": "5180" 986 | }, 987 | { 988 | "name": "invoker_sun_strike", 989 | "id": "5386" 990 | }, 991 | { 992 | "name": "riki_blink_strike", 993 | "id": "5143" 994 | }, 995 | { 996 | "name": "zuus_static_field", 997 | "id": "5112" 998 | }, 999 | { 1000 | "name": "pudge_meat_hook", 1001 | "id": "5075" 1002 | }, 1003 | { 1004 | "name": "pugna_nether_ward", 1005 | "id": "5188" 1006 | }, 1007 | { 1008 | "name": "treant_overgrowth", 1009 | "id": "5437" 1010 | }, 1011 | { 1012 | "name": "zuus_lightning_bolt", 1013 | "id": "5111" 1014 | }, 1015 | { 1016 | "name": "luna_lunar_blessing", 1017 | "id": "5224" 1018 | }, 1019 | { 1020 | "name": "brewmaster_primal_split", 1021 | "id": "5403" 1022 | }, 1023 | { 1024 | "name": "invoker_chaos_meteor", 1025 | "id": "5385" 1026 | }, 1027 | { 1028 | "name": "ogre_magi_fireblast", 1029 | "id": "5438" 1030 | }, 1031 | { 1032 | "name": "tidehunter_kraken_shell", 1033 | "id": "5119" 1034 | }, 1035 | { 1036 | "name": "ursa_enrage", 1037 | "id": "5360" 1038 | }, 1039 | { 1040 | "name": "lich_chain_frost", 1041 | "id": "5137" 1042 | }, 1043 | { 1044 | "name": "razor_eye_of_the_storm", 1045 | "id": "5085" 1046 | }, 1047 | { 1048 | "name": "ursa_fury_swipes", 1049 | "id": "5359" 1050 | }, 1051 | { 1052 | "name": "juggernaut_omni_slash", 1053 | "id": "5030" 1054 | }, 1055 | { 1056 | "name": "obsidian_destroyer_essence_aura", 1057 | "id": "5393" 1058 | }, 1059 | { 1060 | "name": "warlock_shadow_word", 1061 | "id": "5163" 1062 | }, 1063 | { 1064 | "name": "axe_berserkers_call", 1065 | "id": "5007" 1066 | }, 1067 | { 1068 | "name": "dark_seer_wall_of_replica", 1069 | "id": "5258" 1070 | }, 1071 | { 1072 | "name": "razor_unstable_current", 1073 | "id": "5084" 1074 | }, 1075 | { 1076 | "name": "necrolyte_death_pulse", 1077 | "id": "5158" 1078 | }, 1079 | { 1080 | "name": "necrolyte_reapers_scythe", 1081 | "id": "5161" 1082 | }, 1083 | { 1084 | "name": "broodmother_insatiable_hunger", 1085 | "id": "5282" 1086 | }, 1087 | { 1088 | "name": "templar_assassin_psi_blades", 1089 | "id": "5196" 1090 | }, 1091 | { 1092 | "name": "pugna_nether_blast", 1093 | "id": "5186" 1094 | }, 1095 | { 1096 | "name": "leshrac_pulse_nova", 1097 | "id": "5244" 1098 | }, 1099 | { 1100 | "name": "doom_bringer_scorched_earth", 1101 | "id": "5340" 1102 | }, 1103 | { 1104 | "name": "warlock_golem_permanent_immolation", 1105 | "id": "5167" 1106 | }, 1107 | { 1108 | "name": "bloodseeker_thirst", 1109 | "id": "5017" 1110 | }, 1111 | { 1112 | "name": "mirana_starfall", 1113 | "id": "5051" 1114 | }, 1115 | { 1116 | "name": "viper_corrosive_skin", 1117 | "id": "5220" 1118 | }, 1119 | { 1120 | "name": "kunkka_ghostship", 1121 | "id": "5035" 1122 | }, 1123 | { 1124 | "name": "dark_seer_surge", 1125 | "id": "5257" 1126 | }, 1127 | { 1128 | "name": "lone_druid_spirit_bear_return", 1129 | "id": "5418" 1130 | }, 1131 | { 1132 | "name": "meepo_poof", 1133 | "id": "5431" 1134 | }, 1135 | { 1136 | "name": "black_dragon_splash_attack", 1137 | "id": "5324" 1138 | }, 1139 | { 1140 | "name": "rattletrap_battery_assault", 1141 | "id": "5237" 1142 | }, 1143 | { 1144 | "name": "drow_ranger_silence", 1145 | "id": "5020" 1146 | }, 1147 | { 1148 | "name": "nevermore_necromastery", 1149 | "id": "5062" 1150 | }, 1151 | { 1152 | "name": "jakiro_macropyre", 1153 | "id": "5300" 1154 | }, 1155 | { 1156 | "name": "lone_druid_spirit_bear_entangle", 1157 | "id": "5419" 1158 | }, 1159 | { 1160 | "name": "treant_natures_guise", 1161 | "id": "5434" 1162 | }, 1163 | { 1164 | "name": "beastmaster_greater_boar_poison", 1165 | "id": "5352" 1166 | }, 1167 | { 1168 | "name": "tinker_laser", 1169 | "id": "5150" 1170 | }, 1171 | { 1172 | "name": "clinkz_death_pact", 1173 | "id": "5262" 1174 | }, 1175 | { 1176 | "name": "morphling_morph", 1177 | "id": "5054" 1178 | }, 1179 | { 1180 | "name": "morphling_adaptive_strike", 1181 | "id": "5053" 1182 | }, 1183 | { 1184 | "name": "mirana_invis", 1185 | "id": "5049" 1186 | }, 1187 | { 1188 | "name": "enigma_malefice", 1189 | "id": "5146" 1190 | }, 1191 | { 1192 | "name": "skeleton_king_hellfire_blast", 1193 | "id": "5086" 1194 | }, 1195 | { 1196 | "name": "silencer_global_silence", 1197 | "id": "5380" 1198 | }, 1199 | { 1200 | "name": "phantom_assassin_phantom_strike", 1201 | "id": "5191" 1202 | }, 1203 | { 1204 | "name": "lycan_shapeshift", 1205 | "id": "5398" 1206 | }, 1207 | { 1208 | "name": "templar_assassin_refraction", 1209 | "id": "5194" 1210 | }, 1211 | { 1212 | "name": "warlock_fatal_bonds", 1213 | "id": "5162" 1214 | }, 1215 | { 1216 | "name": "death_prophet_carrion_swarm", 1217 | "id": "5090" 1218 | }, 1219 | { 1220 | "name": "lion_finger_of_death", 1221 | "id": "5047" 1222 | }, 1223 | { 1224 | "name": "ursa_earthshock", 1225 | "id": "5357" 1226 | }, 1227 | { 1228 | "name": "life_stealer_open_wounds", 1229 | "id": "5251" 1230 | }, 1231 | { 1232 | "name": "blue_dragonspawn_overseer_evasion", 1233 | "id": "5326" 1234 | }, 1235 | { 1236 | "name": "slardar_slithereen_crush", 1237 | "id": "5115" 1238 | }, 1239 | { 1240 | "name": "razor_plasma_field", 1241 | "id": "5082" 1242 | }, 1243 | { 1244 | "name": "storm_spirit_electric_vortex", 1245 | "id": "5099" 1246 | }, 1247 | { 1248 | "name": "spectre_dispersion", 1249 | "id": "5336" 1250 | }, 1251 | { 1252 | "name": "courier_return_to_base", 1253 | "id": "5205" 1254 | }, 1255 | { 1256 | "name": "sven_gods_strength", 1257 | "id": "5097" 1258 | }, 1259 | { 1260 | "name": "leshrac_lightning_storm", 1261 | "id": "5243" 1262 | }, 1263 | { 1264 | "name": "life_stealer_rage", 1265 | "id": "5249" 1266 | }, 1267 | { 1268 | "name": "spectre_reality", 1269 | "id": "5338" 1270 | }, 1271 | { 1272 | "name": "antimage_blink", 1273 | "id": "5004" 1274 | }, 1275 | { 1276 | "name": "sniper_shrapnel", 1277 | "id": "5154" 1278 | }, 1279 | { 1280 | "name": "rattletrap_hookshot", 1281 | "id": "5240" 1282 | }, 1283 | { 1284 | "name": "invoker_cold_snap", 1285 | "id": "5376" 1286 | }, 1287 | { 1288 | "name": "crystal_maiden_crystal_nova", 1289 | "id": "5126" 1290 | }, 1291 | { 1292 | "name": "bloodseeker_rupture", 1293 | "id": "5018" 1294 | }, 1295 | { 1296 | "name": "kunkka_tidebringer", 1297 | "id": "5032" 1298 | }, 1299 | { 1300 | "name": "invoker_alacrity", 1301 | "id": "5384" 1302 | }, 1303 | { 1304 | "name": "huskar_inner_vitality", 1305 | "id": "5271" 1306 | }, 1307 | { 1308 | "name": "axe_counter_helix", 1309 | "id": "5009" 1310 | }, 1311 | { 1312 | "name": "spectre_spectral_dagger", 1313 | "id": "5334" 1314 | }, 1315 | { 1316 | "name": "furion_force_of_nature", 1317 | "id": "5247" 1318 | }, 1319 | { 1320 | "name": "brewmaster_earth_hurl_boulder", 1321 | "id": "5404" 1322 | }, 1323 | { 1324 | "name": "alchemist_goblins_greed", 1325 | "id": "5368" 1326 | }, 1327 | { 1328 | "name": "windrunner_windrun", 1329 | "id": "5132" 1330 | }, 1331 | { 1332 | "name": "kunkka_return", 1333 | "id": "5034" 1334 | }, 1335 | { 1336 | "name": "backdoor_protection", 1337 | "id": "5350" 1338 | }, 1339 | { 1340 | "name": "dragon_knight_dragon_blood", 1341 | "id": "5228" 1342 | }, 1343 | { 1344 | "name": "vengefulspirit_magic_missile", 1345 | "id": "5122" 1346 | }, 1347 | { 1348 | "name": "phantom_lancer_spirit_lance", 1349 | "id": "5065" 1350 | }, 1351 | { 1352 | "name": "nevermore_shadowraze1", 1353 | "id": "5059" 1354 | }, 1355 | { 1356 | "name": "courier_take_stash_items", 1357 | "id": "5208" 1358 | }, 1359 | { 1360 | "name": "nevermore_shadowraze2", 1361 | "id": "5060" 1362 | }, 1363 | { 1364 | "name": "nevermore_shadowraze3", 1365 | "id": "5061" 1366 | }, 1367 | { 1368 | "name": "weaver_geminate_attack", 1369 | "id": "5291" 1370 | }, 1371 | { 1372 | "name": "alpha_wolf_command_aura", 1373 | "id": "5309" 1374 | }, 1375 | { 1376 | "name": "ancient_apparition_ice_blast", 1377 | "id": "5348" 1378 | }, 1379 | { 1380 | "name": "ogre_magi_ignite", 1381 | "id": "5439" 1382 | }, 1383 | { 1384 | "name": "harpy_storm_chain_lightning", 1385 | "id": "5319" 1386 | }, 1387 | { 1388 | "name": "omniknight_degen_aura", 1389 | "id": "5265" 1390 | }, 1391 | { 1392 | "name": "enchantress_enchant", 1393 | "id": "5268" 1394 | }, 1395 | { 1396 | "name": "leshrac_split_earth", 1397 | "id": "5241" 1398 | }, 1399 | { 1400 | "name": "death_prophet_witchcraft", 1401 | "id": "5092" 1402 | }, 1403 | { 1404 | "name": "viper_nethertoxin", 1405 | "id": "5219" 1406 | }, 1407 | { 1408 | "name": "necrolyte_sadist", 1409 | "id": "5160" 1410 | }, 1411 | { 1412 | "name": "phantom_assassin_coup_de_grace", 1413 | "id": "5193" 1414 | }, 1415 | { 1416 | "name": "ancient_apparition_cold_feet", 1417 | "id": "5345" 1418 | }, 1419 | { 1420 | "name": "puck_dream_coil", 1421 | "id": "5073" 1422 | }, 1423 | { 1424 | "name": "venomancer_poison_nova", 1425 | "id": "5181" 1426 | }, 1427 | { 1428 | "name": "chaos_knight_chaos_bolt", 1429 | "id": "5426" 1430 | }, 1431 | { 1432 | "name": "doom_bringer_devour", 1433 | "id": "5339" 1434 | }, 1435 | { 1436 | "name": "bounty_hunter_shuriken_toss", 1437 | "id": "5285" 1438 | }, 1439 | { 1440 | "name": "blue_dragonspawn_overseer_devotion_aura", 1441 | "id": "5327" 1442 | }, 1443 | { 1444 | "name": "earthshaker_echo_slam", 1445 | "id": "5026" 1446 | }, 1447 | { 1448 | "name": "necronomicon_warrior_sight", 1449 | "id": "5201" 1450 | }, 1451 | { 1452 | "name": "huskar_berserkers_blood", 1453 | "id": "5273" 1454 | }, 1455 | { 1456 | "name": "templar_assassin_psionic_trap", 1457 | "id": "5197" 1458 | }, 1459 | { 1460 | "name": "night_stalker_crippling_fear", 1461 | "id": "5276" 1462 | }, 1463 | { 1464 | "name": "storm_spirit_overload", 1465 | "id": "5100" 1466 | }, 1467 | { 1468 | "name": "alchemist_unstable_concoction_throw", 1469 | "id": "5367" 1470 | }, 1471 | { 1472 | "name": "morphling_morph_str", 1473 | "id": "5056" 1474 | }, 1475 | { 1476 | "name": "doom_bringer_empty", 1477 | "id": "5343" 1478 | }, 1479 | { 1480 | "name": "doom_bringer_empty", 1481 | "id": "5344" 1482 | }, 1483 | { 1484 | "name": "lycan_howl", 1485 | "id": "5396" 1486 | }, 1487 | { 1488 | "name": "beastmaster_hawk_invisibility", 1489 | "id": "5170" 1490 | }, 1491 | { 1492 | "name": "storm_spirit_ball_lightning", 1493 | "id": "5101" 1494 | }, 1495 | { 1496 | "name": "omniknight_purification", 1497 | "id": "5263" 1498 | }, 1499 | { 1500 | "name": "shadow_demon_shadow_poison", 1501 | "id": "5423" 1502 | }, 1503 | { 1504 | "name": "dragon_knight_elder_dragon_form", 1505 | "id": "5229" 1506 | }, 1507 | { 1508 | "name": "juggernaut_blade_dance", 1509 | "id": "5027" 1510 | }, 1511 | { 1512 | "name": "shadow_demon_disruption", 1513 | "id": "5421" 1514 | }, 1515 | { 1516 | "name": "venomancer_venomous_gale", 1517 | "id": "5178" 1518 | }, 1519 | { 1520 | "name": "gnoll_assassin_envenomed_weapon", 1521 | "id": "5296" 1522 | }, 1523 | { 1524 | "name": "alpha_wolf_critical_strike", 1525 | "id": "5308" 1526 | }, 1527 | { 1528 | "name": "shadow_shaman_ether_shock", 1529 | "id": "5078" 1530 | }, 1531 | { 1532 | "name": "night_stalker_hunter_in_the_night", 1533 | "id": "5277" 1534 | }, 1535 | { 1536 | "name": "dragon_knight_dragon_tail", 1537 | "id": "5227" 1538 | }, 1539 | { 1540 | "name": "gyrocopter_call_down", 1541 | "id": "5364" 1542 | }, 1543 | { 1544 | "name": "beastmaster_inner_beast", 1545 | "id": "5172" 1546 | }, 1547 | { 1548 | "name": "pudge_dismember", 1549 | "id": "5077" 1550 | }, 1551 | { 1552 | "name": "windrunner_focusfire", 1553 | "id": "5133" 1554 | }, 1555 | { 1556 | "name": "ghost_frost_attack", 1557 | "id": "5301" 1558 | }, 1559 | { 1560 | "name": "bane_brain_sap", 1561 | "id": "5011" 1562 | }, 1563 | { 1564 | "name": "necrolyte_heartstopper_aura", 1565 | "id": "5159" 1566 | }, 1567 | { 1568 | "name": "chaos_knight_chaos_strike", 1569 | "id": "5428" 1570 | }, 1571 | { 1572 | "name": "skeleton_king_vampiric_aura", 1573 | "id": "5087" 1574 | }, 1575 | { 1576 | "name": "clinkz_strafe", 1577 | "id": "5259" 1578 | }, 1579 | { 1580 | "name": "life_stealer_feast", 1581 | "id": "5250" 1582 | }, 1583 | { 1584 | "name": "faceless_void_chronosphere", 1585 | "id": "5185" 1586 | }, 1587 | { 1588 | "name": "lion_impale", 1589 | "id": "5044" 1590 | }, 1591 | { 1592 | "name": "necronomicon_archer_mana_burn", 1593 | "id": "5203" 1594 | }, 1595 | { 1596 | "name": "axe_culling_blade", 1597 | "id": "5010" 1598 | }, 1599 | { 1600 | "name": "huskar_burning_spear", 1601 | "id": "5272" 1602 | }, 1603 | { 1604 | "name": "brewmaster_earth_pulverize", 1605 | "id": "5406" 1606 | }, 1607 | { 1608 | "name": "kobold_taskmaster_speed_aura", 1609 | "id": "5293" 1610 | }, 1611 | { 1612 | "name": "brewmaster_earth_spell_immunity", 1613 | "id": "5405" 1614 | }, 1615 | { 1616 | "name": "necronomicon_warrior_mana_burn", 1617 | "id": "5202" 1618 | }, 1619 | { 1620 | "name": "furion_sprout", 1621 | "id": "5245" 1622 | }, 1623 | { 1624 | "name": "invoker_empty2", 1625 | "id": "5374" 1626 | }, 1627 | { 1628 | "name": "puck_ethereal_jaunt", 1629 | "id": "5070" 1630 | }, 1631 | { 1632 | "name": "invoker_empty1", 1633 | "id": "5373" 1634 | }, 1635 | { 1636 | "name": "kunkka_torrent", 1637 | "id": "5031" 1638 | }, 1639 | { 1640 | "name": "phantom_lancer_doppelwalk", 1641 | "id": "5066" 1642 | }, 1643 | { 1644 | "name": "undying_tombstone", 1645 | "id": "5444" 1646 | }, 1647 | { 1648 | "name": "bane_nightmare", 1649 | "id": "5014" 1650 | }, 1651 | { 1652 | "name": "zuus_arc_lightning", 1653 | "id": "5110" 1654 | }, 1655 | { 1656 | "name": "undying_tombstone_zombie_aura", 1657 | "id": "5445" 1658 | }, 1659 | { 1660 | "name": "crystal_maiden_brilliance_aura", 1661 | "id": "5128" 1662 | }, 1663 | { 1664 | "name": "bane_enfeeble", 1665 | "id": "5012" 1666 | }, 1667 | { 1668 | "name": "satyr_soulstealer_mana_burn", 1669 | "id": "5315" 1670 | }, 1671 | { 1672 | "name": "gyrocopter_rocket_barrage", 1673 | "id": "5361" 1674 | }, 1675 | { 1676 | "name": "dragon_knight_frost_breath", 1677 | "id": "5232" 1678 | }, 1679 | { 1680 | "name": "invoker_emp", 1681 | "id": "5383" 1682 | }, 1683 | { 1684 | "name": "shadow_demon_soul_catcher", 1685 | "id": "5422" 1686 | }, 1687 | { 1688 | "name": "lina_fiery_soul", 1689 | "id": "5042" 1690 | }, 1691 | { 1692 | "name": "lina_dragon_slave", 1693 | "id": "5040" 1694 | }, 1695 | { 1696 | "name": "sven_storm_bolt", 1697 | "id": "5094" 1698 | }, 1699 | { 1700 | "name": "broodmother_poison_sting", 1701 | "id": "5284" 1702 | }, 1703 | { 1704 | "name": "meepo_earthbind", 1705 | "id": "5430" 1706 | }, 1707 | { 1708 | "name": "phantom_lancer_phantom_edge", 1709 | "id": "5068" 1710 | }, 1711 | { 1712 | "name": "puck_phase_shift", 1713 | "id": "5072" 1714 | }, 1715 | { 1716 | "name": "faceless_void_time_walk", 1717 | "id": "5182" 1718 | }, 1719 | { 1720 | "name": "puck_waning_rift", 1721 | "id": "5071" 1722 | }, 1723 | { 1724 | "name": "lich_frost_armor", 1725 | "id": "5135" 1726 | }, 1727 | { 1728 | "name": "sandking_burrowstrike", 1729 | "id": "5102" 1730 | }, 1731 | { 1732 | "name": "dark_seer_vacuum", 1733 | "id": "5255" 1734 | }, 1735 | { 1736 | "name": "obsidian_destroyer_sanity_eclipse", 1737 | "id": "5394" 1738 | }, 1739 | { 1740 | "name": "enigma_black_hole", 1741 | "id": "5149" 1742 | }, 1743 | { 1744 | "name": "lycan_summon_wolves_critical_strike", 1745 | "id": "5399" 1746 | }, 1747 | { 1748 | "name": "default_attack", 1749 | "id": "5001" 1750 | }, 1751 | { 1752 | "name": "templar_assassin_trap", 1753 | "id": "5198" 1754 | }, 1755 | { 1756 | "name": "neutral_spell_immunity", 1757 | "id": "5303" 1758 | }, 1759 | { 1760 | "name": "nyx_assassin_impale", 1761 | "id": "5462" 1762 | }, 1763 | { 1764 | "name": "nyx_assassin_mana_burn", 1765 | "id": "5463" 1766 | }, 1767 | { 1768 | "name": "nyx_assassin_spiked_carapace", 1769 | "id": "5464" 1770 | }, 1771 | { 1772 | "name": "nyx_assassin_vendetta", 1773 | "id": "5465" 1774 | }, 1775 | { 1776 | "name": "slark_dark_pact", 1777 | "id": "5494" 1778 | }, 1779 | { 1780 | "name": "slark_pounce", 1781 | "id": "5495" 1782 | }, 1783 | { 1784 | "name": "slark_essence_shift", 1785 | "id": "5496" 1786 | }, 1787 | { 1788 | "name": "slark_shadow_dance", 1789 | "id": "5497" 1790 | }, 1791 | { 1792 | "name": "medusa_split_shot", 1793 | "id": "5504" 1794 | }, 1795 | { 1796 | "name": "medusa_mystic_snake", 1797 | "id": "5505" 1798 | }, 1799 | { 1800 | "name": "medusa_mana_shield", 1801 | "id": "5506" 1802 | }, 1803 | { 1804 | "name": "medusa_stone_gaze", 1805 | "id": "5507" 1806 | }, 1807 | { 1808 | "name": "troll_warlord_berserkers_rage", 1809 | "id": "5508" 1810 | }, 1811 | { 1812 | "name": "troll_warlord_whirling_axes_ranged", 1813 | "id": "5509" 1814 | }, 1815 | { 1816 | "name": "troll_warlord_whirling_axes_melee", 1817 | "id": "5510" 1818 | }, 1819 | { 1820 | "name": "troll_warlord_fervor", 1821 | "id": "5511" 1822 | }, 1823 | { 1824 | "name": "troll_warlord_battle_trance", 1825 | "id": "5512" 1826 | }, 1827 | { 1828 | "name": "centaur_hoof_stomp", 1829 | "id": "5514" 1830 | }, 1831 | { 1832 | "name": "centaur_double_edge", 1833 | "id": "5515" 1834 | }, 1835 | { 1836 | "name": "centaur_return", 1837 | "id": "5516" 1838 | }, 1839 | { 1840 | "name": "centaur_stampede", 1841 | "id": "5517" 1842 | }, 1843 | { 1844 | "name": "magnataur_shockwave", 1845 | "id": "5518" 1846 | }, 1847 | { 1848 | "name": "magnataur_empower", 1849 | "id": "5519" 1850 | }, 1851 | { 1852 | "name": "magnataur_skewer", 1853 | "id": "5520" 1854 | }, 1855 | { 1856 | "name": "magnataur_reverse_polarity", 1857 | "id": "5521" 1858 | }, 1859 | { 1860 | "name": "shredder_whirling_death", 1861 | "id": "5524" 1862 | }, 1863 | { 1864 | "name": "shredder_timber_chain", 1865 | "id": "5525" 1866 | }, 1867 | { 1868 | "name": "shredder_reactive_armor", 1869 | "id": "5526" 1870 | }, 1871 | { 1872 | "name": "shredder_chakram", 1873 | "id": "5527" 1874 | }, 1875 | { 1876 | "name": "shredder_return_chakram", 1877 | "id": "5528" 1878 | }, 1879 | { 1880 | "name": "keeper_of_the_light_illuminate", 1881 | "id": "5471" 1882 | }, 1883 | { 1884 | "name": "keeper_of_the_light_mana_leak", 1885 | "id": "5472" 1886 | }, 1887 | { 1888 | "name": "keeper_of_the_light_chakra_magic", 1889 | "id": "5473" 1890 | }, 1891 | { 1892 | "name": "keeper_of_the_light_spirit_form", 1893 | "id": "5474" 1894 | }, 1895 | { 1896 | "name": "keeper_of_the_light_recall", 1897 | "id": "5475" 1898 | }, 1899 | { 1900 | "name": "keeper_of_the_light_blinding_light", 1901 | "id": "5476" 1902 | }, 1903 | { 1904 | "name": "keeper_of_the_light_illuminate_end", 1905 | "id": "5477" 1906 | }, 1907 | { 1908 | "name": "keeper_of_the_light_spirit_form_illuminate", 1909 | "id": "5479" 1910 | }, 1911 | { 1912 | "name": "rubick_telekinesis", 1913 | "id": "5448" 1914 | }, 1915 | { 1916 | "name": "rubick_fade_bolt", 1917 | "id": "5450" 1918 | }, 1919 | { 1920 | "name": "rubick_null_field", 1921 | "id": "5451" 1922 | }, 1923 | { 1924 | "name": "rubick_spell_steal", 1925 | "id": "5452" 1926 | }, 1927 | { 1928 | "name": "disruptor_thunder_strike", 1929 | "id": "5458" 1930 | }, 1931 | { 1932 | "name": "disruptor_glimpse", 1933 | "id": "5459" 1934 | }, 1935 | { 1936 | "name": "disruptor_kinetic_field", 1937 | "id": "5460" 1938 | }, 1939 | { 1940 | "name": "disruptor_static_storm", 1941 | "id": "5461" 1942 | }, 1943 | { 1944 | "name": "naga_siren_mirror_image", 1945 | "id": "5467" 1946 | }, 1947 | { 1948 | "name": "naga_siren_ensnare", 1949 | "id": "5468" 1950 | }, 1951 | { 1952 | "name": "naga_siren_rip_tide", 1953 | "id": "5469" 1954 | }, 1955 | { 1956 | "name": "naga_siren_song_of_the_siren", 1957 | "id": "5470" 1958 | }, 1959 | { 1960 | "name": "visage_grave_chill", 1961 | "id": "5480" 1962 | }, 1963 | { 1964 | "name": "visage_soul_assumption", 1965 | "id": "5481" 1966 | }, 1967 | { 1968 | "name": "visage_gravekeepers_cloak", 1969 | "id": "5482" 1970 | }, 1971 | { 1972 | "name": "visage_summon_familiars", 1973 | "id": "5483" 1974 | }, 1975 | { 1976 | "name": "wisp_tether", 1977 | "id": "5485" 1978 | }, 1979 | { 1980 | "name": "wisp_spirits", 1981 | "id": "5486" 1982 | }, 1983 | { 1984 | "name": "wisp_overcharge", 1985 | "id": "5487" 1986 | }, 1987 | { 1988 | "name": "wisp_relocate", 1989 | "id": "5488" 1990 | }, 1991 | { 1992 | "name": "wisp_tether_break", 1993 | "id": "5489" 1994 | }, 1995 | { 1996 | "name": "wisp_spirits_in", 1997 | "id": "wisp_spirits_out" 1998 | }, 1999 | { 2000 | "name": "bristleback_viscous_nasal_goo", 2001 | "id": "5548" 2002 | }, 2003 | { 2004 | "name": "bristleback_quill_spray", 2005 | "id": "5549" 2006 | }, 2007 | { 2008 | "name": "bristleback_bristleback", 2009 | "id": "5550" 2010 | }, 2011 | { 2012 | "name": "bristleback_warpath", 2013 | "id": "5551" 2014 | }, 2015 | { 2016 | "name": "tusk_ice_shards", 2017 | "id": "5565" 2018 | }, 2019 | { 2020 | "name": "tusk_snowball", 2021 | "id": "5566" 2022 | }, 2023 | { 2024 | "name": "tusk_frozen_sigil", 2025 | "id": "5567" 2026 | }, 2027 | { 2028 | "name": "tusk_walrus_punch", 2029 | "id": "5568" 2030 | }, 2031 | { 2032 | "name": "skywrath_mage_arcane_bolt", 2033 | "id": "5581" 2034 | }, 2035 | { 2036 | "name": "skywrath_mage_concussive_shot", 2037 | "id": "5582" 2038 | }, 2039 | { 2040 | "name": "skywrath_mage_ancient_seal", 2041 | "id": "5583" 2042 | }, 2043 | { 2044 | "name": "skywrath_mage_mystic_flare", 2045 | "id": "5584" 2046 | }, 2047 | { 2048 | "name": "abaddon_death_coil", 2049 | "id": "5585" 2050 | }, 2051 | { 2052 | "name": "abaddon_aphotic_shield", 2053 | "id": "5586" 2054 | }, 2055 | { 2056 | "name": "abaddon_frostmourne", 2057 | "id": "5587" 2058 | }, 2059 | { 2060 | "name": "abaddon_borrowed_time", 2061 | "id": "5588" 2062 | }, 2063 | { 2064 | "name": "elder_titan_echo_stomp", 2065 | "id": "5589" 2066 | }, 2067 | { 2068 | "name": "elder_titan_ancestral_spirit", 2069 | "id": "5591" 2070 | }, 2071 | { 2072 | "name": "elder_titan_natural_order", 2073 | "id": "5593" 2074 | }, 2075 | { 2076 | "name": "elder_titan_earth_splitter", 2077 | "id": "5594" 2078 | }, 2079 | { 2080 | "name": "legion_commander_overwhelming_odds", 2081 | "id": "5595" 2082 | }, 2083 | { 2084 | "name": "legion_commander_press_the_attack", 2085 | "id": "5596" 2086 | }, 2087 | { 2088 | "name": "legion_commander_moment_of_courage", 2089 | "id": "5597" 2090 | }, 2091 | { 2092 | "name": "legion_commander_duel", 2093 | "id": "5598" 2094 | }, 2095 | { 2096 | "name": "techies_land_mines", 2097 | "id": "5599" 2098 | }, 2099 | { 2100 | "name": "techies_stasis_trap", 2101 | "id": "5600" 2102 | }, 2103 | { 2104 | "name": "techies_suicide_squad_attack", 2105 | "id": "5601" 2106 | }, 2107 | { 2108 | "name": "techies_remote_mines", 2109 | "id": "5602" 2110 | }, 2111 | { 2112 | "name": "ember_spirit_searing_chains", 2113 | "id": "5603" 2114 | }, 2115 | { 2116 | "name": "ember_spirit_sleight_of_fist", 2117 | "id": "5604" 2118 | }, 2119 | { 2120 | "name": "ember_spirit_flame_guard", 2121 | "id": "5605" 2122 | }, 2123 | { 2124 | "name": "ember_spirit_fire_remnant", 2125 | "id": "5606" 2126 | }, 2127 | { 2128 | "name": "ember_spirit_activate_fire_remnant", 2129 | "id": "5607" 2130 | }, 2131 | { 2132 | "name": "earth_spirit_boulder_smash", 2133 | "id": "5608" 2134 | }, 2135 | { 2136 | "name": "earth_spirit_rolling_boulder", 2137 | "id": "5609" 2138 | }, 2139 | { 2140 | "name": "earth_spirit_geomagnetic_grip", 2141 | "id": "5610" 2142 | }, 2143 | { 2144 | "name": "earth_spirit_stone_caller", 2145 | "id": "5611" 2146 | }, 2147 | { 2148 | "name": "earth_spirit_magnetize", 2149 | "id": "5612" 2150 | }, 2151 | { 2152 | "name": "abyssal_underlord_firestorm", 2153 | "id": "5613" 2154 | }, 2155 | { 2156 | "name": "abyssal_underlord_pit_of_malice", 2157 | "id": "5614" 2158 | }, 2159 | { 2160 | "name": "abyssal_underlord_atrophy_aura", 2161 | "id": "5615" 2162 | }, 2163 | { 2164 | "name": "abyssal_underlord_dark_rift", 2165 | "id": "5616" 2166 | }, 2167 | { 2168 | "name": "terrorblade_reflection", 2169 | "id": "5619" 2170 | }, 2171 | { 2172 | "name": "terrorblade_conjure_image", 2173 | "id": "5620" 2174 | }, 2175 | { 2176 | "name": "terrorblade_metamorphosis", 2177 | "id": "5621" 2178 | }, 2179 | { 2180 | "name": "terrorblade_sunder", 2181 | "id": "5622" 2182 | }, 2183 | { 2184 | "name": "phoenix_icarus_dive", 2185 | "id": "5623" 2186 | }, 2187 | { 2188 | "name": "phoenix_fire_spirits", 2189 | "id": "5625" 2190 | }, 2191 | { 2192 | "name": "phoenix_sun_ray", 2193 | "id": "5626" 2194 | }, 2195 | { 2196 | "name": "phoenix_supernova", 2197 | "id": "5630" 2198 | }, 2199 | { 2200 | "name": "drow_ranger_wave_of_silence", 2201 | "id": "5632" 2202 | }, 2203 | { 2204 | "name": "oracle_fortunes_end", 2205 | "id": "5637" 2206 | }, 2207 | { 2208 | "name": "oracle_fates_edict", 2209 | "id": "5638" 2210 | }, 2211 | { 2212 | "name": "oracle_purifying_flames", 2213 | "id": "5639" 2214 | }, 2215 | { 2216 | "name": "oracle_false_promise", 2217 | "id": "5640" 2218 | }, 2219 | { 2220 | "name": "winter_wyvern_arctic_burn", 2221 | "id": "5651" 2222 | }, 2223 | { 2224 | "name": "winter_wyvern_splinter_blast", 2225 | "id": "5652" 2226 | }, 2227 | { 2228 | "name": "winter_wyvern_cold_embrace", 2229 | "id": "5653" 2230 | }, 2231 | { 2232 | "name": "winter_wyvern_winters_curse", 2233 | "id": "5654" 2234 | } 2235 | ] 2236 | } --------------------------------------------------------------------------------