· 10 years ago · Sep 16, 2016, 10:02 AM
1import discord
2from discord.ext import commands
3from .utils.dataIO import fileIO
4from .utils import checks
5import os
6import time
7import aiohttp
8import urllib
9import asyncio
10from copy import deepcopy
11import random
12
13# Check if BeautifulSoup4 is installed
14try:
15 from bs4 import BeautifulSoup
16 soupAvailable = True
17except:
18 soupAvailable = False
19
20# Check if Dota2py is installed
21try:
22 from dota2py import api
23 dotaAvailable = True
24except:
25 dotaAvailable = False
26
27# Check if tabulate is installed
28try:
29 from tabulate import tabulate
30 tabulateAvailable = True
31except:
32 tabulateAvailable = False
33
34class Dota:
35 """Dota 2 Red Cog"""
36
37 def __init__(self, bot):
38 self.bot = bot
39 self.dota_settings = fileIO("data/dota/settings.json", "load")
40
41 # Check for key either in settings or in ENV
42 if "key" in self.dota_settings.keys() and self.dota_settings["key"] != "":
43
44 # If exists in setting and is set
45 api.set_api_key(self.dota_settings["key"])
46 self.key = True
47
48 elif os.environ.get("DOTA2_API_KEY") is not None:
49
50 # If exists in env vars and is set
51 api.set_api_key(os.environ.get("DOTA2_API_KEY"))
52 self.key = True
53
54 else:
55 self.key = False
56
57
58 @commands.group(pass_context = True)
59 async def dota(self, ctx):
60 """Returns various data for dota players"""
61
62 if ctx.invoked_subcommand is None:
63 await self.bot.say("Type help dota for info.")
64
65 @dota.command(name = 'setkey', pass_context = True)
66 async def setkey(self, ctx, key):
67 """Sets the Dota 2 Wep API key (PM ONLY)"""
68
69 # Perform the PM check
70 if ctx.message.channel.is_private:
71
72 self.dota_settings["key"] = key.strip()
73 fileIO("data/dota/settings.json", "save", self.dota_settings)
74
75 # Set the client's API key
76 api.set_api_key(self.dota_settings["key"])
77
78 # Change the current key status
79 self.key = True
80
81 await self.bot.say("Key saved and applied")
82 else:
83 await self.bot.say("Please run this command in PM")
84
85 @dota.command(name = 'online', pass_context = True)
86 async def online(self, ctx):
87 """Returns current amount of players"""
88
89 # Build an url
90 url = "https://steamdb.info/app/570/graphs/"
91
92 async with aiohttp.get(url) as response:
93 soupObject = BeautifulSoup(await response.text(), "html.parser")
94
95 # Parse the data and send it
96 try:
97 online = soupObject.find(class_='home-stats').find('li').find('strong').get_text()
98 await self.bot.say(online + ' players are playing this game at the moment')
99 except:
100 await self.bot.say("Couldn't load amount of players. No one is playing this game anymore or there's an error.")
101
102 @dota.command(name = 'hero', pass_context = True)
103 async def hero(self, ctx, *, hero):
104 """Shows some info about hero"""
105
106 # Get and parse the required hero
107 reqHero = urllib.parse.quote(hero.lower())
108
109 # Moved hero table builder to separate function for a more clean code
110 # TODO: Probably should make it a more "global" function and pass down the ctx into it
111 async def buildHeroInfo(payload):
112 herojson = payload
113
114 if herojson["Range"] == 128:
115 herotype = "Melee"
116 else:
117 herotype = "Ranged"
118
119 # Generate the needed table
120 table = [
121 [
122 "HP",
123 herojson["HP"],
124 "%.2f" % (float(herojson["StrGain"]) * 19)
125 ],
126 [
127 "MP",
128 herojson["Mana"],
129 "%.2f" % (float(herojson["IntGain"]) * 19)
130 ],
131 [
132 "AGI",
133 herojson["BaseAgi"],
134 herojson["AgiGain"]
135 ],
136 [
137 "STR",
138 herojson["BaseStr"],
139 herojson["StrGain"]
140 ],
141 [
142 "INT",
143 herojson["BaseInt"],
144 herojson["IntGain"]
145 ],
146 [
147 "Damage",
148 "53~61",
149 ""
150 ],
151 [
152 "Armor",
153 herojson["Armor"],
154 "%.2f" % (float(herojson["AgiGain"]) * 0.14)
155 ],
156 [
157 "Movespeed",
158 herojson["Movespeed"],
159 herojson["AgiGain"]
160 ]
161 ]
162
163 table[1 + herojson["PrimaryStat"]][0] = "[" + table[1 + herojson["PrimaryStat"]][0] + "]"
164
165 # Compose the final message
166 message = "";
167 message += "**" + hero.title() + "** (" + herotype + ")\n"
168 message += "This hero's stats:\n\n"
169 message += "```"
170 message += tabulate(table, headers=["Stat","Value","Gain/lvl"], tablefmt="fancy_grid")
171 message += "```\n"
172
173 # Legs are fun
174 if (herojson["Legs"] > 0):
175 message += "Also you might consider buying " + str(herojson["Legs"]) + " boots, because this hero, apparently, has " + str(herojson["Legs"]) + " legs! ;)"
176 else:
177 message += "Talking about boots... this hero seems to have no legs, so you might consider playing without any ;)"
178
179 await self.bot.say(message)
180
181 # Get the proper hero name
182 url = "http://api.herostats.io/heroes/" + reqHero
183
184 try:
185
186 # Get the info
187 async with aiohttp.get(url) as r:
188 data = await r.json()
189 if "error" not in data.keys():
190
191 # Build the data into a nice table and send
192 await buildHeroInfo(data)
193 else:
194 await self.bot.say(data["error"])
195 except:
196
197 # Nothing can be done
198 await self.bot.say('Dota API is offline')
199
200 @dota.command(name = 'build', pass_context = True)
201 async def build(self, ctx, *, hero):
202 """Gets most popular skillbuild for a hero"""
203
204 # Build an url
205 url = "http://www.dotabuff.com/heroes/" + hero.lower().replace(" ", "-")
206
207 async with aiohttp.get(url, headers = {"User-Agent": "Red-DiscordBot"}) as response:
208 soupObject = BeautifulSoup(await response.text(), "html.parser")
209
210 # "build" will contain a final table
211 # "headers" will contain table headers with lvl numbers
212 build = []
213 headers = ""
214
215 try:
216 skillSoup = soupObject.find(class_='skill-choices')
217
218 # Generate skill tree
219 for skill in enumerate(skillSoup.find_all(class_='skill')):
220
221 # Get skill names for the first row
222 build.append([skill[1].find(class_='line').find(class_='icon').find('img').get('alt')])
223
224 # Generate build order
225 for entry in enumerate(skill[1].find(class_='line').find_all(class_='entry')):
226 if "choice" in entry[1].get("class"):
227 build[skill[0]].append("X")
228 else:
229 build[skill[0]].append(" ")
230
231 # Get a part of the table
232 def getPartialTable(table, start, end):
233 tables = []
234 for row in enumerate(table):
235 if start == 0:
236 result = []
237 else:
238 result = [table[row[0]][0]]
239 result[1:] = row[1][start:end]
240 tables.append(result)
241 return tables
242
243 # Generate 2 messages (for a splitted table)
244 # TODO: Convert into one "for" cycle
245 message = "The most popular build **at the moment**, according to Dotabuff:\n\n"
246 message += "```"
247 headers = ["Skill/Lvl"]
248 headers[len(headers):] = range(1,7)
249 message += tabulate(getPartialTable(build,0,7), headers=headers, tablefmt="fancy_grid")
250 message += "```\n"
251
252 message += "```"
253 headers = ["Skill/Lvl"]
254 headers[len(headers):] = range(7,14)
255 message += tabulate(getPartialTable(build,7,13), headers=headers, tablefmt="fancy_grid")
256 message += "```\n"
257
258 # Send first part
259 await self.bot.say(message)
260
261 message = "```"
262 headers = ["Skill/Lvl"]
263 headers[len(headers):] = range(14,21)
264 message += tabulate(getPartialTable(build,13,19), headers=headers, tablefmt="fancy_grid")
265 message += "```\n"
266
267 # Send second part
268 await self.bot.say(message)
269 except:
270
271 # Nothing can be done
272 await self.bot.say("Error parsing Dotabuff, maybe try again later")
273
274 @dota.command(name = 'items', pass_context = True)
275 async def items(self, ctx, *, hero):
276 """Gets the most popular items for a hero"""
277
278 # Build an url
279 url = "http://www.dotabuff.com/heroes/" + hero.lower().replace(" ", "-")
280
281 async with aiohttp.get(url, headers = {"User-Agent": "Red-DiscordBot"}) as response:
282 soupObject = BeautifulSoup(await response.text(), "html.parser")
283
284 # Get the needed data fron the page
285 # TODO: Add try-except block
286 items = soupObject.find_all("section")[3].find("tbody").find_all("tr")
287
288 # "build" will contain a final table
289 build = []
290
291 # Generate the buld from data
292 for item in items:
293 build.append(
294 [
295 item.find_all("td")[1].find("a").get_text(),
296 item.find_all("td")[2].get_text(),
297 item.find_all("td")[4].get_text()
298 ]
299 )
300
301 # Compose the message
302 message = "The most popular items **at the moment**, according to Dotabuff:\n\n```"
303 message += tabulate(build, headers=["Item", "Matches", "Winrate"], tablefmt="fancy_grid")
304 message += "```"
305
306 await self.bot.say(message)
307
308 @dota.command(name = 'recent', pass_context = True)
309 async def recent(self, ctx, player):
310 """Gets the link to player's latest match"""
311
312 # Check it there is an api key set
313 if not self.key:
314 await self.bot.say("Please set the dota 2 api key using [p]dota setkey command")
315 raise RuntimeError("Please set the dota 2 api key using [p]dota setkey command")
316
317 # Required to check if user provided the ID or not
318 def is_number(s):
319 try:
320 int(s)
321 return True
322 except ValueError:
323 return False
324
325 # Check if user provided the ID
326 if is_number(player.strip()):
327
328 # if he did - assign as-is
329 account_id = player.strip()
330 else:
331 # if he did not - get the id from the vanity name
332 account_id = api.get_steam_id(player)["response"]
333
334 # Check if the result was correcct
335 if (int(account_id["success"]) > 1):
336 await self.bot.say("Player not found :(")
337 else:
338 account_id = account_id["steamid"]
339
340 try:
341 # Get the data from Dota API
342 matches = api.get_match_history(account_id=account_id)["result"]["matches"]
343 match = api.get_match_details(matches[0]["match_id"])
344 heroes = api.get_heroes()
345
346 # Operation was a success
347 dotaServes = True
348 except:
349
350 # Well... if anything fails...
351 dotaServes = False
352 print('Dota servers SO BROKEN!')
353
354 # Proceed to data parsing
355 if dotaServes:
356
357 # Create a proper heroes list
358 heroes = heroes["result"]["heroes"]
359 def build_dict(seq, key):
360 return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))
361 heroes = build_dict(heroes, "id")
362
363 # Reassign match info for ease of use
364 match = match["result"]
365
366 # Construct message
367 message = "Showing the most recent match for **" + player + "** (match id: **" + str(match["match_id"]) + "**)\n"
368 if "radiant_win" in match and match["radiant_win"]:
369 message += "**RADIANT WON**"
370 else:
371 message += "**DIRE WON**"
372
373 m, s = divmod(match["duration"], 60)
374 h, m = divmod(m, 60)
375
376 message += " [" + "%d:%02d:%02d" % (h, m, s) + "]\n"
377
378 # Create a list of played heroes
379 played_heroes = []
380 for player in enumerate(match["players"]):
381 played_heroes.append(heroes[player[1]["hero_id"]]["localized_name"])
382
383 # "table" will be used to store the finalized match data
384 table = []
385
386 # Form Radiant team
387 for i in range(0,5):
388 table.append([
389 played_heroes[i],
390 str(match["players"][i]["kills"]) + "/" + str(match["players"][i]["deaths"]) + "/" + str(match["players"][i]["assists"]),
391 played_heroes[5+i],
392 str(match["players"][5+i]["kills"]) + "/" + str(match["players"][5+i]["deaths"]) + "/" + str(match["players"][5+i]["assists"])
393 ])
394
395 # Compose message
396 message += "\n```"
397 message += tabulate(table, headers=["Radiant Team", "K/D/A", "Dire Team", "K/D/A"], tablefmt="fancy_grid")
398 message += "```"
399 message += "\nDotabuff match link: http://www.dotabuff.com/matches/" + str(match["match_id"])
400
401 await self.bot.say(message)
402 else:
403 await self.bot.say('Oops.. Something is wrong with Dota2 servers, try again later!')
404
405def check_folders():
406 if not os.path.exists("data/dota"):
407 print("Creating data/dota folder...")
408 os.makedirs("data/dota")
409
410def check_files():
411 f = "data/dota/settings.json"
412 if not fileIO(f, "check"):
413 print("Creating empty settings.json...")
414 fileIO(f, "save", {})
415
416def setup(bot):
417 if soupAvailable is False:
418 raise RuntimeError("You don't have BeautifulSoup installed, run\n```pip3 install bs4```And try again")
419 return
420 if dotaAvailable is False:
421 raise RuntimeError("You don't have dota2py installed, run\n```pip3 install dota2py```And try again")
422 return
423 if tabulateAvailable is False:
424 raise RuntimeError("You don't have tabulate installed, run\n```pip3 install tabulate```And try again")
425 return
426 check_folders()
427 check_files()
428 bot.add_cog(Dota(bot))