diff --git a/.gitignore b/.gitignore
index 6da585a..4e7a482 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,3 +160,6 @@ vercel.txt
Pipfile
Pipfile.lock
.vercel
+
+#Chromium
+Chromium
diff --git a/README.md b/README.md
index 8d44ed7..f0c0357 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,7 @@
[![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 Games Stats](https://github.com/Nicconike/Steam-Stats/blob/master/assets/recently_played_games.png)
-![Steam Workshop Stats](https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.svg?sanitize=true)
diff --git a/api/card.py b/api/card.py
index b4e13fd..8ad3a0d 100644
--- a/api/card.py
+++ b/api/card.py
@@ -1,20 +1,98 @@
"""Generate Cards for Steam Stats"""
import datetime
-from html2image import Html2Image
+import math
+import os
+import asyncio
+import time
+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()
-def format_unix_time(unix_time):
- """Convert Unix time to human-readable format"""
- return datetime.datetime.fromtimestamp(unix_time).strftime("%d/%m/%Y")
+# 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')
+MARGIN = 5
+
+
+def download_and_extract_chromium():
+ """Download and extract Chromium from the provided URL"""
+ if not os.path.exists(CHROMIUM_DIR):
+ os.makedirs(CHROMIUM_DIR)
+ zip_path = os.path.join(CHROMIUM_DIR, 'chrome-win.zip')
+ if not os.path.exists(zip_path):
+ print("Downloading Chromium...")
+ response = requests.get(
+ CHROMIUM_ZIP_URL, stream=True, timeout=REQUEST_TIMEOUT)
+ with open(zip_path, 'wb') as file:
+ for chunk in response.iter_content(chunk_size=128):
+ file.write(chunk)
+ print("Chromium downloaded successfully.")
+ if not os.path.exists(CHROMIUM_EXECUTABLE):
+ print("Extracting Chromium...")
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
+ zip_ref.extractall(CHROMIUM_DIR)
+ print("Chromium extracted successfully.")
+
+
+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)
+ 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 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
+ return bounding_box
-def convert_html_to_png(html_file, output_dir, output_file):
- """Convert HTML file to PNG image using html2image"""
- # Initialize Html2Image
- html2png = Html2Image(output_path=output_dir)
+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)
+ page = await browser.newPage()
+ await page.goto(f'file://{os.path.abspath(html_file)}')
+ await page.screenshot({
+ 'path': output_file,
+ 'clip': {
+ 'x': bounding_box['x'],
+ 'y': bounding_box['y'],
+ 'width': bounding_box['width'],
+ 'height': bounding_box['height']
+ }
+ })
+ await browser.close()
- # Convert HTML to PNG
- html2png.screenshot(html_file=html_file, save_as=output_file)
+
+def convert_html_to_png(html_file, output_file, selector):
+ """Convert HTML file to PNG using pyppeteer with clipping"""
+ download_and_extract_chromium()
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+ loop.run_until_complete(html_to_png(html_file, output_file, selector))
+
+
+def format_unix_time(unix_time):
+ """Convert Unix time to human-readable format"""
+ return datetime.datetime.fromtimestamp(unix_time).strftime("%d/%m/%Y")
def generate_card_for_player_summary(player_data):
@@ -51,7 +129,7 @@ def generate_card_for_player_summary(player_data):
@@ -159,15 +239,117 @@ 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)
- html_file = "assets/recently_played_games.html"
- output_dir = "assets"
- output_file = "recently_played_games.png"
- convert_html_to_png(html_file, output_dir, output_file)
+ convert_html_to_png("../assets/recently_played_games.html",
+ "../assets/recently_played_games.png", ".card")
return (
- "![Steam Games Stats]"
+ "![Steam Summary]"
"(https://github.com/Nicconike/Steam-Stats/blob/master/assets/recently_played_games.png)"
)
+
+
+def generate_card_for_steam_workshop(workshop_stats):
+ """Generates HTML content for retrieved Workshop Data"""
+ html_content = f"""
+
+
+
+
+
+
Steam Workshop Stats
+
+
+
+
+
Steam Workshop Stats
+
+
+
+ Workshop Stats |
+ Total |
+
+
+
+
+ Unique Visitors |
+ {workshop_stats["total_unique_visitors"]} |
+
+
+ Current Subscribers |
+ {workshop_stats["total_current_subscribers"]} |
+
+
+ Current Favorites |
+ {workshop_stats["total_current_favorites"]} |
+
+
+
+
+
+
+ """
+ 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")
+
+ return (
+ "![Steam Summary]"
+ "(https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.png"
+ "?sanitize=true)\n"
+ )
+
+
+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 2eb5c20..f6adf8e 100644
--- a/api/main.py
+++ b/api/main.py
@@ -1,12 +1,13 @@
"""Main Runner Script"""
import os
import time
-import numpy as np
-import pandas as pd
-import plotly.graph_objects as go
from steam_stats import get_player_summaries, get_recently_played_games
from steam_workshop import fetch_workshop_item_links, fetch_all_workshop_stats
-from card import generate_card_for_player_summary, generate_card_for_played_games
+from card import (
+ generate_card_for_player_summary,
+ generate_card_for_played_games,
+ generate_card_for_steam_workshop
+)
# Secrets Configuration
STEAM_ID = os.getenv("STEAM_CUSTOM_ID")
@@ -17,57 +18,7 @@
"Missing STEAM_ID in environment variables")
-def generate_svg_for_steam_workshop(workshop_stats):
- """Generates SVG Card for retrieved Workshop Data using Plotly for table creation"""
- # Create the table data
- header_values = ["Workshop Stats", "Total"]
- data = {
- "Workshop_Stats": ["Unique Visitors", "Current Subscribers", "Current Favorites"],
- "Total": [
- workshop_stats["total_unique_visitors"],
- workshop_stats["total_current_subscribers"],
- workshop_stats["total_current_favorites"]
- ]
- }
- df = pd.DataFrame(data)
- # Generate random colors for each row
- colors = [f"rgb({np.random.randint(0, 256)}, {np.random.randint(0, 256)}, {
- np.random.randint(0, 256)})" for _ in range(len(df))]
-
- header = {
- "values": header_values,
- "line_color": "paleturquoise",
- "fill_color": "paleturquoise",
- "align": "center",
- "font": {"color": "black", "size": 16}
- }
-
- cells = {
- "values": [df[col].tolist() for col in df.columns],
- "line_color": [colors],
- "fill_color": [colors],
- "align": "center",
- "font": {"color": "black", "size": 14}
- }
-
- # Create the table figure
- fig = go.Figure(data=[go.Table(header=header, cells=cells)])
-
- # Adjust layout to fit the table size
- fig.update_layout(
- autosize=True,
- margin={"pad": 0}
- )
- fig.write_image("assets/steam_workshop_stats.svg")
-
- return (
- "![Steam Workshop Stats]("
- "https://github.com/Nicconike/Steam-Stats/blob/master/assets/steam_workshop_stats.svg"
- "?sanitize=true)"
- )
-
-
-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:
@@ -120,7 +71,7 @@ def update_readme(markdown_data, start_marker, end_marker, readme_path="README.m
WORKSHOP_MARKDOWN_CONTENT = ""
if links:
workshop_data = fetch_all_workshop_stats(links)
- WORKSHOP_MARKDOWN_CONTENT += generate_svg_for_steam_workshop(
+ WORKSHOP_MARKDOWN_CONTENT += generate_card_for_steam_workshop(
workshop_data)
print("Retrieved all Workshop Stats")
else:
diff --git a/assets/recently_played_games.html b/assets/recently_played_games.html
index 6e67c68..719f361 100644
--- a/assets/recently_played_games.html
+++ b/assets/recently_played_games.html
@@ -5,53 +5,39 @@
Recently Played Games in Last 2 Weeks
-
+
Recently Played Games
-
-
-
-
State of Decay 2 (11.70 hrs)
-
-
-
+
Counter-Strike 2 (7.12 hrs)
-
-
-
-
Wizard with a Gun (1.17 hrs)
-
-
-
+
Perfect Heist 2 (18 mins)
-
-
-
Killing Floor 2 (17 mins)
+
+
+
Wallpaper Engine (9 mins)
-
-
-
Wallpaper Engine (9 mins)
+
+
+
State of Decay 2 (1 mins)
diff --git a/assets/recently_played_games.png b/assets/recently_played_games.png
index e7eb064..64ea5c1 100644
Binary files a/assets/recently_played_games.png and b/assets/recently_played_games.png differ
diff --git a/assets/steam_summary.html b/assets/steam_summary.html
index 99deec8..da010ab 100644
--- a/assets/steam_summary.html
+++ b/assets/steam_summary.html
@@ -4,7 +4,7 @@
-
Responsive SVG Card
+
Steam Player Summary
+
+
+
+
Steam Workshop Stats
+
+
+
+ Workshop Stats |
+ Total |
+
+
+
+
+ Unique Visitors |
+ 3014 |
+
+
+ Current Subscribers |
+ 1775 |
+
+
+ Current Favorites |
+ 83 |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/assets/steam_workshop_stats.png b/assets/steam_workshop_stats.png
new file mode 100644
index 0000000..dce30cf
Binary files /dev/null and b/assets/steam_workshop_stats.png differ
diff --git a/assets/steam_workshop_stats.svg b/assets/steam_workshop_stats.svg
deleted file mode 100644
index 68e23f1..0000000
--- a/assets/steam_workshop_stats.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/style.css b/assets/style.css
index b07a401..a0dd6fb 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -10,8 +10,8 @@ body {
progress {
box-sizing: border-box;
border: solid 0.15em #242b35;
- width: 12.5em;
- height: 1em;
+ width: 8em;
+ height: 0.8em;
border-radius: 0.5em;
background: linear-gradient(#191c23, #2d3341);
font: clamp(.625em, 7.5vw, 5em) monospace;
@@ -33,27 +33,20 @@ progress::-moz-progress-bar {
background: var(--fill);
}
-progress:nth-child(1) {
+.progress-style-1 {
--fill:
+ linear-gradient(rgba(90, 240, 255, 0.85), transparent),
repeating-linear-gradient(90deg,
- transparent 0 0.15em, #f1c00c 0 0.5em) 0.25em,
- linear-gradient(#f3c402, #ed7401);
-}
-
-progress:nth-child(2) {
- --fill:
- linear-gradient(#ffec9d, transparent 85%),
- linear-gradient(90deg, #ffe94b, #f94745);
+ #123c92 0 0.0625em, #1b5ec6 0 1em);
}
-progress:nth-child(3) {
+.progress-style-2 {
--fill:
- linear-gradient(rgba(226, 102, 76, 0.65), transparent),
- repeating-linear-gradient(135deg,
- #a22215 0 0.25em, #be2a20 0 0.5em);
+ linear-gradient(#d0a9e2, transparent 85%),
+ linear-gradient(90deg, #433485, #dd3c6e);
}
-progress:nth-child(4) {
+.progress-style-3 {
--fill:
linear-gradient(rgba(215, 131, 227, 0.5), transparent),
conic-gradient(from -30deg at 25%,
@@ -62,15 +55,22 @@ progress:nth-child(4) {
#8b42ab 240deg, #9956b3 0%) 0/ 0.7em;
}
-progress:nth-child(5) {
+.progress-style-4 {
--fill:
- linear-gradient(#d0a9e2, transparent 85%),
- linear-gradient(90deg, #433485, #dd3c6e);
+ linear-gradient(rgba(226, 102, 76, 0.65), transparent),
+ repeating-linear-gradient(135deg,
+ #a22215 0 0.25em, #be2a20 0 0.5em);
}
-progress:nth-child(6) {
+.progress-style-5 {
+ --fill:
+ linear-gradient(#ffec9d, transparent 85%),
+ linear-gradient(90deg, #ffe94b, #f94745);
+}
+
+.progress-style-6 {
--fill:
- linear-gradient(rgba(90, 240, 255, 0.85), transparent),
repeating-linear-gradient(90deg,
- #123c92 0 0.0625em, #1b5ec6 0 1em);
+ transparent 0 0.15em, #f1c00c 0 0.5em) 0.25em,
+ linear-gradient(#f3c402, #ed7401);
}
diff --git a/requirements.txt b/requirements.txt
index 34a8b66..f27164a 100644
Binary files a/requirements.txt and b/requirements.txt differ