262 lines
13 KiB
Python
262 lines
13 KiB
Python
from cogs.utils import config
|
|
from discord.ext import commands
|
|
from .utils import checks
|
|
|
|
from lxml import etree
|
|
import aiohttp
|
|
import re
|
|
import discord
|
|
import pendulum
|
|
from fuzzywuzzy import process
|
|
|
|
base_url = "http://api.steampowered.com"
|
|
app_id_map = {"portal 2": 620,
|
|
"portal": 400,
|
|
"gmod": 4000,
|
|
"garry's mod": 4000,
|
|
"team fortress 2": 440,
|
|
"tf2": 440,
|
|
"csgo": 430,
|
|
"counter strike: global offensive": 430,
|
|
"css": 240,
|
|
"counter strike: source": 240,
|
|
"cs": 10,
|
|
"counter strike": 10,
|
|
"dota 2": 570,
|
|
"skyrim": 72850,
|
|
"oblivion": 22330,
|
|
"starbound": 211820,
|
|
"terraria": 105600,
|
|
"l4d": 500,
|
|
"left 4 dead": 500,
|
|
"l4d2": 550,
|
|
"left 4 dead 2": 550,
|
|
"xcom 2": 268500,
|
|
"xcom": 200510,
|
|
"ds2": 236430,
|
|
"dark souls 2": 236430,
|
|
"ds3": 374320,
|
|
"dark souls 3": 374320,
|
|
"unturned": 304930,
|
|
"dead island": 91310,
|
|
"dead island 2": 268150,
|
|
"don't starve": 219740,
|
|
"don't starve together": 322330,
|
|
"undertale": 391540,
|
|
"amnesia": 57300,
|
|
"borderlands 2": 49520,
|
|
"borderlands": 8980,
|
|
"soma": 282140,
|
|
"stanley parable": 221910,
|
|
"fallout": 38400,
|
|
"fallout 2": 38410,
|
|
"fallout 3": 22300,
|
|
"fallout 4": 377160,
|
|
"fallout new vegas": 22490
|
|
}
|
|
|
|
|
|
def get_app_id(game: str):
|
|
best_match = process.extractOne(game, app_id_map.keys())
|
|
if best_match[1] > 80:
|
|
return app_id_map.get(best_match[0])
|
|
else:
|
|
return None
|
|
|
|
|
|
class Steam:
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
self.headers = {"User-Agent": "Bonfire/1.0.0"}
|
|
self.session = aiohttp.ClientSession()
|
|
self.key = config.steam_key
|
|
|
|
async def find_id(self, user: str):
|
|
# Get the profile link based on the user provided, and request the xml data for it
|
|
url = 'http://steamcommunity.com/id/{}/?xml=1'.format(user)
|
|
async with self.session.get(url, headers=self.headers) as response:
|
|
data = await response.text()
|
|
# Remove the xml version content, it breaks etree.fromstring
|
|
data = re.sub('<\?xml.*\?>', '', data)
|
|
tree = etree.fromstring(data)
|
|
# Try to find the steam ID, it will be the first item in the list, so try to convert to an int
|
|
# If it can't be converted to an int, we know that this profile doesn't exist
|
|
# The text will be "The specified profile could not be found." but we don't care about the specific text
|
|
# Through testing, it appears even if a profile is private, the steam ID is still public
|
|
try:
|
|
return int(tree[0].text)
|
|
except ValueError:
|
|
return None
|
|
|
|
@commands.group(pass_context=True, invoke_without_command=True)
|
|
@checks.custom_perms(send_messages=True)
|
|
async def steam(self, ctx, member: discord.Member, *option: str):
|
|
"""Handles linking/looking information for a certain user
|
|
Call this command by itself with a user, to view some information
|
|
about that user's steam account, if it is linked
|
|
Option can be achievements, games, info and will default to info"""
|
|
# First make sure the person requested isn't the bot
|
|
# Need to keep up the bot's sassy attitude, right?
|
|
if member == ctx.message.server.me:
|
|
await self.bot.say(
|
|
"I don't play video games anymore, I crushed everyone so badly I made everyone cry last time.")
|
|
return
|
|
# Get the user's steam id if it exists
|
|
try:
|
|
steam_id = config.get_content('steam_users').get(member.id).get('steam_id')
|
|
except AttributeError:
|
|
await self.bot.say("Sorry, but I don't have that user's steam account saved!")
|
|
return
|
|
# Set the url based on which option is provided
|
|
# If no option is provided, we'll hit the IndexError, assume info for that
|
|
try:
|
|
if option[0] == "info":
|
|
url = "{}/ISteamUser/GetPlayerSummaries/v0002/?key={}&steamids={}".format(base_url, self.key, steam_id)
|
|
elif option[0] == "achievements":
|
|
# Attempt to convert the given argument to an int
|
|
# If we can't convert, then an app_id wasn't given, try to find it based on our map of games
|
|
# If the option list doesn't have an index of 1, then no game was given
|
|
try:
|
|
game = " ".join(option[1:])
|
|
if game == "":
|
|
await self.bot.say("Please provide a game you would like to get the achievements for!")
|
|
return
|
|
app_id = int(option[1])
|
|
except ValueError:
|
|
app_id = get_app_id(game.lower())
|
|
if app_id is None:
|
|
await self.bot.say("Sorry, I couldn't find a close match for the game {}".format(game))
|
|
return
|
|
|
|
url = "{}/ISteamUserStats/GetPlayerAchievements/v0001/?key={}&steamid={}&appid={}".format(base_url,
|
|
self.key,
|
|
steam_id,
|
|
app_id)
|
|
elif option[0] == "games":
|
|
await self.bot.say("Currently disabled, only achievements and info are available as options")
|
|
return
|
|
except IndexError:
|
|
option = ['info']
|
|
url = "{}/ISteamUser/GetPlayerSummaries/v0002/?key={}&steamids={}".format(base_url, self.key, steam_id)
|
|
|
|
# Make the request and get the data in json response, all url's we're using will give a json response
|
|
try:
|
|
async with self.session.get(url, headers=self.headers) as response:
|
|
data = await response.json()
|
|
except:
|
|
await self.bot.say("Sorry, I failed looking up that user's information. I'll try better next time ;-;")
|
|
return
|
|
|
|
if option[0] == "info":
|
|
data = data['response']['players'][0]
|
|
# We need to take into account private profiles, so first add public stuff
|
|
# This maps the number that's returned, to what it means
|
|
status_map = {0: "Offline",
|
|
1: "Online",
|
|
2: "Busy",
|
|
3: "Away",
|
|
4: "Snooze",
|
|
5: "Looking to trade",
|
|
6: "Looking to play"}
|
|
# This is an epoch timestamp for when the user last was "Online"
|
|
# Why this is called 'lastlogoff' I'll never know, ask Steam
|
|
last_seen_date = pendulum.from_timestamp(data.get('lastlogoff'))
|
|
# We want the difference between now and then, to see when they were actually last Online
|
|
# Instead of printing the exact time they were online
|
|
last_seen_delta = pendulum.utcnow() - last_seen_date
|
|
fmt = {"URL": data.get('profileurl'),
|
|
"Display Name": data.get('personaname'),
|
|
"Online status": status_map[data.get('personastate')],
|
|
"Last seen": "{} ago".format(last_seen_delta.in_words())}
|
|
# Now check to see if things exist, and add them to the output if they do
|
|
created_date_timestamp = data.get('timecreated')
|
|
if created_date_timestamp:
|
|
created_date = pendulum.from_timestamp(created_date_timestamp)
|
|
fmt["Signup Date"] = created_date.to_date_string()
|
|
# Going to add if value in here just in case, to ensure no blank content is printed
|
|
fmt_string = "\n".join("{}: {}".format(key, value) for key, value in fmt.items() if value)
|
|
await self.bot.say("```\n{}```".format(fmt_string))
|
|
elif option[0] == 'achievements':
|
|
# First ensure that the profile is not private, and the user has played it before
|
|
if not data['playerstats']['success']:
|
|
if data['playerstats']['error'] == "Requested app has no stats":
|
|
await self.bot.say("{} has no achievements on the game {}".format(member.display_name, game))
|
|
elif data['playerstats']['error'] == "Profile is not public":
|
|
await self.bot.say(
|
|
"Sorry, {} has a private steam account! I cannot lookup their achievements!".format(
|
|
member.display_name))
|
|
return
|
|
# Get all achievements for this game, making sure they actually exist
|
|
try:
|
|
all_achievements = data['playerstats']['achievements']
|
|
except KeyError:
|
|
await self.bot.say("Sorry, the game {} has no achievements!".format(game))
|
|
return
|
|
# Now get all achievements that the user has achieved
|
|
successful_achievements = [data for data in all_achievements if data['achieved'] == 1]
|
|
await self.bot.say("{} has achieved {}/{} achievements on the game {}".format(member.display_name,
|
|
len(successful_achievements),
|
|
len(all_achievements), game))
|
|
|
|
@steam.command(name='add', aliases=['link', 'create'], pass_context=True)
|
|
@checks.custom_perms(send_messages=True)
|
|
async def add_steam(self, ctx, profile: str):
|
|
"""This command can be used to link a steam profile to your user"""
|
|
# Attempt to find the user/steamid based on the url provided
|
|
# If a url is not provided that matches steamcommunity.com, assume they provided just the user/id
|
|
try:
|
|
user = re.search("((?<=://)?steamcommunity.com/(id|profile)/)+(.*)", profile).group(3)
|
|
except AttributeError:
|
|
user = profile
|
|
|
|
# To look up userdata, we need the steam ID. Try to convert to an int, if we can, it's the steam ID
|
|
# If we can't convert to an int, use our method to find the steam ID for a certain user
|
|
try:
|
|
steam_id = int(user)
|
|
except ValueError:
|
|
steam_id = await self.find_id(user)
|
|
|
|
if steam_id is None:
|
|
await self.bot.say("Sorry, couldn't find that Steam user!")
|
|
return
|
|
|
|
# Save the author's steam ID, ensuring to only overwrite the steam id if they already exist
|
|
author = ctx.message.author
|
|
steam_users = config.get_content('steam_users') or {}
|
|
if steam_users.get(author.id):
|
|
steam_users[author.id]['steam_id'] = steam_id
|
|
else:
|
|
steam_users[author.id] = {'steam_id': steam_id}
|
|
|
|
config.save_content('steam_users', steam_users)
|
|
await self.bot.say(
|
|
"I have just saved your steam account, you should now be able to view stats for your account!")
|
|
|
|
@commands.command(pass_context=True)
|
|
@checks.custom_perms(send_messages=True)
|
|
async def csgo(self, ctx, member: discord.Member = None):
|
|
"""This command can be used to lookup csgo stats for a user"""
|
|
member = member or ctx.message.author
|
|
try:
|
|
steam_id = config.get_content('steam_users').get(member.id).get('steam_id')
|
|
except AttributeError:
|
|
await self.bot.say("Sorry, but I don't have that user's steam account saved!")
|
|
return
|
|
|
|
url = "{}/ISteamUserStats/GetUserStatsForGame/v0002/?key={}&appid=730&steamid={}".format(base_url, self.key,
|
|
steam_id)
|
|
async with self.session.get(url, headers=self.headers) as response:
|
|
data = await response.json()
|
|
|
|
stuff_to_print = ['total_kills', 'total_deaths', 'total_wins', 'total_mvps']
|
|
try:
|
|
stats = "\n".join(
|
|
"{}: {}".format(d['name'], d['value']) for d in data['playerstats']['stats'] if d['name'] in stuff_to_print)
|
|
except KeyError:
|
|
await self.bot.say("Sorry, {} has no CS:GO stats!".format())
|
|
await self.bot.say(
|
|
"CS:GO Stats for user {}: \n```\n{}```".format(member.display_name, stats.title().replace("_", " ")))
|
|
|
|
|
|
def setup(bot):
|
|
bot.add_cog(Steam(bot))
|