Browse Source

Initial commit

gh-pages
Andrew Zah 2 years ago
commit
9db32cb90d
Signed by: andrei <zah@andrewzah.com> GPG Key ID: 0AE942445EB70FAA

+ 12
- 0
.gitignore View File

@@ -0,0 +1,12 @@
.DS_Store
/doc/
/libs/
/lib/
/.crystal/
/.shards/


# Libraries don't need dependency lock
# Dependencies will be locked in application that uses them
/shard.lock


+ 1
- 0
.travis.yml View File

@@ -0,0 +1 @@
language: crystal

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2016 Andrew Zah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

+ 100
- 0
README.md View File

@@ -0,0 +1,100 @@
# dota

A Crystal client for the [DotA 2 API](https://wiki.teamfortress.com/wiki/WebAPI#Dota_2).

## Installation

Add this to your application's `shard.yml`:

```yaml
dependencies:
dota:
github: azah/dotacr
```

## Usage


```crystal
require "dota"
```

Get your steam API key [here](https://steamcommunity.com/login/home/?goto=%2Fdev%2Fapikey), and configure Dotacr to use it:

```crystal
Dota.configure do |config|
config.api_key = "abcxyz"

# Set API version (defaults to "v1")
# config.api_version = "v1"
end
```

Then use the client:

```crystal
api = Dota.api

api.hero(13) # => (Cached) A single hero - "Puck"
api.heroes # => (Cached) All heroes

api.item(114) # => (Cached) A single item - "Heart of Tarrasque"
api.items # => (Cached) All items

api.ability(5003) # => (Cached) A single ability - "Mana Break"
api.abilities # => (Cached) All abilities

api.team(1375614) # => A single team - "Newbee"
api.teams # => A list of teams

api.teams({"after" => 1375614}) # Allowed options:
#
# :after - Integer, With team IDs equal or greater than this
# :limit - Integer, Amount of teams to return (default is 100)

api.leagues # => All leagues

api.matches(789645621) # => A single match - "Newbee vs Vici Gaming"
api.matches # => A list of matches
api.matches({"hero_id" => 43}) # Allowed options:
#
# :hero_id - Integer, With this hero. See Dota::API::Hero.mapping
# :after - Integer, With match IDs equal or greater than this
# :player_id - Integer, With this player (32-bit Steam ID)
# :league_id - Integer, In this league. Use Dota.leagues to get a list of leagues
# :mode_id - Integer, In this game mode. See Dota::API::Match::MODES
# :skill_level - Integer, In this skill level (ignored if :player_id is provided). See Dota::API::Match::SKILL_LEVELS
# :from - Integer, Minimum timestamp
# :to - Integer, Maximum timestamp
# :min_players - Integer, With at least this number of players
# :league_only - Boolean, Only league matches
# :limit - Integer, Amount of matches to return (default is 100)

api.live_matches # => All live league matches
api.live_matches({"league_id" => 600}) # Allowed options:
#
# :league_id - Integer, In this league. Use Dota.leagues to get a list of leagues
# :match_id - Integer, With this match

api.cosmetic_rarities # => All cosmetic rarities

api.friends(76561198052976237) # => All friends of user
```

TODO: Write usage instructions here

## Development

TODO: Write development instructions here

## Contributing

1. Fork it ( https://github.com/[your-github-name]/dota/fork )
2. Create your feature branch (git checkout -b my-new-feature)
3. Commit your changes (git commit -am 'Add some feature')
4. Push to the branch (git push origin my-new-feature)
5. Create a new Pull Request

## Contributors

- [[your-github-name]](https://github.com/[your-github-name]) Andrew Zah - creator, maintainer

+ 2280
- 0
data/ability.yml
File diff suppressed because it is too large
View File


+ 336
- 0
data/hero.yml View File

@@ -0,0 +1,336 @@
1:
- antimage
- Anti-Mage
2:
- axe
- Axe
3:
- bane
- Bane
4:
- bloodseeker
- Bloodseeker
5:
- crystal_maiden
- Crystal Maiden
6:
- drow_ranger
- Drow Ranger
7:
- earthshaker
- Earthshaker
8:
- juggernaut
- Juggernaut
9:
- mirana
- Mirana
10:
- morphling
- Morphling
11:
- nevermore
- Shadow Fiend
12:
- phantom_lancer
- Phantom Lancer
13:
- puck
- Puck
14:
- pudge
- Pudge
15:
- razor
- Razor
16:
- sand_king
- Sand King
17:
- storm_spirit
- Storm Spirit
18:
- sven
- Sven
19:
- tiny
- Tiny
20:
- vengefulspirit
- Vengeful Spirit
21:
- windrunner
- Windranger
22:
- zuus
- Zeus
23:
- kunkka
- Kunkka
25:
- lina
- Lina
26:
- lion
- Lion
27:
- shadow_shaman
- Shadow Shaman
28:
- slardar
- Slardar
29:
- tidehunter
- Tidehunter
30:
- witch_doctor
- Witch Doctor
31:
- lich
- Lich
32:
- riki
- Riki
33:
- enigma
- Enigma
34:
- tinker
- Tinker
35:
- sniper
- Sniper
36:
- necrolyte
- Necrophos
37:
- warlock
- Warlock
38:
- beastmaster
- Beastmaster
39:
- queenofpain
- Queen of Pain
40:
- venomancer
- Venomancer
41:
- faceless_void
- Faceless Void
42:
- skeleton_king
- Wraith King
43:
- death_prophet
- Death Prophet
44:
- phantom_assassin
- Phantom Assassin
45:
- pugna
- Pugna
46:
- templar_assassin
- Templar Assassin
47:
- viper
- Viper
48:
- luna
- Luna
49:
- dragon_knight
- Dragon Knight
50:
- dazzle
- Dazzle
51:
- rattletrap
- Clockwerk
52:
- leshrac
- Leshrac
53:
- furion
- Nature's Prophet
54:
- life_stealer
- Lifestealer
55:
- dark_seer
- Dark Seer
56:
- clinkz
- Clinkz
57:
- omniknight
- Omniknight
58:
- enchantress
- Enchantress
59:
- huskar
- Huskar
60:
- night_stalker
- Night Stalker
61:
- broodmother
- Broodmother
62:
- bounty_hunter
- Bounty Hunter
63:
- weaver
- Weaver
64:
- jakiro
- Jakiro
65:
- batrider
- Batrider
66:
- chen
- Chen
67:
- spectre
- Spectre
68:
- ancient_apparition
- Ancient Apparition
69:
- doom_bringer
- Doom
70:
- ursa
- Ursa
71:
- spirit_breaker
- Spirit Breaker
72:
- gyrocopter
- Gyrocopter
73:
- alchemist
- Alchemist
74:
- invoker
- Invoker
75:
- silencer
- Silencer
76:
- obsidian_destroyer
- Outworld Devourer
77:
- lycan
- Lycan
78:
- brewmaster
- Brewmaster
79:
- shadow_demon
- Shadow Demon
80:
- lone_druid
- Lone Druid
81:
- chaos_knight
- Chaos Knight
82:
- meepo
- Meepo
83:
- treant
- Treant Protector
84:
- ogre_magi
- Ogre Magi
85:
- undying
- Undying
86:
- rubick
- Rubick
87:
- disruptor
- Disruptor
88:
- nyx_assassin
- Nyx Assassin
89:
- naga_siren
- Naga Siren
90:
- keeper_of_the_light
- Keeper of the Light
91:
- wisp
- Io
92:
- visage
- Visage
93:
- slark
- Slark
94:
- medusa
- Medusa
95:
- troll_warlord
- Troll Warlord
96:
- centaur
- Centaur Warrunner
97:
- magnataur
- Magnus
98:
- shredder
- Timbersaw
99:
- bristleback
- Bristleback
100:
- tusk
- Tusk
101:
- skywrath_mage
- Skywrath Mage
102:
- abaddon
- Abaddon
103:
- elder_titan
- Elder Titan
104:
- legion_commander
- Legion Commander
105:
- techies
- Techies
106:
- ember_spirit
- Ember Spirit
107:
- earth_spirit
- Earth Spirit
108:
- abyssal_underlord
- Underlord
109:
- terrorblade
- Terrorblade
110:
- phoenix
- Phoenix
111:
- oracle
- Oracle
112:
- winter_wyvern
- Winter Wyvern
113:
- arc_warden
- Arc Warden

+ 825
- 0
data/item.yml View File

@@ -0,0 +1,825 @@
0:
- empty
- Empty
1:
- blink
- Blink Dagger
2:
- blades_of_attack
- Blades of Attack
3:
- broadsword
- Broadsword
4:
- chainmail
- Chainmail
5:
- claymore
- Claymore
6:
- helm_of_iron_will
- Helm of Iron Will
7:
- javelin
- Javelin
8:
- mithril_hammer
- Mithril Hammer
9:
- platemail
- Platemail
10:
- quarterstaff
- Quarterstaff
11:
- quelling_blade
- Quelling Blade
12:
- ring_of_protection
- Ring of Protection
13:
- gauntlets
- Gauntlets of Strength
14:
- slippers
- Slippers of Agility
15:
- mantle
- Mantle of Intelligence
16:
- branches
- Iron Branch
17:
- belt_of_strength
- Belt of Strength
18:
- boots_of_elves
- Band of Elvenskin
19:
- robe
- Robe of the Magi
20:
- circlet
- Circlet
21:
- ogre_axe
- Ogre Club
22:
- blade_of_alacrity
- Blade of Alacrity
23:
- staff_of_wizardry
- Staff of Wizardry
24:
- ultimate_orb
- Ultimate Orb
25:
- gloves
- Gloves of Haste
26:
- lifesteal
- Morbid Mask
27:
- ring_of_regen
- Ring of Regen
28:
- sobi_mask
- Sage's Mask
29:
- boots
- Boots of Speed
30:
- gem
- Gem of True Sight
31:
- cloak
- Cloak
32:
- talisman_of_evasion
- Talisman of Evasion
33:
- cheese
- Cheese
34:
- magic_stick
- Magic Stick
35:
- recipe_magic_wand
- "Recipe: Magic Wand"
36:
- magic_wand
- Magic Wand
37:
- ghost
- Ghost Scepter
38:
- clarity
- Clarity
39:
- flask
- Healing Salve
40:
- dust
- Dust of Appearance
41:
- bottle
- Bottle
42:
- ward_observer
- Observer Ward
43:
- ward_sentry
- Sentry Ward
44:
- tango
- Tango
45:
- courier
- Animal Courier
46:
- tpscroll
- Town Portal Scroll
47:
- recipe_travel_boots
- "Recipe: Boots of Travel"
48:
- travel_boots
- Boots of Travel
49:
- recipe_phase_boots
- "Recipe: Phase Boots"
50:
- phase_boots
- Phase Boots
51:
- demon_edge
- Demon Edge
52:
- eagle
- Eaglesong
53:
- reaver
- Reaver
54:
- relic
- Sacred Relic
55:
- hyperstone
- Hyperstone
56:
- ring_of_health
- Ring of Health
57:
- void_stone
- Void Stone
58:
- mystic_staff
- Mystic Staff
59:
- energy_booster
- Energy Booster
60:
- point_booster
- Point Booster
61:
- vitality_booster
- Vitality Booster
62:
- recipe_power_treads
- "Recipe: Power Treads"
63:
- power_treads
- Power Treads
64:
- recipe_hand_of_midas
- "Recipe: Hand of Midas"
65:
- hand_of_midas
- Hand of Midas
66:
- recipe_oblivion_staff
- "Recipe: Oblivion Staff"
67:
- oblivion_staff
- Oblivion Staff
68:
- recipe_pers
- "Recipe: Perseverance"
69:
- pers
- Perseverance
70:
- recipe_poor_mans_shield
- "Recipe: Poor Man's Shield"
71:
- poor_mans_shield
- Poor Man's Shield
72:
- recipe_bracer
- "Recipe: Bracer"
73:
- bracer
- Bracer
74:
- recipe_wraith_band
- "Recipe: Wraith Band"
75:
- wraith_band
- Wraith Band
76:
- recipe_null_talisman
- "Recipe: Null Talisman"
77:
- null_talisman
- Null Talisman
78:
- recipe_mekansm
- "Recipe: Mekansm"
79:
- mekansm
- Mekansm
80:
- recipe_vladmir
- "Recipe: Vladmir's Offering"
81:
- vladmir
- Vladmir's Offering
84:
- flying_courier
- Flying Courier
85:
- recipe_buckler
- "Recipe: Buckler"
86:
- buckler
- Buckler
87:
- recipe_ring_of_basilius
- "Recipe: Ring of Basilius"
88:
- ring_of_basilius
- Ring of Basilius
89:
- recipe_pipe
- "Recipe: Pipe of Insight"
90:
- pipe
- Pipe of Insight
91:
- recipe_urn_of_shadows
- "Recipe: Urn of Shadows"
92:
- urn_of_shadows
- Urn of Shadows
93:
- recipe_headdress
- "Recipe: Headdress"
94:
- headdress
- Headdress
95:
- recipe_sheepstick
- "Recipe: Scythe of Vyse"
96:
- sheepstick
- Scythe of Vyse
97:
- recipe_orchid
- "Recipe: Orchid Malevolence"
98:
- orchid
- Orchid Malevolence
99:
- recipe_cyclone
- "Recipe: Eul's Scepter of Divinity"
100:
- cyclone
- Eul's Scepter of Divinity
101:
- recipe_force_staff
- "Recipe: Force Staff"
102:
- force_staff
- Force Staff
103:
- recipe_dagon
- "Recipe: Dagon"
104:
- dagon
- Dagon
105:
- recipe_necronomicon
- "Recipe: Necronomicon"
106:
- necronomicon
- Necronomicon
107:
- recipe_ultimate_scepter
- "Recipe: Aghanim's Scepter"
108:
- ultimate_scepter
- Aghanim's Scepter
109:
- recipe_refresher
- "Recipe: Refresher Orb"
110:
- refresher
- Refresher Orb
111:
- recipe_assault
- "Recipe: Assault Cuirass"
112:
- assault
- Assault Cuirass
113:
- recipe_heart
- "Recipe: Heart of Tarrasque"
114:
- heart
- Heart of Tarrasque
115:
- recipe_black_king_bar
- "Recipe: Black King Bar"
116:
- black_king_bar
- Black King Bar
117:
- aegis
- Aegis of the Immortal
118:
- recipe_shivas_guard
- "Recipe: Shiva's Guard"
119:
- shivas_guard
- Shiva's Guard
120:
- recipe_bloodstone
- "Recipe: Bloodstone"
121:
- bloodstone
- Bloodstone
122:
- recipe_sphere
- "Recipe: Linken's Sphere"
123:
- sphere
- Linken's Sphere
124:
- recipe_vanguard
- "Recipe: Vanguard"
125:
- vanguard
- Vanguard
126:
- recipe_blade_mail
- "Recipe: Blade Mail"
127:
- blade_mail
- Blade Mail
128:
- recipe_soul_booster
- "Recipe: Soul Booster"
129:
- soul_booster
- Soul Booster
130:
- recipe_hood_of_defiance
- "Recipe: Hood of Defiance"
131:
- hood_of_defiance
- Hood of Defiance
132:
- recipe_rapier
- "Recipe: Divine Rapier"
133:
- rapier
- Divine Rapier
134:
- recipe_monkey_king_bar
- "Recipe: Monkey King Bar"
135:
- monkey_king_bar
- Monkey King Bar
136:
- recipe_radiance
- "Recipe: Radiance"
137:
- radiance
- Radiance
138:
- recipe_butterfly
- "Recipe: Butterfly"
139:
- butterfly
- Butterfly
140:
- recipe_greater_crit
- "Recipe: Daedalus"
141:
- greater_crit
- Daedalus
142:
- recipe_basher
- "Recipe: Skull Basher"
143:
- basher
- Skull Basher
144:
- recipe_bfury
- "Recipe: Battle Fury"
145:
- bfury
- Battle Fury
146:
- recipe_manta
- "Recipe: Manta Style"
147:
- manta
- Manta Style
148:
- recipe_lesser_crit
- "Recipe: Crystalys"
149:
- lesser_crit
- Crystalys
150:
- recipe_armlet
- "Recipe: Armlet of Mordiggian"
151:
- armlet
- Armlet of Mordiggian
152:
- invis_sword
- Shadow Blade
153:
- recipe_sange_and_yasha
- "Recipe: Sange and Yasha"
154:
- sange_and_yasha
- Sange and Yasha
155:
- recipe_satanic
- "Recipe: Satanic"
156:
- satanic
- Satanic
157:
- recipe_mjollnir
- "Recipe: Mjollnir"
158:
- mjollnir
- Mjollnir
159:
- recipe_skadi
- "Recipe: Eye of Skadi"
160:
- skadi
- Eye of Skadi
161:
- recipe_sange
- "Recipe: Sange"
162:
- sange
- Sange
163:
- recipe_helm_of_the_dominator
- "Recipe: Helm of the Dominator"
164:
- helm_of_the_dominator
- Helm of the Dominator
165:
- recipe_maelstrom
- "Recipe: Maelstrom"
166:
- maelstrom
- Maelstrom
167:
- recipe_desolator
- "Recipe: Desolator"
168:
- desolator
- Desolator
169:
- recipe_yasha
- "Recipe: Yasha"
170:
- yasha
- Yasha
171:
- recipe_mask_of_madness
- "Recipe: Mask of Madness"
172:
- mask_of_madness
- Mask of Madness
173:
- recipe_diffusal_blade
- "Recipe: Diffusal Blade"
174:
- diffusal_blade
- Diffusal Blade
175:
- recipe_ethereal_blade
- "Recipe: Ethereal Blade"
176:
- ethereal_blade
- Ethereal Blade
177:
- recipe_soul_ring
- "Recipe: Soul Ring"
178:
- soul_ring
- Soul Ring
179:
- recipe_arcane_boots
- "Recipe: Arcane Boots"
180:
- arcane_boots
- Arcane Boots
181:
- orb_of_venom
- Orb of Venom
182:
- stout_shield
- Stout Shield
183:
- recipe_invis_sword
- "Recipe: Shadow Blade"
184:
- recipe_ancient_janggo
- "Recipe: Drum of Endurance"
185:
- ancient_janggo
- Drum of Endurance
186:
- recipe_medallion_of_courage
- "Recipe: Medallion of Courage"
187:
- medallion_of_courage
- Medallion of Courage
188:
- smoke_of_deceit
- Smoke of Deceit
189:
- recipe_veil_of_discord
- "Recipe: Veil of Discord"
190:
- veil_of_discord
- Veil of Discord
191:
- recipe_necronomicon_2
- "Recipe: Necronomicon"
192:
- recipe_necronomicon_3
- "Recipe: Necronomicon"
193:
- necronomicon_2
- Necronomicon
194:
- necronomicon_3
- Necronomicon
195:
- recipe_diffusal_blade_2
- "Recipe: Diffusal Blade"
196:
- diffusal_blade_2
- Diffusal Blade
197:
- recipe_dagon_2
- "Recipe: Dagon"
198:
- recipe_dagon_3
- "Recipe: Dagon"
199:
- recipe_dagon_4
- "Recipe: Dagon"
200:
- recipe_dagon_5
- "Recipe: Dagon"
201:
- dagon_2
- Dagon
202:
- dagon_3
- Dagon
203:
- dagon_4
- Dagon
204:
- dagon_5
- Dagon
205:
- recipe_rod_of_atos
- "Recipe: Rod of Atos"
206:
- rod_of_atos
- Rod of Atos
207:
- recipe_abyssal_blade
- "Recipe: Abyssal Blade"
208:
- abyssal_blade
- Abyssal Blade
209:
- recipe_heavens_halberd
- "Recipe: Heaven's Halberd"
210:
- heavens_halberd
- Heaven's Halberd
211:
- recipe_ring_of_aquila
- "Recipe: Ring of Aquila"
212:
- ring_of_aquila
- Ring of Aquila
213:
- recipe_tranquil_boots
- "Recipe: Tranquil Boots"
214:
- tranquil_boots
- Tranquil Boots
215:
- shadow_amulet
- Shadow Amulet
216:
- enchanted_mango
- Enchanted Mango
217:
- recipe_ward_dispenser
- "Recipe: Ward Dispenser"
218:
- ward_dispenser
- Ward Dispenser
219:
- recipe_travel_boots_2
- "Recipe: Upgraded Boots of Travel"
220:
- travel_boots_2
- Upgraded Boots of Travel
221:
- recipe_lotus_orb
- "Recipe: Lotus Orb"
226:
- lotus_orb
- Lotus Orb
227:
- recipe_solar_crest
- "Recipe: Solar Crest"
228:
- recipe_octarine_core
- "Recipe: Octarine Core"
229:
- solar_crest
- Solar Crest
230:
- recipe_guardian_greaves
- "Recipe: Guardian Greaves"
231:
- guardian_greaves
- Guardian Greaves
232:
- aether_lens
- Aether Lens
233:
- recipe_aether_lens
- "Recipe: Aether Lens"
234:
- recipe_dragon_lance
- "Recipe: Dragon Lance"
235:
- octarine_core
- Octarine Core
236:
- dragon_lance
- Dragon Lance
237:
- faerie_fire
- Faerie Fire
238:
- recipe_iron_talon
- "Recipe: Iron Talon"
239:
- iron_talon
- Iron Talon
240:
- blight_stone
- Blight Stone
241:
- tango_single
- Tango (Shared)
242:
- crimson_guard
- Crimson Guard
243:
- recipe_crimson_guard
- "Recipe: Crimson Guard"
244:
- wind_lace
- Wind Lace
245:
- recipe_bloodthorn
- "Recipe: Bloodthorn"
246:
- recipe_moon_shard
- "Recipe: Moon Shard"
247:
- moon_shard
- Moon Shard
248:
- recipe_silver_edge
- "Recipe: Silver Edge"
249:
- silver_edge
- Silver Edge
250:
- bloodthorn
- Bloodthorn
251:
- recipe_echo_sabre
- "Recipe: Echo Sabre"
252:
- echo_sabre
- Echo Sabre
253:
- recipe_glimmer_cape
- "Recipe: Glimmer Cape"
254:
- glimmer_cape
- Glimmer Cape
257:
- tome_of_knowledge
- Tome of Knowledge
262:
- recipe_hurricane_pike
- "Recipe: Hurricane Pike"
263:
- hurricane_pike
- Hurricane Pike
264:
- banana
- Banana
265:
- infused_raindrop
- Infused Raindrop
1000:
- halloween_candy_corn
- Greevil Taffy
1001:
- mystery_hook
- DOTA_Tooltip_Ability_item_mystery_hook
1002:
- mystery_arrow
- DOTA_Tooltip_Ability_item_mystery_arrow
1003:
- mystery_missile
- DOTA_Tooltip_Ability_item_mystery_missile
1004:
- mystery_toss
- DOTA_Tooltip_Ability_item_mystery_toss
1005:
- mystery_vacuum
- DOTA_Tooltip_Ability_item_mystery_vacuum
1006:
- halloween_rapier
- DOTA_Tooltip_Ability_item_halloween_rapier
1007:
- greevil_whistle
- Greevil Whistle
1008:
- greevil_whistle_toggle
- Greevil Whistle
1009:
- present
- A Gift!
1010:
- winter_stocking
- Xmas Stocking
1011:
- winter_skates
- Speed Skates
1012:
- winter_cake
- Fruit-bit Cake
1013:
- winter_cookie
- Wizard Cookie
1014:
- winter_coco
- Cocoa with Marshmallows
1015:
- winter_ham
- Clove Studded Ham
1016:
- winter_kringle
- Kringle
1017:
- winter_mushroom
- Snow Mushroom
1018:
- winter_greevil_treat
- Greevil Treat
1019:
- winter_greevil_garbage
- Greevil Chow
1020:
- winter_greevil_chewy
- Greevil Blink Bone

+ 12
- 0
shard.yml View File

@@ -0,0 +1,12 @@
name: dota
version: 0.0.1

dependencies:
cossack:
github: greyblake/crystal-cossack
version: ~> 0.1

authors:
- Andrew Zah <zah@andrewzah.com>

license: MIT

+ 163
- 0
spec/dota_spec.cr View File

@@ -0,0 +1,163 @@
require "./spec_helper"

describe Dota do
it "initializes" do
api = Dota::Dota.new
api.should be_truthy
end

it "configures" do
Dota::Dota.configure do |config|
config.api_key = "xx"
end
Dota::Dota.configuration.api_key.should eq "xx"
end

# set up api
Dota::Dota.configure do |config|
config.api_key = "xx"
end
api = Dota::Dota.api

describe "yaml parsing" do
it "gets item" do
item = api.item(13)
item.id.should eq 13
item.name.should eq "Gauntlets of Strength"
end

it "gets items" do
items = api.items
puts items.size.should eq 275
end

it "gets hero" do
hero = api.hero(13)
hero.id.should eq 13
hero.name.should eq "Puck"
end

it "gets heroes" do
heroes = api.heroes
heroes.size.should eq 112
end

it "gets ability" do
ability = api.ability(5003)
ability.name.should eq "Mana Break"
ability.fullName.should eq "Antimage Mana Break"
end

it "gets abilities" do
abilities = api.abilities
abilities.size.should eq 570
end
end

describe "client requests" do
it "gets leagues" do
api.leagues.should be_a Array(Dota::API::League)
end

it "gets live matches" do
api.live_matches.should be_a Array(Dota::API::LiveMatch)
end

it "gets friends" do
friends = api.friends(76561198052976237)
friends.should be_a Array(Dota::API::Friend)
end

it "gets cosmetic rarities" do
rarities = api.cosmetic_rarities
rarities.should be_a Array(Dota::API::Cosmetic::Rarity)
rarities.size.should eq 8
rarities[0].name.should eq "common"
end

it "gets match" do
m = api.match(789645621)

m.match_id.should eq 789645621
m.radiant_win.should eq true
m.duration.should eq 908
m.start_time.should eq 1405973570
m.match_seq_num.should eq 709365483
m.tower_status_radiant.should eq Dota::API::MatchStatus::Towers.new(2039_i64)
m.tower_status_dire.should eq Dota::API::MatchStatus::Towers.new(1974_i64)
m.first_blood_time.should eq 33
m.lobby_type.should eq Dota::API::MatchStatus::GameModes::CaptainsMode
m.cluster.should eq 111
m.human_players.should eq 10
m.leagueid.should eq 600
m.radiant_team_id.should eq 1375614
m.radiant_name.should eq "Newbee"
m.radiant_logo.should eq "351645122255494137"
m.radiant_team_complete.should eq 1
m.dire_team_id.should eq 726228
m.dire_name.should eq "Vici Gaming"
m.dire_logo.should eq "35248220277958798"
m.dire_team_complete.should eq 1
m.radiant_captain.should eq 98887913
m.dire_captain.should eq 91698091

m.picks_bans.should be_a Array(Dota::API::Match::Draft)
describe "draft" do
draft = m.picks_bans.not_nil!
draft[0].is_pick.should eq false
draft[0].hero_id.should eq 15
draft[0].order.should eq 0
draft[0].team.should eq Dota::API::PlayerStatus::Teams::Radiant
end

m.players.should be_a Array(Dota::API::Match::Player)
describe "player" do
player = m.players.not_nil![0]
player.account_id.should eq 98887913
player.player_slot.should eq 0
player.hero_id.should eq 69
player.kills.should eq 2
player.deaths.should eq 1
player.assists.should eq 13
player.leaver_status.should eq Dota::API::PlayerStatus::Status::Played
player.last_hits.should eq 45
player.denies.should eq 0
player.gold_per_min.should eq 437
player.xp_per_min.should eq 460
player.item0_id.should eq 1
player.item1_id.should eq 34
player.item2_id.should eq 0
player.item3_id.should eq 79
player.item4_id.should eq 214
player.item5_id.should eq 38
end
end

it "gets matches with options set to default" do
ms = api.matches.not_nil!
ms.should be_truthy
ms.size.should eq 100
ms.should be_a Array(Dota::API::BasicMatch)

describe "basic match" do
bm = ms[0]
bm.should be_a Dota::API::BasicMatch
bm.match_id.should be > 0
bm.match_seq_num.should be > 0
bm.players.should be_a Array(Dota::API::BasicPlayer)
end
end

it "gets matches with options" do
describe "limit" do
ms = api.matches({"limit" => 5}).not_nil!
ms.size.should eq 5
end

describe "limit and tournament only" do
ms = api.matches({"limit" => 4, "league_only" => true}).not_nil!
ms.size.should eq 4
end
end
end
end

+ 2
- 0
spec/spec_helper.cr View File

@@ -0,0 +1,2 @@
require "spec"
require "../src/dota"

+ 26
- 0
src/dota.cr View File

@@ -0,0 +1,26 @@
require "yaml"
require "json"

require "./dota/utils/mapped"
require "./dota/api/status/*"
require "./dota/api/cosmetic/rarity"
require "./dota/*"
require "./dota/api/*"

module Dota
class Dota
property :configuration

def self.api
@@client ||= API::Client.new
end

def self.configure(&block : Configuration -> _)
api.configure(&block)
end

def self.configuration
api.configuration
end
end
end

+ 31
- 0
src/dota/api/ability.cr View File

@@ -0,0 +1,31 @@
module Dota
module API
class Ability
include Utilities::Mapped
extend Utilities::Mapped

getter id : Int32, name : String, fullName : String
private getter internalName : String

def initialize(id : Int32)
@id = id
@internalName = mapping["#{@id}"][0].to_s
@name = mapping["#{@id}"][1].to_s
@fullName = mapping["#{@id}"][2].to_s
end

def image_url(type = :lg)
# Possible values for type:
# :hp1 - 90x90 PNG image
# :hp2 - 105x105 PNG image
# :lg - 128x128 PNG image

if @internalName == "stats"
"https://steamcdn-a.akamaihd.net/apps/dota2/images/workshop/itembuilder/stats.png"
else
"http://cdn.dota2.com/apps/dota2/images/abilities/#{@internalName}_#{type}.png"
end
end
end
end
end

+ 27
- 0
src/dota/api/basic_match.cr View File

@@ -0,0 +1,27 @@
module Dota
module API
include MatchStatus

class BasicMatchesList
JSON.mapping(
status: Int32,
num_results: Int32,
total_results: Int32,
results_remaining: Int32,
matches: Array(BasicMatch)
)
end

class BasicMatch
JSON.mapping(
match_id: Int64,
match_seq_num: Int64,
start_time: Int32,
lobby_type: GameModes,
radiant_team_id: Int32,
dire_team_id: Int32,
players: Array(BasicPlayer)
)
end
end
end

+ 11
- 0
src/dota/api/basic_player.cr View File

@@ -0,0 +1,11 @@
module Dota
module API
class BasicPlayer
JSON.mapping(
account_id: {type: Int64, nilable: true},
player_slot: Int8,
hero_id: Int8
)
end
end
end

+ 143
- 0
src/dota/api/client.cr View File

@@ -0,0 +1,143 @@
require "cossack"
require "http"

module Dota
module API
class Client
@configuration : Configuration?

def configuration
@configuration ||= Configuration.new
end

def configure
yield configuration
end

def item(id)
Item.new(id)
end

def items
Item.all
end

def hero(id)
Hero.find(id)
end

def heroes
Hero.all
end

def ability(id)
Ability.new(id)
end

def abilities
Ability.all
end

def team(teamID : Int32)
options = {
"start_at_team_id" => teamID,
"teams_requested" => 1,
}
response = get("GetTeamInfoByTeamID", TeamsList, "IDOTA2Match_570", options)
if response.teams.size > 0
response.teams[0]
end
end

def teams(options = {} of String => Int32)
options["teams_requested"] = options.delete("limit").not_nil! if options.has_key?("limit")
options["start_at_team_id"] = options.delete(:after).not_nil! if options.has_key?("after")

response = get("GetTeamInfoByTeamID", TeamsList, "IDOTA2Match_570", options)
response.teams if response.teams.size > 0
end

def match(matchID : Int32)
response = get("GetMatchDetails", Match, "IDOTA2Match_570", {"match_id" => matchID})
end

# Gets basic matches. For a closer analysis, use #match_detail
def matches(options = {} of String => Int32 | Bool)
# the remapping is to make user input more friendly. e.g. date_min -> from.
options["game_mode"] = options.delete("mode_id").not_nil! if options.has_key?("mode_id")
options["skill"] = options.delete("skill_level").not_nil! if options.has_key?("skill_level")
options["date_min"] = options.delete("from").not_nil! if options.has_key?("from")
options["date_max"] = options.delete("to").not_nil! if options.has_key?("to")
options["account_id"] = options.delete("player_id").not_nil! if options.has_key?("player_id")
options["start_at_match_id"] = options.delete("after").not_nil! if options.has_key?("after")
options["matches_requested"] = options.delete("limit").not_nil! if options.has_key?("limit")
options["tournament_games_only"] = options.delete("league_only").not_nil! if options.has_key?("league_only")

response = get("GetMatchHistory", BasicMatchesList, "IDOTA2Match_570", options)
response.matches if response.matches.size > 0
end

def leagues(options = {"language" => "en"})
response = get("GetLeagueListing", LeaguesList, "IDOTA2Match_570", options)
response.leagues if response.leagues.size > 0
end

def live_matches(options = {} of String => Int32 | String)
response = get("GetLiveLeagueGames", LiveMatchesList, "IDOTA2Match_570", options)
response.games if response.games.size > 0
end

def cosmetic_rarities(options = {"language" => "en"})
response = get("GetRarities", Cosmetic::RaritiesList, "IEconDOTA2_570", options)
response.rarities
end

def friends(user_id)
response = get("GetFriendList", FriendsList, "ISteamUser", {"steamid" => user_id})
response.friends if response.friends.size > 0
end

def get(method, klass, interface, params)
do_request(method, klass, interface, params)
end

private def do_request(method,
klass : T.class,
interface = "IDOTA2Match_570",
params = {} of String => String) : T forall T
method_version = params.delete(:api_version) || configuration.api_version
url = "https://api.steampowered.com/#{interface}/#{method}/#{method_version}"
stringParams = {"key" => configuration.api_key.as(String)}
params.each { |k, v| stringParams[k] = "#{v}" }

@cossack = Cossack::Client.new(url)
response = @cossack.not_nil!.get("", stringParams)
body = response.body

# This catches typos in interface names,
# etc. However some responses respond with
# missing in the JSON content, but a 200
# status code
if response.status != 200
case response.status
when 400...499
raise ClientErrorException.new("#{response.status}: #{response.body}")
when 500...599
raise ServerErrorException.new("#{response.status}: #{response.body}")
end
end

# Lazy, lazy valve is not consistent
# the root key is different among interfaces.
if interface == "ISteamUser"
body = response.body.sub("friendslist", "result")
end

object = Response(T | ErrorResponse).from_json body
result = object.result
raise ResponseException.new(result.error) if result.is_a?(ErrorResponse)
result
end
end
end
end

+ 22
- 0
src/dota/api/cosmetic/rarity.cr View File

@@ -0,0 +1,22 @@
module Dota
module API
module Cosmetic
class RaritiesList
JSON.mapping(
count: Int8,
rarities: Array(Rarity)
)
end

class Rarity
JSON.mapping(
name: String,
id: Int8,
order: Int8,
color: String,
localized_name: String
)
end
end
end
end

+ 12
- 0
src/dota/api/exception.cr View File

@@ -0,0 +1,12 @@
module Dota
module API
class ClientErrorException < Exception
end

class ServerErrorException < Exception
end

class ResponseException < Exception
end
end
end

+ 17
- 0
src/dota/api/friend.cr View File

@@ -0,0 +1,17 @@
module Dota
module API
class FriendsList
JSON.mapping(
friends: Array(Friend)
)
end

class Friend
JSON.mapping(
steamid: String,
relationship: String,
friend_since: Int32
)
end
end
end

+ 35
- 0
src/dota/api/hero.cr View File

@@ -0,0 +1,35 @@
module Dota
module API
class Hero
include Utilities::Mapped
extend Utilities::Mapped

getter id : Int32, name : String
private getter internalName : String

def self.find(id)
if mapping["#{id}"]?
new(id)
else
raise Exception.new("Hero does not exist")
end
end

def initialize(id : Int32)
@id = id
@internalName = mapping["#{id}"][0].to_s
@name = mapping["#{id}"][1].to_s
end

def image_url(type = :full)
# Possible values for type:
# :full - full quality horizontal portrait (256x114px, PNG)
# :lg - large horizontal portrait (205x11px, PNG)
# :sb - small horizontal portrait (59x33px, PNG)
# :vert - full quality vertical portrait (234x272px, JPEG)

"http://cdn.dota2.com/apps/dota2/images/heroes/#{@internal_name}_#{type}.#{type == :vert ? "jpg" : "png"}"
end
end
end
end

+ 25
- 0
src/dota/api/item.cr View File

@@ -0,0 +1,25 @@
module Dota
module API
class Item
include Utilities::Mapped
extend Utilities::Mapped

getter id : Int32, name : String
private getter internalName : String

def initialize(id)
@id = id
@internalName = mapping["#{id}"][0].to_s
@name = mapping["#{id}"][1].to_s
end

# Possible values for type:
# :lg - 85x64 PNG image
# :eg - 27x20 PNG image
def image_url(type = :lg)
filename = "#{@internalName.sub(/\Arecipe_/, "")}_#{type}.png"
"http://cdn.dota2.com/apps/dota2/images/items/#{filename}"
end
end
end
end

+ 31
- 0
src/dota/api/league.cr View File

@@ -0,0 +1,31 @@
module Dota
module API
class LeaguesList
property :leagues

JSON.mapping(
leagues: Array(League)
)
end

class League
JSON.mapping(
leagueid: Int32,
name: String,
description: String,
tournament_url: String,
itemdef: Int32
)

enum Tiers
Amateur = 1
Professional
Premier
end

def to_s
"League: #{@name}"
end
end
end
end

+ 118
- 0
src/dota/api/live_match.cr View File

@@ -0,0 +1,118 @@
module Dota
module API
class LiveMatchesList
JSON.mapping(
games: Array(LiveMatch)
)
end

class LiveMatch
JSON.mapping(
players: Array(SimplePlayer),
match_id: Int64,
lobby_id: Int64,
spectators: Int32,
series_id: Int32,
game_number: Int32,
league_id: Int32,
stream_delay_s: Int32,
radiant_series_wins: Int32,
dire_series_wins: Int32,
series_type: Int32,
league_series_id: Int32,
league_game_id: Int32,
stage_name: String,
league_tier: Int32,
dire_team: {type: Team, nilable: true},
radiant_team: {type: Team, nilable: true},
scoreboard: {type: Scoreboard, nilable: true}
)

class SimplePlayer
include Dota::API::PlayerStatus
JSON.mapping(
account_id: Int32,
name: String,
hero_id: Int32,
team: Teams
)
end

class Team
JSON.mapping(
team_name: String,
team_id: Int32,
team_logo: Int32,
complete: Bool
)
end

class Scoreboard
JSON.mapping(
duration: Float32,
roshan_respawn_timer: Int32,
radiant: Side,
dire: Side
)
end

class Side
include Dota::API::MatchStatus

class Pick
JSON.mapping(hero_id: Int32)
end

class Ban
JSON.mapping(hero_id: Int32)
end

class Ability
JSON.mapping(
ability_id: Int32,
ability_level: Int32
)
end

JSON.mapping(
score: Int32,
tower_state: Towers,
barracks_state: Barracks,
picks: {type: Array(Pick), nilable: true},
bans: {type: Array(Ban), nilable: true},
players: Array(ComplexPlayer),
abilities: {type: Array(Ability), nilable: true}
)
end

class ComplexPlayer
JSON.mapping(
account_id: Int32,
player_slot: Int32,
hero_id: Int32,
level: Int32,
kills: Int32,
death: Int32,
assists: Int32,
last_hits: Int32,
denies: Int32,
gold: Int32,
gold_per_min: Int32,
xp_per_min: Int32,
ultimate_state: Int32,
ultimate_cooldown: Int32,
respawn_timer: Int32,
position_x: Float32,
position_y: Float32,
net_worth: Int32,
item0_id: {type: Int16, key: "item0"},
item1_id: {type: Int16, key: "item1"},
item2_id: {type: Int16, key: "item2"},
item3_id: {type: Int16, key: "item3"},
item4_id: {type: Int16, key: "item4"},
item5_id: {type: Int16, key: "item5"}
)
end
end
end
end

+ 84
- 0
src/dota/api/match.cr View File

@@ -0,0 +1,84 @@
module Dota
module API
class Match
include Dota::API::MatchStatus
include Dota::API::PlayerStatus

module LogoConverter
def self.from_json(parser : JSON::PullParser) : String
parser.read_raw.to_s
end
end

JSON.mapping(
match_id: Int64,
radiant_win: Bool,
duration: Int32,
pre_game_duration: Int32,
start_time: Int32,
match_seq_num: Int64,
tower_status_radiant: Towers,
tower_status_dire: Towers,
barracks_status_radiant: Barracks,
barracks_status_dire: Barracks,
cluster: Int32,
first_blood_time: Int32,
lobby_type: GameModes,
human_players: Int8,
leagueid: Int32,
positive_votes: Int32,
negative_votes: Int32,
game_mode: GameModes,
flags: Int32,
engine: Int32,
radiant_score: Int32,
dire_score: Int32,
radiant_team_id: {type: Int32, nilable: true},
radiant_name: {type: String, nilable: true},
radiant_logo: {type: String, nilable: true, converter: LogoConverter},
radiant_team_complete: {type: Int32, nilable: true},
dire_team_id: {type: Int32, nilable: true},
dire_name: {type: String, nilable: true},
dire_logo: {type: String, nilable: true, converter: LogoConverter},
dire_team_complete: {type: Int32, nilable: true},
radiant_captain: {type: Int32, nilable: true},
dire_captain: {type: Int32, nilable: true},
picks_bans: {type: Array(Draft), nilable: true},
players: {type: Array(Player), nilable: true}
)

class Draft
JSON.mapping(
is_pick: Bool,
hero_id: Int32,
team: Teams,
order: Int32
)
end

class Player < BasicPlayer
include Dota::API::PlayerStatus

JSON.mapping(
account_id: {type: Int64, nilable: true},
player_slot: Int8,
hero_id: Int8,
kills: Int16,
deaths: Int16,
assists: Int16,
leaver_status: Status,
last_hits: Int16,
denies: Int16,
gold_per_min: Int16,
xp_per_min: Int16,
item0_id: {type: Int16, key: "item_0"},
item1_id: {type: Int16, key: "item_1"},
item2_id: {type: Int16, key: "item_2"},
item3_id: {type: Int16, key: "item_3"},
item4_id: {type: Int16, key: "item_4"},
item5_id: {type: Int16, key: "item_5"}
)
end
end
end
end

+ 15
- 0
src/dota/api/response.cr View File

@@ -0,0 +1,15 @@
module Dota
module API
class Response(T)
JSON.mapping(
result: T
)
end

class ErrorResponse
JSON.mapping(
error: String
)
end
end
end

+ 2
- 0
src/dota/api/scheduled_match.cr View File

@@ -0,0 +1,2 @@
# The API request for this isn't working at this time.
# Will add scheduled matches once data is being returned.

+ 84
- 0
src/dota/api/status/match_status.cr View File

@@ -0,0 +1,84 @@
module Dota
module API
module MatchStatus
@[Flags]
enum Towers : Int64
AncientTop
AncientBottom
BottomTier3
BottomTier2
BottomTier1
MiddleTier3
MiddleTier2
MiddleTier1
TopTier3
TopTier2
TopTier1

def self.new(pull : JSON::PullParser)
Towers.new(pull.read_int)
end
end

@[Flags]
enum Barracks : Int64
BottomRanged
BottomMelee
MiddleRanged
MiddleMelee
TopRanged
TopMelee

def self.new(pull : JSON::PullParser)
Barracks.new(pull.read_int)
end
end

enum Types
Invalid = -1
PublicMatchmaking
Practice
Tournament
Tutorial
CoopWithBots
TeamMatch
SoloQueue
Ranked
SoloMid1v1
end

enum GameModes
None
AllPick
CaptainsMode
RandomDraft
SingleDraft
AllRandom
Intro
Diretide
ReverseCaptainsMode
TheGreeviling
Tutorial
MidOnly
LeastPlayed
LimitedHeroPool
CompendiumMatchmaking
Custom
CaptainsDraft
BalancedDraft
AbilityDraft
Event
AllRandomDeathMatch
SoloMid1v1
RankedAllPick
end

enum SkillLevels
Any
Normal
High
VeryHigh
end