3 Commits

Author SHA1 Message Date
Sweetbread 15e11e21d0 Update docker image
Docker Build and Push / build-and-push (push) Failing after 1m34s
2026-02-04 15:51:14 +03:00
Sweetbread 6ed04e501d Add OG meta 2026-02-04 15:31:04 +03:00
Sweetbread 909800c553 Add Steam info 2026-02-04 15:31:04 +03:00
10 changed files with 204 additions and 12 deletions
+7
View File
@@ -11,6 +11,12 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: 'Docker Tags'
id: tags
uses: cssnr/docker-tags-action@v2
with:
images: 'g.lair.moe/${{ vars.DOCKER_USERNAME }}/lair.moe'
- name: Login to Docker Registry
uses: docker/login-action@v2
with:
@@ -27,5 +33,6 @@ jobs:
context: .
push: ${{ github.event_name == 'push' }}
tags: g.lair.moe/${{ vars.DOCKER_USERNAME }}/lair.moe:latest
labels: ${{ steps.tags.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
+32 -11
View File
@@ -1,25 +1,46 @@
FROM node:18-alpine as sass
FROM node:18-alpine AS sass-builder
RUN NODE_OPTIONS=--dns-result-order=ipv4first npm install -g sass
RUN npm install -g sass@latest --omit=dev --no-fund --no-audit
WORKDIR /build
COPY ./blueprints ./blueprints
RUN sass ./blueprints:./blueprints \
--no-source-map \
--style=compressed
--style=compressed \
--quiet
FROM python:3.11-slim
RUN apt-get update && \
apt-get install --no-install-recommends -y \
libmagic1 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --no-deps -r requirements.txt
COPY . .
COPY --from=sass /build/blueprints/ ./blueprints/
RUN apt update && apt upgrade
RUN apt install libmagic1 -y
RUN pip install --no-cache-dir -r requirements.txt
COPY --from=sass-builder /build/blueprints/ ./blueprints/
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
RUN useradd -m -u 1001 appuser && \
chown -R appuser:appuser /app
CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--workers", "4"]
USER appuser
ENV FLASK_ENV=production \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
CMD ["gunicorn", "app:app", \
"-b", "0.0.0.0:80", \
"--workers", "4", \
"--worker-class", "sync", \
"--worker-tmp-dir", "/dev/shm", \
"--access-logfile", "-", \
"--error-logfile", "-", \
"--log-level", "info"]
+23 -1
View File
@@ -14,6 +14,20 @@ from flask import (
)
from .modules.api.lb import listens, listening
from .modules.api.steam import recent, owned
def tmsmp(min: int) -> str:
if min < 60:
return f"{min} m"
elif min < 60*24:
return f"{min/60:.1f} h"
else:
return f"{min/60/24:.1f} d"
def rtmsmp(unix: int) -> str:
return datetime \
.utcfromtimestamp(unix) \
.strftime('%Y-%m-%d %H:%M:%S')
bp = Blueprint(
"risdeveau",
@@ -49,4 +63,12 @@ def mb_cover(mbid):
@bp.route("/")
def index():
return render_tmpl('index.html', lb=listens, lb_now=listening)
return render_tmpl(
'index.html',
lb=listens,
lb_now=listening,
recent=recent.get('data', {}),
owned=owned.get('data', {}),
tmsmp=tmsmp,
rtmsmp=rtmsmp
)
+63
View File
@@ -0,0 +1,63 @@
from os import environ
from flask import Flask, jsonify
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.interval import IntervalTrigger
import requests
from datetime import datetime
import atexit
import re
from urllib.parse import urlparse, parse_qs
TOKEN = environ.get("STEAM_TOKEN")
MY_ID = 76561198826355942
recent = {}
owned = {}
def get_cover_url(appid: int) -> str:
return f"https://shared.fastly.steamstatic.com/store_item_assets//steam/apps/{appid}/header.jpg"
def inject_cover_url(json: dict) -> dict:
if 'games' in json.keys():
for i, g in enumerate(json['games']):
json['games'][i]['h_cover'] = f"https://shared.fastly.steamstatic.com/store_item_assets//steam/apps/{g['appid']}/header.jpg"
json['games'][i]['v_cover'] = f"https://shared.fastly.steamstatic.com/store_item_assets//steam/apps/{g['appid']}/library_600x900.jpg"
return json
def steam_request(interface: str, method: str, v: int = 1, **kwargs) -> requests.Response:
return requests.get(
f"https://api.steampowered.com/{interface}/{method}/v{v:04}/",
params=dict({"key": TOKEN}, **kwargs),
timeout=10
)
def api_request(cache, *args, **kwargs):
try:
response = steam_request(*args, **kwargs)
if response.status_code == 200:
cache.update({
'data': inject_cover_url(response.json().get("response")),
'last_updated': datetime.now().isoformat(),
'status': 'success'
})
else:
cache['status'] = f'error: {response.status_code}'
except Exception as e:
cache['status'] = f'error: {str(e)}'
scheduler = BackgroundScheduler()
scheduler.add_job(
func=lambda: api_request(recent, "IPlayerService", "GetRecentlyPlayedGames", steamid=76561198826355942),
trigger=IntervalTrigger(minutes=15),
id='risdeveau.steam.recent',
replace_existing=True
)
scheduler.add_job(
func=lambda: api_request(owned, "IPlayerService", "GetOwnedGames", steamid=76561198826355942, include_appinfo=1, include_played_free_games=1),
trigger=IntervalTrigger(minutes=15),
id='risdeveau.steam.recent',
replace_existing=True
)
scheduler.start()
atexit.register(lambda: scheduler.shutdown())
@@ -48,6 +48,21 @@ h3 {
}
}
.steam {
.block {
display: flex;
img {
height: 7rem;
margin-right: .5rem;
}
p {
margin: .5rem 0;
}
}
}
table, tbody {
vertical-align: baseline;
border-collapse: collapse;
@@ -23,6 +23,7 @@
'info',
'contacts',
'listenbrainz',
'steam',
'donate',
'88x31'
) %}
+52
View File
@@ -0,0 +1,52 @@
<div class="block steam">
<h2><a href="https://steamcommunity.com/id/risdeveau">Steam</a></h2>
{% if recent.games %}
<h3>Recently played:</h3>
{% for g in recent.games %}
<a href="https://store.steampowered.com/app/{{ g.appid }}" class="block">
<picture>
<source media="(max-width: 45rem)" srcset="{{ g.v_cover }}">
<img src="{{ g.h_cover }}">
</picture>
<div>
<strong>{{ g.name }}</strong>
<p>Played last 2 weeks: {{ tmsmp(g.playtime_2weeks) }}
<p>
Total played:
{{ tmsmp(g.playtime_linux_forever) }} (<abbr title="On Linux">L</abbr>) +
{{ tmsmp(g.playtime_windows_forever) }} (<abbr title="On Windows">W</abbr>) =
{{ tmsmp(g.playtime_forever) }} (<abbr title="Total">T</abbr>)
</p>
</div>
</a>
{% endfor %}
{% endif %}
{% if owned.games %}
<h3>Top played games:</h3>
{% set owned_games = owned.games | sort(attribute="playtime_forever", reverse=true) %}
{% for g in owned_games[:5] %}
<a href="https://store.steampowered.com/app/{{ g.appid }}" class="block">
<picture>
<source media="(max-width: 45rem)" srcset="{{ g.v_cover }}">
<img src="{{ g.h_cover }}">
</picture>
<div>
<strong>{{ g.name }}</strong>
<p>
Total played:
{{ tmsmp(g.playtime_linux_forever) }} (<abbr title="On Linux">L</abbr>) +
{{ tmsmp(g.playtime_windows_forever) }} (<abbr title="On Windows">W</abbr>) =
{{ tmsmp(g.playtime_forever) }} (<abbr title="Total">T</abbr>)
</p>
{% if g.rtime_last_played != 0 %}
<p>Last played: {{ rtmsmp(g.rtime_last_played) }}</p>
{% endif %}
</div>
</a>
{% endfor %}
{% endif %}
</div>
+7
View File
@@ -13,6 +13,13 @@
></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mock-email" content="admin@example.com">
<!-- og meta -->
<meta property="og:type" value="website" />
<meta property="og:url" value="https://lair.moe" />
<meta property="og:title" value="Lair.moe" />
<meta property="og:image" value="https://lair.moe/static/icon/lair.webp" />
<meta property="og:description" value="{{ _("description") }}" />
</head>
<body>
{% include 'header.tmpl' %}
+2
View File
@@ -8,6 +8,8 @@ about host = About host
contacts = Contacts
donate = Donate
description = Small personal site
[index]
altfronts = Altfronts
+2
View File
@@ -8,6 +8,8 @@ about host = О хосте
contacts = Контакты
donate = Донат
description = Небольшой личный сайт
[index]
altfronts = Альтфронты