(in-package :arclade) (defvar steam-host "api.steampowered.com") ;; JSON ;; * The API returns an object containing the named object with the result data. ;; * Arrays are represented as an array with the name of the type of the objects ;; in the array (ie. an object named "items" containing an array of objects ;; of type "item" would be represented as an object named "items" containing ;; an array named "item" containing several objects following the "item" structure). ;; * Null is represented as JSON's null. (defun steam-games-uri () "Builds the uri & query params to get owned games for the configured steam id." (quri:render-uri (quri:make-uri-http :host steam-host :path "IPlayerService/GetOwnedGames/v0001/" :query (quri:url-encode-params `(("key" . ,(steam-key *config*)) ("steamid" . ,(steam-user-id *config*)) ("include_appinfo" . "true")))))) (defun fetch-steam-games () "Fetch configured user's steam games and return parsed JSON." (derrida:with-keypaths ((games :|response| :|games|)) (json:parse (flexi-streams:octets-to-string (drakma:http-request (steam-games-uri)))) games)) (defun make-steam-game (data) "Make a STEAM-GAME instance from DATA, a parsed JSON form from Steam's API." (with-plist ((id :|appid|) (playtime :|playtime_forever|) (name :|name|) (icon-url :|img_icon_url|) (last-played :|rtime_last_played|)) data (let ((game (make-instance 'steam-game :name name :appid id))) (setf (playtime game) playtime (icon-url game) icon-url (last-played game) last-played) game))) (defun steam-game-schema-uri (game) "Returns URI and query params to get detailed info about a game. RESULT DATA game gameName (string) Steam internal (non-localized) name of game. gameVersion (int) Steam release version number currently live on Steam. availableGameStats achievements (Optional) (array) name (string) API Name of achievement. defaultvalue (int) Always 0 (player's default state is unachieved). displayName (string) Display title string of achievement. hidden (int) If achievement is hidden to the user before earning achievement, value is 1. 0 if public. description (string) Display description string of achievement. icon (string) Absolute URL of earned achievement icon art. icongray (string) Absolute URL of un-earned achievement icon art." (quri:render-uri (quri:make-uri-http :host steam-host :path "ISteamUserStats/GetSchemaForGame/v2/" :query (quri:url-encode-params `(("key" . ,(steam-key *config*)) ("appid" . ,(appid game)) ("l" . "en")))))) (defun fetch-steam-game-schema (game) (derrida:with-keypaths ((achievements :|game| :|availableGameStats| :|achievements|)) (json:parse (flexi-streams:octets-to-string (drakma:http-request (steam-game-schema-uri game)))) achievements)) (defun steam-achievements-uri (game) "Builds uri & query params for configured steam id's achievements for GAME." (quri:render-uri (quri:make-uri-http :host steam-host :path "ISteamUserStats/GetPlayerAchievements/v0001/" :query (quri:url-encode-params `(("key" . ,(steam-key *config*)) ("steamid" . ,(steam-user-id *config*)) ("appid" . ,(appid game)) ("l" . "en")))))) (defun fetch-steam-achievements (steam-game) (derrida:with-keypaths ((success :|playerstats| :|success|) (achievements :|playerstats| :|achievements|)) (json:parse (flexi-streams:octets-to-string (drakma:http-request (steam-achievements-uri steam-game)))) (when success achievements))) (defun make-steam-achievement (game schema stats) (with-plist ((icon :|icon|) (icongray :|icongray|)) schema (with-plist ((name :|name|) (desc :|description|) (time :|unlocktime|) (achieved :|achieved|) (apiname :|apiname|)) stats (db:with-transaction () (let ((rec (make-instance 'steam-achievement :game game :name name :description desc))) (setf (icon rec) icon (icongray rec) icongray (apiname rec) apiname) (unless (zerop achieved) (setf (fulfillment rec) (+ time *epoch*))) rec))))) (defun load-steam-games () "Fetch and wrap STEAM-GAMEs as persistent objects. WARNING! Not idempotent!" (db:with-transaction () (loop for data in (fetch-steam-games) do (make-steam-game data)))) (defun load-achievement-data (game) "Fetch and wrap STEAM-ACHIEVEMENTs as persistent objects. WARNING! Not idempotent!" (loop for schema in (fetch-steam-game-schema game) for achievements in (fetch-steam-achievements game) do (make-steam-achievement game schema achievements)))