JustTemmie / steam-presence

A script that takes the game you're playing on steam and displays it on discord
MIT License
243 stars 18 forks source link

Delay in the webpage steam miniprofile #49

Closed johnnyHoog closed 4 months ago

johnnyHoog commented 4 months ago

userID type 3. = - 76561197960265728

    URL = f"https://steamcommunity.com/miniprofile/{int(i) - 76561197960265728}"

Now there will be a delay in the URL of this user's Steam community profile page. There will be a delay of about 5 minutes. The user's rich Presence can no longer be updated in real time. However, I found through testing that the user status obtained using the Steam Web API is still updated in real time, but only part of the rich status information is displayed. For example, in cs2, the map and score data provided by this API will not be updated. You just can know if the user is in the game.so this API has no effect. In addition, I also found that only the rich status data of the steam profile page on the web side is delayed. The rich status data in the steam client friend panel are all real-time, but it seems very useless if you can only see the rich status of your friends. Of course, in order to obtain the personal rich status of any user we specify. So I would like to ask the author, is there any way to solve the problem of the delay of the url miniprofile page on the web page?

JustTemmie commented 4 months ago

yeah, i also noticed this literally yesterday but just brushed it off as "steam servers are probably just dying"

i don't know how to exactly fix this as i'm pretty sure this is the only endpoint that steam servers rich presence information to - i'll update you if i find anything though

JustTemmie commented 4 months ago

:+1:

JustTemmie commented 4 months ago

i believe that, and everything under the steamgames.com domain is part of the steam SDK - it's sadly not something i can use without a ton on effort and a lot of jank

the web api is located here, although their documentation SUCKS so good luck finding anything useful on there :p

johnnyHoog commented 4 months ago

Now the delay has become 10 minutes, but it is still updated at a fixed time. It seems that it can be understood as the delay set by steamcommunity?

JustTemmie commented 4 months ago

the script queries the endpoint every 20ish seconds, so yea it's just a delay set by steamcommunity

PatrickSzela commented 4 months ago

I might've figured out a way to retrieve Rich Presence by using Steam's API which seems to always return fresh data, but unfortunately it's much more complicated than just simply extracting it from mini profile.

Basically Steam has an undocumented endpoint https://api.steampowered.com/IPlayerService/GetPlayerLinkDetails/v1/?key={steam_api_key}&steamids[0]={steam_user_id} which contains current Rich Presence localization data under rich_presence_kv field (stored in Valve's key value format):

{
  "response": {
    "accounts": [
      {
        "public_data": {
          // trimmed
        },
        "private_data": {
          "persona_state": 1,
          "persona_state_flags": 0,
          "time_created": 1329677940,
          "game_id": "1086940",
          "lobby_steam_id": "REMOVED",
          "rich_presence_kv": "\"rp\"\n{\n\t\"steam_display\"\t\t\"#OnMap\"\n\t\"game_state\"\t\t\"Lone Adventurer\"\n\t\"state_details\"\t\t\"Reunion Camp\"\n}\n",
          // trimmed
        }
      }
    ]
  }
}

You can then use that data and generate a localized string by using game's localization strings, but to fetch them you'd have to use an unofficial Python Steam module since it doesn't seem like there's any Web API that returns them.

Here's a very simple (and naive) implementation without any safeguards:

from steam.client import SteamClient
from steam import webapi
import vdf
import re

key = "STEAM_KEY_HERE"
steamId = "STEAM_ID_HERE"

# fetch data from undocumented endpoint which contains rich presence data
playerLinkDetails = webapi.get('IPlayerService', 'GetPlayerLinkDetails', params={
  "key": key,
  "steamids[0]": steamId
})

privateData = playerLinkDetails['response']['accounts'][0]['private_data']
gameID = int(privateData["game_id"])

richPresence = vdf.loads(privateData['rich_presence_kv'])['rp']

# it's possible to also retrieve the same data using SteamClient, but it requires user's login, password and Steam Guard code, anonymous login won't work
# client = SteamClient()
# client.login_cli()
# print(client.user.rich_presence)
# client.logout()

# get localization key
locKey = richPresence['steam_display']

# to get product info which contains localization strings, we can use anonymous login
client = SteamClient()
client.anonymous_login()

productInfo = client.get_product_info(apps=[gameID])

client.logout()

# for some reason the localization keys from `get_product_info()` are all lowercase, compared to localization keys in Rich Presence data...
string = productInfo['apps'][gameID]['localization']['richpresence']['english']['tokens'][locKey.lower()]

# replace %key% with data supplied from rich presence
# TODO: handle other substitution formats 
# https://partner.steamgames.com/doc/api/ISteamFriends#richpresencelocalization
print(re.sub(
  r"[\%](.*?)[\%]",
  lambda m: str(richPresence.get(m.group(1), m.group(0))),
  string
))

Implementing that in Steam Presence would probably require rewriting some parts of it. And to reduce the risk of user being banned by flooding Steam's API with multiple requests every ~20 secs, anonymous login should probably only be triggered during script's initialization, and fetching localization strings should happen when the game starts.

JustTemmie commented 4 months ago

i'll look into the python libraries code to try finding out how it's done - i'm not adding a massive module like that for just a single feature

additionally i should note i've spent the last week refactoring the code, including some caching stuff so implementing this should honestly be easier than the scuffed web scraping currently taking place

thanks for pointing me in the right direction and telling me about the endpoint!

JustTemmie commented 4 months ago

okay i've read up on it a fair bit now, there is no web api endpoint, it's exclusive to the steam client using it's own communication protocol, my options boil down to

  1. using the library
  2. emulating the steam client myself
  3. using the system's steam client and run queries in the background with it
  4. parsing a locally installed appinfo.vdf file found at .local/share/Steam/appcache/appinfo.vdf for localization data

i'll have to think about it - but this is fairly complex and will likely take a couple of weeks before i get around to finishing it

PatrickSzela commented 4 months ago

okay i've read up on it a fair bit now, there is no web api endpoint, it's exclusive to the steam client using it's own communication protocol, my options boil down to

  1. using the library
  2. emulating the steam client myself

Yup, I didn't even bother suggesting writing your own implementation of Steam's communication protocol just to retrieve the localization keys and recommended to use the library instead, especially after seeing how much it takes to implement the protocol. If you do decide to go this route though, I might also suggest checking out how SteamUser handles it. It's even more advanced than the Python library - it even automatically handles the Rich Presence localization substitution - but unfortunately it's made with Node.js in mind so it won't really work for this project.

  1. using the system's steam client and run queries in the background with it

Honestly, I had no clue that's even possible. Will this work for people running Steam from Flatpak or Snap though?

  1. parsing a locally installed appinfo.vdf file found at .local/share/Steam/appcache/appinfo.vdf for localization data

That's actually a really nice find. After a bit of googling it seems like Steam has changed the format of this file a few times in the past (according to the parser written by the creator of SteamDB), so it might happen again in the future, after which you might be forced to update the way you parse it.