diff --git a/README.md b/README.md index f0c0357..bfc9853 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,10 @@ [![Visitor Badge](https://badges.pufler.dev/visits/nicconike/steam-stats)](https://badges.pufler.dev) +![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 Summary](https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.png) diff --git a/api/card.py b/api/card.py index 3bcbd26..b3d3259 100644 --- a/api/card.py +++ b/api/card.py @@ -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""" @@ -43,11 +40,15 @@ 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'''() => {{ @@ -55,19 +56,22 @@ async def get_element_bounding_box(html_file, 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({ @@ -79,6 +83,7 @@ async def html_to_png(html_file, output_file, selector): 'height': bounding_box['height'] } }) + await page.close() await browser.close() @@ -176,11 +181,11 @@ def generate_card_for_player_summary(player_data): """ - 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]" @@ -239,11 +244,11 @@ def generate_card_for_played_games(games_data): """ - 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]" @@ -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) {{ @@ -327,29 +332,13 @@ def generate_card_for_steam_workshop(workshop_stats): """ - 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") diff --git a/api/main.py b/api/main.py index 4d46ef9..52491ac 100644 --- a/api/main.py +++ b/api/main.py @@ -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: @@ -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: diff --git a/api/steam_workshop.py b/api/steam_workshop.py index b2ce0ad..a3b9005 100644 --- a/api/steam_workshop.py +++ b/api/steam_workshop.py @@ -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): @@ -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 = {} @@ -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") diff --git a/assets/steam_summary.html b/assets/steam_summary.html index da010ab..cbf8c1d 100644 --- a/assets/steam_summary.html +++ b/assets/steam_summary.html @@ -37,7 +37,7 @@
Avatar

Name: Nicco

-

Status: Offline

+

Status: Online

Country: IN Flag diff --git a/assets/steam_summary.png b/assets/steam_summary.png index 223b04f..73bae6b 100644 Binary files a/assets/steam_summary.png and b/assets/steam_summary.png differ diff --git a/assets/steam_workshop_stats.html b/assets/steam_workshop_stats.html index 18b82a9..9fb2a0a 100644 --- a/assets/steam_workshop_stats.html +++ b/assets/steam_workshop_stats.html @@ -35,7 +35,7 @@ text-align: center; } th { - background-color: #4CAF50; + background-color: #6495ED; color: white; } tr:nth-child(even) { diff --git a/assets/steam_workshop_stats.png b/assets/steam_workshop_stats.png index dce30cf..2c95002 100644 Binary files a/assets/steam_workshop_stats.png and b/assets/steam_workshop_stats.png differ