AstraOS Ver. 1.28:
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 hinzugefügt
This commit is contained in:
parent
5455ec17d0
commit
d9bdb3d527
|
|
@ -5,15 +5,15 @@ SSH_USER='astraos'
|
|||
SSH_PASS='iwkms@1812:('
|
||||
VPNAPI_KEY='015ada9472664329affb2f26d7036d47'
|
||||
|
||||
# Discord OAuth2 Credentials
|
||||
DISCORD_CLIENT_ID='1395018660267036712'
|
||||
DISCORD_CLIENT_SECRET='QVGmwtTDsuE8mh2YlBMqeHndeRRKW9-a'
|
||||
DISCORD_BOT_TOKEN='MTM5NTAxODY2MDI2NzAzNjcxMg.GJE-k4.W0o09yJZfG3onzyzHLbXu4vBrKPXFS1IVuzhMA'
|
||||
DISCORD_REDIRECT_URI='https://astra-os.de/callback/discord'
|
||||
|
||||
# Impressum (vom Benutzer bereitgestellt)
|
||||
IMPRESSUM_NAME='Elias Geworski'
|
||||
IMPRESSUM_ADDRESS='Taiostraße 4b, 90562 Heroldsberg'
|
||||
IMPRESSUM_EMAIL='elias.gew@hotmail.com'
|
||||
IMPRESSUM_PHONE=''
|
||||
IMPRESSUM_COMPANY_REG=''
|
||||
|
||||
VALORANT_API_KEY=HDEV-1dbf4057-a238-4e2f-805d-f9b268b101e2
|
||||
|
|
|
|||
140
AstraOS/Frontend/HTML/ayrantracker.html
Normal file
140
AstraOS/Frontend/HTML/ayrantracker.html
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{% extends "sidebar.html" %}
|
||||
|
||||
{% block title %}AstraOS — AyranTracker{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="max-w-4xl 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">
|
||||
<h3 class="text-lg font-bold text-purple-300 mb-4 text-center">Spieler-Analyse</h3>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-2">Riot ID Name</label>
|
||||
<input type="text" id="riotName" placeholder="Z.B. TenZ" 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">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-semibold text-slate-400 mb-2">Tag</label>
|
||||
<input type="text" id="riotTag" placeholder="Z.B. 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">
|
||||
</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 %}">
|
||||
Analyse starten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="profileSection" class="hidden space-y-6">
|
||||
<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 md:grid-cols-3 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">
|
||||
<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">Loading...</h4>
|
||||
<p id="playerLevel" class="text-xs text-slate-400 mt-1 bg-slate-950 px-2 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-lg font-bold text-purple-300 mt-1">Unranked</p>
|
||||
<p id="playerRR" class="text-xs text-slate-500">0 RR</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-black/40 border border-purple-500/10 p-6 rounded-2xl backdrop-blur-md md:col-span-2 space-y-6">
|
||||
<h4 class="text-sm font-bold text-slate-400 tracking-wider uppercase">Berechnete Performance (Letzte Matches)</h4>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl">
|
||||
<span class="block text-xs text-slate-500 font-medium">Winrate</span>
|
||||
<span id="statWinrate" class="text-2xl font-black text-emerald-400">0%</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl">
|
||||
<span class="block text-xs text-slate-500 font-medium">K/D Ratio</span>
|
||||
<span id="statKD" class="text-2xl font-black text-purple-400">0.0</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl">
|
||||
<span class="block text-xs text-slate-500 font-medium">Ø Schaden / Runde (ADR)</span>
|
||||
<span id="statADR" class="text-2xl font-black text-blue-400">0.0</span>
|
||||
</div>
|
||||
<div class="bg-slate-950/50 border border-slate-900 p-4 rounded-xl">
|
||||
<span class="block text-xs text-slate-500 font-medium">Analysierte Matches</span>
|
||||
<span id="statMatches" class="text-2xl font-black text-slate-300">0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h5 class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-3">Top Agenten</h5>
|
||||
<div id="agentContainer" class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('searchBtn').addEventListener('click', async () => {
|
||||
const name = document.getElementById('riotName').value.trim();
|
||||
const tag = document.getElementById('riotTag').value.trim();
|
||||
const btn = document.getElementById('searchBtn');
|
||||
|
||||
if(!name || !tag) {
|
||||
alert("Bitte fülle Name und Tag aus!");
|
||||
return;
|
||||
}
|
||||
|
||||
btn.innerText = "Analysiere...";
|
||||
btn.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/profile/${encodeURIComponent(name)}/${encodeURIComponent(tag)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if(response.ok && data.status === "success") {
|
||||
document.getElementById('playerNameTag').innerText = `${data.account.name}#${data.account.tag}`;
|
||||
document.getElementById('playerLevel').innerText = `Level: ${data.account.level}`;
|
||||
document.getElementById('playerCard').src = data.account.card_url || 'https://media.valorant-api.com/playercards/9fb348bc-41a4-91ad-d131-1ca1efdc3535/smallart.png';
|
||||
|
||||
document.getElementById('playerRank').innerText = data.rank.current_tier_name;
|
||||
document.getElementById('playerRR').innerText = `${data.rank.ranking_in_tier} RR`;
|
||||
|
||||
document.getElementById('statWinrate').innerText = data.calculated_stats.win_rate;
|
||||
document.getElementById('statKD').innerText = data.calculated_stats.kd_ratio;
|
||||
document.getElementById('statADR').innerText = data.calculated_stats.avg_damage_round;
|
||||
document.getElementById('statMatches').innerText = data.calculated_stats.matches_analyzed;
|
||||
|
||||
const agentContainer = document.getElementById('agentContainer');
|
||||
agentContainer.innerHTML = '';
|
||||
if(data.top_agents.length === 0) {
|
||||
agentContainer.innerHTML = '<p class="text-xs text-slate-500 py-2">Keine Agenten-Daten verfügbar.</p>';
|
||||
} else {
|
||||
data.top_agents.forEach(agent => {
|
||||
agentContainer.insertAdjacentHTML('beforeend', `
|
||||
<div class="flex justify-between items-center bg-slate-900/50 p-3 rounded-lg border border-purple-950/40">
|
||||
<span class="text-xs font-bold text-purple-300">${agent.name}</span>
|
||||
<span class="text-xs font-semibold text-slate-400 bg-slate-950 px-2 py-0.5 rounded">${agent.count}x gespielt</span>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('searchSection').classList.add('hidden');
|
||||
document.getElementById('profileSection').classList.remove('hidden');
|
||||
} else {
|
||||
alert("Fehler: " + (data.message || "Spieler nicht gefunden"));
|
||||
}
|
||||
} catch (error) {
|
||||
alert("Verbindungsfehler zur API.");
|
||||
} finally {
|
||||
btn.innerText = "Analyse starten";
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -272,6 +272,16 @@
|
|||
🚚 ETS2 DLCs
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if 'view_ayrantracker' in session.get('permissions', []) %}
|
||||
<a href="{{ url_for('ayrantracker') }}" class="px-3 py-2 rounded-md transition flex-shrink-0
|
||||
{% if active_page == 'ayrantracker' %}
|
||||
{% if maintenance_active %}bg-yellow-500/10 text-yellow-300{% else %}bg-purple-500/10 text-purple-300{% endif %}
|
||||
{% else %}
|
||||
{% if maintenance_active %}hover:bg-yellow-500/10 hover:text-yellow-300{% else %}hover:bg-purple-500/10 hover:text-purple-300{% endif %}
|
||||
{% endif %}">
|
||||
🥛 AyranTracker
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if not private_tools.has_access %}
|
||||
<div class="block px-3 py-1 rounded-md text-gray-500 text-sm italic">
|
||||
Du hast nicht genügend Berechtigungen für diesen Bereich.
|
||||
|
|
|
|||
|
|
@ -91,7 +91,8 @@ try:
|
|||
"instranet",
|
||||
"view_ip_analyzer",
|
||||
"view_age_verification",
|
||||
"manage_age_verification"
|
||||
"manage_age_verification",
|
||||
"view_ayrantracker"
|
||||
]
|
||||
if roles_collection.count_documents({}) == 0:
|
||||
print("WARNUNG: Keine Rollen in der Datenbank gefunden. Erstelle Standard-Rollen...")
|
||||
|
|
@ -1520,6 +1521,117 @@ def update_maintenance():
|
|||
def page_not_found(e):
|
||||
return render_template('404.html'), 404
|
||||
|
||||
VALORANT_API_KEY = os.getenv("VALORANT_API_KEY", "")
|
||||
VALORANT_API_BASE = "https://api.henrikdev.xyz/valorant"
|
||||
|
||||
|
||||
@app.route('/tools/ayrantracker')
|
||||
@login_required
|
||||
@permission_required('view_ayrantracker')
|
||||
def ayrantracker():
|
||||
return render_template('ayrantracker.html', active_page='ayrantracker')
|
||||
|
||||
|
||||
@app.route('/api/v1/profile/<name>/<tag>', methods=['GET'])
|
||||
@login_required
|
||||
@permission_required('view_ayrantracker')
|
||||
def get_valorant_stats(name, tag):
|
||||
# Sicherheitsprüfung, falls du vergessen hast, den Key in die .env einzutragen
|
||||
if not VALORANT_API_KEY:
|
||||
return jsonify({"status": "error", "message": "API-Key Konfiguration fehlt in der .env-Datei."}), 500
|
||||
|
||||
headers = {"Authorization": VALORANT_API_KEY}
|
||||
|
||||
try:
|
||||
acc_res = requests.get(f"{VALORANT_API_BASE}/v1/account/{name}/{tag}?force=true", headers=headers, timeout=10)
|
||||
if acc_res.status_code != 200:
|
||||
return jsonify(
|
||||
{"status": "error", "message": "Spieler nicht gefunden oder API-Limit erreicht."}), acc_res.status_code
|
||||
|
||||
acc_data = acc_res.json().get('data', {}) or {}
|
||||
region = acc_data.get("region", "eu")
|
||||
|
||||
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 {}
|
||||
if not mmr_data: mmr_data = {}
|
||||
|
||||
matches_res = requests.get(f"{VALORANT_API_BASE}/v3/matches/{region}/{name}/{tag}?size=15&mode=competitive",
|
||||
headers=headers, timeout=10)
|
||||
matches_list = matches_res.json().get('data', []) if matches_res.status_code == 200 else []
|
||||
if not matches_list: matches_list = []
|
||||
|
||||
match_count = len(matches_list)
|
||||
wins = 0
|
||||
total_kills = 0
|
||||
total_deaths = 0
|
||||
total_damage = 0
|
||||
total_rounds = 0
|
||||
agent_counts = {}
|
||||
|
||||
for match in matches_list:
|
||||
players = match.get('players', {}).get('all_players', [])
|
||||
me = next((p for p in players if
|
||||
p.get('name', '').lower() == name.lower() and p.get('tag', '').lower() == tag.lower()), None)
|
||||
if not me:
|
||||
continue
|
||||
|
||||
agent_name = me.get('character', 'Unknown')
|
||||
agent_counts[agent_name] = agent_counts.get(agent_name, 0) + 1
|
||||
|
||||
stats = me.get('stats', {})
|
||||
total_kills += stats.get('kills', 0)
|
||||
total_deaths += stats.get('deaths', 0)
|
||||
|
||||
teams = match.get('teams', {})
|
||||
my_team_color = me.get('team', '').lower()
|
||||
my_team_data = teams.get(my_team_color, {})
|
||||
|
||||
if my_team_data.get('has_won') is True:
|
||||
wins += 1
|
||||
|
||||
rounds_played = (teams.get('red', {}).get('rounds_won', 0) or 0) + (
|
||||
teams.get('blue', {}).get('rounds_won', 0) or 0)
|
||||
total_rounds += rounds_played
|
||||
|
||||
round_data = match.get('rounds', [])
|
||||
for r in round_data:
|
||||
player_stats = r.get('player_stats', [])
|
||||
p_round_stat = next((ps for ps in player_stats if ps.get('player_name', '').lower() == name.lower()),
|
||||
None)
|
||||
if p_round_stat:
|
||||
total_damage += p_round_stat.get('damage', 0)
|
||||
|
||||
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
|
||||
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)
|
||||
top_agents = [{"name": a[0], "count": a[1]} for a in sorted_agents[:2]]
|
||||
|
||||
return jsonify({
|
||||
"status": "success",
|
||||
"account": {
|
||||
"name": acc_data.get('name'),
|
||||
"tag": acc_data.get('tag'),
|
||||
"level": acc_data.get('account_level'),
|
||||
"card_url": acc_data.get('card', {}).get('small') if acc_data.get('card') else None
|
||||
},
|
||||
"rank": {
|
||||
"current_tier_name": mmr_data.get('currenttierpatched', 'Unranked') or 'Unranked',
|
||||
"ranking_in_tier": mmr_data.get('ranking_in_tier', 0) or 0,
|
||||
},
|
||||
"calculated_stats": {
|
||||
"win_rate": f"{winrate_calculated}%",
|
||||
"kd_ratio": str(kd_calculated),
|
||||
"avg_damage_round": str(adr_calculated),
|
||||
"matches_analyzed": match_count
|
||||
},
|
||||
"top_agents": top_agents
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"status": "error", "message": f"Interner Fehler: {str(e)}"}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.config['SESSION_COOKIE_SECURE'] = False
|
||||
print("INFO: Starte den Server im Entwicklungsmodus ohne HTTPS.")
|
||||
|
|
|
|||
Loading…
Reference in a new issue