diff --git a/.github/workflows/quality-check.yml b/.github/workflows/quality-check.yml index 0f2b7b4..87903d8 100644 --- a/.github/workflows/quality-check.yml +++ b/.github/workflows/quality-check.yml @@ -1,9 +1,10 @@ name: Quality Check on: - push: - branches: - - master + schedule: + # Runs every Monday at 12AM IST (UTC+5:30) + - cron: "30 18 * * 0" + workflow_dispatch: pull_request: branches: - master diff --git a/.github/workflows/steam-stats.yml b/.github/workflows/steam-stats.yml index 41bbae8..da01405 100644 --- a/.github/workflows/steam-stats.yml +++ b/.github/workflows/steam-stats.yml @@ -5,6 +5,9 @@ on: # Runs every Monday at 12AM IST (UTC+5:30) - cron: "30 18 * * 0" workflow_dispatch: + pull_request: + branches: + - master jobs: update-readme: diff --git a/README.md b/README.md index ba97249..37f0cd2 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,7 @@ [![Visitor Badge](https://badges.pufler.dev/visits/nicconike/steam-stats)](https://badges.pufler.dev) -![Steam Games Stats](https://github.com/Nicconike/Steam-Stats/blob/master/assets/recently_played_games.svg?sanitize=true) -![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 10215de..ceb4d25 100644 --- a/api/card.py +++ b/api/card.py @@ -1,8 +1,8 @@ -"""Generate Card for Steam Stats""" -import json +"""Generate Cards for Steam Stats""" +import datetime import os from dotenv import load_dotenv -from steam_stats import get_player_summaries +from steam_stats import get_player_summaries, get_recently_played_games load_dotenv() @@ -11,25 +11,38 @@ STEAM_ID = os.getenv("STEAM_ID") +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): """Generate HTML content based on Steam Player Summary Data""" + if not player_data: + return None summary_data = player_data["response"]["players"][0] personaname = summary_data["personaname"] personastate = summary_data["personastate"] avatarfull = summary_data["avatarfull"] loccountrycode = summary_data["loccountrycode"] + lastlogoff = summary_data["lastlogoff"] + timecreated = summary_data["timecreated"] gameextrainfo = summary_data.get("gameextrainfo", None) + # Convert lastlogoff & timecreated from Unix time to human-readable format + lastlogoff_str = format_unix_time(lastlogoff) + timecreated_str = format_unix_time(timecreated) + personastate_map = { - 0: 'Offline', - 1: 'Online', - 2: 'Busy', - 3: 'Away', - 4: 'Snooze', - 5: 'Looking to trade', - 6: 'Looking to play' + 0: "Offline", + 1: "Online", + 2: "Busy", + 3: "Away", + 4: "Snooze", + 5: "Looking to trade", + 6: "Looking to play" } - personastate_value = personastate_map.get(personastate, 'Unknown') + personastate_value = personastate_map.get(personastate, "Unknown") html_content = f""" @@ -41,7 +54,7 @@ def generate_card_for_player_summary(player_data): + +
+
+

Recently Played Games

+ {progress_bars} +
""" - return html_content + with open("../assets/recently_played_games.html", "w", encoding="utf-8") as file: + file.write(html_content) -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") + return ( + "![Steam Games Stats](" + "https://github.com/Nicconike/Steam-Stats/blob/master/assets/recently_played_games.html" + "?sanitize=true)" + ) if __name__ == "__main__": summary = get_player_summaries() - html = generate_card_for_player_summary(summary) - print(html) + recent_game = get_recently_played_games() + summary_content = generate_card_for_player_summary(summary) + recently_played_games = generate_card_for_played_games( + recent_game) diff --git a/api/main.py b/api/main.py index 86e6243..24da4e1 100644 --- a/api/main.py +++ b/api/main.py @@ -4,48 +4,17 @@ import numpy as np import pandas as pd import plotly.graph_objects as go -from steam_stats import get_recently_played_games +from steam_stats import get_player_summaries, get_recently_played_games from steam_workshop import fetch_workshop_item_links, fetch_all_workshop_stats -import pygal +from card import generate_card_for_player_summary, generate_card_for_played_games # Secrets Configuration STEAM_ID = os.getenv("STEAM_CUSTOM_ID") - -def generate_svg_for_recently_played_games(player_data): - """Generate SVG for Recently Played Games in Steam in the last 2 weeks""" - bar_chart = pygal.HorizontalBar( - legend_at_bottom=True, rounded_bars=15, explicit_size=True, width=800, height=400) - bar_chart.title = "Playtime in the Last Two Weeks (hours)" - - # Add data to the chart - if player_data and "response" in player_data and "games" in player_data["response"]: - for game in player_data["response"]["games"]: - if "name" in game and "playtime_2weeks" in game: - playtime_minutes = game["playtime_2weeks"] - playtime_hours = playtime_minutes / 60 # for plotting - - # Determine the label based on the original playtime in minutes - if playtime_minutes >= 60: - # Display in hours if 60 mins or more - label = f"{game["name"]} ({playtime_hours:.2f} hrs)" - else: - # Display in minutes if less than 60 - label = f"{game["name"]} ({playtime_minutes} mins)" - - # Add to chart using the hours value for consistency in scaling - bar_chart.add(label, playtime_hours) - else: - print("No game data available to display") - - # Render the chart to an SVG file - bar_chart.render_to_file("assets/recently_played_games.svg") - - return ( - "![Steam Games Stats](" - "https://github.com/Nicconike/Steam-Stats/blob/master/assets/recently_played_games.svg" - "?sanitize=true)" - ) +# Verify that the environment variables are loaded correctly +if not STEAM_ID: + raise ValueError( + "Missing STEAM_ID in environment variables") def generate_svg_for_steam_workshop(workshop_stats): @@ -62,8 +31,8 @@ def generate_svg_for_steam_workshop(workshop_stats): } 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))] + 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, @@ -89,7 +58,7 @@ def generate_svg_for_steam_workshop(workshop_stats): autosize=True, margin={"pad": 0} ) - fig.write_image("assets/steam_workshop_stats.svg") + fig.write_image("../assets/steam_workshop_stats.svg") return ( "![Steam Workshop Stats](" @@ -98,7 +67,7 @@ def generate_svg_for_steam_workshop(workshop_stats): ) -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: @@ -128,16 +97,25 @@ def update_readme(markdown_data, start_marker, end_marker, readme_path="README.m if __name__ == "__main__": # Start the timer start_time = time.time() + player_summary = get_player_summaries() recently_played_games = get_recently_played_games() links = fetch_workshop_item_links(STEAM_ID) USER_MARKDOWN_CONTENT = "" - if recently_played_games: - USER_MARKDOWN_CONTENT += generate_svg_for_recently_played_games( + if player_summary and recently_played_games: + summary_content = generate_card_for_player_summary(player_summary) + recent_games = generate_card_for_played_games( recently_played_games) - print("Successfully retrieved Steam User Data") + + if summary_content and recent_games: + USER_MARKDOWN_CONTENT += summary_content + USER_MARKDOWN_CONTENT += recent_games + print("Successfully retrieved Steam User Data") + else: + print( + "Failed to generate HTML content for Steam Summary or Recently Played Games") else: - print("Failed to fetch Steam data for Recently Played Games") + print("Failed to fetch Steam Summary & Games Data") WORKSHOP_MARKDOWN_CONTENT = "" if links: @@ -153,7 +131,7 @@ def update_readme(markdown_data, start_marker, end_marker, readme_path="README.m "", "") update_readme(WORKSHOP_MARKDOWN_CONTENT, "", "") - print("README.md has been successfully updated.") + print("README.md has been successfully updated") else: print("Failed to fetch or process data") diff --git a/api/steam_workshop.py b/api/steam_workshop.py index 87b4019..b2ce0ad 100644 --- a/api/steam_workshop.py +++ b/api/steam_workshop.py @@ -94,8 +94,8 @@ def fetch_individual_workshop_stats(item_url): for row in stats_table.find_all("tr"): cells = row.find_all("td") if len(cells) == 2: - key = cells[1].text.strip().lower().replace(' ', '_') - value = cells[0].text.strip().replace(',', '') + key = cells[1].text.strip().lower().replace(" ", "_") + value = cells[0].text.strip().replace(",", "") try: stats[key] = int(value) if value else 0 except ValueError: @@ -119,9 +119,9 @@ def fetch_individual_workshop_stats(item_url): e}. The structure of the page might have changed") # Ensure all expected stats are present, even if they are zero - stats['unique_visitors'] = stats.get('unique_visitors', 0) - stats['current_subscribers'] = stats.get('current_subscribers', 0) - stats['current_favorites'] = stats.get('current_favorites', 0) + stats["unique_visitors"] = stats.get("unique_visitors", 0) + stats["current_subscribers"] = stats.get("current_subscribers", 0) + stats["current_favorites"] = stats.get("current_favorites", 0) return stats diff --git a/assets/recently_played_games.html b/assets/recently_played_games.html new file mode 100644 index 0000000..502e7d8 --- /dev/null +++ b/assets/recently_played_games.html @@ -0,0 +1,61 @@ + + + + + + + Recently Played Games in Last 2 Weeks + + + +
+
+

Recently Played Games

+ +
+ State of Decay 2 + + State of Decay 2 (11.95 hrs) +
+ +
+ Counter-Strike 2 + + Counter-Strike 2 (7.12 hrs) +
+ +
+ Wizard with a Gun + + Wizard with a Gun (1.17 hrs) +
+ +
+ Perfect Heist 2 + + Perfect Heist 2 (18 mins) +
+ +
+ Killing Floor 2 + + Killing Floor 2 (17 mins) +
+ +
+ Wallpaper Engine + + Wallpaper Engine (9 mins) +
+ +
+
+ + + \ No newline at end of file diff --git a/assets/recently_played_games.svg b/assets/recently_played_games.svg deleted file mode 100644 index 1c6c9f3..0000000 --- a/assets/recently_played_games.svg +++ /dev/null @@ -1,4 +0,0 @@ - -Playtime in the Last Two Weeks (hours)0123456789101112130.1518.695361710250047211.43589743589740.283333333322.3220080167971176.461538461538420.322.77533880511548141.487179487179451.16666666746.34853979767131106.512820512820537.116666667208.1876312273334471.5384615384615513.43333333380.0000000000000636.56410256410256Playtime in the Last Two Weeks (hours)State of Decay 2 (13.43 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) \ No newline at end of file diff --git a/assets/steam_summary.html b/assets/steam_summary.html new file mode 100644 index 0000000..7a11313 --- /dev/null +++ b/assets/steam_summary.html @@ -0,0 +1,52 @@ + + + + + + + Responsive SVG Card + + + +
+
+ Avatar +

Name: Nicco

+

Status: Online

+

Country: IN + Flag +

+

Last Logoff: 29/05/2024

+

Gaming Since: 12/07/2017

+ +
+
+ + + \ No newline at end of file diff --git a/assets/steam_workshop_stats.svg b/assets/steam_workshop_stats.svg index 2d4b2fc..aeaa744 100644 --- a/assets/steam_workshop_stats.svg +++ b/assets/steam_workshop_stats.svg @@ -1 +1 @@ -Unique VisitorsCurrent SubscribersCurrent FavoritesWorkshop Stats3005177482Total \ No newline at end of file +Unique VisitorsCurrent SubscribersCurrent FavoritesWorkshop Stats3005177583Total \ No newline at end of file diff --git a/assets/style.css b/assets/style.css new file mode 100644 index 0000000..b07a401 --- /dev/null +++ b/assets/style.css @@ -0,0 +1,76 @@ +body { + display: grid; + grid-gap: 2vmin; + place-content: center; + margin: 0; + min-height: 100vh; + background: #cbcfd6; +} + +progress { + box-sizing: border-box; + border: solid 0.15em #242b35; + width: 12.5em; + height: 1em; + border-radius: 0.5em; + background: linear-gradient(#191c23, #2d3341); + font: clamp(.625em, 7.5vw, 5em) monospace; +} + +progress::-webkit-progress-bar { + background: transparent; +} + +progress::-webkit-progress-value { + border-radius: 0.35em; + box-shadow: inset 0 0.05em 0.05em rgba(255, 255, 255, 0.35); + background: var(--fill); +} + +progress::-moz-progress-bar { + border-radius: 0.35em; + box-shadow: inset 0 0.05em 0.05em rgba(255, 255, 255, 0.35); + background: var(--fill); +} + +progress:nth-child(1) { + --fill: + 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); +} + +progress:nth-child(3) { + --fill: + linear-gradient(rgba(226, 102, 76, 0.65), transparent), + repeating-linear-gradient(135deg, + #a22215 0 0.25em, #be2a20 0 0.5em); +} + +progress:nth-child(4) { + --fill: + linear-gradient(rgba(215, 131, 227, 0.5), transparent), + conic-gradient(from -30deg at 25%, + transparent 240deg, #8b42ab 0%) 0/ 0.7em, + conic-gradient(from -30deg at 75%, + #8b42ab 240deg, #9956b3 0%) 0/ 0.7em; +} + +progress:nth-child(5) { + --fill: + linear-gradient(#d0a9e2, transparent 85%), + linear-gradient(90deg, #433485, #dd3c6e); +} + +progress:nth-child(6) { + --fill: + linear-gradient(rgba(90, 240, 255, 0.85), transparent), + repeating-linear-gradient(90deg, + #123c92 0 0.0625em, #1b5ec6 0 1em); +} diff --git a/requirements.txt b/requirements.txt index 0ba6fb6..0d80c7d 100644 Binary files a/requirements.txt and b/requirements.txt differ