AstraOS Ver. 1.33:
All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 12s
All checks were successful
Deploy AstraOS Panel / deploy (push) Successful in 12s
- AyranTracker bearbeitet
This commit is contained in:
parent
fa504cec60
commit
f6bbc601b5
|
|
@ -3,204 +3,206 @@
|
||||||
{% block title %}AstraOS — AyranTracker{% endblock %}
|
{% block title %}AstraOS — AyranTracker{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-7xl mx-auto px-4 py-6">
|
<div class="max-w-6xl mx-auto px-4 py-6">
|
||||||
<header class="mb-10 text-center">
|
|
||||||
<h2 class="text-4xl font-black tracking-widest text-transparent bg-clip-text bg-gradient-to-r from-purple-400 via-violet-200 to-white">
|
|
||||||
AYRAN<span class="text-purple-400">TRACKER</span>
|
|
||||||
</h2>
|
|
||||||
<p class="text-sm text-slate-400 mt-2">Professional Valorant Analytics für AstraOS</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div id="searchSection" class="bg-black/40 border {% if maintenance_active %}border-yellow-500/30{% else %}border-purple-500/20{% endif %} rounded-2xl p-8 backdrop-blur-md shadow-xl max-w-md mx-auto">
|
<div id="searchSection" class="w-full flex flex-col items-center justify-center text-center my-12">
|
||||||
<h3 class="text-lg font-bold text-purple-300 mb-4 text-center">Spieler-Analyse</h3>
|
<div class="mb-8">
|
||||||
<div class="space-y-4">
|
<span class="px-3 py-1 bg-purple-500/10 text-purple-400 border border-purple-500/20 rounded-full text-xs font-semibold uppercase tracking-wider">
|
||||||
|
Live Match Analytics
|
||||||
|
</span>
|
||||||
|
<h1 class="text-3xl md:text-4xl font-black mt-4 mb-2 tracking-tight uppercase">
|
||||||
|
Track Your <span class="text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-violet-300">Performance</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-full max-w-xl bg-slate-950/60 p-5 rounded-xl border border-purple-900/30 shadow-2xl backdrop-blur-md">
|
||||||
|
<form id="searchForm" class="flex flex-col sm:flex-row gap-2">
|
||||||
|
<div class="flex-grow">
|
||||||
|
<input type="text" id="riotInput" placeholder="Riot ID #TAG (z.B. Drax#SWEAT)" required class="w-full bg-slate-900/90 border border-purple-900/40 rounded-lg px-4 py-3 text-sm text-white focus:outline-none focus:border-purple-500 text-center sm:text-left">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="bg-purple-600 hover:bg-purple-700 text-white text-sm font-bold px-6 py-3 rounded-lg uppercase tracking-wider transition whitespace-nowrap">
|
||||||
|
Analyse
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="profileSection" class="w-full hidden">
|
||||||
|
|
||||||
|
<div class="bg-slate-950/60 border border-purple-900/30 rounded-2xl p-6 flex flex-col md:flex-row justify-between items-center gap-6 mb-6 backdrop-blur-md">
|
||||||
|
<div class="flex items-center space-x-4 text-center md:text-left flex-col md:flex-row">
|
||||||
|
<div id="playerCard" class="w-20 h-20 bg-purple-950 border border-purple-500/30 rounded-xl flex items-center justify-center text-2xl font-bold text-purple-300">VAL</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold text-slate-400 mb-2">Riot ID (Name#TAG)</label>
|
<div class="flex items-center justify-center md:justify-start space-x-2">
|
||||||
<input type="text" id="riotIdInput" placeholder="Z.B. TenZ#NA1" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-purple-500 transition text-white">
|
<h2 id="displayPlayerName" class="text-2xl font-black tracking-wide">Player</h2>
|
||||||
|
<span id="displayPlayerTag" class="text-slate-400 font-bold bg-slate-900 px-2 py-0.5 rounded text-xs">#TAG</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-slate-400 mt-1">Account Level: <span id="displayLevel" class="text-purple-400 font-bold">--</span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-slate-900/80 border border-purple-900/40 p-4 rounded-xl flex items-center space-x-4 min-w-[240px]">
|
||||||
|
<div id="rankImageContainer" class="w-12 h-12 flex items-center justify-center bg-purple-950/40 border border-purple-900/20 rounded-lg p-1">
|
||||||
|
<img id="displayRankImage" src="" alt="Rank Icon" class="hidden w-full h-full object-contain">
|
||||||
|
<span id="rankFallbackIcon" class="text-xl">🏆</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs font-semibold text-slate-400 mb-2">Match Typ</label>
|
<p class="text-[10px] text-slate-400 uppercase tracking-widest font-bold">Current Rank</p>
|
||||||
<select id="matchModeSelect" class="w-full bg-slate-950 border border-slate-800 rounded-lg px-4 py-2.5 text-sm focus:outline-none focus:border-purple-500 transition text-white">
|
<p id="displayRank" class="text-lg font-black text-purple-300">Loading...</p>
|
||||||
<option value="competitive" selected>Competitive (Standard)</option>
|
<p id="displayRR" class="text-xs text-slate-500">-- RR</p>
|
||||||
<option value="swiftplay">Swiftplay</option>
|
|
||||||
<option value="unrated">Unrated</option>
|
|
||||||
<option value="spikerush">Spike Rush</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
<button id="searchBtn" class="w-full py-3 rounded-lg font-bold text-sm transition text-white {% if maintenance_active %}bg-yellow-600 hover:bg-yellow-700{% else %}bg-purple-600 hover:bg-purple-700{% endif %}">
|
</div>
|
||||||
Analyse starten
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
|
<div class="bg-slate-950/40 border border-purple-950 p-4 rounded-xl">
|
||||||
|
<p class="text-xs text-slate-400 font-semibold uppercase tracking-wider mb-1">Win Rate</p>
|
||||||
|
<p id="calcWinrate" class="text-2xl font-black text-emerald-400">--</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-950/40 border border-purple-950 p-4 rounded-xl">
|
||||||
|
<p class="text-xs text-slate-400 font-semibold uppercase tracking-wider mb-1">Average K/D Ratio</p>
|
||||||
|
<p id="calcKD" class="text-2xl font-black text-slate-100">--</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-slate-950/40 border border-purple-950 p-4 rounded-xl">
|
||||||
|
<p class="text-xs text-slate-400 font-semibold uppercase tracking-wider mb-1">Best Agent (by KDA)</p>
|
||||||
|
<p id="calcBestAgent" class="text-2xl font-black text-purple-400">--</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
|
||||||
|
<div class="lg:col-span-2 bg-slate-950/40 border border-purple-900/20 rounded-2xl p-5">
|
||||||
|
<h3 id="matchHistoryHeader" class="text-sm font-bold uppercase tracking-wider mb-4 text-slate-300">Recent Matches</h3>
|
||||||
|
<div id="matchHistoryContainer" class="space-y-2.5"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-slate-950/40 border border-purple-900/20 rounded-2xl p-5">
|
||||||
|
<h3 class="text-sm font-bold uppercase tracking-wider mb-4 text-slate-300">Played Agents Performance</h3>
|
||||||
|
<div id="agentContainer" class="space-y-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="window.location.reload()" class="mt-8 text-xs text-purple-400 hover:text-purple-300 uppercase font-bold transition flex items-center space-x-2 mx-auto">
|
||||||
|
<span>← Neue Suche</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="profileSection" class="hidden space-y-8 w-full">
|
|
||||||
<button onclick="location.reload()" class="text-xs font-bold text-slate-400 hover:text-white transition flex items-center gap-1">
|
|
||||||
← Neue Suche
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
|
||||||
<div class="bg-black/40 border border-purple-500/10 p-6 rounded-2xl flex flex-col items-center text-center backdrop-blur-md justify-center">
|
|
||||||
<img id="playerCard" src="" alt="Player Card" class="w-24 h-24 rounded-xl object-cover mb-4 border border-purple-500/30 shadow-md">
|
|
||||||
<h4 id="playerNameTag" class="text-xl font-black text-white truncate max-w-full">Loading...</h4>
|
|
||||||
<p id="playerLevel" class="text-xs text-slate-400 mt-1 bg-slate-950 px-3 py-0.5 rounded-full">Level: --</p>
|
|
||||||
|
|
||||||
<div class="mt-6 w-full border-t border-slate-800/60 pt-4">
|
|
||||||
<p class="text-xs text-slate-400 font-semibold">Aktueller Rang</p>
|
|
||||||
<p id="playerRank" class="text-xl font-bold text-purple-300 mt-1">Unranked</p>
|
|
||||||
<p id="playerRR" class="text-xs text-slate-500 mt-0.5">0 RR</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="lg:col-span-3 bg-black/40 border border-purple-500/10 p-6 rounded-2xl backdrop-blur-md flex flex-col justify-between">
|
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
||||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl text-center">
|
|
||||||
<span class="block text-xs text-slate-500 font-semibold uppercase tracking-wider">Winrate</span>
|
|
||||||
<span id="statWinrate" class="text-3xl font-black text-emerald-400 block mt-2">0%</span>
|
|
||||||
</div>
|
|
||||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl text-center">
|
|
||||||
<span class="block text-xs text-slate-500 font-semibold uppercase tracking-wider">K/D Ratio</span>
|
|
||||||
<span id="statKD" class="text-3xl font-black text-purple-400 block mt-2">0.0</span>
|
|
||||||
</div>
|
|
||||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl text-center">
|
|
||||||
<span class="block text-xs text-slate-500 font-semibold uppercase tracking-wider">Ø Schaden / Rnd.</span>
|
|
||||||
<span id="statADR" class="text-3xl font-black text-blue-400 block mt-2">0.0</span>
|
|
||||||
</div>
|
|
||||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl text-center">
|
|
||||||
<span class="block text-xs text-slate-500 font-semibold uppercase tracking-wider">Analysiert</span>
|
|
||||||
<span id="statMatches" class="text-3xl font-black text-slate-300 block mt-2">0</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-6">
|
|
||||||
<h5 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3">Top Agenten in dieser Session</h5>
|
|
||||||
<div id="agentContainer" class="grid grid-cols-1 sm:grid-cols-2 gap-3"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="space-y-4 pt-4">
|
|
||||||
<h4 class="text-lg font-black text-slate-300 tracking-wider uppercase border-b border-slate-900 pb-2">Match Historie (Letzte 20 Matches)</h4>
|
|
||||||
<div id="matchHistoryContainer" class="space-y-4">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Korrigiertes Mapping mit den exakten UUID-Bildern von valorant-api.com
|
document.getElementById('searchForm').addEventListener('submit', async function(e) {
|
||||||
const mapIcons = {
|
e.preventDefault();
|
||||||
"ascent": "https://media.valorant-api.com/maps/7eaecc1b-4337-bbf6-6ab9-04b8f06b3319/listviewicon.png",
|
|
||||||
"split": "https://media.valorant-api.com/maps/d960549e-485c-e861-8d71-aa9d1aed12a2/listviewicon.png",
|
|
||||||
"fracture": "https://media.valorant-api.com/maps/b5294485-4d60-346e-e89e-00a4c527a405/listviewicon.png",
|
|
||||||
"bind": "https://media.valorant-api.com/maps/2c9d57ec-4431-9c5e-2939-8f9ef6dd5cba/listviewicon.png",
|
|
||||||
"breeze": "https://media.valorant-api.com/maps/2fb9a4fd-47b8-4e7d-a969-74b4046ebd53/listviewicon.png",
|
|
||||||
"district": "https://media.valorant-api.com/maps/690b3ed2-4dff-945b-8223-6da834e30d24/listviewicon.png",
|
|
||||||
"haven": "https://media.valorant-api.com/maps/2bee0dc1-43e0-ab88-66d5-a3a69470566e/listviewicon.png",
|
|
||||||
"icebox": "https://media.valorant-api.com/maps/e2ad5c54-4114-a870-96ae-19b2e030d18e/listviewicon.png",
|
|
||||||
"pearl": "https://media.valorant-api.com/maps/fd2673d9-41d7-4987-ee8b-5f1905d92b74/listviewicon.png",
|
|
||||||
"lotus": "https://media.valorant-api.com/maps/2fe4ed3a-450a-948b-6d6b-e89a78e680a9/listviewicon.png",
|
|
||||||
"sunset": "https://media.valorant-api.com/maps/92dac14f-4ee3-5e18-bfbf-9a96d5d5e7d6/listviewicon.png",
|
|
||||||
"abyss": "https://media.valorant-api.com/maps/224b0a95-4ae7-8a10-5a2b-2895a10a5344/listviewicon.png"
|
|
||||||
};
|
|
||||||
|
|
||||||
document.getElementById('searchBtn').addEventListener('click', async () => {
|
const rawInput = document.getElementById('riotInput').value.trim();
|
||||||
const riotId = document.getElementById('riotIdInput').value.trim();
|
if (!rawInput.includes('#')) {
|
||||||
const mode = document.getElementById('matchModeSelect').value;
|
alert("Bitte gib deine ID im Format 'Name#Tag' an.");
|
||||||
const btn = document.getElementById('searchBtn');
|
|
||||||
|
|
||||||
if (!riotId.includes('#')) {
|
|
||||||
alert("Format ungültig! Bitte nutze Name#TAG (z.B. TenZ#NA1)");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = riotId.split('#');
|
const [name, tag] = rawInput.split('#').map(p => p.trim());
|
||||||
const name = parts[0].trim();
|
if (!name || !tag) {
|
||||||
const tag = parts[1].trim();
|
alert("Name oder Tag unvollständig.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
btn.innerText = "Analysiere...";
|
const btn = this.querySelector('button[type="submit"]');
|
||||||
|
btn.innerText = "Analyzing...";
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}?mode=${mode}`);
|
const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}?mode=competitive`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok && data.status === "success") {
|
if (data.status === "success") {
|
||||||
document.getElementById('playerNameTag').innerText = `${data.account.name}#${data.account.tag}`;
|
// Spieler- & Account-Daten binden
|
||||||
document.getElementById('playerLevel').innerText = `Level: ${data.account.level}`;
|
document.getElementById('displayPlayerName').innerText = data.account.name;
|
||||||
document.getElementById('playerCard').src = data.account.card_url || 'https://media.valorant-api.com/playercards/9fb348bc-41a4-91ad-d131-1ca1efdc3535/smallart.png';
|
document.getElementById('displayPlayerTag').innerText = `#${data.account.tag.toUpperCase()}`;
|
||||||
|
document.getElementById('displayLevel').innerText = data.account.level;
|
||||||
|
document.getElementById('displayRank').innerText = data.rank.current_tier_name;
|
||||||
|
document.getElementById('displayRR').innerText = `${data.rank.ranking_in_tier} RR`;
|
||||||
|
|
||||||
document.getElementById('playerRank').innerText = data.rank.current_tier_name;
|
// Dynamisches Einbinden des Rang-Bildes
|
||||||
document.getElementById('playerRR').innerText = `${data.rank.ranking_in_tier} RR`;
|
const rankImg = document.getElementById('displayRankImage');
|
||||||
|
const rankFallback = document.getElementById('rankFallbackIcon');
|
||||||
|
if (data.rank.rank_image_url) {
|
||||||
|
rankImg.src = data.rank.rank_image_url;
|
||||||
|
rankImg.classList.remove('hidden');
|
||||||
|
rankFallback.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
rankImg.classList.add('hidden');
|
||||||
|
rankFallback.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('statWinrate').innerText = data.calculated_stats.win_rate;
|
if (data.account.card_url) {
|
||||||
document.getElementById('statKD').innerText = data.calculated_stats.kd_ratio;
|
const cardDiv = document.getElementById('playerCard');
|
||||||
document.getElementById('statADR').innerText = data.calculated_stats.avg_damage_round;
|
cardDiv.style.backgroundImage = `url('${data.account.card_url}')`;
|
||||||
document.getElementById('statMatches').innerText = data.calculated_stats.matches_analyzed;
|
cardDiv.style.backgroundSize = 'cover';
|
||||||
|
cardDiv.innerText = '';
|
||||||
|
}
|
||||||
|
|
||||||
const agentContainer = document.getElementById('agentContainer');
|
// Global berechnete Werte füllen
|
||||||
agentContainer.innerHTML = '';
|
document.getElementById('calcWinrate').innerText = data.calculated_stats.win_rate;
|
||||||
data.top_agents.forEach(agent => {
|
document.getElementById('calcKD').innerText = data.calculated_stats.kd_ratio;
|
||||||
agentContainer.insertAdjacentHTML('beforeend', `
|
document.getElementById('calcBestAgent').innerText = data.calculated_stats.best_agent;
|
||||||
<div class="flex justify-between items-center bg-slate-950/60 p-3 rounded-lg border border-slate-900 text-sm">
|
|
||||||
<span class="font-bold text-purple-300">${agent.name}</span>
|
|
||||||
<span class="text-slate-400 bg-slate-950 px-2 py-0.5 rounded font-bold">${agent.count} Match(es)</span>
|
|
||||||
</div>
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Tabellen-Überschrift aktualisieren
|
||||||
|
document.getElementById('matchHistoryHeader').innerText = `Recent Matches (Competitive - Last ${data.calculated_stats.matches_analyzed})`;
|
||||||
|
|
||||||
|
// Matches-Liste befüllen (Hier wurden alle fehlerhaften Assets entfernt)
|
||||||
const historyContainer = document.getElementById('matchHistoryContainer');
|
const historyContainer = document.getElementById('matchHistoryContainer');
|
||||||
historyContainer.innerHTML = '';
|
historyContainer.innerHTML = '';
|
||||||
|
|
||||||
if (!data.matches || data.matches.length === 0) {
|
if (data.matches.length === 0) {
|
||||||
historyContainer.innerHTML = '<p class="text-xs text-slate-500 text-center py-6">Keine Matches gefunden.</p>';
|
historyContainer.innerHTML = '<p class="text-xs text-slate-500 p-4 text-center">Keine aktuellen Competitive Matches gefunden.</p>';
|
||||||
} else {
|
} else {
|
||||||
data.matches.forEach(match => {
|
data.matches.forEach(match => {
|
||||||
const borderClass = match.has_won ? 'border-emerald-500/40 bg-emerald-950/5' : 'border-rose-500/40 bg-rose-950/5';
|
const borderColor = match.has_won ? 'border-emerald-500' : 'border-rose-500';
|
||||||
const badgeBg = match.has_won ? 'bg-emerald-500/20 text-emerald-400' : 'bg-rose-500/20 text-rose-400';
|
const textColor = match.has_won ? 'text-emerald-400' : 'text-rose-400';
|
||||||
const statusLabel = match.has_won ? 'SIEG' : 'NIEDERLAGE';
|
|
||||||
|
|
||||||
// Bestimme das richtige ListView Icon aus dem korrigierten Map-Mapping
|
let rrDisplay = "0 RR", rrClass = "text-slate-400 bg-slate-900 border border-slate-800";
|
||||||
const mapKey = match.map.toLowerCase().trim();
|
|
||||||
const mapIconUrl = mapIcons[mapKey] || 'https://media.valorant-api.com/maps/7eaecc1b-4337-bbf6-6ab9-04b8f06b3319/listviewicon.png';
|
|
||||||
|
|
||||||
let rrDisplay = '';
|
|
||||||
if (match.mmr_change !== null && mode === 'competitive') {
|
|
||||||
if (match.mmr_change > 0) {
|
if (match.mmr_change > 0) {
|
||||||
rrDisplay = `<span class="text-emerald-400 font-black text-sm bg-emerald-950/90 px-3 py-1.5 rounded-lg border border-emerald-500/30 shadow-sm">+${match.mmr_change} RR</span>`;
|
rrDisplay = `+${match.mmr_change} RR`;
|
||||||
|
rrClass = "text-emerald-400 bg-emerald-950/40 border border-emerald-900/30";
|
||||||
} else if (match.mmr_change < 0) {
|
} else if (match.mmr_change < 0) {
|
||||||
rrDisplay = `<span class="text-rose-400 font-black text-sm bg-rose-950/90 px-3 py-1.5 rounded-lg border border-rose-500/30 shadow-sm">${match.mmr_change} RR</span>`;
|
rrDisplay = `${match.mmr_change} RR`;
|
||||||
} else {
|
rrClass = "text-rose-400 bg-rose-950/40 border border-rose-900/30";
|
||||||
rrDisplay = `<span class="text-slate-400 font-black text-sm bg-slate-950 px-3 py-1.5 rounded-lg border border-slate-900 shadow-sm">0 RR</span>`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
historyContainer.insertAdjacentHTML('beforeend', `
|
const matchHTML = `
|
||||||
<div class="border rounded-2xl p-5 flex flex-col md:flex-row justify-between items-center transition duration-200 hover:bg-slate-900/20 ${borderClass}" style="min-height: 100px;">
|
<div 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-slate-900/90">
|
||||||
|
|
||||||
<div class="flex items-center gap-5 w-full md:w-auto">
|
|
||||||
<img src="${mapIconUrl}" alt="${match.map}" class="w-24 h-14 rounded-xl object-cover border border-slate-800 shadow-md hidden sm:block">
|
|
||||||
|
|
||||||
<img src="${match.agent_img}" alt="${match.agent}" class="w-14 h-14 rounded-xl border border-slate-800 bg-slate-950 object-cover shadow-inner">
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-3">
|
<span class="font-black ${textColor} tracking-wider mr-2">${match.has_won ? 'WIN' : 'LOSS'}</span>
|
||||||
<span class="text-base font-black text-white">${match.agent}</span>
|
<span class="font-bold text-slate-200">${match.score}</span>
|
||||||
<span class="text-xs px-2 py-0.5 rounded-md font-black tracking-wider ${badgeBg}">${statusLabel}</span>
|
<span class="text-slate-400 text-xs block mt-0.5">${match.map} • <span class="text-purple-300">${match.agent}</span></span>
|
||||||
</div>
|
</div>
|
||||||
<span class="block text-xs text-slate-400 font-semibold mt-1">Map: <span class="text-white">${match.map}</span> • <span class="capitalize text-purple-400 font-bold">${match.mode}</span></span>
|
<div class="text-center">
|
||||||
|
<p class="text-[10px] text-slate-500 uppercase tracking-wider">K / D / A</p>
|
||||||
|
<p class="font-bold text-slate-200">${match.kills} / <span class="text-rose-400/80">${match.deaths}</span> / ${match.assists}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="text-right flex flex-col items-end justify-center">
|
||||||
|
<span class="font-semibold text-xs px-2.5 py-1 rounded-md ${rrClass}">
|
||||||
<div class="flex justify-between md:justify-end items-center w-full md:w-auto mt-4 md:mt-0 gap-8 border-t md:border-t-0 border-slate-900 pt-3 md:pt-0">
|
|
||||||
<div class="text-left md:text-right space-y-0.5">
|
|
||||||
<span class="block text-sm font-mono font-black text-slate-200 tracking-wide">KDA: ${match.kills} / ${match.deaths} / ${match.assists}</span>
|
|
||||||
<span class="block text-xs text-slate-400">Ergebnis: <span class="font-bold text-slate-300 text-sm">${match.score}</span></span>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center min-w-[90px] justify-end">
|
|
||||||
${rrDisplay}
|
${rrDisplay}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
`;
|
||||||
|
historyContainer.insertAdjacentHTML('beforeend', matchHTML);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alle gespielten Agenten ausgeben
|
||||||
|
const agentContainer = document.getElementById('agentContainer');
|
||||||
|
agentContainer.innerHTML = '';
|
||||||
|
if (data.all_agents.length === 0) {
|
||||||
|
agentContainer.innerHTML = '<p class="text-xs text-slate-500 text-center py-2">Keine Agenten-Daten.</p>';
|
||||||
|
} else {
|
||||||
|
data.all_agents.forEach(agent => {
|
||||||
|
agentContainer.insertAdjacentHTML('beforeend', `
|
||||||
|
<div class="bg-slate-900/50 p-3 rounded-lg border border-purple-950/40 flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<p class="text-xs font-bold text-purple-300">${agent.name}</p>
|
||||||
|
<p class="text-[10px] text-slate-500 mt-0.5">KDA Ø: <span class="text-slate-300 font-mono">${agent.kda}</span></p>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-semibold text-slate-400 bg-slate-950 px-2 py-1 rounded">${agent.count}x gespielt</span>
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
@ -209,12 +211,12 @@
|
||||||
document.getElementById('searchSection').classList.add('hidden');
|
document.getElementById('searchSection').classList.add('hidden');
|
||||||
document.getElementById('profileSection').classList.remove('hidden');
|
document.getElementById('profileSection').classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
alert("Fehler: " + (data.message || "Spieler konnte nicht geladen werden. Checke Name, Tag und Modus."));
|
alert("Fehler: " + data.message);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Verbindungsabbruch oder API temporär offline.");
|
alert("API-Fehler oder Verbindungsabbruch.");
|
||||||
} finally {
|
} finally {
|
||||||
btn.innerText = "Analyse starten";
|
btn.innerText = "Analyse";
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1530,6 +1530,7 @@ VALORANT_API_BASE = "https://api.henrikdev.xyz/valorant"
|
||||||
@login_required
|
@login_required
|
||||||
@permission_required('view_ayrantracker')
|
@permission_required('view_ayrantracker')
|
||||||
def ayrantracker():
|
def ayrantracker():
|
||||||
|
# Übergibt das Template im AstraOS-Design
|
||||||
return render_template('ayrantracker.html', active_page='ayrantracker')
|
return render_template('ayrantracker.html', active_page='ayrantracker')
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1553,11 +1554,12 @@ def get_valorant_stats(name, tag):
|
||||||
acc_data = acc_res.json().get('data', {}) or {}
|
acc_data = acc_res.json().get('data', {}) or {}
|
||||||
region = acc_data.get("region", "eu")
|
region = acc_data.get("region", "eu")
|
||||||
|
|
||||||
# 2. Aktueller Rang (MMR v1)
|
# 2. Aktueller Rang (MMR v1) mit Bild-Pfad Erweiterung
|
||||||
mmr_res = requests.get(f"{VALORANT_API_BASE}/v1/mmr/{region}/{name}/{tag}", headers=headers, timeout=10)
|
mmr_res = requests.get(f"{VALORANT_API_BASE}/v1/mmr/{region}/{name}/{tag}", headers=headers, timeout=10)
|
||||||
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 = {}
|
||||||
|
|
||||||
# 3. RR-Verlauf (MMR History) abrufen
|
# 3. RR-Verlauf (MMR History) für 20 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,
|
||||||
|
|
@ -1568,7 +1570,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 für mehr Übersicht
|
# 4. Match Historie auf size=20 angefordert
|
||||||
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 []
|
||||||
|
|
@ -1578,36 +1580,46 @@ def get_valorant_stats(name, tag):
|
||||||
total_kills = 0
|
total_kills = 0
|
||||||
total_deaths = 0
|
total_deaths = 0
|
||||||
total_assists = 0
|
total_assists = 0
|
||||||
total_damage = 0
|
|
||||||
total_rounds = 0
|
# Struktur für die Erfassung ALLER Agenten-Stats (für KDA)
|
||||||
agent_counts = {}
|
agent_stats = {}
|
||||||
processed_matches = []
|
processed_matches = []
|
||||||
|
|
||||||
for match in matches_list:
|
for match in matches_list:
|
||||||
|
if not match: continue
|
||||||
metadata = match.get('metadata', {})
|
metadata = match.get('metadata', {})
|
||||||
match_id = metadata.get('matchid')
|
match_id = metadata.get('matchid')
|
||||||
players = match.get('players', {}).get('all_players', [])
|
players = match.get('players', {}).get('all_players', [])
|
||||||
|
|
||||||
|
# Fehlertoleranter Spielerabgleich
|
||||||
me = next((p for p in players if
|
me = next((p for p in players if
|
||||||
p.get('name', '').lower() == name.lower() and p.get('tag', '').lower() == tag.lower()), None)
|
p.get('name', '').lower() == name.lower() and p.get('tag', '').lower() == tag.lower()), None)
|
||||||
if not me:
|
if not me:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
match_count += 1
|
match_count += 1
|
||||||
agent_name = me.get('character', 'Unknown')
|
agent_name = me.get('character', 'Unknown') or 'Unknown'
|
||||||
agent_counts[agent_name] = agent_counts.get(agent_name, 0) + 1
|
|
||||||
|
|
||||||
stats = me.get('stats', {})
|
stats = me.get('stats', {})
|
||||||
m_kills = stats.get('kills', 0)
|
m_kills = stats.get('kills', 0) or 0
|
||||||
m_deaths = stats.get('deaths', 0)
|
m_deaths = stats.get('deaths', 0) or 0
|
||||||
m_assists = stats.get('assists', 0)
|
m_assists = stats.get('assists', 0) or 0
|
||||||
|
|
||||||
total_kills += m_kills
|
total_kills += m_kills
|
||||||
total_deaths += m_deaths
|
total_deaths += m_deaths
|
||||||
total_assists += m_assists
|
total_assists += m_assists
|
||||||
|
|
||||||
|
# Spezifische Werte für diesen Agenten sammeln
|
||||||
|
if agent_name not in agent_stats:
|
||||||
|
agent_stats[agent_name] = {"count": 0, "kills": 0, "deaths": 0, "assists": 0}
|
||||||
|
|
||||||
|
agent_stats[agent_name]["count"] += 1
|
||||||
|
agent_stats[agent_name]["kills"] += m_kills
|
||||||
|
agent_stats[agent_name]["deaths"] += m_deaths
|
||||||
|
agent_stats[agent_name]["assists"] += m_assists
|
||||||
|
|
||||||
teams = match.get('teams', {})
|
teams = match.get('teams', {})
|
||||||
my_team_color = me.get('team', '').lower()
|
my_team_color = (me.get('team', '') or '').lower()
|
||||||
my_team_data = teams.get(my_team_color, {})
|
my_team_data = teams.get(my_team_color, {})
|
||||||
|
|
||||||
has_won = my_team_data.get('has_won') is True
|
has_won = my_team_data.get('has_won') is True
|
||||||
|
|
@ -1616,23 +1628,12 @@ def get_valorant_stats(name, tag):
|
||||||
|
|
||||||
red_rounds = teams.get('red', {}).get('rounds_won', 0) or 0
|
red_rounds = teams.get('red', {}).get('rounds_won', 0) or 0
|
||||||
blue_rounds = teams.get('blue', {}).get('rounds_won', 0) or 0
|
blue_rounds = teams.get('blue', {}).get('rounds_won', 0) or 0
|
||||||
rounds_played = red_rounds + blue_rounds
|
|
||||||
total_rounds += rounds_played
|
|
||||||
|
|
||||||
if my_team_color == 'red':
|
if my_team_color == 'red':
|
||||||
score_str = f"{red_rounds} : {blue_rounds}"
|
score_str = f"{red_rounds} : {blue_rounds}"
|
||||||
else:
|
else:
|
||||||
score_str = f"{blue_rounds} : {red_rounds}"
|
score_str = f"{blue_rounds} : {red_rounds}"
|
||||||
|
|
||||||
match_damage = 0
|
|
||||||
round_data = match.get('rounds', [])
|
|
||||||
for r in round_data:
|
|
||||||
p_round_stat = next(
|
|
||||||
(ps for ps in r.get('player_stats', []) if ps.get('player_name', '').lower() == name.lower()), None)
|
|
||||||
if p_round_stat:
|
|
||||||
match_damage += p_round_stat.get('damage', 0)
|
|
||||||
total_damage += match_damage
|
|
||||||
|
|
||||||
mmr_change = rr_history.get(match_id, 0) if match_mode == 'competitive' else None
|
mmr_change = rr_history.get(match_id, 0) if match_mode == 'competitive' else None
|
||||||
map_name = metadata.get('map', 'Unknown')
|
map_name = metadata.get('map', 'Unknown')
|
||||||
|
|
||||||
|
|
@ -1640,7 +1641,6 @@ def get_valorant_stats(name, tag):
|
||||||
"map": map_name,
|
"map": map_name,
|
||||||
"mode": metadata.get('mode', 'Unknown'),
|
"mode": metadata.get('mode', 'Unknown'),
|
||||||
"agent": agent_name,
|
"agent": agent_name,
|
||||||
"agent_img": me.get('assets', {}).get('agent', {}).get('small', ''),
|
|
||||||
"kills": m_kills,
|
"kills": m_kills,
|
||||||
"deaths": m_deaths,
|
"deaths": m_deaths,
|
||||||
"assists": m_assists,
|
"assists": m_assists,
|
||||||
|
|
@ -1649,12 +1649,35 @@ def get_valorant_stats(name, tag):
|
||||||
"mmr_change": mmr_change
|
"mmr_change": mmr_change
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Berechnungen für globale Stats
|
||||||
kd_calculated = round(total_kills / total_deaths, 2) if total_deaths > 0 else total_kills
|
kd_calculated = round(total_kills / total_deaths, 2) if total_deaths > 0 else total_kills
|
||||||
winrate_calculated = round((wins / match_count) * 100, 1) if match_count > 0 else 0
|
winrate_calculated = round((wins / match_count) * 100, 1) if match_count > 0 else 0
|
||||||
adr_calculated = round(total_damage / total_rounds, 1) if total_rounds > 0 else 0
|
|
||||||
|
|
||||||
sorted_agents = sorted(agent_counts.items(), key=lambda x: x[1], reverse=True)
|
# Alle Agenten verarbeiten & Best Agent ermitteln (nach KDA)
|
||||||
top_agents = [{"name": a[0], "count": a[1]} for a in sorted_agents[:2]]
|
all_agents = []
|
||||||
|
best_agent_name = "Keiner"
|
||||||
|
highest_agent_kda = -1.0
|
||||||
|
|
||||||
|
for name_key, data in agent_stats.items():
|
||||||
|
a_kills = data["kills"]
|
||||||
|
a_deaths = data["deaths"]
|
||||||
|
a_assists = data["assists"]
|
||||||
|
|
||||||
|
# KDA-Formel: (Kills + Assists) / Deaths
|
||||||
|
a_kda = round((a_kills + a_assists) / a_deaths, 2) if a_deaths > 0 else float(a_kills + a_assists)
|
||||||
|
|
||||||
|
all_agents.append({
|
||||||
|
"name": name_key,
|
||||||
|
"count": data["count"],
|
||||||
|
"kda": a_kda
|
||||||
|
})
|
||||||
|
|
||||||
|
if a_kda > highest_agent_kda:
|
||||||
|
highest_agent_kda = a_kda
|
||||||
|
best_agent_name = name_key
|
||||||
|
|
||||||
|
# Sortiere die Liste nach der Anzahl der Spiele (Häufigkeit)
|
||||||
|
all_agents = sorted(all_agents, key=lambda x: x["count"], reverse=True)
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"status": "success",
|
"status": "success",
|
||||||
|
|
@ -1667,14 +1690,15 @@ def get_valorant_stats(name, tag):
|
||||||
"rank": {
|
"rank": {
|
||||||
"current_tier_name": mmr_data.get('currenttierpatched', 'Unranked') or 'Unranked',
|
"current_tier_name": mmr_data.get('currenttierpatched', 'Unranked') or 'Unranked',
|
||||||
"ranking_in_tier": mmr_data.get('ranking_in_tier', 0) or 0,
|
"ranking_in_tier": mmr_data.get('ranking_in_tier', 0) or 0,
|
||||||
|
"rank_image_url": mmr_data.get('images', {}).get('small') if mmr_data.get('images') else None
|
||||||
},
|
},
|
||||||
"calculated_stats": {
|
"calculated_stats": {
|
||||||
"win_rate": f"{winrate_calculated}%",
|
"win_rate": f"{winrate_calculated}%",
|
||||||
"kd_ratio": str(kd_calculated),
|
"kd_ratio": str(kd_calculated),
|
||||||
"avg_damage_round": str(adr_calculated),
|
"best_agent": best_agent_name,
|
||||||
"matches_analyzed": match_count
|
"matches_analyzed": match_count
|
||||||
},
|
},
|
||||||
"top_agents": top_agents,
|
"all_agents": all_agents,
|
||||||
"matches": processed_matches
|
"matches": processed_matches
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue