quick fix 2

This commit is contained in:
Bartłomiej Patyk
2025-10-22 19:05:25 +02:00
commit e5e64b6dc8
598 changed files with 300649 additions and 0 deletions

19
webserver/README.md Normal file
View File

@ -0,0 +1,19 @@
Simple Flask backend serving Access DB data for the web UI
Files added:
- app.py - Flask app exposing /api/playcounts and static files under / (serves web/main.html and web/danebota.html)
- web/data.js - fetches /api/playcounts and renders basic stats into the page
- requirements.txt - minimal Python deps
Run (Windows PowerShell):
```powershell
python -m venv .venv; .\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python app.py
```
Notes:
- This project expects the Access ODBC driver to be installed on Windows: "Microsoft Access Database Engine" or full Office with the Access components. If pyodbc fails to connect, install the Access Database Engine redistributable (search Microsoft Download Center for "AccessDatabaseEngine").
- The Access file `playcount.accdb` must exist in the project root (next to `app.py`).
- For production use, run behind a proper WSGI server and secure CORS instead of the permissive header used here for convenience.

271
webserver/app.py Normal file
View File

@ -0,0 +1,271 @@
from flask import Flask, jsonify, send_from_directory, make_response, request
import os
import pyodbc
app = Flask(__name__, static_folder='web', static_url_path='')
def get_access_db_path():
# Database file next to this script
base = os.path.dirname("C:/Users/marek_yrlsweo/Documents/furrystatus/bot/webserver")
return os.path.join(base, 'playcount.accdb')
def read_access_db(max_rows=1000):
db_path = get_access_db_path()
if not os.path.exists(db_path):
return {'error': 'database file not found', 'path': db_path}
conn_str = (
r'Driver={Microsoft Access Driver (*.mdb, *.accdb)};'
f'DBQ={db_path};'
)
try:
conn = pyodbc.connect(conn_str, autocommit=True)
cursor = conn.cursor()
# list user tables
tables = []
for row in cursor.tables():
ttype = getattr(row, 'TABLE_TYPE', None) or row[3]
tname = getattr(row, 'TABLE_NAME', None) or row[2]
if ttype and ttype.upper() == 'TABLE':
# skip system tables
if tname.startswith('MSys'):
continue
tables.append(tname)
data = {}
for table in tables:
try:
cursor.execute(f'SELECT * FROM [{table}]')
cols = [column[0] for column in cursor.description] if cursor.description else []
rows = []
for i, r in enumerate(cursor.fetchmany(max_rows)):
rows.append({cols[j]: r[j] for j in range(len(cols))})
data[table] = rows
except Exception as e:
data[table] = {'error': str(e)}
cursor.close()
conn.close()
return {'tables': tables, 'data': data}
except Exception as e:
return {'error': 'pyodbc connection failed', 'detail': str(e)}
@app.route('/')
def index():
return send_from_directory(app.static_folder, 'main.html')
@app.route('/danebota.html')
def danebota_html():
return send_from_directory(app.static_folder, 'danebota.html')
@app.route('/api/playcounts')
def api_playcounts():
result = read_access_db()
resp = make_response(jsonify(result))
# allow simple fetches from the same host or other origins during development
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
def _coerce_int(v):
try:
if v is None:
return 0
if isinstance(v, (int,)):
return v
# handle Decimal, floats and numeric strings
return int(float(v))
except Exception:
return 0
def _find_key(keys, candidates):
# keys: iterable of strings (actual column names)
# candidates: list of substrings to search for (lowercase)
low_map = {k.lower(): k for k in keys}
for cand in candidates:
for lk, orig in low_map.items():
if cand in lk:
return orig
return None
def compute_summary_from_readdata(read_result):
# read_result expected to have 'data' mapping table->rows
if not read_result or 'data' not in read_result:
return {'error': 'no data available'}
data = read_result['data']
servers = {} # server -> {'total': int, 'tracks': {track: int}}
total_plays = 0
# candidate substrings to detect columns
server_candidates = ['server', 'guild', 'serverid', 'guildid']
track_candidates = ['track', 'song', 'file', 'title', 'name']
playcount_candidates = ['playcount', 'plays', 'count', 'play_count', 'times']
for table, rows in (data.items() if isinstance(data, dict) else []):
if not isinstance(rows, list):
continue
if len(rows) == 0:
continue
# determine column names from first row
keys = list(rows[0].keys())
k_server = _find_key(keys, server_candidates)
k_track = _find_key(keys, track_candidates)
k_play = _find_key(keys, playcount_candidates)
for r in rows:
# fallback values
server_val = r.get(k_server) if k_server else None
track_val = r.get(k_track) if k_track else None
play_val = r.get(k_play) if k_play else None
# If server not present, lump under 'unknown'
server_key = str(server_val) if server_val is not None else 'unknown'
track_key = str(track_val) if track_val is not None else '(unknown track)'
plays = _coerce_int(play_val)
if server_key not in servers:
servers[server_key] = {'total': 0, 'tracks': {}}
servers[server_key]['total'] += plays
servers[server_key]['tracks'][track_key] = servers[server_key]['tracks'].get(track_key, 0) + plays
total_plays += plays
return {
'total_servers': len(servers),
'total_plays': total_plays,
'servers': servers,
}
@app.route('/api/playcounts/summary')
def api_playcounts_summary():
read = read_access_db(max_rows=10000)
if 'error' in read:
resp = make_response(jsonify(read))
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
summary = compute_summary_from_readdata(read)
# Try to read bot_status.json (written by bot.py) to get accurate guild count
try:
status_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bot_status.json')
if os.path.exists(status_path):
import json
with open(status_path, 'r', encoding='utf-8') as f:
bot_status = json.load(f)
# override total_servers if available
if isinstance(bot_status, dict) and 'guild_count' in bot_status:
summary['bot_status'] = bot_status
summary['total_servers'] = bot_status.get('guild_count', summary.get('total_servers', 0))
except Exception:
# non-fatal; continue with original summary
pass
resp = make_response(jsonify(summary))
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
def write_to_db(table_name, column1_name, value1, column2_name, value2):
db_path = get_access_db_path()
if not os.path.exists(db_path):
return {'success': False, 'error': f'Database file not found: {db_path}'}
conn_str = (
r'Driver={Microsoft Access Driver (*.mdb, *.accdb)};'
f'DBQ={db_path};'
)
try:
# Uwaga: Użycie parametrów zapytania (znak ?) jest kluczowe dla
# bezpieczeństwa (ochrona przed SQL injection)
conn = pyodbc.connect(conn_str)
cursor = conn.cursor()
# Budowanie zapytania INSERT
# W Access należy użyć nawiasów kwadratowych dla nazw kolumn,
# szczególnie jeśli zawierają spacje lub znaki specjalne
sql_query = f"INSERT INTO [{table_name}] ([{column1_name}], [{column2_name}]) VALUES (?, ?)"
# Wykonanie zapytania z danymi
cursor.execute(sql_query, value1, value2)
# Zatwierdzenie zmian
conn.commit()
# Zamknięcie połączenia
cursor.close()
conn.close()
return {'success': True, 'message': 'Data inserted successfully.'}
except Exception as e:
return {'success': False, 'error': f'pyodbc write failed: {str(e)}'}
# ----------------------------------------------------------------------
# ... (funkcja read_access_db - bez zmian) ...
# ... (trasy index, danebota_html, api_playcounts - bez zmian) ...
# ... (funkcje _coerce_int, _find_key, compute_summary_from_readdata - bez zmian) ...
# ... (trasa api_playcounts_summary - bez zmian) ...
# ----------------------------------------------------------------------
# NOWA TRASA: Strona HTML dla sugestii
# ----------------------------------------------------------------------
@app.route('/sugestie.html')
def sugestie_html():
# Zakładając, że plik sugestie.html znajduje się w folderze 'web'
return send_from_directory(app.static_folder, 'sugestie.html')
# ----------------------------------------------------------------------
# NOWA TRASA API: Odbieranie danych z JavaScript
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
@app.route('/api/sugestie', methods=['POST'])
def api_sugestie():
# Wymagane jest, aby dane były przesyłane metodą POST w formacie JSON
if not request.is_json:
return make_response(jsonify({'success': False, 'error': 'Missing JSON in request'}), 400)
data = request.get_json()
# 1. Zmienna 1 jest nadal obowiązkowa
zmienna1 = data.get('zmienna1')
# 2. Zmienna 2 jest teraz nieobowiązkowa (ustawiamy pusty ciąg, jeśli brak)
zmienna2 = data.get('zmienna2', '') # <-- Zmiana: ustawiamy domyślną wartość
# Sprawdzenie, czy obowiązkowa zmienna1 jest obecna
if zmienna1 is None: # Zmieniono warunek na sprawdzenie tylko zmiennej1
return make_response(jsonify({'success': False, 'error': 'Missing required variable: zmienna1'}), 400)
# UWAGA: Nazwy tabeli i kolumn muszą być zgodne z bazą Access!
TABLE_NAME = "Sugestie" # Zmień na faktyczną nazwę tabeli
COLUMN1_NAME = "NazwaUtworu" # Zmień na faktyczną nazwę kolumny
COLUMN2_NAME = "Link" # Zmień na faktyczną nazwę kolumny
# Wywołanie funkcji zapisu do bazy danych
# Wartość zmienna2 (pusty ciąg lub podana) zostanie przekazana
result = write_to_db(TABLE_NAME, COLUMN1_NAME, zmienna1, COLUMN2_NAME, zmienna2)
resp = make_response(jsonify(result))
resp.headers['Access-Control-Allow-Origin'] = '*'
return resp
if __name__ == '__main__':
# Development server
app.run(host='100.100.23.87', port=5000, debug=False)

25
webserver/bot_status.json Normal file
View File

@ -0,0 +1,25 @@
{
"guild_count": 5,
"guilds": [
{
"id": 1016761768460156968,
"name": "《🔒》Vip Server"
},
{
"id": 1213542239893069824,
"name": "Bot BDSM"
},
{
"id": 1293641444195565580,
"name": "♥←~Sea~Busters~→♥"
},
{
"id": 1303446293485715477,
"name": "server for softfrog emojis 𓆏"
},
{
"id": 1340254368850772038,
"name": "stalking fmbot"
}
]
}

5
webserver/pyvenv.cfg Normal file
View File

@ -0,0 +1,5 @@
home = C:\Program Files\Python313
include-system-site-packages = false
version = 3.13.7
executable = C:\Program Files\Python313\python.exe
command = C:\Program Files\Python313\python.exe -m venv C:\Users\marek_yrlsweo\Documents\furrystatus\bot

3
webserver/startweb.bat Normal file
View File

@ -0,0 +1,3 @@
@echo off
cd "C:\Users\marek_yrlsweo\Documents\furrystatus\bot\webserver"
python app.py

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./webcss/nav.css">
<link rel="stylesheet" href="./webcss/main.css">
<title>Kontakt</title>
</head>
<body>
<nav>
<ul>
<li><a href="main.html">Strona główna</a></li>
<li><a href="danebota.html">Dane Bota</a></li>
<li><a href="sugestie.html">Sugestie</a></li>
<li><a href="https://discord.com/oauth2/authorize?client_id=1379068224947093536">dodaj mojego bota!</a></li>
<li><a href="Kontakt.html">Kontakt</a></li>
</ul>
</nav>
<main>
<h1>Kontakty</h1>
<p>Możesz się ze mną skontaktować na Discordie: softfrog</p>
</main>
</body>
</html>

View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./webcss/nav.css">
<link rel="stylesheet" href="./webcss/bota.css">
<link rel="stylesheet" href="./webcss/main.css">
<!-- load the script at the end of body instead of link in head -->
<title>Danebota</title>
</head>
<body>
<nav>
<ul>
<li><a href="main.html">Strona główna</a></li>
<li><a href="danebota.html">Dane Bota</a></li>
<li><a href="sugestie.html">Sugestie</a></li>
<li><a href="https://discord.com/oauth2/authorize?client_id=1379068224947093536">dodaj mojego bota!</a></li>
<li><a href="Kontakt.html">Kontakt</a></li>
</ul>
</nav>
<main>
<h1>Dane Bota</h1>
<p>Tu znajdziesz informacje o bocie.</p>
<section>
<h2>Statystyki</h2>
<div id="bot-stats">
<p>Liczba serwerów: 56</p>
</div>
</section>
</main>
<script src="./data.js"></script>
</body>
</html>

62
webserver/web/data.js Normal file
View File

@ -0,0 +1,62 @@
// Fetch playcount data from backend API and expose a helper to render it
async function fetchPlaycounts() {
try {
const resp = await fetch('/api/playcounts');
if (!resp.ok) throw new Error('Network response was not ok');
return await resp.json();
} catch (err) {
return { error: err.message };
}
}
// Simple renderer for danebota.html - injects table counts
async function renderStats() {
const out = document.getElementById('bot-stats');
if (!out) return;
out.textContent = 'Ładowanie…';
// prefer the summary endpoint which gives aggregated totals
try {
const resp = await fetch('/api/playcounts/summary');
if (!resp.ok) throw new Error('Network response was not ok');
const summary = await resp.json();
if (summary.error) {
out.textContent = 'Błąd: ' + (summary.detail || summary.error);
return;
}
let html = '';
html += `<p>Liczba serwerów z discord: ${summary.total_servers}</p>`;
html += `<p>Suma wszystkich odtworzeń: ${summary.total_plays}</p>`;
// Render each server and its top 5 tracks
const servers = summary.servers || {};
for (const [server, info] of Object.entries(servers)) {
html += `<section class="server-block"><h3>Serwer: ${server}</h3>`;
html += `<p>Łącznie odtworzeń: ${info.total}</p>`;
const tracks = info.tracks || {};
// sort tracks by plays desc
const sorted = Object.entries(tracks).sort((a, b) => b[1] - a[1]).slice(0, 10000);
if (sorted.length === 0) {
html += `<p>Brak danych o utworach.</p>`;
} else {
html += `<ol>`;
html += `<br>`;
for (const [tname, plays] of sorted) {
html += `<li style="margin-top: 10px;">${tname}${plays} odtworzeń</li>`;
}
html += `</ol>`;
}
html += `</section>`;
}
out.innerHTML = html;
} catch (err) {
out.textContent = 'Błąd sieci: ' + err.message;
}
}
// Auto-run when included from danebota.html
if (typeof window !== 'undefined') {
window.addEventListener('DOMContentLoaded', renderStats);
}

33
webserver/web/main.html Normal file
View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./webcss/nav.css">
<link rel="stylesheet" href="./webcss/main.css">
<title>Główna Strona</title>
</head>
<body>
<nav>
<ul>
<li><a href="main.html">Strona główna</a></li>
<li><a href="danebota.html">Dane Bota</a></li>
<li><a href="sugestie.html">Sugestie</a></li>
<li><a href="https://discord.com/oauth2/authorize?client_id=1379068224947093536">dodaj mojego bota!</a></li>
<li><a href="Kontakt.html">Kontakt</a></li>
</ul>
</nav>
<main>
<h1>Witamy na Stronie Głównej</h1>
<p>To jest główna strona mojego bota.</p>
<br><br>
<section>
<h1>Update</h1>
<h2>General Update #1 19.10.2025</h2>
<p>1. dodano statystyki bota na danebota</p>
<p>2. </p>
<p>3. </p>
</section>
</main>
</body>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./webcss/nav.css">
<link rel="stylesheet" href="./webcss/main.css">
<title>sugestie</title>
</head>
<body>
<nav>
<ul>
<li><a href="main.html">Strona główna</a></li>
<li><a href="danebota.html">Dane Bota</a></li>
<li><a href="sugestie.html">Sugestie</a></li>
<li><a href="https://discord.com/oauth2/authorize?client_id=1379068224947093536">dodaj mojego bota!</a></li>
<li><a href="Kontakt.html">Kontakt</a></li>
</ul>
</nav>
<main>
<h1>Podaj swoją sugestię muzyki</h1>
<div id="dataContainer">
<div class="input-group">
<label for="var1">Nazwa Utworu: (OBOWIĄZKOWY)</label>
<br>
<input type="text" name="sugestia" id="sugestia" style="height: 30px; width: 400px;" placeholder="Twoja sugestia..." required>
</div>
<div class="input-group">
<label for="var2">Link:</label>
<br>
<input type="url" name="link" id="link" style="height: 30px; width: 400px;" placeholder="Link do utworu...">
</div>
<button id="sendButton">Wyślij do Bazy</button>
</div>
<div id="status"></div>
</main>
<script src="./sugestie.js"></script>
</body>
</html>

60
webserver/web/sugestie.js Normal file
View File

@ -0,0 +1,60 @@
// Zmieniamy 'sugestionForm' i 'submit' na 'sendButton' i 'click'
document.getElementById('sendButton').addEventListener('click', async function(event) {
// Nie musimy już wywoływać event.preventDefault(), ponieważ to nie jest submit formularza
const statusDiv = document.getElementById('status');
statusDiv.textContent = 'Wysyłanie danych...';
statusDiv.style.color = 'orange';
// 1. Pobranie zmiennych z pól wejściowych
const zmienna1 = document.getElementById('sugestia').value;
const zmienna2 = document.getElementById('link').value;
// Weryfikacja obowiązkowej zmiennej 1
if (!zmienna1.trim()) {
statusDiv.textContent = 'BŁĄD: Zmienna 1 jest obowiązkowa!';
statusDiv.style.color = 'red';
return; // Zatrzymuje wysyłanie
}
// 2. Przygotowanie danych jako obiekt JavaScript (JSON)
const dataToSend = {
zmienna1: zmienna1,
zmienna2: zmienna2
};
// 3. Wysłanie danych do API za pomocą Fetch API
try {
const response = await fetch('/api/sugestie', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(dataToSend)
});
const result = await response.json();
if (response.ok && result.success) {
// Sukces
statusDiv.textContent = 'Sukces! Twoje dane nigdy nie będą przeczytane';
statusDiv.style.color = 'green';
// Opcjonalne: Wyczyść pola po sukcesie
document.getElementById('sugestia').value = '';
document.getElementById('link').value = '';
} else {
// Błąd
const errorMessage = result.error || 'Nieznany błąd zapisu.';
statusDiv.textContent = 'BŁĄD: ' + errorMessage;
statusDiv.style.color = 'red';
}
} catch (error) {
// Błąd sieci
statusDiv.textContent = 'BŁĄD POŁĄCZENIA: Nie można połączyć się z serwerem Flask.';
statusDiv.style.color = 'red';
console.error('Fetch error:', error);
}
});