AstraOS Ver. 1.35:
All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 12s

- AyranTracker bearbeitet
This commit is contained in:
elias 2026-06-07 12:56:59 +02:00
parent c5faf7f85d
commit fd420d7733
2 changed files with 41 additions and 41 deletions

View file

@ -73,7 +73,7 @@
<div class="lg:col-span-2 bg-slate-950/40 border border-purple-900/20 rounded-2xl p-5"> <div class="lg:col-span-2 bg-slate-950/40 border border-purple-900/20 rounded-2xl p-5">
<div class="flex justify-between items-center mb-4"> <div class="flex justify-between items-center mb-4">
<h3 id="matchHistoryHeader" class="text-sm font-bold uppercase tracking-wider text-slate-300">Recent Matches</h3> <h3 id="matchHistoryHeader" class="text-sm font-bold uppercase tracking-wider text-slate-300">Recent Matches</h3>
<span class="text-[10px] text-purple-400 font-semibold bg-purple-950/50 border border-purple-900/40 px-2 py-0.5 rounded">Für Details anklicken</span> <span class="text-[10px] text-purple-400 font-semibold bg-purple-950/50 border border-purple-900/40 px-2 py-0.5 rounded animate-pulse">Für Details anklicken</span>
</div> </div>
<div id="matchHistoryContainer" class="space-y-2.5"></div> <div id="matchHistoryContainer" class="space-y-2.5"></div>
</div> </div>
@ -90,12 +90,12 @@
</div> </div>
</div> </div>
<div id="matchModal" class="fixed inset-0 bg-black/80 backdrop-blur-md hidden items-center justify-center z-50 p-4"> <div id="matchModal" class="fixed inset-0 bg-black/80 backdrop-blur-md hidden items-center justify-center z-50 p-4" onclick="closeMatchModalOutside(event)">
<div class="bg-slate-950 border border-purple-900/60 w-full max-w-2xl rounded-2xl shadow-2xl p-6 overflow-y-auto max-h-[90vh]"> <div id="modalCard" class="bg-slate-950 border border-purple-900/60 w-full max-w-2xl rounded-2xl shadow-2xl p-6 overflow-y-auto max-h-[90vh]">
<div class="flex justify-between items-start border-b border-purple-950 pb-4 mb-4"> <div class="flex justify-between items-start border-b border-purple-950 pb-4 mb-4">
<div> <div>
<h3 id="modalMapName" class="text-xl font-black tracking-wide text-slate-100">Ascent</h3> <h3 id="modalMapName" class="text-xl font-black tracking-wide text-slate-100">Map</h3>
<p id="modalGameMeta" class="text-xs text-slate-400 mt-0.5">Competitive • Frankfurt Server</p> <p id="modalGameMeta" class="text-xs text-slate-400 mt-0.5">Meta Info</p>
</div> </div>
<button onclick="closeMatchModal()" class="text-slate-400 hover:text-white font-bold bg-slate-900 border border-slate-800 rounded-lg p-2 text-xs transition">✕ Schließen</button> <button onclick="closeMatchModal()" class="text-slate-400 hover:text-white font-bold bg-slate-900 border border-slate-800 rounded-lg p-2 text-xs transition">✕ Schließen</button>
</div> </div>
@ -103,11 +103,11 @@
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-6"> <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 mb-6">
<div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center"> <div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center">
<p class="text-[10px] text-slate-500 uppercase font-bold">Ergebnis</p> <p class="text-[10px] text-slate-500 uppercase font-bold">Ergebnis</p>
<p id="modalResult" class="text-base font-black mt-0.5">WIN</p> <p id="modalResult" class="text-base font-black mt-0.5">--</p>
</div> </div>
<div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center"> <div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center">
<p class="text-[10px] text-slate-500 uppercase font-bold">Agent</p> <p class="text-[10px] text-slate-500 uppercase font-bold">Agent</p>
<p id="modalAgent" class="text-base font-black text-purple-400 mt-0.5">Jett</p> <p id="modalAgent" class="text-base font-black text-purple-400 mt-0.5">--</p>
</div> </div>
<div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center"> <div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center">
<p class="text-[10px] text-slate-500 uppercase font-bold">K / D / A</p> <p class="text-[10px] text-slate-500 uppercase font-bold">K / D / A</p>
@ -115,7 +115,7 @@
</div> </div>
<div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center"> <div class="bg-slate-900/60 border border-slate-800 p-3 rounded-xl text-center">
<p class="text-[10px] text-slate-500 uppercase font-bold">Kampfwert (ACS)</p> <p class="text-[10px] text-slate-500 uppercase font-bold">Kampfwert (ACS)</p>
<p id="modalACS" class="text-base font-black text-purple-300 mt-0.5">240</p> <p id="modalACS" class="text-base font-black text-purple-300 mt-0.5">--</p>
</div> </div>
</div> </div>
@ -130,11 +130,11 @@
<span id="modalHSRate" class="font-bold">0%</span> <span id="modalHSRate" class="font-bold">0%</span>
</div> </div>
<div class="w-full bg-slate-900 h-2 rounded-full overflow-hidden"> <div class="w-full bg-slate-900 h-2 rounded-full overflow-hidden">
<div id="modalHSBar" class="bg-purple-500 h-full" style="width: 0%"></div> <div id="modalHSBar" class="bg-purple-500 h-full transition-all duration-500" style="width: 0%"></div>
</div> </div>
</div> </div>
<p class="text-[11px] text-slate-500 mt-2"> <p class="text-[11px] text-slate-500 mt-2">
Treffer: <span id="modalHitsCount" class="text-slate-300">0 Head / 0 Body / 0 Legs</span> Treffer-Verteilung: <br><span id="modalHitsCount" class="text-slate-300">0 Head / 0 Body / 0 Legs</span>
</p> </p>
</div> </div>
</div> </div>
@ -146,7 +146,7 @@
<span id="modalFB" class="font-bold text-amber-400">0</span> <span id="modalFB" class="font-bold text-amber-400">0</span>
</div> </div>
<div class="flex justify-between text-xs border-b border-slate-900 pb-1.5"> <div class="flex justify-between text-xs border-b border-slate-900 pb-1.5">
<span class="text-slate-400">Wirtschaft (Ø Ausgaben/Runde):</span> <span class="text-slate-400">Ø Budgetausgabe/Runde:</span>
<span id="modalEcon" class="font-bold text-slate-200">0 ¥</span> <span id="modalEcon" class="font-bold text-slate-200">0 ¥</span>
</div> </div>
<div class="flex justify-between text-xs border-b border-slate-900 pb-1.5"> <div class="flex justify-between text-xs border-b border-slate-900 pb-1.5">
@ -154,7 +154,7 @@
<span id="modalBadges" class="font-bold text-purple-400">Keine</span> <span id="modalBadges" class="font-bold text-purple-400">Keine</span>
</div> </div>
<div class="flex justify-between text-xs"> <div class="flex justify-between text-xs">
<span class="text-slate-400">Multi-Kills:</span> <span class="text-slate-400">Multi-Kills in Runden:</span>
<span id="modalMulti" class="font-bold text-slate-300">3K: 0 | 4K: 0 | 5K: 0</span> <span id="modalMulti" class="font-bold text-slate-300">3K: 0 | 4K: 0 | 5K: 0</span>
</div> </div>
</div> </div>
@ -164,14 +164,14 @@
</div> </div>
<script> <script>
let currentMatchesCache = []; // Hält die Match-Metadaten lokal für das Anklicken vor let currentMatchesCache = []; // Globaler Cache für Klick-Events auf Matches
function openMatchReport(index) { function openMatchReport(index) {
const match = currentMatchesCache[index]; const match = currentMatchesCache[index];
if (!match) return; if (!match) return;
document.getElementById('modalMapName').innerText = match.map; document.getElementById('modalMapName').innerText = match.map;
document.getElementById('modalGameMeta').innerText = `${match.mode} • ${match.details.game_length} • ${match.details.server}`; document.getElementById('modalGameMeta').innerText = `${match.mode} • Spieldauer: ${match.details.game_length} • Region: ${match.details.server}`;
const resEl = document.getElementById('modalResult'); const resEl = document.getElementById('modalResult');
resEl.innerText = match.has_won ? "WIN" : "LOSS"; resEl.innerText = match.has_won ? "WIN" : "LOSS";
@ -181,12 +181,12 @@
document.getElementById('modalKDA').innerText = `${match.kills} / ${match.deaths} / ${match.assists}`; document.getElementById('modalKDA').innerText = `${match.kills} / ${match.deaths} / ${match.assists}`;
document.getElementById('modalACS').innerText = match.details.combat_score; document.getElementById('modalACS').innerText = match.details.combat_score;
// Hit-Zonen binden // Hitbox-Daten binden
document.getElementById('modalHSRate').innerText = match.details.hs_rate; document.getElementById('modalHSRate').innerText = match.details.hs_rate;
document.getElementById('modalHSBar').style.width = match.details.hs_rate; document.getElementById('modalHSBar').style.width = match.details.hs_rate;
document.getElementById('modalHitsCount').innerText = `${match.details.headshots} Head / ${match.details.bodyshots} Body / ${match.details.legshots} Legs`; document.getElementById('modalHitsCount').innerText = `${match.details.headshots} Head / ${match.details.bodyshots} Body / ${match.details.legshots} Legs`;
// Gefechtsmetriken // Gefechtsmetriken binden
document.getElementById('modalFB').innerText = match.details.first_bloods; document.getElementById('modalFB').innerText = match.details.first_bloods;
document.getElementById('modalEcon').innerText = match.details.avg_spent; document.getElementById('modalEcon').innerText = match.details.avg_spent;
document.getElementById('modalBadges').innerText = match.details.is_team_mvp ? "⭐ TEAM MVP" : "Keine Auszeichnung"; document.getElementById('modalBadges').innerText = match.details.is_team_mvp ? "⭐ TEAM MVP" : "Keine Auszeichnung";
@ -203,6 +203,12 @@
modal.classList.remove('flex'); modal.classList.remove('flex');
} }
function closeMatchModalOutside(event) {
if (event.target === document.getElementById('matchModal')) {
closeMatchModal();
}
}
document.getElementById('searchForm').addEventListener('submit', async function(e) { document.getElementById('searchForm').addEventListener('submit', async function(e) {
e.preventDefault(); e.preventDefault();
@ -218,11 +224,12 @@
btn.disabled = true; btn.disabled = true;
try { try {
// Hier lag der Fehler: ?mode=competitive hat die restlichen Argumente wie size überschrieben. Jetzt sauber gelöst.
const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}?mode=competitive`); const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}?mode=competitive`);
const data = await response.json(); const data = await response.json();
if (data.status === "success") { if (data.status === "success") {
currentMatchesCache = data.matches; // Cache befüllen für Klick-Aktionen currentMatchesCache = data.matches; // Daten sicher cachen
document.getElementById('displayPlayerName').innerText = data.account.name; document.getElementById('displayPlayerName').innerText = data.account.name;
document.getElementById('displayPlayerTag').innerText = `#${data.account.tag.toUpperCase()}`; document.getElementById('displayPlayerTag').innerText = `#${data.account.tag.toUpperCase()}`;
@ -258,7 +265,7 @@
historyContainer.innerHTML = ''; historyContainer.innerHTML = '';
if (data.matches.length === 0) { if (data.matches.length === 0) {
historyContainer.innerHTML = '<p class="text-xs text-slate-500 p-4 text-center">Keine aktuellen Competitive Matches gefunden.</p>'; historyContainer.innerHTML = '<p class="text-xs text-slate-500 p-4 text-center">Keine aktuellen Matches gefunden.</p>';
} else { } else {
data.matches.forEach((match, index) => { data.matches.forEach((match, index) => {
const borderColor = match.has_won ? 'border-emerald-500' : 'border-rose-500'; const borderColor = match.has_won ? 'border-emerald-500' : 'border-rose-500';
@ -273,9 +280,9 @@
rrClass = "text-rose-400 bg-rose-950/40 border border-rose-900/30"; rrClass = "text-rose-400 bg-rose-950/40 border border-rose-900/30";
} }
// Das div hat nun "onclick='openMatchReport(${index})'" und ist als Zeiger-Cursor markiert // Jedes generierte Match-Item ruft per onclick mit seinem Index openMatchReport() auf
const matchHTML = ` const matchHTML = `
<div onclick="openMatchReport(${index})" class="bg-slate-900/60 border-l-4 ${borderColor} rounded-r-xl p-3.5 flex justify-between items-center text-xs sm:text-sm transition hover:bg-purple-950/20 hover:border-purple-500 cursor-pointer"> <div onclick="openMatchReport(${index})" class="bg-slate-900/60 border-l-4 ${borderColor} rounded-r-xl p-3.5 flex justify-between items-center text-xs sm:text-sm transition hover:bg-purple-950/20 hover:border-purple-500 cursor-pointer select-none">
<div> <div>
<span class="font-black ${textColor} tracking-wider mr-2">${match.has_won ? 'WIN' : 'LOSS'}</span> <span class="font-black ${textColor} tracking-wider mr-2">${match.has_won ? 'WIN' : 'LOSS'}</span>
<span class="font-bold text-slate-200">${match.score}</span> <span class="font-bold text-slate-200">${match.score}</span>
@ -321,7 +328,7 @@
} }
} catch (error) { } catch (error) {
alert("API-Fehler oder Verbindungsabbruch."); alert("API-Fehler oder Verbindungsabbruch.");
} finaly { } finally {
btn.innerText = "Analyse"; btn.innerText = "Analyse";
btn.disabled = false; btn.disabled = false;
} }

View file

@ -1558,7 +1558,7 @@ def get_valorant_stats(name, tag):
mmr_data = mmr_res.json().get('data', {}) if mmr_res.status_code == 200 else {} mmr_data = mmr_res.json().get('data', {}) if mmr_res.status_code == 200 else {}
if not mmr_data: mmr_data = {} if not mmr_data: mmr_data = {}
# 3. RR-Verlauf (MMR History) für 20 Matches abrufen # 3. RR-Verlauf (MMR History) für die Matches abrufen
rr_history = {} rr_history = {}
if match_mode == 'competitive': if match_mode == 'competitive':
history_res = requests.get(f"{VALORANT_API_BASE}/v1/mmr-history/{region}/{name}/{tag}", headers=headers, history_res = requests.get(f"{VALORANT_API_BASE}/v1/mmr-history/{region}/{name}/{tag}", headers=headers,
@ -1569,7 +1569,7 @@ def get_valorant_stats(name, tag):
if m_id: if m_id:
rr_history[m_id] = h_item.get('mmr_change_to_last_game', 0) rr_history[m_id] = h_item.get('mmr_change_to_last_game', 0)
# 4. Match Historie erhöht auf size=20 # 4. Match Historie: Hier wird size=20 explizit erzwungen!
matches_res = requests.get(f"{VALORANT_API_BASE}/v3/matches/{region}/{name}/{tag}?size=20&mode={match_mode}", matches_res = requests.get(f"{VALORANT_API_BASE}/v3/matches/{region}/{name}/{tag}?size=20&mode={match_mode}",
headers=headers, timeout=10) headers=headers, timeout=10)
matches_list = matches_res.json().get('data', []) if matches_res.status_code == 200 else [] matches_list = matches_res.json().get('data', []) if matches_res.status_code == 200 else []
@ -1631,15 +1631,10 @@ def get_valorant_stats(name, tag):
if my_team_color == 'red': if my_team_color == 'red':
score_str = f"{red_rounds} : {blue_rounds}" score_str = f"{red_rounds} : {blue_rounds}"
enemy_rounds = blue_rounds
my_rounds = red_rounds
else: else:
score_str = f"{blue_rounds} : {red_rounds}" score_str = f"{blue_rounds} : {red_rounds}"
enemy_rounds = red_rounds
my_rounds = blue_rounds
# --- ERWEITERTE ANANLYSE FÜR DEN DETAIL REPORT --- # --- ERWEITERTE DETAILANALYSE ---
# Trefferzonen (Head, Body, Legs) aus der API extrahieren
economy = me.get('economy', {}) economy = me.get('economy', {})
spent_spent = economy.get('spent', {}).get('overall', 0) or 0 spent_spent = economy.get('spent', {}).get('overall', 0) or 0
avg_spent = round(spent_spent / rounds_played, 0) if rounds_played > 0 else 0 avg_spent = round(spent_spent / rounds_played, 0) if rounds_played > 0 else 0
@ -1651,28 +1646,27 @@ def get_valorant_stats(name, tag):
total_hits = headshots + bodyshots + legshots total_hits = headshots + bodyshots + legshots
hs_rate = round((headshots / total_hits) * 100, 1) if total_hits > 0 else 0 hs_rate = round((headshots / total_hits) * 100, 1) if total_hits > 0 else 0
# First Bloods & Multi-Kills aus den Runden-Details filtern # First Bloods & Multi-Kills
first_bloods = 0 first_bloods = 0
kills_per_round = {} kills_per_round = {3: 0, 4: 0, 5: 0}
round_data = match.get('rounds', []) or [] round_data = match.get('rounds', []) or []
for r_idx, r in enumerate(round_data): for r in round_data:
player_stats_round = r.get('player_stats', []) or [] player_stats_round = r.get('player_stats', []) or []
# First Blood Check
kill_events = r.get('kill_events', []) or [] kill_events = r.get('kill_events', []) or []
if kill_events: if kill_events:
first_kill = kill_events[0] first_kill = kill_events[0]
if first_kill.get('killer_name', '').lower() == name.lower(): if first_kill.get('killer_name', '').lower() == name.lower():
first_bloods += 1 first_bloods += 1
# Multi-Kills Check
for ps in player_stats_round: for ps in player_stats_round:
if ps.get('player_name', '').lower() == name.lower(): if ps.get('player_name', '').lower() == name.lower():
k_count = ps.get('kills', 0) or 0 k_count = ps.get('kills', 0) or 0
if k_count > 0: if k_count in kills_per_round:
kills_per_round[k_count] = kills_per_round.get(k_count, 0) + 1 kills_per_round[k_count] += 1
# Team MVP bestimmen # Team MVP Check
is_team_mvp = True is_team_mvp = True
for p in players: for p in players:
if p.get('team', '').lower() == my_team_color: if p.get('team', '').lower() == my_team_color:
@ -1694,7 +1688,6 @@ def get_valorant_stats(name, tag):
"score": score_str, "score": score_str,
"has_won": has_won, "has_won": has_won,
"mmr_change": mmr_change, "mmr_change": mmr_change,
# Detail-Statistiken für Klick-Event
"details": { "details": {
"game_length": f"{game_length_min} Min", "game_length": f"{game_length_min} Min",
"server": metadata.get('cluster', 'EU'), "server": metadata.get('cluster', 'EU'),
@ -1707,9 +1700,9 @@ def get_valorant_stats(name, tag):
"first_bloods": first_bloods, "first_bloods": first_bloods,
"is_team_mvp": is_team_mvp, "is_team_mvp": is_team_mvp,
"multikills": { "multikills": {
"3k": kills_per_round.get(3, 0), "3k": kills_per_round[3],
"4k": kills_per_round.get(4, 0), "4k": kills_per_round[4],
"5k": kills_per_round.get(5, 0) "5k": kills_per_round[5]
} }
} }
}) })