added favorite streamers feature

main
oscar 2 weeks ago
parent 4c6bc25398
commit f2de819f58

@ -70,4 +70,11 @@ def db_get_favorites(page: int, per_page: int):
total = cur.fetchone()[0] total = cur.fetchone()[0]
cur.close(); conn.close() cur.close(); conn.close()
return rows, total return rows, total
def db_get_favorite_users():
conn, cur = get_local_db_connection()
cur.execute("SELECT username FROM favorite_users")
favorite_users = [r['username'] for r in cur.fetchall()]
cur.close(); conn.close()
return favorite_users

@ -91,3 +91,18 @@ def get_online():
for s in streamers: for s in streamers:
s["is_online"] = (s.get("status") == "Channel online") s["is_online"] = (s.get("status") == "Channel online")
return jsonify(streamers) return jsonify(streamers)
@api.route("/api/favorite_user", methods=["POST"])
def favorite_user():
data = request.get_json(force=True)
username = data.get("username")
fav = bool(data.get("favorite"))
conn, cur = get_local_db_connection()
if fav:
cur.execute("INSERT INTO favorite_users (username) VALUES (%s) ON CONFLICT DO NOTHING", (username,))
else:
cur.execute("DELETE FROM favorite_users WHERE username = %s", (username,))
conn.commit()
cur.close(); conn.close()
return {"ok": True}

@ -1,7 +1,7 @@
from flask import Blueprint, render_template, request, send_file, jsonify from flask import Blueprint, render_template, request, send_file, jsonify
import math import math
from helpers.db import db_get_videos, db_get_video, db_get_recent from helpers.db import db_get_videos, db_get_video, db_get_recent
from helpers.favorites import mark_favorites, db_get_favorites, db_get_fav_set from helpers.favorites import mark_favorites, db_get_favorites, db_get_fav_set, db_get_favorite_users
from helpers.cache import build_cache from helpers.cache import build_cache
from config import VIDEOS_PER_PAGE, DASHBOARD_PER_PAGE, get_local_db_connection from config import VIDEOS_PER_PAGE, DASHBOARD_PER_PAGE, get_local_db_connection
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
@ -120,6 +120,8 @@ def dashboard():
start_idx = (page - 1) * DASHBOARD_PER_PAGE start_idx = (page - 1) * DASHBOARD_PER_PAGE
paginated = items[start_idx:start_idx + DASHBOARD_PER_PAGE] paginated = items[start_idx:start_idx + DASHBOARD_PER_PAGE]
favorite_users = db_get_favorite_users()
return render_template( return render_template(
"main.html", "main.html",
storage_usage=paginated, storage_usage=paginated,
@ -134,6 +136,7 @@ def dashboard():
end_date=end_str, end_date=end_str,
online_set=online_usernames, online_set=online_usernames,
recording_offline_set=recording_offline_usernames, recording_offline_set=recording_offline_usernames,
favorite_users=favorite_users,
) )
@web.route("/user/<username>") @web.route("/user/<username>")

@ -172,6 +172,16 @@ tr:nth-child(even){background:#181818}
.fav-btn[aria-pressed="true"]{color:gold} .fav-btn[aria-pressed="true"]{color:gold}
.fav-btn:hover{transform:scale(1.05)} .fav-btn:hover{transform:scale(1.05)}
.user-fav {
position: static; /* no absolute positioning */
background: none;
padding: 0;
margin-right: 6px; /* little gap before username */
font-size: 16px;
backdrop-filter: none;
}
/* ========================= /* =========================
Responsive tweaks Responsive tweaks
========================= */ ========================= */

@ -51,7 +51,15 @@
{% for key, stats in storage_usage %} {% for key, stats in storage_usage %}
{% set user, platform = key.split("::") %} {% set user, platform = key.split("::") %}
<tr data-username="{{ user|lower }}"> <tr data-username="{{ user|lower }}">
<td> <td>
<!-- ⭐ favorite star on the left -->
<button class="fav-btn user-fav"
data-username="{{ user }}"
aria-pressed="{{ 'true' if user in favorite_users else 'false' }}">
</button>
<a href="/user/{{ user }}">{{ user }}</a> <a href="/user/{{ user }}">{{ user }}</a>
{% set uname = user.lower() %} {% set uname = user.lower() %}
{% if uname in online_set %} {% if uname in online_set %}
@ -62,10 +70,15 @@
<span class="status-dot dot-offline" title="Offline"></span> <span class="status-dot dot-offline" title="Offline"></span>
{% endif %} {% endif %}
</td> </td>
<td><a href="https://{{ platform }}.com/{{ user }}">{{ platform }}</a></td> <td><a href="https://{{ platform }}.com/{{ user }}">{{ platform }}</a></td>
<td>{{ "%.2f"|format(stats.total_size) }}</td> <td>{{ "%.2f"|format(stats.total_size) }}</td>
<td>{{ stats.video_count }}</td> <td>{{ stats.video_count }}</td>
<td>{{ "%.2f"|format(avg_sizes[key]) }}</td> <td>{{ "%.2f"|format(avg_sizes[key]) }}</td>
<td> <td>
{% if stats.last_online %} {% if stats.last_online %}
{{ stats.last_online.strftime('%Y-%m-%d') }} {{ stats.last_online.strftime('%Y-%m-%d') }}
@ -73,6 +86,7 @@
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -104,4 +118,32 @@ async function refreshStatusDots() {
} }
refreshStatusDots(); setInterval(refreshStatusDots, 20000); refreshStatusDots(); setInterval(refreshStatusDots, 20000);
</script> </script>
<script>
document.addEventListener("click", async (e) => {
if (e.target.classList.contains("fav-btn")) {
const btn = e.target;
const username = btn.dataset.username;
const videoId = btn.dataset.videoId;
const isFav = btn.getAttribute("aria-pressed") === "true";
const newState = !isFav;
btn.setAttribute("aria-pressed", newState);
try {
if (username) {
await fetch("/api/favorite_user", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, favorite: newState })
});
} else if (videoId) {
await fetch(`/api/fav/toggle/${videoId}`, { method: "POST" });
}
} catch (err) {
console.error("Favorite toggle failed:", err);
}
}
});
</script>
{% endblock %} {% endblock %}

Loading…
Cancel
Save