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)