Skip to content

Commit

Permalink
Update Card
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicconike committed Jun 2, 2024
1 parent c5c3592 commit db1b981
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 86 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
[![Visitor Badge](https://badges.pufler.dev/visits/nicconike/steam-stats)](https://badges.pufler.dev)

<!-- Steam-Stats start -->
![Steam Summary](https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_summary.png)
![Steam Summary](https://github.com/Nicconike/Steam-Stats/blob/master/assets/recently_played_games.png)
<!-- Steam-Stats end -->

<!-- Steam-Workshop start -->
![Steam Summary](https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.png)
<!-- Steam-Workshop end -->
81 changes: 35 additions & 46 deletions api/card.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@
import math
import os
import asyncio
import time
import tempfile
import zipfile
from pyppeteer import launch
import requests
from steam_stats import get_player_summaries, get_recently_played_games
from steam_workshop import fetch_workshop_item_links, fetch_all_workshop_stats
from dotenv import load_dotenv

load_dotenv()

# Secrets Configuration
STEAM_API_KEY = os.getenv("STEAM_API_KEY")
STEAM_ID = os.getenv("STEAM_ID")
STEAM_CUSTOM_ID = os.getenv("STEAM_CUSTOM_ID")
REQUEST_TIMEOUT = (10, 15)

CHROMIUM_ZIP_URL = "https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win_x64/1309077/chrome-win.zip"
CHROMIUM_DIR = os.path.join(os.getcwd(), 'chromium')
CHROMIUM_EXECUTABLE = os.path.join(CHROMIUM_DIR, 'chrome-win', 'chrome.exe')
CHROMIUM_ZIP_URL = (
"https://commondatastorage.googleapis.com/chromium-browser-snapshots/Win_x64/1309077/"
"chrome-win.zip"
)
CHROMIUM_DIR = os.path.join(os.getcwd(), "chromium")
CHROMIUM_EXECUTABLE = os.path.join(CHROMIUM_DIR, "chrome-win", "chrome.exe")
MARGIN = 5

# Set the PYPPETEER_HOME environment variable to a directory with appropriate permissions
os.environ["PYPPETEER_HOME"] = os.path.join(os.getcwd(), "pyppeteer_home")


def download_and_extract_chromium():
"""Download and extract Chromium from the provided URL"""
Expand All @@ -43,31 +40,38 @@ def download_and_extract_chromium():
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(CHROMIUM_DIR)
print("Chromium extracted successfully.")
# Ensure the Chromium executable has the correct permissions
os.chmod(CHROMIUM_EXECUTABLE, 0o755)


async def get_element_bounding_box(html_file, selector):
"""Get the bounding box of the specified element using pyppeteer"""
browser = await launch(headless=True, executablePath=CHROMIUM_EXECUTABLE)
user_data_dir = tempfile.mkdtemp()
browser = await launch(headless=True, executablePath=CHROMIUM_EXECUTABLE, args=['--no-sandbox'],
userDataDir=user_data_dir)
page = await browser.newPage()
await page.goto(f'file://{os.path.abspath(html_file)}')
bounding_box = await page.evaluate(f'''() => {{
var element = document.querySelector('{selector}');
var rect = element.getBoundingClientRect();
return {{x: rect.x, y: rect.y, width: rect.width, height: rect.height}};
}}''')
await page.close()
await browser.close()
# Add margin to the bounding box
bounding_box['x'] = max(bounding_box['x'] - MARGIN, 0)
bounding_box['y'] = max(bounding_box['y'] - MARGIN, 0)
bounding_box['width'] += 2 * MARGIN
bounding_box['height'] += 2 * MARGIN
bounding_box["x"] = max(bounding_box["x"] - MARGIN, 0)
bounding_box["y"] = max(bounding_box["y"] - MARGIN, 0)
bounding_box["width"] += 2 * MARGIN
bounding_box["height"] += 2 * MARGIN
return bounding_box


async def html_to_png(html_file, output_file, selector):
"""Convert HTML file to PNG using pyppeteer with clipping"""
bounding_box = await get_element_bounding_box(html_file, selector)
browser = await launch(headless=True, executablePath=CHROMIUM_EXECUTABLE)
user_data_dir = tempfile.mkdtemp()
browser = await launch(headless=True, executablePath=CHROMIUM_EXECUTABLE, args=['--no-sandbox'],
userDataDir=user_data_dir)
page = await browser.newPage()
await page.goto(f'file://{os.path.abspath(html_file)}')
await page.screenshot({
Expand All @@ -79,6 +83,7 @@ async def html_to_png(html_file, output_file, selector):
'height': bounding_box['height']
}
})
await page.close()
await browser.close()


Expand Down Expand Up @@ -176,11 +181,11 @@ def generate_card_for_player_summary(player_data):
</body>
</html>
"""
with open("assets/steam_summary.html", "w", encoding="utf-8") as file:
with open("../assets/steam_summary.html", "w", encoding="utf-8") as file:
file.write(html_content)

convert_html_to_png("assets/steam_summary.html",
"assets/steam_summary.png", ".card")
convert_html_to_png("../assets/steam_summary.html",
"../assets/steam_summary.png", ".card")

return (
"![Steam Summary]"
Expand Down Expand Up @@ -239,11 +244,11 @@ def generate_card_for_played_games(games_data):
</html>
"""

with open("assets/recently_played_games.html", "w", encoding="utf-8") as file:
with open("../assets/recently_played_games.html", "w", encoding="utf-8") as file:
file.write(html_content)

convert_html_to_png("assets/recently_played_games.html",
"assets/recently_played_games.png", ".card")
convert_html_to_png("../assets/recently_played_games.html",
"../assets/recently_played_games.png", ".card")

return (
"![Steam Summary]"
Expand Down Expand Up @@ -290,7 +295,7 @@ def generate_card_for_steam_workshop(workshop_stats):
text-align: center;
}}
th {{
background-color: #4CAF50;
background-color: #6495ED;
color: white;
}}
tr:nth-child(even) {{
Expand Down Expand Up @@ -327,29 +332,13 @@ def generate_card_for_steam_workshop(workshop_stats):
</body>
</html>
"""
with open("assets/steam_workshop_stats.html", "w", encoding="utf-8") as file:
with open("../assets/steam_workshop_stats.html", "w", encoding="utf-8") as file:
file.write(html_content)

convert_html_to_png("assets/steam_workshop_stats.html",
"assets/steam_workshop_stats.png", ".card")
convert_html_to_png("../assets/steam_workshop_stats.html",
"../assets/steam_workshop_stats.png", ".card")

return (
"![Steam Summary]"
"(https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.png"
"?sanitize=true)\n"
"(https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.png)"
)


if __name__ == "__main__":
start_time = time.time()
player_summary = get_player_summaries()
recently_played_games = get_recently_played_games()
links = fetch_workshop_item_links(STEAM_CUSTOM_ID)
workshop_data = fetch_all_workshop_stats(links)
generate_card_for_player_summary(player_summary)
generate_card_for_played_games(recently_played_games)
generate_card_for_steam_workshop(workshop_data)
end_time = time.time() # End the timer
total_time = end_time-start_time
total_time = round(total_time, 3) # Total time
print(f"Total Execution Time: {total_time} seconds")
12 changes: 8 additions & 4 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,21 @@
generate_card_for_played_games,
generate_card_for_steam_workshop
)
from dotenv import load_dotenv

load_dotenv()

# Secrets Configuration
STEAM_ID = os.getenv("STEAM_CUSTOM_ID")
STEAM_API_KEY = os.getenv("STEAM_API_KEY")
STEAM_CUSTOM_ID = os.getenv("STEAM_CUSTOM_ID")

# Verify that the environment variables are loaded correctly
if not STEAM_ID:
if not STEAM_CUSTOM_ID:
raise ValueError(
"Missing STEAM_ID in environment variables")


def update_readme(markdown_data, start_marker, end_marker, readme_path="README.md"):
def update_readme(markdown_data, start_marker, end_marker, readme_path="../README.md"):
"""Updates the README.md file with the provided Markdown content within specified markers."""
# Read the current README content
with open(readme_path, "r", encoding="utf-8") as file:
Expand Down Expand Up @@ -50,7 +54,7 @@ def update_readme(markdown_data, start_marker, end_marker, readme_path="README.m
start_time = time.time()
player_summary = get_player_summaries()
recently_played_games = get_recently_played_games()
links = fetch_workshop_item_links(STEAM_ID)
links = fetch_workshop_item_links(STEAM_CUSTOM_ID, STEAM_API_KEY)

USER_MARKDOWN_CONTENT = ""
if player_summary and recently_played_games:
Expand Down
120 changes: 86 additions & 34 deletions api/steam_workshop.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,40 @@
"""Scrape Steam Workshop Data"""
import json
import os
import requests
from bs4 import BeautifulSoup, Tag
from dotenv import load_dotenv

# A reasonable timeout for the request (connection and read timeout)
REQUEST_TIMEOUT = (10, 15)
load_dotenv()

# Secrets Configuration
STEAM_API_KEY = os.getenv("STEAM_API_KEY")
STEAM_CUSTOM_ID = os.getenv("STEAM_CUSTOM_ID")

def fetch_workshop_item_links(steam_id):
"""Fetch each workshop item's link, navigating through all pages"""
base_url = f"https://steamcommunity.com/id/{steam_id}/myworkshopfiles/"
item_links = []
page_number = 1

while True:
url = f"{base_url}?p={page_number}"
try:
response = requests.get(url, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
soup = BeautifulSoup(response.content, "html.parser")
workshop_items = soup.find_all("div", class_="workshopItem")

if not workshop_items:
print(f"No workshop items found on page {page_number}")
break

item_links.extend(extract_links(workshop_items))
# A reasonable timeout for the request (connection and read timeout)
REQUEST_TIMEOUT = (10, 15)

if not has_next_page(soup):
break
GET_SERVER_INFO_URL = 'https://api.steampowered.com/ISteamWebAPIUtil/GetServerInfo/v1/'

page_number += 1

except requests.exceptions.RequestException as e:
handle_request_exception(e)
break
except AttributeError as e:
print(f"Parsing error: {
e}. The structure of the page might have changed")
break
def get_server_info(api_key):
"""Fetch server information from the Steam Web API"""
try:
response = requests.get(GET_SERVER_INFO_URL, params={
'key': api_key}, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error fetching server info: {e}")
return None

if not item_links and page_number == 1:
raise ValueError("No items were found in your Steam Workshop")

return item_links
def is_server_online(api_key):
"""Check if the server is online based on the GetServerInfo response"""
serverinfo = get_server_info(api_key)
if serverinfo and 'servertime' in serverinfo:
return True
return False


def extract_links(workshop_items):
Expand Down Expand Up @@ -80,6 +72,49 @@ def handle_request_exception(e):
print(f"An error occurred: {e}")


def fetch_workshop_item_links(steam_id, api_key):
"""Fetch each workshop item's link, navigating through all pages"""
if not is_server_online(api_key):
raise ConnectionError(
"Steam Community is currently offline. Please try again later")

base_url = f"https://steamcommunity.com/id/{steam_id}/myworkshopfiles/"
item_links = []
page_number = 1

while True:
url = f"{base_url}?p={page_number}"
try:
response = requests.get(url, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
soup = BeautifulSoup(response.content, "html.parser")
workshop_items = soup.find_all("div", class_="workshopItem")

if not workshop_items:
print(f"No workshop items found on page {page_number}")
break

item_links.extend(extract_links(workshop_items))

if not has_next_page(soup):
break

page_number += 1

except requests.exceptions.RequestException as e:
handle_request_exception(e)
break
except AttributeError as e:
print(f"Parsing error: {
e}. The structure of the page might have changed")
break

if not item_links and page_number == 1:
raise ValueError("No items were found in your Steam Workshop")

return item_links


def fetch_individual_workshop_stats(item_url):
"""Fetch Author Stats from a Workshop Item"""
stats = {}
Expand Down Expand Up @@ -164,3 +199,20 @@ def fetch_all_workshop_stats(item_links):
"total_current_favorites": total_current_favorites,
"individual_stats": all_stats
}


def save_to_file(data, filename):
"""Save fetched data to a file in JSON format"""
if data is not None:
with open(filename, 'w', encoding='utf-8') as file:
# Use json.dump to write the JSON data to the file
json.dump(data, file, indent=4)
print(f"Data saved to {filename}")
else:
print("No data to save")


if __name__ == "__main__":
itemlinks = fetch_workshop_item_links(STEAM_CUSTOM_ID, STEAM_API_KEY)
workshop_data = fetch_all_workshop_stats(itemlinks)
save_to_file(workshop_data, "workshop_data.json")
2 changes: 1 addition & 1 deletion assets/steam_summary.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<div class="content">
<img id="avatar" class="avatar" src="https://avatars.steamstatic.com/0718d631353dd49100dde0be17dd7e88e984478f_full.jpg" alt="Avatar">
<h2 id="name">Name: Nicco</h2>
<p id="status">Status: Offline</p>
<p id="status">Status: Online</p>
<p id="country">Country: <span id="country-code">IN</span>
<img id="flag" class="flag"
src="https://flagcdn.com/w320/in.png" alt="Flag">
Expand Down
Binary file modified assets/steam_summary.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion assets/steam_workshop_stats.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
text-align: center;
}
th {
background-color: #4CAF50;
background-color: #6495ED;
color: white;
}
tr:nth-child(even) {
Expand Down
Binary file modified assets/steam_workshop_stats.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

1 comment on commit db1b981

@vercel
Copy link

@vercel vercel bot commented on db1b981 Jun 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.