Browse Source

Initial commit

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

+ 12
- 0
.gitignore View File

@@ -0,0 +1,12 @@
1
+.DS_Store
2
+/doc/
3
+/libs/
4
+/lib/
5
+/.crystal/
6
+/.shards/
7
+
8
+
9
+# Libraries don't need dependency lock
10
+# Dependencies will be locked in application that uses them
11
+/shard.lock
12
+

+ 1
- 0
.travis.yml View File

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

+ 21
- 0
LICENSE View File

@@ -0,0 +1,21 @@
1
+The MIT License (MIT)
2
+
3
+Copyright (c) 2016 Andrew Zah
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in
13
+all copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+THE SOFTWARE.

+ 100
- 0
README.md View File

@@ -0,0 +1,100 @@
1
+# dota
2
+
3
+A Crystal client for the [DotA 2 API](https://wiki.teamfortress.com/wiki/WebAPI#Dota_2).
4
+
5
+## Installation
6
+
7
+Add this to your application's `shard.yml`:
8
+
9
+```yaml
10
+dependencies:
11
+  dota:
12
+    github: azah/dotacr
13
+```
14
+
15
+## Usage
16
+
17
+
18
+```crystal
19
+require "dota"
20
+```
21
+
22
+Get your steam API key [here](https://steamcommunity.com/login/home/?goto=%2Fdev%2Fapikey), and configure Dotacr to use it:
23
+
24
+```crystal
25
+Dota.configure do |config|
26
+  config.api_key = "abcxyz"
27
+
28
+  # Set API version (defaults to "v1")
29
+  # config.api_version = "v1"
30
+end
31
+```
32
+
33
+Then use the client:
34
+
35
+```crystal
36
+api = Dota.api
37
+
38
+api.hero(13)                            # => (Cached) A single hero - "Puck"
39
+api.heroes                              # => (Cached) All heroes
40
+
41
+api.item(114)                           # => (Cached) A single item - "Heart of Tarrasque"
42
+api.items                               # => (Cached) All items
43
+
44
+api.ability(5003)                       # => (Cached) A single ability - "Mana Break"
45
+api.abilities                           # => (Cached) All abilities
46
+
47
+api.team(1375614)                       # => A single team - "Newbee"
48
+api.teams                               # => A list of teams
49
+
50
+api.teams({"after" => 1375614})         # Allowed options:
51
+                                        #
52
+                                        # :after - Integer, With team IDs equal or greater than this
53
+                                        # :limit - Integer, Amount of teams to return (default is 100)
54
+
55
+api.leagues                             # => All leagues
56
+
57
+api.matches(789645621)                  # => A single match - "Newbee vs Vici Gaming"
58
+api.matches                             # => A list of matches
59
+api.matches({"hero_id" => 43})          # Allowed options:
60
+                                        #
61
+                                        # :hero_id     - Integer, With this hero. See Dota::API::Hero.mapping
62
+                                        # :after       - Integer, With match IDs equal or greater than this
63
+                                        # :player_id   - Integer, With this player (32-bit Steam ID)
64
+                                        # :league_id   - Integer, In this league. Use Dota.leagues to get a list of leagues
65
+                                        # :mode_id     - Integer, In this game mode. See Dota::API::Match::MODES
66
+                                        # :skill_level - Integer, In this skill level (ignored if :player_id is provided). See Dota::API::Match::SKILL_LEVELS
67
+                                        # :from        - Integer, Minimum timestamp
68
+                                        # :to          - Integer, Maximum timestamp
69
+                                        # :min_players - Integer, With at least this number of players
70
+                                        # :league_only - Boolean, Only league matches
71
+                                        # :limit       - Integer, Amount of matches to return (default is 100)
72
+
73
+api.live_matches                        # => All live league matches
74
+api.live_matches({"league_id" => 600})  # Allowed options:
75
+                                        #
76
+                                        # :league_id - Integer, In this league. Use Dota.leagues to get a list of leagues
77
+                                        # :match_id  - Integer, With this match
78
+
79
+api.cosmetic_rarities                   # => All cosmetic rarities
80
+
81
+api.friends(76561198052976237)           # => All friends of user
82
+```
83
+
84
+TODO: Write usage instructions here
85
+
86
+## Development
87
+
88
+TODO: Write development instructions here
89
+
90
+## Contributing
91
+
92
+1. Fork it ( https://github.com/[your-github-name]/dota/fork )
93
+2. Create your feature branch (git checkout -b my-new-feature)
94
+3. Commit your changes (git commit -am 'Add some feature')
95
+4. Push to the branch (git push origin my-new-feature)
96
+5. Create a new Pull Request
97
+
98
+## Contributors
99
+
100
+- [[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
+1:
2
+  - antimage
3
+  - Anti-Mage
4
+2:
5
+  - axe
6
+  - Axe
7
+3:
8
+  - bane
9
+  - Bane
10
+4:
11
+  - bloodseeker
12
+  - Bloodseeker
13
+5:
14
+  - crystal_maiden
15
+  - Crystal Maiden
16
+6:
17
+  - drow_ranger
18
+  - Drow Ranger
19
+7:
20
+  - earthshaker
21
+  - Earthshaker
22
+8:
23
+  - juggernaut
24
+  - Juggernaut
25
+9:
26
+  - mirana
27
+  - Mirana
28
+10:
29
+  - morphling
30
+  - Morphling
31
+11:
32
+  - nevermore
33
+  - Shadow Fiend
34
+12:
35
+  - phantom_lancer
36
+  - Phantom Lancer
37
+13:
38
+  - puck
39
+  - Puck
40
+14:
41
+  - pudge
42
+  - Pudge
43
+15:
44
+  - razor
45
+  - Razor
46
+16:
47
+  - sand_king
48
+  - Sand King
49
+17:
50
+  - storm_spirit
51
+  - Storm Spirit
52
+18:
53
+  - sven
54
+  - Sven
55
+19:
56
+  - tiny
57
+  - Tiny
58
+20:
59
+  - vengefulspirit
60
+  - Vengeful Spirit
61
+21:
62
+  - windrunner
63
+  - Windranger
64
+22:
65
+  - zuus
66
+  - Zeus
67
+23:
68
+  - kunkka
69
+  - Kunkka
70
+25:
71
+  - lina
72
+  - Lina
73
+26:
74
+  - lion
75
+  - Lion
76
+27:
77
+  - shadow_shaman
78
+  - Shadow Shaman
79
+28:
80
+  - slardar
81
+  - Slardar
82
+29:
83
+  - tidehunter
84
+  - Tidehunter
85
+30:
86
+  - witch_doctor
87
+  - Witch Doctor
88
+31:
89
+  - lich
90
+  - Lich
91
+32:
92
+  - riki
93
+  - Riki
94
+33:
95
+  - enigma
96
+  - Enigma
97
+34:
98
+  - tinker
99
+  - Tinker
100
+35:
101
+  - sniper
102
+  - Sniper
103
+36:
104
+  - necrolyte
105
+  - Necrophos
106
+37:
107
+  - warlock
108
+  - Warlock
109
+38:
110
+  - beastmaster
111
+  - Beastmaster
112
+39:
113
+  - queenofpain
114
+  - Queen of Pain
115
+40:
116
+  - venomancer
117
+  - Venomancer
118
+41:
119
+  - faceless_void
120
+  - Faceless Void
121
+42:
122
+  - skeleton_king
123
+  - Wraith King
124
+43:
125
+  - death_prophet
126
+  - Death Prophet
127
+44:
128
+  - phantom_assassin
129
+  - Phantom Assassin
130
+45:
131
+  - pugna
132
+  - Pugna
133
+46:
134
+  - templar_assassin
135
+  - Templar Assassin
136
+47:
137
+  - viper
138
+  - Viper
139
+48:
140
+  - luna
141
+  - Luna
142
+49:
143
+  - dragon_knight
144
+  - Dragon Knight
145
+50:
146
+  - dazzle
147
+  - Dazzle
148
+51:
149
+  - rattletrap
150
+  - Clockwerk
151
+52:
152
+  - leshrac
153
+  - Leshrac
154
+53:
155
+  - furion
156
+  - Nature's Prophet
157
+54:
158
+  - life_stealer
159
+  - Lifestealer
160
+55:
161
+  - dark_seer
162
+  - Dark Seer
163
+56:
164
+  - clinkz
165
+  - Clinkz
166
+57:
167
+  - omniknight
168
+  - Omniknight
169
+58:
170
+  - enchantress
171
+  - Enchantress
172
+59:
173
+  - huskar
174
+  - Huskar
175
+60:
176
+  - night_stalker
177
+  - Night Stalker
178
+61:
179
+  - broodmother
180
+  - Broodmother
181
+62:
182
+  - bounty_hunter
183
+  - Bounty Hunter
184
+63:
185
+  - weaver
186
+  - Weaver
187
+64:
188
+  - jakiro
189
+  - Jakiro
190
+65:
191
+  - batrider
192
+  - Batrider
193
+66:
194
+  - chen
195
+  - Chen
196
+67:
197
+  - spectre
198
+  - Spectre
199
+68:
200
+  - ancient_apparition
201
+  - Ancient Apparition
202
+69:
203
+  - doom_bringer
204
+  - Doom
205
+70:
206
+  - ursa
207
+  - Ursa
208
+71:
209
+  - spirit_breaker
210
+  - Spirit Breaker
211
+72:
212
+  - gyrocopter
213
+  - Gyrocopter
214
+73:
215
+  - alchemist
216
+  - Alchemist
217
+74:
218
+  - invoker
219
+  - Invoker
220
+75:
221
+  - silencer
222
+  - Silencer
223
+76:
224
+  - obsidian_destroyer
225
+  - Outworld Devourer
226
+77:
227
+  - lycan
228
+  - Lycan
229
+78:
230
+  - brewmaster
231
+  - Brewmaster
232
+79:
233
+  - shadow_demon
234
+  - Shadow Demon
235
+80:
236
+  - lone_druid
237
+  - Lone Druid
238
+81:
239
+  - chaos_knight
240
+  - Chaos Knight
241
+82:
242
+  - meepo
243
+  - Meepo
244
+83:
245
+  - treant
246
+  - Treant Protector
247
+84:
248
+  - ogre_magi
249
+  - Ogre Magi
250
+85:
251
+  - undying
252
+  - Undying
253
+86:
254
+  - rubick
255
+  - Rubick
256
+87:
257
+  - disruptor
258
+  - Disruptor
259
+88:
260
+  - nyx_assassin
261
+  - Nyx Assassin
262
+89:
263
+  - naga_siren
264
+  - Naga Siren
265
+90:
266
+  - keeper_of_the_light
267
+  - Keeper of the Light
268
+91:
269
+  - wisp
270
+  - Io
271
+92:
272
+  - visage
273
+  - Visage
274
+93:
275
+  - slark
276
+  - Slark
277
+94:
278
+  - medusa
279
+  - Medusa
280
+95:
281
+  - troll_warlord
282
+  - Troll Warlord
283
+96:
284
+  - centaur
285
+  - Centaur Warrunner
286
+97:
287
+  - magnataur
288
+  - Magnus
289
+98:
290
+  - shredder
291
+  - Timbersaw
292
+99:
293
+  - bristleback
294
+  - Bristleback
295
+100:
296
+  - tusk
297
+  - Tusk
298
+101:
299
+  - skywrath_mage
300
+  - Skywrath Mage
301
+102:
302
+  - abaddon
303
+  - Abaddon
304
+103:
305
+  - elder_titan
306
+  - Elder Titan
307
+104:
308
+  - legion_commander
309
+  - Legion Commander
310
+105:
311
+  - techies
312
+  - Techies
313
+106:
314
+  - ember_spirit
315
+  - Ember Spirit
316
+107:
317
+  - earth_spirit
318
+  - Earth Spirit
319
+108:
320
+  - abyssal_underlord
321
+  - Underlord
322
+109:
323
+  - terrorblade
324
+  - Terrorblade
325
+110:
326
+  - phoenix
327
+  - Phoenix
328
+111:
329
+  - oracle
330
+  - Oracle
331
+112:
332
+  - winter_wyvern
333
+  - Winter Wyvern
334
+113:
335
+  - arc_warden
336
+  - Arc Warden

+ 825
- 0
data/item.yml View File

@@ -0,0 +1,825 @@
1
+0:
2
+  - empty
3
+  - Empty
4
+1:
5
+  - blink
6
+  - Blink Dagger
7
+2:
8
+  - blades_of_attack
9
+  - Blades of Attack
10
+3:
11
+  - broadsword
12
+  - Broadsword
13
+4:
14
+  - chainmail
15
+  - Chainmail
16
+5:
17
+  - claymore
18
+  - Claymore
19
+6:
20
+  - helm_of_iron_will
21
+  - Helm of Iron Will
22
+7:
23
+  - javelin
24
+  - Javelin
25
+8:
26
+  - mithril_hammer
27
+  - Mithril Hammer
28
+9:
29
+  - platemail
30
+  - Platemail
31
+10:
32
+  - quarterstaff
33
+  - Quarterstaff
34
+11:
35
+  - quelling_blade
36
+  - Quelling Blade
37
+12:
38
+  - ring_of_protection
39
+  - Ring of Protection
40
+13:
41
+  - gauntlets
42
+  - Gauntlets of Strength
43
+14:
44
+  - slippers
45
+  - Slippers of Agility
46
+15:
47
+  - mantle
48
+  - Mantle of Intelligence
49
+16:
50
+  - branches
51
+  - Iron Branch
52
+17:
53
+  - belt_of_strength
54
+  - Belt of Strength
55
+18:
56
+  - boots_of_elves
57
+  - Band of Elvenskin
58
+19:
59
+  - robe
60
+  - Robe of the Magi
61
+20:
62
+  - circlet
63
+  - Circlet
64
+21:
65
+  - ogre_axe
66
+  - Ogre Club
67
+22:
68
+  - blade_of_alacrity
69
+  - Blade of Alacrity
70
+23:
71
+  - staff_of_wizardry
72
+  - Staff of Wizardry
73
+24:
74
+  - ultimate_orb
75
+  - Ultimate Orb
76
+25:
77
+  - gloves
78
+  - Gloves of Haste
79
+26:
80
+  - lifesteal
81
+  - Morbid Mask
82
+27:
83
+  - ring_of_regen
84
+  - Ring of Regen
85
+28:
86
+  - sobi_mask
87
+  - Sage's Mask
88
+29:
89
+  - boots
90
+  - Boots of Speed
91
+30:
92
+  - gem
93
+  - Gem of True Sight
94
+31:
95
+  - cloak
96
+  - Cloak
97
+32:
98
+  - talisman_of_evasion
99
+  - Talisman of Evasion
100
+33:
101
+  - cheese
102
+  - Cheese
103
+34:
104
+  - magic_stick
105
+  - Magic Stick
106
+35:
107
+  - recipe_magic_wand
108
+  - "Recipe: Magic Wand"
109
+36:
110
+  - magic_wand
111
+  - Magic Wand
112
+37:
113
+  - ghost
114
+  - Ghost Scepter
115
+38:
116
+  - clarity
117
+  - Clarity
118
+39:
119
+  - flask
120
+  - Healing Salve
121
+40:
122
+  - dust
123
+  - Dust of Appearance
124
+41:
125
+  - bottle
126
+  - Bottle
127
+42:
128
+  - ward_observer
129
+  - Observer Ward
130
+43:
131
+  - ward_sentry
132
+  - Sentry Ward
133
+44:
134
+  - tango
135
+  - Tango
136
+45:
137
+  - courier
138
+  - Animal Courier
139
+46:
140
+  - tpscroll
141
+  - Town Portal Scroll
142
+47:
143
+  - recipe_travel_boots
144
+  - "Recipe: Boots of Travel"
145
+48:
146
+  - travel_boots
147
+  - Boots of Travel
148
+49:
149
+  - recipe_phase_boots
150
+  - "Recipe: Phase Boots"
151
+50:
152
+  - phase_boots
153
+  - Phase Boots
154
+51:
155
+  - demon_edge
156
+  - Demon Edge
157
+52:
158
+  - eagle
159
+  - Eaglesong
160
+53:
161
+  - reaver
162
+  - Reaver
163
+54:
164
+  - relic
165
+  - Sacred Relic
166
+55:
167
+  - hyperstone
168
+  - Hyperstone
169
+56:
170
+  - ring_of_health
171
+  - Ring of Health
172
+57:
173
+  - void_stone
174
+  - Void Stone
175
+58:
176
+  - mystic_staff
177
+  - Mystic Staff
178
+59:
179
+  - energy_booster
180
+  - Energy Booster
181
+60:
182
+  - point_booster
183
+  - Point Booster
184
+61:
185
+  - vitality_booster
186
+  - Vitality Booster
187
+62:
188
+  - recipe_power_treads
189
+  - "Recipe: Power Treads"
190
+63:
191
+  - power_treads
192
+  - Power Treads
193
+64:
194
+  - recipe_hand_of_midas
195
+  - "Recipe: Hand of Midas"
196
+65:
197
+  - hand_of_midas
198
+  - Hand of Midas
199
+66:
200
+  - recipe_oblivion_staff
201
+  - "Recipe: Oblivion Staff"
202
+67:
203
+  - oblivion_staff
204
+  - Oblivion Staff
205
+68:
206
+  - recipe_pers
207
+  - "Recipe: Perseverance"
208
+69:
209
+  - pers
210
+  - Perseverance
211
+70:
212
+  - recipe_poor_mans_shield
213
+  - "Recipe: Poor Man's Shield"
214
+71:
215
+  - poor_mans_shield
216
+  - Poor Man's Shield
217
+72:
218
+  - recipe_bracer
219
+  - "Recipe: Bracer"
220
+73:
221
+  - bracer
222
+  - Bracer
223
+74:
224
+  - recipe_wraith_band
225
+  - "Recipe: Wraith Band"
226
+75:
227
+  - wraith_band
228
+  - Wraith Band
229
+76:
230
+  - recipe_null_talisman
231
+  - "Recipe: Null Talisman"
232
+77:
233
+  - null_talisman
234
+  - Null Talisman
235
+78:
236
+  - recipe_mekansm
237
+  - "Recipe: Mekansm"
238
+79:
239
+  - mekansm
240
+  - Mekansm
241
+80:
242
+  - recipe_vladmir
243
+  - "Recipe: Vladmir's Offering"
244
+81:
245
+  - vladmir
246
+  - Vladmir's Offering
247
+84:
248
+  - flying_courier
249
+  - Flying Courier
250
+85:
251
+  - recipe_buckler
252
+  - "Recipe: Buckler"
253
+86:
254
+  - buckler
255
+  - Buckler
256
+87:
257
+  - recipe_ring_of_basilius
258
+  - "Recipe: Ring of Basilius"
259
+88:
260
+  - ring_of_basilius
261
+  - Ring of Basilius
262
+89:
263
+  - recipe_pipe
264
+  - "Recipe: Pipe of Insight"
265
+90:
266
+  - pipe
267
+  - Pipe of Insight
268
+91:
269
+  - recipe_urn_of_shadows
270
+  - "Recipe: Urn of Shadows"
271
+92:
272
+  - urn_of_shadows
273
+  - Urn of Shadows
274
+93:
275
+  - recipe_headdress
276
+  - "Recipe: Headdress"
277
+94:
278
+  - headdress
279
+  - Headdress
280
+95:
281
+  - recipe_sheepstick
282
+  - "Recipe: Scythe of Vyse"
283
+96:
284
+  - sheepstick
285
+  - Scythe of Vyse
286
+97:
287
+  - recipe_orchid
288
+  - "Recipe: Orchid Malevolence"
289
+98:
290
+  - orchid
291
+  - Orchid Malevolence
292
+99:
293
+  - recipe_cyclone
294
+  - "Recipe: Eul's Scepter of Divinity"
295
+100:
296
+  - cyclone
297
+  - Eul's Scepter of Divinity
298
+101:
299
+  - recipe_force_staff
300
+  - "Recipe: Force Staff"
301
+102:
302
+  - force_staff
303
+  - Force Staff
304
+103:
305
+  - recipe_dagon
306
+  - "Recipe: Dagon"
307
+104:
308
+  - dagon
309
+  - Dagon
310
+105:
311
+  - recipe_necronomicon
312
+  - "Recipe: Necronomicon"
313
+106:
314
+  - necronomicon
315
+  - Necronomicon
316
+107:
317
+  - recipe_ultimate_scepter
318
+  - "Recipe: Aghanim's Scepter"
319
+108:
320
+  - ultimate_scepter
321
+  - Aghanim's Scepter
322
+109:
323
+  - recipe_refresher
324
+  - "Recipe: Refresher Orb"
325
+110:
326
+  - refresher
327
+  - Refresher Orb
328
+111:
329
+  - recipe_assault
330
+  - "Recipe: Assault Cuirass"
331
+112:
332
+  - assault
333
+  - Assault Cuirass
334
+113:
335
+  - recipe_heart
336
+  - "Recipe: Heart of Tarrasque"
337
+114:
338
+  - heart
339
+  - Heart of Tarrasque
340
+115:
341
+  - recipe_black_king_bar
342
+  - "Recipe: Black King Bar"
343
+116:
344
+  - black_king_bar
345
+  - Black King Bar
346
+117:
347
+  - aegis
348
+  - Aegis of the Immortal
349
+118:
350
+  - recipe_shivas_guard
351
+  - "Recipe: Shiva's Guard"
352
+119:
353
+  - shivas_guard
354
+  - Shiva's Guard
355
+120:
356
+  - recipe_bloodstone
357
+  - "Recipe: Bloodstone"
358
+121:
359
+  - bloodstone
360
+  - Bloodstone
361
+122:
362
+  - recipe_sphere
363
+  - "Recipe: Linken's Sphere"
364
+123:
365
+  - sphere
366
+  - Linken's Sphere
367
+124:
368
+  - recipe_vanguard
369
+  - "Recipe: Vanguard"
370
+125:
371
+  - vanguard
372
+  - Vanguard
373
+126:
374
+  - recipe_blade_mail
375
+  - "Recipe: Blade Mail"
376
+127:
377
+  - blade_mail
378
+  - Blade Mail
379
+128:
380
+  - recipe_soul_booster
381
+  - "Recipe: Soul Booster"
382
+129:
383
+  - soul_booster
384
+  - Soul Booster
385
+130:
386
+  - recipe_hood_of_defiance
387
+  - "Recipe: Hood of Defiance"
388
+131:
389
+  - hood_of_defiance
390
+  - Hood of Defiance
391
+132:
392
+  - recipe_rapier
393
+  - "Recipe: Divine Rapier"
394
+133:
395
+  - rapier
396
+  - Divine Rapier
397
+134:
398
+  - recipe_monkey_king_bar
399
+  - "Recipe: Monkey King Bar"
400
+135:
401
+  - monkey_king_bar
402
+  - Monkey King Bar
403
+136:
404
+  - recipe_radiance
405
+  - "Recipe: Radiance"
406
+137:
407
+  - radiance
408
+  - Radiance
409
+138:
410
+  - recipe_butterfly
411
+  - "Recipe: Butterfly"
412
+139:
413
+  - butterfly
414
+  - Butterfly
415
+140:
416
+  - recipe_greater_crit
417
+  - "Recipe: Daedalus"
418
+141:
419
+  - greater_crit
420
+  - Daedalus
421
+142:
422
+  - recipe_basher
423
+  - "Recipe: Skull Basher"
424
+143:
425
+  - basher
426
+  - Skull Basher
427
+144:
428
+  - recipe_bfury
429
+  - "Recipe: Battle Fury"
430
+145:
431
+  - bfury
432
+  - Battle Fury
433
+146:
434
+  - recipe_manta
435
+  - "Recipe: Manta Style"
436
+147:
437
+  - manta
438
+  - Manta Style
439
+148:
440
+  - recipe_lesser_crit
441
+  - "Recipe: Crystalys"
442
+149:
443
+  - lesser_crit
444
+  - Crystalys
445
+150:
446
+  - recipe_armlet
447
+  - "Recipe: Armlet of Mordiggian"
448
+151:
449
+  - armlet
450
+  - Armlet of Mordiggian
451
+152:
452
+  - invis_sword
453
+  - Shadow Blade
454
+153:
455
+  - recipe_sange_and_yasha
456
+  - "Recipe: Sange and Yasha"
457
+154:
458
+  - sange_and_yasha
459
+  - Sange and Yasha
460
+155:
461
+  - recipe_satanic
462
+  - "Recipe: Satanic"
463
+156:
464
+  - satanic
465
+  - Satanic
466
+157:
467
+  - recipe_mjollnir
468
+  - "Recipe: Mjollnir"
469
+158:
470
+  - mjollnir
471
+  - Mjollnir
472
+159:
473
+  - recipe_skadi
474
+  - "Recipe: Eye of Skadi"
475
+160:
476
+  - skadi
477
+  - Eye of Skadi
478
+161:
479
+  - recipe_sange
480
+  - "Recipe: Sange"
481
+162:
482
+  - sange
483
+  - Sange
484
+163:
485
+  - recipe_helm_of_the_dominator
486
+  - "Recipe: Helm of the Dominator"
487
+164:
488
+  - helm_of_the_dominator
489
+  - Helm of the Dominator
490
+165:
491
+  - recipe_maelstrom
492
+  - "Recipe: Maelstrom"
493
+166:
494
+  - maelstrom
495
+  - Maelstrom
496
+167:
497
+  - recipe_desolator
498
+  - "Recipe: Desolator"
499
+168:
500
+  - desolator
501
+  - Desolator
502
+169:
503
+  - recipe_yasha
504
+  - "Recipe: Yasha"
505
+170:
506
+  - yasha
507
+  - Yasha
508
+171:
509
+  - recipe_mask_of_madness
510
+  - "Recipe: Mask of Madness"
511
+172:
512
+  - mask_of_madness
513
+  - Mask of Madness
514
+173:
515
+  - recipe_diffusal_blade
516
+  - "Recipe: Diffusal Blade"
517
+174:
518
+  - diffusal_blade
519
+  - Diffusal Blade
520
+175:
521
+  - recipe_ethereal_blade
522
+  - "Recipe: Ethereal Blade"
523
+176:
524
+  - ethereal_blade
525
+  - Ethereal Blade
526
+177:
527
+  - recipe_soul_ring
528
+  - "Recipe: Soul Ring"
529
+178:
530
+  - soul_ring
531
+  - Soul Ring
532
+179:
533
+  - recipe_arcane_boots
534
+  - "Recipe: Arcane Boots"
535
+180:
536
+  - arcane_boots
537
+  - Arcane Boots
538
+181:
539
+  - orb_of_venom
540
+  - Orb of Venom
541
+182:
542
+  - stout_shield
543
+  - Stout Shield
544
+183:
545
+  - recipe_invis_sword
546
+  - "Recipe: Shadow Blade"
547
+184:
548
+  - recipe_ancient_janggo
549
+  - "Recipe: Drum of Endurance"
550
+185:
551
+  - ancient_janggo
552
+  - Drum of Endurance
553
+186:
554
+  - recipe_medallion_of_courage
555
+  - "Recipe: Medallion of Courage"
556
+187:
557
+  - medallion_of_courage
558
+  - Medallion of Courage
559
+188:
560
+  - smoke_of_deceit
561
+  - Smoke of Deceit
562
+189:
563
+  - recipe_veil_of_discord
564
+  - "Recipe: Veil of Discord"
565
+190:
566
+  - veil_of_discord
567
+  - Veil of Discord
568
+191:
569
+  - recipe_necronomicon_2
570
+  - "Recipe: Necronomicon"
571
+192:
572
+  - recipe_necronomicon_3
573
+  - "Recipe: Necronomicon"
574
+193:
575
+  - necronomicon_2
576
+  - Necronomicon
577
+194:
578
+  - necronomicon_3
579
+  - Necronomicon
580
+195:
581
+  - recipe_diffusal_blade_2
582
+  - "Recipe: Diffusal Blade"
583
+196:
584
+  - diffusal_blade_2
585
+  - Diffusal Blade
586
+197:
587
+  - recipe_dagon_2
588
+  - "Recipe: Dagon"
589
+198:
590
+  - recipe_dagon_3
591
+  - "Recipe: Dagon"
592
+199:
593
+  - recipe_dagon_4
594
+  - "Recipe: Dagon"
595
+200:
596
+  - recipe_dagon_5
597
+  - "Recipe: Dagon"
598
+201:
599
+  - dagon_2
600
+  - Dagon
601
+202:
602
+  - dagon_3
603
+  - Dagon
604
+203:
605
+  - dagon_4
606
+  - Dagon
607
+204:
608
+  - dagon_5
609
+  - Dagon
610
+205:
611
+  - recipe_rod_of_atos
612
+  - "Recipe: Rod of Atos"
613
+206:
614
+  - rod_of_atos
615
+  - Rod of Atos
616
+207:
617
+  - recipe_abyssal_blade
618
+  - "Recipe: Abyssal Blade"
619
+208:
620
+  - abyssal_blade
621
+  - Abyssal Blade
622
+209:
623
+  - recipe_heavens_halberd
624
+  - "Recipe: Heaven's Halberd"
625
+210:
626
+  - heavens_halberd
627
+  - Heaven's Halberd
628
+211:
629
+  - recipe_ring_of_aquila
630
+  - "Recipe: Ring of Aquila"
631
+212:
632
+  - ring_of_aquila
633
+  - Ring of Aquila
634
+213:
635
+  - recipe_tranquil_boots
636
+  - "Recipe: Tranquil Boots"
637
+214:
638
+  - tranquil_boots
639
+  - Tranquil Boots
640
+215:
641
+  - shadow_amulet
642
+  - Shadow Amulet
643
+216:
644
+  - enchanted_mango
645
+  - Enchanted Mango
646
+217:
647
+  - recipe_ward_dispenser
648
+  - "Recipe: Ward Dispenser"
649
+218:
650
+  - ward_dispenser
651
+  - Ward Dispenser
652
+219:
653
+  - recipe_travel_boots_2
654
+  - "Recipe: Upgraded Boots of Travel"
655
+220:
656
+  - travel_boots_2
657
+  - Upgraded Boots of Travel
658
+221:
659
+  - recipe_lotus_orb
660
+  - "Recipe: Lotus Orb"
661
+226:
662
+  - lotus_orb
663
+  - Lotus Orb
664
+227:
665
+  - recipe_solar_crest
666
+  - "Recipe: Solar Crest"
667
+228:
668
+  - recipe_octarine_core
669
+  - "Recipe: Octarine Core"
670
+229:
671
+  - solar_crest
672
+  - Solar Crest
673
+230:
674
+  - recipe_guardian_greaves
675
+  - "Recipe: Guardian Greaves"
676
+231:
677
+  - guardian_greaves
678
+  - Guardian Greaves
679
+232:
680
+  - aether_lens
681
+  - Aether Lens
682
+233:
683
+  - recipe_aether_lens
684
+  - "Recipe: Aether Lens"
685
+234:
686
+  - recipe_dragon_lance
687
+  - "Recipe: Dragon Lance"
688
+235:
689
+  - octarine_core
690
+  - Octarine Core
691
+236:
692
+  - dragon_lance
693
+  - Dragon Lance
694
+237:
695
+  - faerie_fire
696
+  - Faerie Fire
697
+238:
698
+  - recipe_iron_talon
699
+  - "Recipe: Iron Talon"
700
+239:
701
+  - iron_talon
702
+  - Iron Talon
703
+240:
704
+  - blight_stone
705
+  - Blight Stone
706
+241:
707
+  - tango_single
708
+  - Tango (Shared)
709
+242:
710
+  - crimson_guard
711
+  - Crimson Guard
712
+243:
713
+  - recipe_crimson_guard
714
+  - "Recipe: Crimson Guard"
715
+244:
716
+  - wind_lace
717
+  - Wind Lace
718
+245:
719
+  - recipe_bloodthorn
720
+  - "Recipe: Bloodthorn"
721
+246:
722
+  - recipe_moon_shard
723
+  - "Recipe: Moon Shard"
724
+247:
725
+  - moon_shard
726
+  - Moon Shard
727
+248:
728
+  - recipe_silver_edge
729
+  - "Recipe: Silver Edge"
730
+249:
731
+  - silver_edge
732
+  - Silver Edge
733
+250:
734
+  - bloodthorn
735
+  - Bloodthorn
736
+251:
737
+  - recipe_echo_sabre
738
+  - "Recipe: Echo Sabre"
739
+252:
740
+  - echo_sabre
741
+  - Echo Sabre
742
+253:
743
+  - recipe_glimmer_cape
744
+  - "Recipe: Glimmer Cape"
745
+254:
746
+  - glimmer_cape
747
+  - Glimmer Cape
748
+257:
749
+  - tome_of_knowledge
750
+  - Tome of Knowledge
751
+262:
752
+  - recipe_hurricane_pike
753
+  - "Recipe: Hurricane Pike"
754
+263:
755
+  - hurricane_pike
756
+  - Hurricane Pike
757
+264:
758
+  - banana
759
+  - Banana
760
+265:
761
+  - infused_raindrop
762
+  - Infused Raindrop
763
+1000:
764
+  - halloween_candy_corn
765
+  - Greevil Taffy
766
+1001:
767
+  - mystery_hook
768
+  - DOTA_Tooltip_Ability_item_mystery_hook
769
+1002:
770
+  - mystery_arrow
771
+  - DOTA_Tooltip_Ability_item_mystery_arrow
772
+1003:
773
+  - mystery_missile
774
+  - DOTA_Tooltip_Ability_item_mystery_missile
775
+1004:
776
+  - mystery_toss
777
+  - DOTA_Tooltip_Ability_item_mystery_toss
778
+1005:
779
+  - mystery_vacuum
780
+  - DOTA_Tooltip_Ability_item_mystery_vacuum
781
+1006:
782
+  - halloween_rapier
783
+  - DOTA_Tooltip_Ability_item_halloween_rapier
784
+1007:
785
+  - greevil_whistle
786
+  - Greevil Whistle
787
+1008:
788
+  - greevil_whistle_toggle
789
+  - Greevil Whistle
790
+1009:
791
+  - present
792
+  - A Gift!
793
+1010:
794
+  - winter_stocking
795
+  - Xmas Stocking
796
+1011:
797
+  - winter_skates
798
+  - Speed Skates
799
+1012:
800
+  - winter_cake
801
+  - Fruit-bit Cake
802
+1013:
803
+  - winter_cookie
804
+  - Wizard Cookie
805
+1014:
806
+  - winter_coco
807
+  - Cocoa with Marshmallows
808
+1015:
809
+  - winter_ham
810
+  - Clove Studded Ham
811
+1016:
812
+  - winter_kringle
813
+  - Kringle
814
+1017:
815
+  - winter_mushroom
816
+  - Snow Mushroom
817
+1018:
818
+  - winter_greevil_treat
819
+  - Greevil Treat
820
+1019:
821
+  - winter_greevil_garbage
822
+  - Greevil Chow
823
+1020:
824
+  - winter_greevil_chewy
825
+  - Greevil Blink Bone

+ 12
- 0
shard.yml View File

@@ -0,0 +1,12 @@
1
+name: dota
2
+version: 0.0.1
3
+
4
+dependencies:
5
+  cossack:
6
+    github: greyblake/crystal-cossack
7
+    version: ~> 0.1
8
+
9
+authors:
10
+  - Andrew Zah <zah@andrewzah.com>
11
+
12
+license: MIT

+ 163
- 0
spec/dota_spec.cr View File

@@ -0,0 +1,163 @@
1
+require "./spec_helper"
2
+
3
+describe Dota do
4
+  it "initializes" do
5
+    api = Dota::Dota.new
6
+    api.should be_truthy
7
+  end
8
+
9
+  it "configures" do
10
+    Dota::Dota.configure do |config|
11
+      config.api_key = "xx"
12
+    end
13
+    Dota::Dota.configuration.api_key.should eq "xx"
14
+  end
15
+
16
+  # set up api
17
+  Dota::Dota.configure do |config|
18
+    config.api_key = "xx"
19
+  end
20
+  api = Dota::Dota.api
21
+
22
+  describe "yaml parsing" do
23
+    it "gets item" do
24
+      item = api.item(13)
25
+      item.id.should eq 13
26
+      item.name.should eq "Gauntlets of Strength"
27
+    end
28
+
29
+    it "gets items" do
30
+      items = api.items
31
+      puts items.size.should eq 275
32
+    end
33
+
34
+    it "gets hero" do
35
+      hero = api.hero(13)
36
+      hero.id.should eq 13
37
+      hero.name.should eq "Puck"
38
+    end
39
+
40
+    it "gets heroes" do
41
+      heroes = api.heroes
42
+      heroes.size.should eq 112
43
+    end
44
+
45
+    it "gets ability" do
46
+      ability = api.ability(5003)
47
+      ability.name.should eq "Mana Break"
48
+      ability.fullName.should eq "Antimage Mana Break"
49
+    end
50
+
51
+    it "gets abilities" do
52
+      abilities = api.abilities
53
+      abilities.size.should eq 570
54
+    end
55
+  end
56
+
57
+  describe "client requests" do
58
+    it "gets leagues" do
59
+      api.leagues.should be_a Array(Dota::API::League)
60
+    end
61
+
62
+    it "gets live matches" do
63
+      api.live_matches.should be_a Array(Dota::API::LiveMatch)
64
+    end
65
+
66
+    it "gets friends" do
67
+      friends = api.friends(76561198052976237)
68
+      friends.should be_a Array(Dota::API::Friend)
69
+    end
70
+
71
+    it "gets cosmetic rarities" do
72
+      rarities = api.cosmetic_rarities
73
+      rarities.should be_a Array(Dota::API::Cosmetic::Rarity)
74
+      rarities.size.should eq 8
75
+      rarities[0].name.should eq "common"
76
+    end
77
+
78
+    it "gets match" do
79
+      m = api.match(789645621)
80
+
81
+      m.match_id.should eq 789645621
82
+      m.radiant_win.should eq true
83
+      m.duration.should eq 908
84
+      m.start_time.should eq 1405973570
85
+      m.match_seq_num.should eq 709365483
86
+      m.tower_status_radiant.should eq Dota::API::MatchStatus::Towers.new(2039_i64)
87
+      m.tower_status_dire.should eq Dota::API::MatchStatus::Towers.new(1974_i64)
88
+      m.first_blood_time.should eq 33
89
+      m.lobby_type.should eq Dota::API::MatchStatus::GameModes::CaptainsMode
90
+      m.cluster.should eq 111
91
+      m.human_players.should eq 10
92
+      m.leagueid.should eq 600
93
+      m.radiant_team_id.should eq 1375614
94
+      m.radiant_name.should eq "Newbee"
95
+      m.radiant_logo.should eq "351645122255494137"
96
+      m.radiant_team_complete.should eq 1
97
+      m.dire_team_id.should eq 726228
98
+      m.dire_name.should eq "Vici Gaming"
99
+      m.dire_logo.should eq "35248220277958798"
100
+      m.dire_team_complete.should eq 1
101
+      m.radiant_captain.should eq 98887913
102
+      m.dire_captain.should eq 91698091
103
+
104
+      m.picks_bans.should be_a Array(Dota::API::Match::Draft)
105
+      describe "draft" do
106
+        draft = m.picks_bans.not_nil!
107
+        draft[0].is_pick.should eq false
108
+        draft[0].hero_id.should eq 15
109
+        draft[0].order.should eq 0
110
+        draft[0].team.should eq Dota::API::PlayerStatus::Teams::Radiant
111
+      end
112
+
113
+      m.players.should be_a Array(Dota::API::Match::Player)
114
+      describe "player" do
115
+        player = m.players.not_nil![0]
116
+        player.account_id.should eq 98887913
117
+        player.player_slot.should eq 0
118
+        player.hero_id.should eq 69
119
+        player.kills.should eq 2
120
+        player.deaths.should eq 1
121
+        player.assists.should eq 13
122
+        player.leaver_status.should eq Dota::API::PlayerStatus::Status::Played
123
+        player.last_hits.should eq 45
124
+        player.denies.should eq 0
125
+        player.gold_per_min.should eq 437
126
+        player.xp_per_min.should eq 460
127
+        player.item0_id.should eq 1
128
+        player.item1_id.should eq 34
129
+        player.item2_id.should eq 0
130
+        player.item3_id.should eq 79
131
+        player.item4_id.should eq 214
132
+        player.item5_id.should eq 38
133
+      end
134
+    end
135
+
136
+    it "gets matches with options set to default" do
137
+      ms = api.matches.not_nil!
138
+      ms.should be_truthy
139
+      ms.size.should eq 100
140
+      ms.should be_a Array(Dota::API::BasicMatch)
141
+
142
+      describe "basic match" do
143
+        bm = ms[0]
144
+        bm.should be_a Dota::API::BasicMatch
145
+        bm.match_id.should be > 0
146
+        bm.match_seq_num.should be > 0
147
+        bm.players.should be_a Array(Dota::API::BasicPlayer)
148
+      end
149
+    end
150
+
151
+    it "gets matches with options" do
152
+      describe "limit" do
153
+        ms = api.matches({"limit" => 5}).not_nil!
154
+        ms.size.should eq 5
155
+      end
156
+
157
+      describe "limit and tournament only" do
158
+        ms = api.matches({"limit" => 4, "league_only" => true}).not_nil!
159
+        ms.size.should eq 4
160
+      end
161
+    end
162
+  end
163
+end

+ 2
- 0
spec/spec_helper.cr View File

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

+ 26
- 0
src/dota.cr View File

@@ -0,0 +1,26 @@
1
+require "yaml"
2
+require "json"
3
+
4
+require "./dota/utils/mapped"
5
+require "./dota/api/status/*"
6
+require "./dota/api/cosmetic/rarity"
7
+require "./dota/*"
8
+require "./dota/api/*"
9
+
10
+module Dota
11
+  class Dota
12
+    property :configuration
13
+
14
+    def self.api
15
+      @@client ||= API::Client.new
16
+    end
17
+
18
+    def self.configure(&block : Configuration -> _)
19
+      api.configure(&block)
20
+    end
21
+
22
+    def self.configuration
23
+      api.configuration
24
+    end
25
+  end
26
+end

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

@@ -0,0 +1,31 @@
1
+module Dota
2
+  module API
3
+    class Ability
4
+      include Utilities::Mapped
5
+      extend Utilities::Mapped
6
+
7
+      getter id : Int32, name : String, fullName : String
8
+      private getter internalName : String
9
+
10
+      def initialize(id : Int32)
11
+        @id = id
12
+        @internalName = mapping["#{@id}"][0].to_s
13
+        @name = mapping["#{@id}"][1].to_s
14
+        @fullName = mapping["#{@id}"][2].to_s
15
+      end
16
+
17
+      def image_url(type = :lg)
18
+        # Possible values for type:
19
+        # :hp1 - 90x90 PNG image
20
+        # :hp2 - 105x105 PNG image
21
+        # :lg - 128x128 PNG image
22
+
23
+        if @internalName == "stats"
24
+          "https://steamcdn-a.akamaihd.net/apps/dota2/images/workshop/itembuilder/stats.png"
25
+        else
26
+          "http://cdn.dota2.com/apps/dota2/images/abilities/#{@internalName}_#{type}.png"
27
+        end
28
+      end
29
+    end
30
+  end
31
+end

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

@@ -0,0 +1,27 @@
1
+module Dota
2
+  module API
3
+    include MatchStatus
4
+
5
+    class BasicMatchesList
6
+      JSON.mapping(
7
+        status: Int32,
8
+        num_results: Int32,
9
+        total_results: Int32,
10
+        results_remaining: Int32,
11
+        matches: Array(BasicMatch)
12
+      )
13
+    end
14
+
15
+    class BasicMatch
16
+      JSON.mapping(
17
+        match_id: Int64,
18
+        match_seq_num: Int64,
19
+        start_time: Int32,
20
+        lobby_type: GameModes,
21
+        radiant_team_id: Int32,
22
+        dire_team_id: Int32,
23
+        players: Array(BasicPlayer)
24
+      )
25
+    end
26
+  end
27
+end

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

@@ -0,0 +1,11 @@
1
+module Dota
2
+  module API
3
+    class BasicPlayer
4
+      JSON.mapping(
5
+        account_id: {type: Int64, nilable: true},
6
+        player_slot: Int8,
7
+        hero_id: Int8
8
+      )
9
+    end
10
+  end
11
+end

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

@@ -0,0 +1,143 @@
1
+require "cossack"
2
+require "http"
3
+
4
+module Dota
5
+  module API
6
+    class Client
7
+      @configuration : Configuration?
8
+
9
+      def configuration
10
+        @configuration ||= Configuration.new
11
+      end
12
+
13
+      def configure
14
+        yield configuration
15
+      end
16
+
17
+      def item(id)
18
+        Item.new(id)
19
+      end
20
+
21
+      def items
22
+        Item.all
23
+      end
24
+
25
+      def hero(id)
26
+        Hero.find(id)
27
+      end
28
+
29
+      def heroes
30
+        Hero.all
31
+      end
32
+
33
+      def ability(id)
34
+        Ability.new(id)
35
+      end
36
+
37
+      def abilities
38
+        Ability.all
39
+      end
40
+
41
+      def team(teamID : Int32)
42
+        options = {
43
+          "start_at_team_id" => teamID,
44
+          "teams_requested"  => 1,
45
+        }
46
+        response = get("GetTeamInfoByTeamID", TeamsList, "IDOTA2Match_570", options)
47
+        if response.teams.size > 0
48
+          response.teams[0]
49
+        end
50
+      end
51
+
52
+      def teams(options = {} of String => Int32)
53
+        options["teams_requested"] = options.delete("limit").not_nil! if options.has_key?("limit")
54
+        options["start_at_team_id"] = options.delete(:after).not_nil! if options.has_key?("after")
55
+
56
+        response = get("GetTeamInfoByTeamID", TeamsList, "IDOTA2Match_570", options)
57
+        response.teams if response.teams.size > 0
58
+      end
59
+
60
+      def match(matchID : Int32)
61
+        response = get("GetMatchDetails", Match, "IDOTA2Match_570", {"match_id" => matchID})
62
+      end
63
+
64
+      # Gets basic matches. For a closer analysis, use #match_detail
65
+      def matches(options = {} of String => Int32 | Bool)
66
+        # the remapping is to make user input more friendly. e.g. date_min -> from.
67
+        options["game_mode"] = options.delete("mode_id").not_nil! if options.has_key?("mode_id")
68
+        options["skill"] = options.delete("skill_level").not_nil! if options.has_key?("skill_level")
69
+        options["date_min"] = options.delete("from").not_nil! if options.has_key?("from")
70
+        options["date_max"] = options.delete("to").not_nil! if options.has_key?("to")
71
+        options["account_id"] = options.delete("player_id").not_nil! if options.has_key?("player_id")
72
+        options["start_at_match_id"] = options.delete("after").not_nil! if options.has_key?("after")
73
+        options["matches_requested"] = options.delete("limit").not_nil! if options.has_key?("limit")
74
+        options["tournament_games_only"] = options.delete("league_only").not_nil! if options.has_key?("league_only")
75
+
76
+        response = get("GetMatchHistory", BasicMatchesList, "IDOTA2Match_570", options)
77
+        response.matches if response.matches.size > 0
78
+      end
79
+
80
+      def leagues(options = {"language" => "en"})
81
+        response = get("GetLeagueListing", LeaguesList, "IDOTA2Match_570", options)
82
+        response.leagues if response.leagues.size > 0
83
+      end
84
+
85
+      def live_matches(options = {} of String => Int32 | String)
86
+        response = get("GetLiveLeagueGames", LiveMatchesList, "IDOTA2Match_570", options)
87
+        response.games if response.games.size > 0
88
+      end
89
+
90
+      def cosmetic_rarities(options = {"language" => "en"})
91
+        response = get("GetRarities", Cosmetic::RaritiesList, "IEconDOTA2_570", options)
92
+        response.rarities
93
+      end
94
+
95
+      def friends(user_id)
96
+        response = get("GetFriendList", FriendsList, "ISteamUser", {"steamid" => user_id})
97
+        response.friends if response.friends.size > 0
98
+      end
99
+
100
+      def get(method, klass, interface, params)
101
+        do_request(method, klass, interface, params)
102
+      end
103
+
104
+      private def do_request(method,
105
+                             klass : T.class,
106
+                             interface = "IDOTA2Match_570",
107
+                             params = {} of String => String) : T forall T
108
+        method_version = params.delete(:api_version) || configuration.api_version
109
+        url = "https://api.steampowered.com/#{interface}/#{method}/#{method_version}"
110
+        stringParams = {"key" => configuration.api_key.as(String)}
111
+        params.each { |k, v| stringParams[k] = "#{v}" }
112
+
113
+        @cossack = Cossack::Client.new(url)
114
+        response = @cossack.not_nil!.get("", stringParams)
115
+        body = response.body
116
+
117
+        # This catches typos in interface names,
118
+        # etc. However some responses respond with
119
+        # missing in the JSON content, but a 200
120
+        # status code
121
+        if response.status != 200
122
+          case response.status
123
+          when 400...499
124
+            raise ClientErrorException.new("#{response.status}: #{response.body}")
125
+          when 500...599
126
+            raise ServerErrorException.new("#{response.status}: #{response.body}")
127
+          end
128
+        end
129
+
130
+        # Lazy, lazy valve is not consistent
131
+        # the root key is different among interfaces.
132
+        if interface == "ISteamUser"
133
+          body = response.body.sub("friendslist", "result")
134
+        end
135
+
136
+        object = Response(T | ErrorResponse).from_json body
137
+        result = object.result
138
+        raise ResponseException.new(result.error) if result.is_a?(ErrorResponse)
139
+        result
140
+      end
141
+    end
142
+  end
143
+end

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

@@ -0,0 +1,22 @@
1
+module Dota
2
+  module API
3
+    module Cosmetic
4
+      class RaritiesList
5
+        JSON.mapping(
6
+          count: Int8,
7
+          rarities: Array(Rarity)
8
+        )
9
+      end
10
+
11
+      class Rarity
12
+        JSON.mapping(
13
+          name: String,
14
+          id: Int8,
15
+          order: Int8,
16
+          color: String,
17
+          localized_name: String
18
+        )
19
+      end
20
+    end
21
+  end
22
+end

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

@@ -0,0 +1,12 @@
1
+module Dota
2
+  module API
3
+    class ClientErrorException < Exception
4
+    end
5
+
6
+    class ServerErrorException < Exception
7
+    end
8
+
9
+    class ResponseException < Exception
10
+    end
11
+  end
12
+end

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

@@ -0,0 +1,17 @@
1
+module Dota
2
+  module API
3
+    class FriendsList
4
+      JSON.mapping(
5
+        friends: Array(Friend)
6
+      )
7
+    end
8
+
9
+    class Friend
10
+      JSON.mapping(
11
+        steamid: String,
12
+        relationship: String,
13
+        friend_since: Int32
14
+      )
15
+    end
16
+  end
17
+end

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

@@ -0,0 +1,35 @@
1
+module Dota
2
+  module API
3
+    class Hero
4
+      include Utilities::Mapped
5
+      extend Utilities::Mapped
6
+
7
+      getter id : Int32, name : String
8
+      private getter internalName : String
9
+
10
+      def self.find(id)
11
+        if mapping["#{id}"]?
12
+          new(id)
13
+        else
14
+          raise Exception.new("Hero does not exist")
15
+        end
16
+      end
17
+
18
+      def initialize(id : Int32)
19
+        @id = id
20
+        @internalName = mapping["#{id}"][0].to_s
21
+        @name = mapping["#{id}"][1].to_s
22
+      end
23
+
24
+      def image_url(type = :full)
25
+        # Possible values for type:
26
+        # :full - full quality horizontal portrait (256x114px, PNG)
27
+        # :lg - large horizontal portrait (205x11px, PNG)
28
+        # :sb - small horizontal portrait (59x33px, PNG)
29
+        # :vert - full quality vertical portrait (234x272px, JPEG)
30
+
31
+        "http://cdn.dota2.com/apps/dota2/images/heroes/#{@internal_name}_#{type}.#{type == :vert ? "jpg" : "png"}"
32
+      end
33
+    end
34
+  end
35
+end

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

@@ -0,0 +1,25 @@
1
+module Dota
2
+  module API
3
+    class Item
4
+      include Utilities::Mapped
5
+      extend Utilities::Mapped
6
+
7
+      getter id : Int32, name : String
8
+      private getter internalName : String
9
+
10
+      def initialize(id)
11
+        @id = id
12
+        @internalName = mapping["#{id}"][0].to_s
13
+        @name = mapping["#{id}"][1].to_s
14
+      end
15
+
16
+      # Possible values for type:
17
+      # :lg - 85x64 PNG image
18
+      # :eg - 27x20 PNG image
19
+      def image_url(type = :lg)
20
+        filename = "#{@internalName.sub(/\Arecipe_/, "")}_#{type}.png"
21
+        "http://cdn.dota2.com/apps/dota2/images/items/#{filename}"
22
+      end
23
+    end
24
+  end
25
+end

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

@@ -0,0 +1,31 @@
1
+module Dota
2
+  module API
3
+    class LeaguesList
4
+      property :leagues
5
+
6
+      JSON.mapping(
7
+        leagues: Array(League)
8
+      )
9
+    end
10
+
11
+    class League
12
+      JSON.mapping(
13
+        leagueid: Int32,
14
+        name: String,
15
+        description: String,
16
+        tournament_url: String,
17
+        itemdef: Int32
18
+      )
19
+
20
+      enum Tiers
21
+        Amateur      = 1
22
+        Professional
23
+        Premier
24
+      end
25
+
26
+      def to_s
27
+        "League: #{@name}"
28
+      end
29
+    end
30
+  end
31
+end

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

@@ -0,0 +1,118 @@
1
+module Dota
2
+  module API
3
+    class LiveMatchesList
4
+      JSON.mapping(
5
+        games: Array(LiveMatch)
6
+      )
7
+    end
8
+
9
+    class LiveMatch
10
+      JSON.mapping(
11
+        players: Array(SimplePlayer),
12
+        match_id: Int64,
13
+        lobby_id: Int64,
14
+        spectators: Int32,
15
+        series_id: Int32,
16
+        game_number: Int32,
17
+        league_id: Int32,
18
+        stream_delay_s: Int32,
19
+        radiant_series_wins: Int32,
20
+        dire_series_wins: Int32,
21
+        series_type: Int32,
22
+        league_series_id: Int32,
23
+        league_game_id: Int32,
24
+        stage_name: String,
25
+        league_tier: Int32,
26
+        dire_team: {type: Team, nilable: true},
27
+        radiant_team: {type: Team, nilable: true},
28
+        scoreboard: {type: Scoreboard, nilable: true}
29
+      )
30
+
31
+      class SimplePlayer
32
+        include Dota::API::PlayerStatus
33
+        JSON.mapping(
34
+          account_id: Int32,
35
+          name: String,
36
+          hero_id: Int32,
37
+          team: Teams
38
+        )
39
+      end
40
+
41
+      class Team
42
+        JSON.mapping(
43
+          team_name: String,
44
+          team_id: Int32,
45
+          team_logo: Int32,
46
+          complete: Bool
47
+        )
48
+      end
49
+
50
+      class Scoreboard
51
+        JSON.mapping(
52
+          duration: Float32,
53
+          roshan_respawn_timer: Int32,
54
+          radiant: Side,
55
+          dire: Side
56
+        )
57
+      end
58
+
59
+      class Side
60
+        include Dota::API::MatchStatus
61
+
62
+        class Pick
63
+          JSON.mapping(hero_id: Int32)
64
+        end
65
+
66
+        class Ban
67
+          JSON.mapping(hero_id: Int32)
68
+        end
69
+
70
+        class Ability
71
+          JSON.mapping(
72
+            ability_id: Int32,
73
+            ability_level: Int32
74
+          )
75
+        end
76
+
77
+        JSON.mapping(
78
+          score: Int32,
79
+          tower_state: Towers,
80
+          barracks_state: Barracks,
81
+          picks: {type: Array(Pick), nilable: true},
82
+          bans: {type: Array(Ban), nilable: true},
83
+          players: Array(ComplexPlayer),
84
+          abilities: {type: Array(Ability), nilable: true}
85
+        )
86
+      end
87
+
88
+      class ComplexPlayer
89
+        JSON.mapping(
90
+          account_id: Int32,
91
+          player_slot: Int32,
92
+          hero_id: Int32,
93
+          level: Int32,
94
+          kills: Int32,
95
+          death: Int32,
96
+          assists: Int32,
97
+          last_hits: Int32,
98
+          denies: Int32,
99
+          gold: Int32,
100
+          gold_per_min: Int32,
101
+          xp_per_min: Int32,
102
+          ultimate_state: Int32,
103
+          ultimate_cooldown: Int32,
104
+          respawn_timer: Int32,
105
+          position_x: Float32,
106
+          position_y: Float32,
107
+          net_worth: Int32,
108
+          item0_id: {type: Int16, key: "item0"},
109
+          item1_id: {type: Int16, key: "item1"},
110
+          item2_id: {type: Int16, key: "item2"},
111
+          item3_id: {type: Int16, key: "item3"},
112
+          item4_id: {type: Int16, key: "item4"},
113
+          item5_id: {type: Int16, key: "item5"}
114
+        )
115
+      end
116
+    end
117
+  end
118
+end

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

@@ -0,0 +1,84 @@
1
+module Dota
2
+  module API
3
+    class Match
4
+      include Dota::API::MatchStatus
5
+      include Dota::API::PlayerStatus
6
+
7
+      module LogoConverter
8
+        def self.from_json(parser : JSON::PullParser) : String
9
+          parser.read_raw.to_s
10
+        end
11
+      end
12
+
13
+      JSON.mapping(
14
+        match_id: Int64,
15
+        radiant_win: Bool,
16
+        duration: Int32,
17
+        pre_game_duration: Int32,
18
+        start_time: Int32,
19
+        match_seq_num: Int64,
20
+        tower_status_radiant: Towers,
21
+        tower_status_dire: Towers,
22
+        barracks_status_radiant: Barracks,
23
+        barracks_status_dire: Barracks,
24
+        cluster: Int32,
25
+        first_blood_time: Int32,
26
+        lobby_type: GameModes,
27
+        human_players: Int8,
28
+        leagueid: Int32,
29
+        positive_votes: Int32,
30
+        negative_votes: Int32,
31
+        game_mode: GameModes,
32
+        flags: Int32,
33
+        engine: Int32,
34
+        radiant_score: Int32,
35
+        dire_score: Int32,
36
+        radiant_team_id: {type: Int32, nilable: true},
37
+        radiant_name: {type: String, nilable: true},
38
+        radiant_logo: {type: String, nilable: true, converter: LogoConverter},
39
+        radiant_team_complete: {type: Int32, nilable: true},
40
+        dire_team_id: {type: Int32, nilable: true},
41
+        dire_name: {type: String, nilable: true},
42
+        dire_logo: {type: String, nilable: true, converter: LogoConverter},
43
+        dire_team_complete: {type: Int32, nilable: true},
44
+        radiant_captain: {type: Int32, nilable: true},
45
+        dire_captain: {type: Int32, nilable: true},
46
+        picks_bans: {type: Array(Draft), nilable: true},
47
+        players: {type: Array(Player), nilable: true}
48
+      )
49
+
50
+      class Draft
51
+        JSON.mapping(
52
+          is_pick: Bool,
53
+          hero_id: Int32,
54
+          team: Teams,
55
+          order: Int32
56
+        )
57
+      end
58
+
59
+      class Player < BasicPlayer
60
+        include Dota::API::PlayerStatus
61
+
62
+        JSON.mapping(
63
+          account_id: {type: Int64, nilable: true},
64
+          player_slot: Int8,
65
+          hero_id: Int8,
66
+          kills: Int16,
67
+          deaths: Int16,
68
+          assists: Int16,
69
+          leaver_status: Status,
70
+          last_hits: Int16,
71
+          denies: Int16,
72
+          gold_per_min: Int16,
73
+          xp_per_min: Int16,
74
+          item0_id: {type: Int16, key: "item_0"},
75
+          item1_id: {type: Int16, key: "item_1"},
76
+          item2_id: {type: Int16, key: "item_2"},
77
+          item3_id: {type: Int16, key: "item_3"},
78
+          item4_id: {type: Int16, key: "item_4"},
79
+          item5_id: {type: Int16, key: "item_5"}
80
+        )
81
+      end
82
+    end
83
+  end
84
+end

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

@@ -0,0 +1,15 @@
1
+module Dota
2
+  module API
3
+    class Response(T)
4
+      JSON.mapping(
5
+        result: T
6
+      )
7
+    end
8
+
9
+    class ErrorResponse
10
+      JSON.mapping(
11
+        error: String
12
+      )
13
+    end
14
+  end
15
+end

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

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

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

@@ -0,0 +1,84 @@
1
+module Dota
2
+  module API
3
+    module MatchStatus
4
+      @[Flags]
5
+      enum Towers : Int64
6
+        AncientTop
7
+        AncientBottom
8
+        BottomTier3
9
+        BottomTier2
10
+        BottomTier1
11
+        MiddleTier3
12
+        MiddleTier2
13
+        MiddleTier1
14
+        TopTier3
15
+        TopTier2
16
+        TopTier1
17
+
18
+        def self.new(pull : JSON::PullParser)
19
+          Towers.new(pull.read_int)
20
+        end
21
+      end
22
+
23
+      @[Flags]
24
+      enum Barracks : Int64
25
+        BottomRanged
26
+        BottomMelee
27
+        MiddleRanged
28
+        MiddleMelee
29
+        TopRanged
30
+        TopMelee
31
+
32
+        def self.new(pull : JSON::PullParser)
33
+          Barracks.new(pull.read_int)
34
+        end
35
+      end
36
+
37
+      enum Types
38
+        Invalid           = -1
39
+        PublicMatchmaking
40
+        Practice
41
+        Tournament
42
+        Tutorial
43
+        CoopWithBots
44
+        TeamMatch
45
+        SoloQueue
46
+        Ranked
47
+        SoloMid1v1
48
+      end
49
+
50
+      enum GameModes
51
+        None
52
+        AllPick
53
+        CaptainsMode
54
+        RandomDraft
55
+        SingleDraft
56
+        AllRandom
57
+        Intro
58
+        Diretide
59
+        ReverseCaptainsMode
60
+        TheGreeviling
61
+        Tutorial
62
+        MidOnly
63
+        LeastPlayed
64
+        LimitedHeroPool
65
+        CompendiumMatchmaking
66
+        Custom
67
+        CaptainsDraft
68
+        BalancedDraft
69
+        AbilityDraft
70
+        Event
71
+        AllRandomDeathMatch
72
+        SoloMid1v1
73
+        RankedAllPick
74
+      end
75
+
76
+      enum SkillLevels
77
+        Any
78
+        Normal
79
+        High
80
+        VeryHigh
81
+      end
82
+    end
83
+  end
84
+end

+ 22
- 0
src/dota/api/status/player_status.cr View File

@@ -0,0 +1,22 @@
1
+module Dota
2
+  module API
3
+    module PlayerStatus
4
+      enum Teams
5
+        Radiant
6
+        Dire
7
+        Broadcaster
8
+        Unassigned  = 4
9
+      end
10
+
11
+      enum Status
12
+        Played
13
+        Disconnected
14
+        DisconnectedWithAbandon
15
+        Abandoned
16
+        AFK
17
+        NeverConnected
18
+        TimedOut
19
+      end
20
+    end
21
+  end
22
+end

+ 32
- 0
src/dota/api/team.cr View File

@@ -0,0 +1,32 @@
1
+module Dota
2
+  module API
3
+    class TeamsList
4
+      JSON.mapping(
5
+        status: Int8,
6
+        teams: Array(Team)
7
+      )
8
+    end
9
+
10
+    class Team
11
+      JSON.mapping(
12
+        name: String,
13
+        tag: String,
14
+        time_created: Int32,
15
+        calibration_games_remaining: Int8,
16
+        logo: Int64,
17
+        logo_sponsor: Int64,
18
+        country_code: String,
19
+        url: String,
20
+        games_played: Int16,
21
+        admin_account_id: Int32,
22
+        player_0_account_id: {type: Int32, nilable: true},
23
+        player_1_account_id: {type: Int32, nilable: true},
24
+        player_2_account_id: {type: Int32, nilable: true},
25
+        player_3_account_id: {type: Int32, nilable: true},
26
+        player_4_account_id: {type: Int32, nilable: true},
27
+        player_5_account_id: {type: Int32, nilable: true},
28
+        player_6_account_id: {type: Int32, nilable: true}
29
+      )
30
+    end
31
+  end
32
+end

+ 13
- 0
src/dota/configuration.cr View File

@@ -0,0 +1,13 @@
1
+module Dota
2
+  class Configuration
3
+    property api_key : String?
4
+    getter api_version : String
5
+
6
+    DEFAULT_API_VERSION = "v1"
7
+    OPTIONS             = [API_KEY, API_VERSION]
8
+
9
+    def initialize
10
+      @api_version = DEFAULT_API_VERSION
11
+    end
12
+  end
13
+end

+ 20
- 0
src/dota/utils/mapped.cr View File

@@ -0,0 +1,20 @@
1
+module Dota
2
+  module Utilities
3
+    module Mapped
4
+      @@mapping : YAML::Any?
5
+      @@all : YAML::Any?
6
+
7
+      def mapping
8
+        @@mapping ||= begin
9
+          filename = "#{{{@type.name.split("::").last.downcase}}}.yml"
10
+          path = File.join(".", "data", filename)
11
+          YAML.parse(File.read(path))
12
+        end
13
+      end
14
+
15
+      def all
16
+        @@all ||= mapping.map { |id| new(id.to_s.to_i32) }
17
+      end
18
+    end
19
+  end
20
+end

+ 3
- 0
src/dota/version.cr View File

@@ -0,0 +1,3 @@
1
+module Dota
2
+  VERSION = "0.0.1"
3
+end

Loading…
Cancel
Save