Add Steam integration showing online status and recent games
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled

Fetches player summary and recently played games from Steam API with
5-minute server-side caching. Displays in the home sidebar with online
indicator and game artwork.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 01:59:34 +00:00
parent 747563c6c9
commit 264df132df
13 changed files with 772 additions and 2 deletions

View File

@@ -14,6 +14,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
const spotifyRecent = ref([]);
const rowingSessions = ref([]);
const gitFeed = ref(null);
const steamStatus = ref(null);
const radioLive = ref(false);
async function fetchAll() {
@@ -27,6 +28,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
spotifyRecent { track { name album { name images { url } } artists { name } } playedAt }
rowingSessions { id date time distance timePer500m calories }
giteaFeed { avatarUrl repoUrl repoName opType commitMessage createdAt }
steamStatus { online recentGames { appId name playtime2Weeks playtimeForever headerImageUrl } }
me { id username admin }
}
`),
@@ -38,6 +40,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
spotifyRecent.value = data.spotifyRecent || [];
rowingSessions.value = data.rowingSessions;
gitFeed.value = data.giteaFeed || null;
steamStatus.value = data.steamStatus || null;
me.value = data.me || null;
loaded.value = true;
} catch (err) {
@@ -67,6 +70,7 @@ export const useHomeDataStore = defineStore("homeData", () => {
spotifyRecent,
rowingSessions,
gitFeed,
steamStatus,
radioLive,
fetchAll,
fetchRadioStatus,

42
vue/src/stores/steam.js Normal file
View File

@@ -0,0 +1,42 @@
import { defineStore } from "pinia";
import { ref, watch } from "vue";
import { gql } from "@/graphql";
import { useHomeDataStore } from "@/stores/homeData";
export const useSteamStore = defineStore("steam", () => {
const steamStatus = ref({ online: false, recentGames: [] });
const homeData = useHomeDataStore();
watch(
() => homeData.steamStatus,
(newStatus) => {
if (newStatus) {
steamStatus.value = newStatus;
}
},
{ immediate: true },
);
async function fetchSteam() {
try {
const data = await gql(`
query {
steamStatus {
online
recentGames { appId name playtime2Weeks playtimeForever headerImageUrl }
}
}
`);
if (data.steamStatus) {
steamStatus.value = data.steamStatus;
}
} catch (err) {
console.error("Failed to fetch Steam status", err);
}
}
return {
steamStatus,
fetchSteam,
};
});

View File

@@ -20,6 +20,7 @@ import Favorites from "./Favorites.vue";
// import Gym from "./Gym.vue";
import Gym2 from "./Gym2.vue";
import Consumption from "./Consumption.vue";
import Steam from "./Steam.vue";
</script>
<template>
@@ -30,6 +31,7 @@ import Consumption from "./Consumption.vue";
class="flex-1 flex flex-col min-h-0 background-children border-children gap-2"
>
<Chat class="flex-1 min-h-0" />
<Steam />
</div>
<div class="sidebar-image">
<Miku class="border-tertiary border bg-bg_secondary h-60" />

View File

@@ -0,0 +1,66 @@
<script setup>
import { onMounted, onUnmounted } from "vue";
import { storeToRefs } from "pinia";
import { useSteamStore } from "@/stores/steam";
import { useHomeDataStore } from "@/stores/homeData";
import Header from "@/components/text/Header.vue";
const steamStore = useSteamStore();
const { steamStatus } = storeToRefs(steamStore);
const homeData = useHomeDataStore();
const { loaded } = storeToRefs(homeData);
let refreshInterval;
onMounted(() => {
refreshInterval = setInterval(() => steamStore.fetchSteam(), 5 * 60 * 1000);
});
onUnmounted(() => clearInterval(refreshInterval));
function formatHours(minutes) {
const hrs = (minutes / 60).toFixed(1);
return `${hrs}h`;
}
</script>
<template>
<div class="flex flex-col min-h-0 overflow-hidden">
<Header class="text-left">
<span class="flex items-center gap-2">
Steam
<span
class="inline-block w-2 h-2 rounded-full"
:class="steamStatus.online ? 'bg-green-500' : 'bg-gray-400'"
:title="steamStatus.online ? 'Online' : 'Offline'"
/>
</span>
</Header>
<div v-if="!loaded" class="p-2 text-sm">Loading...</div>
<div
v-else-if="steamStatus.recentGames.length"
class="flex-1 overflow-y-auto flex flex-col gap-2 p-1"
>
<div
v-for="game in steamStatus.recentGames"
:key="game.appId"
class="flex flex-col"
>
<img
:src="game.headerImageUrl"
:alt="game.name"
class="w-full object-cover"
loading="lazy"
/>
<div class="px-1 py-0.5 text-xs">
<p class="font-bold truncate">{{ game.name }}</p>
<p class="text-tertiary">
{{ formatHours(game.playtime2Weeks) }} last 2 weeks
</p>
</div>
</div>
</div>
<div v-else class="p-2 text-sm">No recent games.</div>
</div>
</template>