Rework my page and add info from listenbrainz
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import os
|
||||
import magic
|
||||
from pathlib import Path
|
||||
from htmlmin import minify
|
||||
from flask import Blueprint, render_template, send_from_directory, send_file, abort
|
||||
from datetime import datetime, timedelta
|
||||
from musicbrainzngs import get_image_front
|
||||
from flask import (
|
||||
Blueprint,
|
||||
render_template,
|
||||
send_file,
|
||||
send_from_directory,
|
||||
make_response,
|
||||
abort,
|
||||
)
|
||||
|
||||
from .modules.api.lb import listens, listening
|
||||
|
||||
bp = Blueprint(
|
||||
"risdeveau",
|
||||
@@ -11,10 +23,10 @@ bp = Blueprint(
|
||||
static_folder=None
|
||||
)
|
||||
|
||||
def render_tmpl(filename: str) -> str:
|
||||
def render_tmpl(filename: str, **kwargs) -> str:
|
||||
template_path = os.path.join("risdeveau/templates", filename)
|
||||
return minify(
|
||||
render_template(template_path),
|
||||
render_template(template_path, **kwargs),
|
||||
remove_empty_space=True
|
||||
)
|
||||
|
||||
@@ -26,14 +38,15 @@ def static(filename: str):
|
||||
return send_file(path)
|
||||
return abort(404)
|
||||
|
||||
@bp.route("/asset/mb/<mbid>")
|
||||
def mb_cover(mbid):
|
||||
r = make_response(image := get_image_front(mbid, "250"))
|
||||
r.headers['Content-Type'] = magic.from_buffer(image[:2048], mime=True)
|
||||
r.headers['Cache-Control'] = 'public, max-age=86400'
|
||||
r.headers['Expires'] = (datetime.now() + timedelta(days=1)) \
|
||||
.strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
return r
|
||||
|
||||
@bp.route("/")
|
||||
def index():
|
||||
return render_tmpl('index.html')
|
||||
|
||||
@bp.route("/contacts")
|
||||
def contacts():
|
||||
return render_tmpl('contacts.html')
|
||||
|
||||
@bp.route("/donate")
|
||||
def donate():
|
||||
return render_tmpl('donate.html')
|
||||
return render_tmpl('index.html', lb=listens, lb_now=listening)
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
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
|
||||
|
||||
listens = {}
|
||||
listening = {}
|
||||
|
||||
def yt_cover(youtube_url):
|
||||
parsed_url = urlparse(youtube_url)
|
||||
|
||||
if parsed_url.netloc in ("youtube.com", "music.youtube.com"):
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
video_id = query_params.get('v', [None])[0]
|
||||
|
||||
elif parsed_url.netloc == 'youtu.be':
|
||||
video_id = parsed_url.path[1:]
|
||||
|
||||
if not video_id:
|
||||
return
|
||||
|
||||
return f"http://img.youtube.com/vi/{video_id}/sddefault.jpg"
|
||||
|
||||
def parse_listens(data: dict) -> dict:
|
||||
new_data = {
|
||||
"count": data["count"],
|
||||
"listens": []
|
||||
}
|
||||
|
||||
for track in data["listens"]:
|
||||
track = track["track_metadata"]
|
||||
|
||||
new_track = {
|
||||
"artist_name": track["artist_name"],
|
||||
"track_name": track["track_name"]
|
||||
}
|
||||
|
||||
if mb := track.get("mbid_mapping"):
|
||||
new_track["id"] = mb.get("caa_release_mbid", mb["release_mbid"])
|
||||
new_track["artist_name"] = mb["artists"][0]["artist_credit_name"]
|
||||
new_track["track_name"] = mb["recording_name"]
|
||||
elif info := track.get("additional_info"):
|
||||
if info \
|
||||
.get("music_service_name", "") \
|
||||
.lower() in ("youtube", "youtube music"):
|
||||
if cover := yt_cover(track["additional_info"]["origin_url"]):
|
||||
new_track["cover_url"] = cover
|
||||
|
||||
if "cover_url" not in new_track.keys() and "id" in new_track.keys():
|
||||
new_track["cover_url"] = "/asset/mb/" + new_track["id"]
|
||||
|
||||
new_data["listens"].append(new_track)
|
||||
|
||||
return new_data
|
||||
|
||||
def api_request(url: str, cache):
|
||||
try:
|
||||
response = requests.get(url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
cache.update({
|
||||
'data': parse_listens(response.json().get("payload")),
|
||||
'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("https://api.listenbrainz.org/1/user/risdeveau/listens?count=5", listens),
|
||||
trigger=IntervalTrigger(minutes=1),
|
||||
id='risdeveau.listenbrainz.listens',
|
||||
replace_existing=True
|
||||
)
|
||||
scheduler.add_job(
|
||||
func=lambda: api_request("https://api.listenbrainz.org/1/user/risdeveau/playing-now", listening),
|
||||
trigger=IntervalTrigger(seconds=15),
|
||||
id='risdeveau.listenbrainz.playing-now',
|
||||
replace_existing=True
|
||||
)
|
||||
scheduler.start()
|
||||
|
||||
atexit.register(lambda: scheduler.shutdown())
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 13 KiB |
@@ -1,29 +1,5 @@
|
||||
@use "sass:color";
|
||||
|
||||
// Palette: Catppuccin Mocha
|
||||
// https://catppuccin.com/palette/
|
||||
$base: #1e1e2e;
|
||||
$text: #cdd6f4;
|
||||
|
||||
$mantle: #181825;
|
||||
$crust: #11111b;
|
||||
|
||||
$overlay0: #6c7086;
|
||||
$overlay1: #7f849c;
|
||||
$overlay2: #9399b2;
|
||||
|
||||
$surface0: #313244;
|
||||
$surface1: #45475a;
|
||||
$surface2: #585b70;
|
||||
|
||||
$subtext0: #a6adc8;
|
||||
$subtext1: #bac2de;
|
||||
|
||||
$red: #f38ba8;
|
||||
$green: #a6e3a1;
|
||||
$peach: #fab387;
|
||||
$blue: #89b4fa;
|
||||
$mauve: #8839ef;
|
||||
@use "../../../root/static/style/catppuccin" as theme;
|
||||
|
||||
h3 {
|
||||
margin-block-end: 0;
|
||||
@@ -58,6 +34,20 @@ h3 {
|
||||
}
|
||||
}
|
||||
|
||||
.track {
|
||||
display: flex;
|
||||
|
||||
&.active {
|
||||
box-shadow: theme.$green 0 0 5px 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
table, tbody {
|
||||
vertical-align: baseline;
|
||||
border-collapse: collapse;
|
||||
@@ -66,7 +56,7 @@ table, tbody {
|
||||
border-radius: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: color.change($surface1, $alpha:75%);
|
||||
background-color: color.change(theme.$surface1, $alpha:75%);
|
||||
}
|
||||
|
||||
th {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<div class="88-31">
|
||||
<a href="https://chest.lair.moe" class="disabled">
|
||||
<img src="/static/img/88x31/gf.png"/>
|
||||
</a>
|
||||
<a href="https://preview.about.akarpov.ru" id="pie">
|
||||
<img src="/static/img/88x31/withpie.gif"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="88-31">
|
||||
<a href="https://g.lair.moe/Sweetbread/nixos-config">
|
||||
<img src="/static/img/88x31/nixos.webp"/>
|
||||
</a>
|
||||
<img src="/static/img/88x31/teto.webp"/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,24 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Sweet Bread</title>
|
||||
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
<link rel="stylesheet" href="/static/style/risdeveau.css">
|
||||
<link rel="icon" type="image/webp" href="/static/icon/us/risdeveau.webp" />
|
||||
<script
|
||||
src="https://track.lair.moe/api/script.js"
|
||||
data-site-id="1"
|
||||
defer
|
||||
></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% block head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{% include 'risdeveau/templates/header.tmpl' %}
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
+2
-12
@@ -1,14 +1,4 @@
|
||||
{% extends 'risdeveau/templates/base.tmpl' %}
|
||||
|
||||
{% block head %}
|
||||
<style>
|
||||
main {
|
||||
width: -webkit-fill-available;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="block">
|
||||
<h3>Development</h3>
|
||||
<div class="blocks badges">
|
||||
<a class="block" href="//g.lair.moe/Sweetbread">
|
||||
@@ -55,4 +45,4 @@
|
||||
GameBanana
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</div>
|
||||
@@ -0,0 +1,19 @@
|
||||
<div>
|
||||
<h3>Wallets</h3>
|
||||
<div class="blocks qr">
|
||||
<div class="block qr">
|
||||
<p>POL, BNB</p>
|
||||
<img src="/static/img/wallets/evm.webp">
|
||||
</div>
|
||||
|
||||
<div class="block qr">
|
||||
<p>TON</p>
|
||||
<img src="/static/img/wallets/ton.webp">
|
||||
</div>
|
||||
|
||||
<div class="block qr">
|
||||
<p>XMR</p>
|
||||
<img src="/static/img/wallets/xmr.webp">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,21 +0,0 @@
|
||||
{% extends 'risdeveau/templates/base.tmpl' %}
|
||||
|
||||
{% block content %}
|
||||
<h3>Wallets</h3>
|
||||
<div class="blocks qr">
|
||||
<div class="block qr">
|
||||
<p>POL, BNB</p>
|
||||
<img src="/static/img/wallets/evm.webp">
|
||||
</div>
|
||||
|
||||
<div class="block qr">
|
||||
<p>TON</p>
|
||||
<img src="/static/img/wallets/ton.webp">
|
||||
</div>
|
||||
|
||||
<div class="block qr">
|
||||
<p>XMR</p>
|
||||
<img src="/static/img/wallets/xmr.webp">
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,20 +0,0 @@
|
||||
<header>
|
||||
{%- if request.path != url_for('.index') %}
|
||||
<a href="{{ url_for('.index') }}">Main</a>
|
||||
{%- else %}
|
||||
<a href="{{ url_for('root.index') }}">Lair</a>
|
||||
{%- endif %}
|
||||
|
||||
<div class="header-links">
|
||||
{%- for (l, t) in (
|
||||
('.contacts', _('contacts')),
|
||||
('.donate', _('donate'))
|
||||
) %}
|
||||
{%- if url_for(l) == request.path %}
|
||||
<strong>{{ t }}</strong>
|
||||
{%- else %}
|
||||
<a href="{{ url_for(l) }}">{{ t }}</a>
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
</header>
|
||||
@@ -1,69 +1,33 @@
|
||||
{% extends 'risdeveau/templates/base.tmpl' %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Sweet Bread</title>
|
||||
|
||||
{% block content %}
|
||||
<div class="block">
|
||||
<table>
|
||||
<tr>
|
||||
<th>DoB</th>
|
||||
<td>2005-01-13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Languages</th>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Russian</td>
|
||||
<td>Native</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>English</td>
|
||||
<td>B2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>French</td>
|
||||
<td>A1?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>German</td>
|
||||
<td>A2?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Japanese</td>
|
||||
<td>Beginner</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Student</th>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Programmer</td>
|
||||
<td>2/4yr.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Translator</td>
|
||||
<td>2/3yr.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<link rel="stylesheet" href="/static/style/main.css">
|
||||
<link rel="stylesheet" href="/static/style/risdeveau.css">
|
||||
<link rel="icon" type="image/webp" href="/static/icon/us/risdeveau.webp" />
|
||||
<script
|
||||
src="https://track.lair.moe/api/script.js"
|
||||
data-site-id="1"
|
||||
defer
|
||||
></script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a href="{{ url_for('root.index') }}">Lair</a>
|
||||
</header>
|
||||
|
||||
<div class="88-31">
|
||||
<a href="https://chest.lair.moe" class="disabled">
|
||||
<img src="/static/img/88x31/gf.png"/>
|
||||
</a>
|
||||
<a href="https://preview.about.akarpov.ru" id="pie">
|
||||
<img src="/static/img/88x31/withpie.gif"/>
|
||||
</a>
|
||||
</div>
|
||||
<div class="88-31">
|
||||
<a href="https://g.lair.moe/Sweetbread/nixos-config">
|
||||
<img src="/static/img/88x31/nixos.webp"/>
|
||||
</a>
|
||||
<img src="/static/img/88x31/teto.webp"/>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<main>
|
||||
{% for m in (
|
||||
'info',
|
||||
'contacts',
|
||||
'listenbrainz',
|
||||
'donate',
|
||||
'88x31'
|
||||
) %}
|
||||
{% include 'risdeveau/templates/%s.htm' % m %}
|
||||
{% endfor %}
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<div class="block">
|
||||
<table>
|
||||
<tr>
|
||||
<th>DoB</th>
|
||||
<td>2005-01-13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Languages</th>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Russian</td>
|
||||
<td>Native</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>English</td>
|
||||
<td>B2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>French</td>
|
||||
<td>A1?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>German</td>
|
||||
<td>A2?</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Japanese</td>
|
||||
<td>Beginner</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Student</th>
|
||||
<td>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Programmer</td>
|
||||
<td>2/4yr.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Translator</td>
|
||||
<td>2/3yr.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,23 @@
|
||||
{% macro track_block(track, is_active=false) %}
|
||||
<div class="block track{% if is_active %} active{% endif %}">
|
||||
{% if track.cover_url %}
|
||||
<img src="{{ track.cover_url }}"/>
|
||||
{% endif %}
|
||||
<div>
|
||||
<p><b>{{ track.artist_name }}</b></p>
|
||||
<p>{{ track.track_name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="block">
|
||||
<h2><a href="https://listenbrainz.org/user/risdeveau/">Listenbrainz</a></h2>
|
||||
{% if lb_now.data and lb_now.data.listens.0 %}
|
||||
{{ track_block(lb_now.data.listens.0, is_active=true) }}
|
||||
{% endif %}
|
||||
{% if lb.data and lb.data.listens %}
|
||||
{% for track in lb.data.listens %}
|
||||
{{ track_block(track) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
Reference in New Issue
Block a user