From d80dbd5d01eeb6fd0f2f00ce3232a529aac3f0a1 Mon Sep 17 00:00:00 2001 From: shaman Date: Tue, 18 Mar 2025 01:14:50 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 700 +++++++++++++++++++++++--------------------------------- 1 file changed, 291 insertions(+), 409 deletions(-) diff --git a/main.py b/main.py index d349422..02d5c0a 100644 --- a/main.py +++ b/main.py @@ -1,409 +1,291 @@ -import os -import time -import asyncio -import aiosqlite -from quart import Quart, render_template, request, send_from_directory, Response -from datetime import datetime -from babel.dates import format_datetime -<<<<<<< HEAD -from urllib.parse import quote -======= ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a - - - -app = Quart(__name__, template_folder='frontend', static_folder='frontend') - -DB_PATH = 'maps.db' -DATA = "/data" - -GAME_MODES = { - "Classic": "Классический", - "Deathmatch": "Бой насмерть", - "Demolition": "Уничтожение объекта", - "Armsrace": "Гонка вооружений", - "Custom": "Пользовательский", - "Training": "Обучение", - "Co-op Strike": "Совместный налёт", - "Wingman": "Напарники", - "Flying Scoutsman": "Перелётные снайперы" -} - -last_download_times = {} -DOWNLOAD_COOLDOWN = 10 - -async def get_maps(page=1, per_page=30): - print(f"Запрос карт на странице {page}, с {per_page} картами на странице.") - async with aiosqlite.connect(DB_PATH) as conn: - cursor = await conn.cursor() - - offset = (page - 1) * per_page - - await cursor.execute(''' - SELECT FilePath, Title, COALESCE(Stars, 0) as Stars, Description - FROM maps - ORDER BY DateTime DESC - LIMIT ? OFFSET ? - ''', (per_page, offset)) - - maps = await cursor.fetchall() - - print(f"Получено {len(maps)} карт на странице {page}.") - return maps - -def get_image_path(filepath): -<<<<<<< HEAD - print(f"Получение пути изображения для файла: {filepath}") - image_path = os.path.join(DATA, filepath) - if not os.path.exists(image_path): - print("Изображение не найдено, возвращаем дефолтное.") -======= - image_path = os.path.join(DATA, filepath) - if not os.path.exists(image_path): ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a - return "/images/image.jpg" - return f"/images/{filepath.split('/')[0]}/{filepath.split('/')[1]}/{filepath.split('/')[1]}.jpg" - -def get_star_image(stars): - print(f"Получение изображения для {stars} звезд.") - if stars is None or stars == 0: - return "/stars/0-star.png" - return f"/stars/{stars}-star.png" - -@app.route('/images/') -async def serve_image(filename): -<<<<<<< HEAD - print(f"Запрос изображения с именем: {filename}") - image_path = os.path.join(DATA, filename) - if os.path.exists(image_path): - print(f"Изображение {filename} найдено и отправляется.") -======= - image_path = os.path.join(DATA, filename) - if os.path.exists(image_path): ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a - return await send_from_directory(DATA, filename) - else: - default_image_path = os.path.join(DATA, 'image.jpg') - if os.path.exists(default_image_path): -<<<<<<< HEAD - print("Изображение не найдено, отправляем дефолтное.") - return await send_from_directory(DATA, 'image.jpg') - print("Не найдено ни одного изображения.") -======= - return await send_from_directory(DATA, 'image.jpg') ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a - return "Default image not found", 404 - -@app.route('/stars/') -async def serve_star_image(filename): -<<<<<<< HEAD - print(f"Запрос изображения звезды: {filename}") - stars = os.path.join(DATA, 'stars') - star_path = os.path.join(stars, filename) - if os.path.exists(star_path): - print(f"Звезда {filename} найдена и отправляется.") -======= - stars = os.path.join(DATA, 'stars') - star_path = os.path.join(stars, filename) - if os.path.exists(star_path): ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a - return await send_from_directory(stars, filename) - else: - print("Изображение звезды не найдено.") - return "Star image not found", 404 - -@app.route('/download_bsp') -async def download_bsp(): - user_ip = request.remote_addr - print(f"Запрос на скачивание от IP: {user_ip}") - - current_time = time.time() - last_time = last_download_times.get(user_ip, 0) - - if current_time - last_time < DOWNLOAD_COOLDOWN: - wait_time = DOWNLOAD_COOLDOWN - (current_time - last_time) - print(f"Пользователь должен подождать {int(wait_time)} секунд до следующего скачивания.") - return f"Please wait {int(wait_time)} seconds before downloading again.", 429 - - last_download_times[user_ip] = current_time - print("Тайм-аут скачивания обновлен.") - - image_path = request.args.get('image_path') - if not image_path: - print("Не указан путь к изображению.") - return "No image path provided", 400 - - image_folder = os.path.dirname(image_path.replace("/images/", "")) - bsp_filename = None - - for file in os.listdir(os.path.join(DATA, image_folder)): - if file.endswith('.bsp'): - bsp_filename = file - break - - if not bsp_filename: - print("Не найден .bsp файл в той же директории.") - return "No .bsp file found in the same directory", 404 - - file_path = os.path.join(DATA, image_folder, bsp_filename) -<<<<<<< HEAD - print(f"Найден файл для скачивания: {file_path}") -======= ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a - - SPEED_LIMIT = 60 * 1024 * 1024 // 8 - - async def file_stream(): - print("Начало передачи файла по частям.") - with open(file_path, 'rb') as f: - while chunk := f.read(SPEED_LIMIT): - yield chunk - await asyncio.sleep(1) - - headers = { - "Content-Disposition": f"attachment; filename={bsp_filename}" - } - return Response(file_stream(), headers=headers, content_type='application/octet-stream') - -@app.route('/main') -async def main_page(): - print("Запрос страницы карты с параметрами:", request.args) - image_url = request.args.get('image_url', 'default_image.jpg') - map_title = request.args.get('map_title', 'Default Map Title') - - async with aiosqlite.connect(DB_PATH) as conn: - cursor = await conn.cursor() - await cursor.execute(''' - SELECT GameMode, Tags, FilePath, DateTime, YoutubeLink, Description - FROM maps - WHERE Title = ? - ''', (map_title,)) - row = await cursor.fetchone() - - game_mode = row[0] if row else None - tags = row[1] if row else None - file_path = row[2] if row else None - date_time = row[3] if row else None - youtube_link = row[4] if row else None - description = row[5] if row else "Нет описания" - - if date_time: - dt = datetime.fromisoformat(date_time) - added_time = format_datetime(dt, format='d MMM y г., HH:mm', locale='ru_RU') - else: - added_time = 'Не найдено' - - if game_mode: - game_modes = game_mode.split(', ') - game_modes = [GAME_MODES.get(mode, mode) for mode in game_modes] - game_mode = ', '.join(game_modes) - else: - game_mode = 'Не найден' - - if tags: - tags_list = tags.split(', ') - tags_list = [GAME_MODES.get(tag, tag) for tag in tags_list] - tags = ', '.join(tags_list) - else: - tags = 'Не найдено' - - if file_path: - file_size = os.path.getsize(os.path.join(DATA, file_path)) - file_size_mb = file_size / (1024 * 1024) - file_size_display = f"{file_size_mb:.2f} MB" - else: - file_size_display = 'Не найден' - - print(f"Отправка страницы карты {map_title} с данными:") - print(f"Игра: {game_mode}, Теги: {tags}, Размер файла: {file_size_display}, Время добавления: {added_time}") - - return await render_template( - 'main.html', - image_url=image_url, - map_title=map_title, - game_mode=game_mode, - tags=tags, - file_size=file_size_display, - added_time=added_time, - youtube_link=youtube_link, - description=description - ) - - -@app.route('/') -async def index(): - print("Запрос главной страницы с параметрами фильтрации:", request.args) - page = int(request.args.get('page', 1)) - selected_game_modes = request.args.getlist('game_modes') - start_date = request.args.get('start_date') - end_date = request.args.get('end_date') - search_title = request.args.get('search_title') - selected_stars = request.args.get('stars') - - maps_data = await get_maps_filtered(page, selected_game_modes, start_date, end_date, search_title, selected_stars) - - async with aiosqlite.connect(DB_PATH) as conn: - cursor = await conn.cursor() - - query = 'SELECT COUNT(*) FROM maps WHERE 1=1' - params = [] - - if selected_game_modes: - placeholders = ', '.join('?' for _ in selected_game_modes) - query += f' AND GameMode IN ({placeholders})' - params.extend(selected_game_modes) - - if start_date: - query += ' AND DateTime >= ?' - params.append(start_date) - if end_date: - query += ' AND DateTime <= ?' - params.append(end_date) - - if search_title: - query += ' AND Title LIKE ?' - params.append(f'%{search_title}%') - - if selected_stars: - query += ' AND Stars = ?' - params.append(selected_stars) - - await cursor.execute(query, params) - total_maps = await cursor.fetchone() - - total_maps = total_maps[0] - per_page = 30 - total_pages = (total_maps + per_page - 1) // per_page - - filters = '&'.join( - [f'game_modes={mode}' for mode in selected_game_modes] + - ([f'start_date={start_date}'] if start_date else []) + - ([f'end_date={end_date}'] if end_date else []) + - ([f'search_title={search_title}'] if search_title else []) + - ([f'stars={selected_stars}'] if selected_stars else []) - ) - - print(f"Общее количество карт: {total_maps}, Страниц: {total_pages}") - return await render_template( - 'workshop.html', - maps_data=maps_data, - page=page, - total_pages=total_pages, - get_image_path=get_image_path, - get_star_image=get_star_image, - selected_game_modes=selected_game_modes, - filters=filters, - selected_stars=selected_stars - ) - -async def get_maps_filtered(page=1, selected_game_modes=None, start_date=None, end_date=None, search_title=None, selected_stars=None): - print(f"Фильтрация карт с параметрами: {locals()}") - async with aiosqlite.connect(DB_PATH) as conn: - cursor = await conn.cursor() - - offset = (page - 1) * 30 - query = ''' - SELECT FilePath, Title, COALESCE(Stars, 0) as Stars, Description - FROM maps - WHERE 1=1 - ''' - params = [] - - if selected_stars: - query += ' AND Stars = ?' - params.append(selected_stars) - - if search_title: - query += ' AND Title LIKE ?' - params.append(f'%{search_title}%') - - if selected_game_modes: - query += ' AND GameMode IN ({})'.format(','.join('?' for _ in selected_game_modes)) - params.extend(selected_game_modes) - - if start_date: - query += ' AND DateTime >= ?' - params.append(start_date) - if end_date: - query += ' AND DateTime <= ?' - params.append(end_date) - - query += ' ORDER BY DateTime DESC LIMIT ? OFFSET ?' - params.extend([30, offset]) - - await cursor.execute(query, params) - maps = await cursor.fetchall() - - print(f"Найдено {len(maps)} карт после фильтрации.") - return maps - - -@app.route('/favicon.ico') -async def favicon(): - return await send_from_directory(os.getcwd(), 'favicon.ico') - - -@app.route('/sitemap.xml') -async def sitemap(): - domain = "https://csgoworkshop.ru" - async with aiosqlite.connect(DB_PATH) as conn: - cursor = await conn.cursor() - - # Получаем список всех карт - await cursor.execute('SELECT Title FROM maps') - maps = await cursor.fetchall() - - # Создаем базовые URL для sitemap - urls = [ - f"{domain}/", - f"{domain}/main", - ] - - # Добавляем URL-адреса для всех карт - for map_title, in maps: - encoded_title = quote(map_title) - urls.append(f"{domain}/main?map_title={encoded_title}") - - # Генерируем XML - sitemap_xml = '\n' - sitemap_xml += '\n' - - for url in urls: - sitemap_xml += " \n" - sitemap_xml += f" {url}\n" - sitemap_xml += f" {datetime.utcnow().strftime('%Y-%m-%d')}\n" - sitemap_xml += " weekly\n" - sitemap_xml += " 0.8\n" - sitemap_xml += " \n" - - sitemap_xml += '' - - return Response(sitemap_xml, content_type='application/xml') - - - -@app.route('/robots.txt') -async def robots_txt(): - content = """ - User-agent: * - Disallow: - """ - return Response(content, content_type='text/plain') - - -if __name__ == '__main__': -<<<<<<< HEAD - print("Запуск приложения...") - import hypercorn.asyncio - from hypercorn.config import Config - - config = Config() - config.bind = ["0.0.0.0:5000"] - hypercorn.asyncio.run(app, config) -======= - import hypercorn.asyncio - from hypercorn.config import Config - config = Config() - config.bind = ["0.0.0.0:5000"] - hypercorn.asyncio.run(app, config) ->>>>>>> 9cc0929a09596abeb93ee7f2711527fb5da25f7a +import os +import time +import asyncio +import aiosqlite +from quart import Quart, render_template, request, send_from_directory, Response +from datetime import datetime +from babel.dates import format_datetime + + + +app = Quart(__name__, template_folder='frontend', static_folder='frontend') + +DB_PATH = 'maps.db' +DATA = "/data" + +GAME_MODES = { + "Classic": "Классический", + "Deathmatch": "Бой насмерть", + "Demolition": "Уничтожение объекта", + "Armsrace": "Гонка вооружений", + "Custom": "Пользовательский", + "Training": "Обучение", + "Co-op Strike": "Совместный налёт", + "Wingman": "Напарники", + "Flying Scoutsman": "Перелётные снайперы" +} + +last_download_times = {} +DOWNLOAD_COOLDOWN = 10 + +async def get_maps(page=1, per_page=30): + async with aiosqlite.connect(DB_PATH) as conn: + cursor = await conn.cursor() + + offset = (page - 1) * per_page + + await cursor.execute(''' + SELECT FilePath, Title, COALESCE(Stars, 0) as Stars, Description + FROM maps + ORDER BY DateTime DESC + LIMIT ? OFFSET ? + ''', (per_page, offset)) + + maps = await cursor.fetchall() + + return maps + +def get_image_path(filepath): + image_path = os.path.join(DATA, filepath) + if not os.path.exists(image_path): + return "/images/image.jpg" + return f"/images/{filepath.split('/')[0]}/{filepath.split('/')[1]}/{filepath.split('/')[1]}.jpg" + +def get_star_image(stars): + if stars is None or stars == 0: + return "/stars/0-star.png" + return f"/stars/{stars}-star.png" + +@app.route('/images/') +async def serve_image(filename): + image_path = os.path.join(DATA, filename) + if os.path.exists(image_path): + return await send_from_directory(DATA, filename) + else: + default_image_path = os.path.join(DATA, 'image.jpg') + if os.path.exists(default_image_path): + return await send_from_directory(DATA, 'image.jpg') + return "Default image not found", 404 + +@app.route('/stars/') +async def serve_star_image(filename): + stars = os.path.join(DATA, 'stars') + star_path = os.path.join(stars, filename) + if os.path.exists(star_path): + return await send_from_directory(stars, filename) + else: + return "Star image not found", 404 + +@app.route('/download_bsp') +async def download_bsp(): + user_ip = request.remote_addr + + current_time = time.time() + last_time = last_download_times.get(user_ip, 0) + + if current_time - last_time < DOWNLOAD_COOLDOWN: + wait_time = DOWNLOAD_COOLDOWN - (current_time - last_time) + return f"Please wait {int(wait_time)} seconds before downloading again.", 429 + + last_download_times[user_ip] = current_time + + image_path = request.args.get('image_path') + if not image_path: + return "No image path provided", 400 + + image_folder = os.path.dirname(image_path.replace("/images/", "")) + bsp_filename = None + + for file in os.listdir(os.path.join(DATA, image_folder)): + if file.endswith('.bsp'): + bsp_filename = file + break + + if not bsp_filename: + return "No .bsp file found in the same directory", 404 + + file_path = os.path.join(DATA, image_folder, bsp_filename) + + SPEED_LIMIT = 40 * 1024 * 1024 // 8 + + async def file_stream(): + with open(file_path, 'rb') as f: + while chunk := f.read(SPEED_LIMIT): + yield chunk + await asyncio.sleep(1) + + headers = { + "Content-Disposition": f"attachment; filename={bsp_filename}" + } + return Response(file_stream(), headers=headers, content_type='application/octet-stream') + +@app.route('/main') +async def main_page(): + image_url = request.args.get('image_url', 'default_image.jpg') + map_title = request.args.get('map_title', 'Default Map Title') + + async with aiosqlite.connect(DB_PATH) as conn: + cursor = await conn.cursor() + await cursor.execute(''' + SELECT GameMode, Tags, FilePath, DateTime, YoutubeLink, Description + FROM maps + WHERE Title = ? + ''', (map_title,)) + row = await cursor.fetchone() + + game_mode = row[0] if row else None + tags = row[1] if row else None + file_path = row[2] if row else None + date_time = row[3] if row else None + youtube_link = row[4] if row else None + description = row[5] if row else "Нет описания" + + if date_time: + dt = datetime.fromisoformat(date_time) + added_time = format_datetime(dt, format='d MMM y г., HH:mm', locale='ru_RU') + else: + added_time = 'Не найдено' + + if game_mode: + game_modes = game_mode.split(', ') + game_modes = [GAME_MODES.get(mode, mode) for mode in game_modes] + game_mode = ', '.join(game_modes) + else: + game_mode = 'Не найден' + + if tags: + tags_list = tags.split(', ') + tags_list = [GAME_MODES.get(tag, tag) for tag in tags_list] + tags = ', '.join(tags_list) + else: + tags = 'Не найдено' + + if file_path: + file_size = os.path.getsize(os.path.join(DATA, file_path)) + file_size_mb = file_size / (1024 * 1024) + file_size_display = f"{file_size_mb:.2f} MB" + else: + file_size_display = 'Не найден' + + return await render_template( + 'main.html', + image_url=image_url, + map_title=map_title, + game_mode=game_mode, + tags=tags, + file_size=file_size_display, + added_time=added_time, + youtube_link=youtube_link, + description=description + ) + + +@app.route('/') +async def index(): + page = int(request.args.get('page', 1)) + selected_game_modes = request.args.getlist('game_modes') + start_date = request.args.get('start_date') + end_date = request.args.get('end_date') + search_title = request.args.get('search_title') + selected_stars = request.args.get('stars') + + maps_data = await get_maps_filtered(page, selected_game_modes, start_date, end_date, search_title, selected_stars) + + async with aiosqlite.connect(DB_PATH) as conn: + cursor = await conn.cursor() + + query = 'SELECT COUNT(*) FROM maps WHERE 1=1' + params = [] + + if selected_game_modes: + placeholders = ', '.join('?' for _ in selected_game_modes) + query += f' AND GameMode IN ({placeholders})' + params.extend(selected_game_modes) + + if start_date: + query += ' AND DateTime >= ?' + params.append(start_date) + if end_date: + query += ' AND DateTime <= ?' + params.append(end_date) + + if search_title: + query += ' AND Title LIKE ?' + params.append(f'%{search_title}%') + + if selected_stars: + query += ' AND Stars = ?' + params.append(selected_stars) + + await cursor.execute(query, params) + total_maps = await cursor.fetchone() + + total_maps = total_maps[0] + per_page = 30 + total_pages = (total_maps + per_page - 1) // per_page + + filters = '&'.join( + [f'game_modes={mode}' for mode in selected_game_modes] + + ([f'start_date={start_date}'] if start_date else []) + + ([f'end_date={end_date}'] if end_date else []) + + ([f'search_title={search_title}'] if search_title else []) + + ([f'stars={selected_stars}'] if selected_stars else []) + ) + + return await render_template( + 'workshop.html', + maps_data=maps_data, + page=page, + total_pages=total_pages, + get_image_path=get_image_path, + get_star_image=get_star_image, + selected_game_modes=selected_game_modes, + filters=filters, + selected_stars=selected_stars + ) + +async def get_maps_filtered(page=1, selected_game_modes=None, start_date=None, end_date=None, search_title=None, selected_stars=None): + async with aiosqlite.connect(DB_PATH) as conn: + cursor = await conn.cursor() + + offset = (page - 1) * 30 + query = ''' + SELECT FilePath, Title, COALESCE(Stars, 0) as Stars, Description + FROM maps + WHERE 1=1 + ''' + params = [] + + if selected_stars: + query += ' AND Stars = ?' + params.append(selected_stars) + + if search_title: + query += ' AND Title LIKE ?' + params.append(f'%{search_title}%') + + if selected_game_modes: + query += ' AND GameMode IN ({})'.format(','.join('?' for _ in selected_game_modes)) + params.extend(selected_game_modes) + + if start_date: + query += ' AND DateTime >= ?' + params.append(start_date) + if end_date: + query += ' AND DateTime <= ?' + params.append(end_date) + + query += ' ORDER BY DateTime DESC LIMIT ? OFFSET ?' + params.extend([30, offset]) + + await cursor.execute(query, params) + maps = await cursor.fetchall() + + return maps + +if __name__ == '__main__': + import hypercorn.asyncio + from hypercorn.config import Config + config = Config() + config.bind = ["0.0.0.0:5000"] + hypercorn.asyncio.run(app, config)