From 1b59ca427943dae0a89b10cebeeea2628b13b132 Mon Sep 17 00:00:00 2001 From: projectdx Date: Thu, 8 Jan 2026 19:47:41 +0900 Subject: [PATCH] fix: Add public API for Chrome extension (no login required) - New endpoints: /public/youtube/formats, /public/youtube/add - Update extension to use public API - Add contextMenus/notifications permissions --- chrome_extension/manifest.json | 8 +-- chrome_extension/popup.js | 4 +- info.yaml | 2 +- setup.py | 95 ++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 6 deletions(-) diff --git a/chrome_extension/manifest.json b/chrome_extension/manifest.json index 04f0ec7..d91de63 100644 --- a/chrome_extension/manifest.json +++ b/chrome_extension/manifest.json @@ -5,13 +5,15 @@ "description": "YouTube 영상을 GDM(gommi_downloader_manager)으로 전송하여 다운로드", "permissions": [ "activeTab", - "storage" + "storage", + "contextMenus", + "notifications" ], "host_permissions": [ "https://www.youtube.com/*", "https://youtu.be/*", - "http://localhost:*/*", - "http://127.0.0.1:*/*" + "http://*/*", + "https://*/*" ], "action": { "default_popup": "popup.html", diff --git a/chrome_extension/popup.js b/chrome_extension/popup.js index 630cb09..b526777 100644 --- a/chrome_extension/popup.js +++ b/chrome_extension/popup.js @@ -93,7 +93,7 @@ async function fetchVideoInfo() { try { const response = await fetch( - `${serverUrl}/gommi_downloader_manager/ajax/queue/youtube_formats?url=${encodeURIComponent(currentUrl)}`, + `${serverUrl}/gommi_downloader_manager/public/youtube/formats?url=${encodeURIComponent(currentUrl)}`, { method: 'GET' } ); @@ -166,7 +166,7 @@ async function startDownload() { try { const response = await fetch( - `${serverUrl}/gommi_downloader_manager/ajax/queue/youtube_add`, + `${serverUrl}/gommi_downloader_manager/public/youtube/add`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, diff --git a/info.yaml b/info.yaml index 3f2a172..8da289c 100644 --- a/info.yaml +++ b/info.yaml @@ -1,6 +1,6 @@ title: "GDM" package_name: gommi_downloader_manager -version: '0.2.19' +version: '0.2.20' description: FlaskFarm 범용 다운로더 큐 - YouTube, 애니24, 링크애니, Anilife 지원 developer: projectdx home: https://gitea.yommi.duckdns.org/projectdx/gommi_downloader_manager diff --git a/setup.py b/setup.py index 8f7cf81..67aa346 100644 --- a/setup.py +++ b/setup.py @@ -65,3 +65,98 @@ try: except Exception as e: P.logger.error(f'Exception:{str(e)}') P.logger.error(traceback.format_exc()) + + +# ===== Public API for Chrome Extension (No Login Required) ===== +try: + from flask import Blueprint, request, jsonify + + public_api = Blueprint(f'{package_name}_public_api', package_name, url_prefix=f'/{package_name}/public') + + @public_api.route('/youtube/formats', methods=['GET', 'POST']) + def youtube_formats(): + """YouTube 품질 목록 조회 (인증 불필요)""" + url = request.args.get('url') or request.form.get('url', '') + if not url: + return jsonify({'ret': 'error', 'msg': 'URL이 필요합니다.'}) + + try: + import yt_dlp + ydl_opts = {'quiet': True, 'no_warnings': True} + + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=False) + + formats = [{'id': 'bestvideo+bestaudio/best', 'label': '최고 품질', 'note': ''}] + heights = set() + for f in info.get('formats', []): + h = f.get('height') + if h and f.get('vcodec') != 'none': + heights.add(h) + + for h in sorted(heights, reverse=True): + if h >= 2160: formats.append({'id': 'bestvideo[height<=2160]+bestaudio/best', 'label': '4K', 'note': ''}) + elif h >= 1080: formats.append({'id': 'bestvideo[height<=1080]+bestaudio/best', 'label': '1080p', 'note': '권장'}) + elif h >= 720: formats.append({'id': 'bestvideo[height<=720]+bestaudio/best', 'label': '720p', 'note': ''}) + + formats.append({'id': 'bestaudio/best', 'label': '오디오만', 'note': ''}) + + # 중복 제거 + seen, unique = set(), [] + for f in formats: + if f['id'] not in seen: + seen.add(f['id']) + unique.append(f) + + return jsonify({ + 'ret': 'success', + 'title': info.get('title', ''), + 'thumbnail': info.get('thumbnail', ''), + 'duration': info.get('duration', 0), + 'formats': unique + }) + except Exception as e: + return jsonify({'ret': 'error', 'msg': str(e)}) + + @public_api.route('/youtube/add', methods=['POST']) + def youtube_add(): + """YouTube 다운로드 추가 (인증 불필요)""" + try: + if request.is_json: + data = request.get_json() + else: + data = request.form.to_dict() + + url = data.get('url', '') + if not url or ('youtube.com' not in url and 'youtu.be' not in url): + return jsonify({'ret': 'error', 'msg': '유효한 YouTube URL이 필요합니다.'}) + + format_id = data.get('format', 'bestvideo+bestaudio/best') + + from framework import F + from tool import ToolUtil + save_path = ToolUtil.make_path(P.ModelSetting.get('save_path')) + + item = ModuleQueue.add_download( + url=url, + save_path=save_path, + source_type='youtube', + caller_plugin='chrome_extension', + format=format_id + ) + + if item: + return jsonify({'ret': 'success', 'id': item.id, 'msg': '다운로드가 추가되었습니다.'}) + else: + return jsonify({'ret': 'error', 'msg': '다운로드 추가 실패'}) + except Exception as e: + P.logger.error(f'Public API youtube_add error: {e}') + return jsonify({'ret': 'error', 'msg': str(e)}) + + # Blueprint 등록 + from framework import F + F.app.register_blueprint(public_api) + P.logger.info(f'Public API registered: /{package_name}/public/') + +except Exception as e: + P.logger.warning(f'Public API registration failed: {e}')