Files
kurwa-strona/webserver/app.py

271 lines
9.6 KiB
Python
Raw Normal View History

2025-10-22 19:05:25 +02:00
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)