Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated Status Code #2

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
# Team-Steam
# Problem Statement

Video Games are plentiful and of varied genres, difficulties, lengths, and hardware requirements. Steam's recommendation system, among others, is a powerful tool that assists players in finding games they may enjoy, but we believe that with the currently available data there are more player-oriented methods to improve recommendations. New players may want to visit older games before playing the most recent iteration in a series. Veterans may like to play games they missed on launch and forgot to revisit. Wassi aims to create an AI-powered and interactive recommendation system that guides players through their gaming journey, regardless of where they are starting from. Wassi aims to recommend great games, not just popular ones, to improve players' experience. Wassi will offer to players a variety of choices, not limited to AAA titles, to introduce players to lesser-known studios and contribute to a healthier video game market.


# Steps to Acheive

✅ Grab the necessary API keys from steam
Run the API through ensign to upload and store the data
Upload the data to the jupyter notebook
Clean/Preprocess the data
Create the ML algo
Deploy the model
2 changes: 1 addition & 1 deletion publisher.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

# GLOBAL VARIABLES
GAME_LIST_ENDPOINT = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
ALL_GAMES_TOPIC = "all_games_json"
ALL_GAMES_TOPIC = "ISteamApps_GetAppList_v2 "



Expand Down
187 changes: 187 additions & 0 deletions publisher_new.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import os
import json
import asyncio
import warnings
import time

import requests
from datetime import datetime
from pyensign.events import Event
from pyensign.ensign import Ensign

# TODO Python>3.10 needs to ignore DeprecationWarning: There is no current event loop
warnings.filterwarnings("ignore")

# GLOBAL VARIABLES
GAME_LIST_ENDPOINT = "https://api.steampowered.com/ISteamApps/GetAppList/v2/"
PLAYER_QUERY = "http://api.steampowered.com/ISteamUserStats/GetNumberOfCurrentPlayers/v1/?appid="



class SteamPublisher:
"""
SteamPublisher queries the steam API and publishes events to Ensign.
"""

def __init__(self, topic="ISteamApps_GetAppList_v2", interval=900, steam_key='B83E88D5A9D4B00E1A139A6D9EC3E77B', game_list_endpoint=GAME_LIST_ENDPOINT, base_uri=PLAYER_QUERY, ensign_creds=""):
"""
Parameters
----------
topic : string, default: "steam-stats-json"
The name of the topic you wish to publish to. If the topic doesn't yet
exist, Ensign will create it for you. Tips on topic naming conventions can
be found at https://ensign.rotational.dev/getting-started/topics/

steam_key : string, default: None
You can put your API key for the Steam Developer API here. If you leave it
blank, the publisher will attempt to read it from your environment variables

interval : int, default: 900
The number of seconds to wait between API calls so we don't irritate the
nice people at Steam.
"""
self.topic = topic
self.interval = interval

if steam_key is None:
self.steam_key = os.getenv("STEAM_API_KEY")
else:
self.steam_key = steam_key
if self.steam_key is None:
raise ValueError(
"STEAM_API_KEY environment variable must be set")

self.game_list_endpoint = game_list_endpoint
self.base_uri = base_uri

# NOTE: If you need an Ensign client_id & client_secret, register for a free
# account at: https://rotational.app/register

# Start a connection to the Ensign server. If you do not supply connection
# details, PyEnsign will read them from your environment variables.
self.ensign = Ensign(cred_path=ensign_creds)

# Alternatively you can supply `client_id` & `client_secret` as string args, eg
# self.ensign = Ensign(client_id="your_client_id", client_secret="your_secret")

async def print_ack(self, ack):
"""
Enable the Ensign server to notify the Publisher the event has been acknowledged

This is optional for you, but can be very helpful for communication in
asynchronous contexts!
"""
ts = datetime.fromtimestamp(
ack.committed.seconds + ack.committed.nanos / 1e9)
print(f"Event committed at {ts}")

async def print_nack(self, nack):
"""
Enable the Ensign server to notify the Publisher the event has NOT been
acknowledged

This is optional for you, but can be very helpful for communication in
asynchronous contexts!
"""
print(f"Event was not committed with error {nack.code}: {nack.error}")


def format_query(self, game):
"""
Use the base query to specify a game-specific query
"""
return self.base_uri + str(game)

def get_game_list(self):
"""
Wrapper for an intermediate call to get the latest game list

Returns a list of dictionaries of the form:
{
"name": "name_of_game",
"appid": "steam_identifier
}
"""
game_info = requests.get(self.game_list_endpoint).json()
game_list = game_info.get("applist", None)
if game_list is None:
raise Exception("missing game list in Steam API response")
all_games = game_list.get("apps", None)
if all_games is None:
raise Exception("missing app names in Steam API response")

return all_games

def create_event(self, response, game_id, game_name):
if game_name == "":
game_name = "N/A"
data = {
"game": game_name,
"id": game_id,
"count": response["response"]["result"]
}

return Event(json.dumps(data).encode("utf-8"), mimetype="application/json")

async def recv_and_publish(self):
"""
At some interval (`self.interval`), ping the API to get any newly updated
events from the last interval period

Publish report data to the `self.topic`
"""
await self.ensign.ensure_topic_exists(self.topic)

api_calls = 0
MAX_CALLS = 100000
MIN_SLEEP = 0.1
while True:
all_games = self.get_game_list()

# Retrieve the player count for the current game/appid
for game in all_games:
game_name = game.get("name", None)
game_id = game.get("appid", None)
# TODO: does it make sense to just skip them if they don't have an ID?
if game_id is None:
continue

request = self.format_query(game_id)
print(request)
resp = requests.get(request)
if resp.status_code == 200:
response = resp.json()
event = self.create_event(response, game_id, game_name)
await self.ensign.publish(
self.topic,
event,
on_ack=self.print_ack,
on_nack=self.print_nack
)
else:
print(resp.status_code)
# api_calls += 1

# calls_remaining = MAX_CALLS - api_calls
# time_remaining = 86400 - (time.time() % 86400)
# required_sleep = time_remaining/calls_remaining
#time.sleep()

# Convert the response to an event and publish it

# if time.time() % 86400 < 1:
# api_calls=0

# sleep for a bit before we ping the API again
await asyncio.sleep(self.interval)

def run(self):
"""
Run the steam publisher forever.
"""
asyncio.run(self.recv_and_publish())


if __name__ == "__main__":
publisher = SteamPublisher(ensign_creds="secret/publish_creds.json")
publisher.run()
File renamed without changes.
4 changes: 4 additions & 0 deletions secret/publish_creds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"ClientID": "VLNAuMblbcOYpzAoUptAtuQyUbCNIpLA",
"ClientSecret": "iMqE73ICff5vM9afseftpxMpoCuoNwRQ2LoxP4MAuu5UdU9Fl4qoXm3jY02o5D2g"
}
4 changes: 4 additions & 0 deletions secret/subscribe_creds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"ClientID": "VLNAuMblbcOYpzAoUptAtuQyUbCNIpLA",
"ClientSecret": "iMqE73ICff5vM9afseftpxMpoCuoNwRQ2LoxP4MAuu5UdU9Fl4qoXm3jY02o5D2g"
}
60 changes: 60 additions & 0 deletions subscriber.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import json
import asyncio
import warnings

from pyensign.ensign import Ensign
from pyensign.api.v1beta1.ensign_pb2 import Nack


# TODO Python>3.10 needs to ignore DeprecationWarning: There is no current event loop
warnings.filterwarnings("ignore")

class SteamSubscriber:
"""
SteamSubscriber queries the SteamPublisher for events.
"""

def __init__(self, topic="steam-stats-json", ensign_creds=""):
"""
Parameters
----------
topic : string, default: "steam-stats-json"
The name of the topic you wish to publish to. If the topic doesn't yet
exist, Ensign will create it for you. Tips on topic naming conventions can
be found at https://ensign.rotational.dev/getting-started/topics/
"""
self.topic = topic
self.ensign = Ensign(cred_path=ensign_creds)

def run(self):
"""
Run the subscriber forever.
"""
asyncio.run(self.subscribe())

async def handle_event(self, event):
"""
Decode and ack the event.
"""
try:
data = json.loads(event.data)
except json.JSONDecodeError:
print("Received invalid JSON in event payload:", event.data)
await event.nack(Nack.Code.UNKNOWN_TYPE)
return

print("New steam report received:", data)
await event.ack()

async def subscribe(self):
"""
Subscribe to SteamPublisher events from Ensign
"""
id = await self.ensign.topic_id(self.topic)
async for event in self.ensign.subscribe(id):
await self.handle_event(event)


if __name__ == "__main__":
subscriber = SteamSubscriber(ensign_creds="secret/subscribe_creds.json")
subscriber.run()