diff --git a/README.md b/README.md index 344b38d..7206175 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,11 @@ ## ๐Ÿ“ ๋ณ€๊ฒฝ ์ด๋ ฅ (Changelog) +### v0.6.25 (2026-01-09) +- **์ž๊ฐ€ ์—…๋ฐ์ดํŠธ ๊ธฐ๋Šฅ ์ถ”๊ฐ€**: ๋ชจ๋“  ์„ค์ • ํŽ˜์ด์ง€ (Ohli24, Anilife, Linkkf)์—์„œ "์—…๋ฐ์ดํŠธ" ๋ฒ„ํŠผ ํด๋ฆญ์œผ๋กœ Git Pull ๋ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ ํ•ซ ๋ฆฌ๋กœ๋“œ ์ง€์› +- **๋ฒ„์ „ ์ฒดํฌ API**: GitHub์—์„œ ์ตœ์‹  ๋ฒ„์ „ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€ ์—…๋ฐ์ดํŠธ ์•Œ๋ฆผ ํ‘œ์‹œ (1์‹œ๊ฐ„ ์บ์‹ฑ) +- **๊ณตํ†ต ๋ฒ ์ด์Šค ํ†ตํ•ฉ**: `AnimeModuleBase`์— `get_update_info`, `reload_plugin` ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€๋กœ ๋ชจ๋“  ๋ชจ๋“ˆ์—์„œ ์ž๋™ ์‚ฌ์šฉ ๊ฐ€๋Šฅ + ### v0.6.24 (2026-01-08) - **Ohli24 GDM ์—ฐ๋™ ๋ฒ„๊ทธ ์ˆ˜์ •**: - **์ธ๋„ค์ผ ๋ˆ„๋ฝ ํ•ด๊ฒฐ**: GDM ์œ„์ž„ ์‹œ `image` ํ‚ค๋ฅผ `thumbnail`๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋งคํ•‘ํ•˜์—ฌ ๋ชฉ๋ก์—์„œ ์ด๋ฏธ์ง€๊ฐ€ ๋ณด์ด๋„๋ก ์ˆ˜์ •. diff --git a/info.yaml b/info.yaml index 736c659..20e6716 100644 --- a/info.yaml +++ b/info.yaml @@ -1,5 +1,5 @@ title: "์• ๋‹ˆ ๋‹ค์šด๋กœ๋”" -version: 0.6.23 +version: 0.6.25 package_name: "anime_downloader" developer: "projectdx" description: "anime downloader" diff --git a/mod_base.py b/mod_base.py index 9e84fbf..c03bcc2 100644 --- a/mod_base.py +++ b/mod_base.py @@ -5,6 +5,10 @@ import os, traceback, time, json from datetime import datetime class AnimeModuleBase(PluginModuleBase): + # ์—…๋ฐ์ดํŠธ ์ฒดํฌ ์บ์‹ฑ (ํด๋ž˜์Šค ๋ ˆ๋ฒจ) + _last_update_check = 0 + _latest_version = None + def __init__(self, P, setup_default=None, **kwargs): super(AnimeModuleBase, self).__init__(P, **kwargs) self.P = P # Ensure P is available via self.P @@ -131,6 +135,37 @@ class AnimeModuleBase(PluginModuleBase): arg3 = request.form.get('arg3') or request.args.get('arg3') return self.process_command(command, arg1, arg2, arg3, req) + elif sub == 'self_update': + # ์ž๊ฐ€ ์—…๋ฐ์ดํŠธ (Git Pull) ๋ฐ ๋ชจ๋“ˆ ๋ฆฌ๋กœ๋“œ + try: + import subprocess + plugin_path = os.path.dirname(os.path.dirname(__file__)) if '__file__' in dir() else os.path.dirname(__file__) + # ์‹ค์ œ ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฃจํŠธ ๋””๋ ‰ํ† ๋ฆฌ + plugin_path = os.path.dirname(__file__) + self.P.logger.info(f"์• ๋‹ˆ ๋‹ค์šด๋กœ๋” ์ž๊ฐ€ ์—…๋ฐ์ดํŠธ ์‹œ์ž‘: {plugin_path}") + + cmd = ['git', '-C', plugin_path, 'pull'] + process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + stdout, stderr = process.communicate() + + if process.returncode != 0: + raise Exception(f"Git pull ์‹คํŒจ: {stderr}") + + self.P.logger.info(f"Git pull ๊ฒฐ๊ณผ: {stdout}") + + # ๋ชจ๋“ˆ ๋ฆฌ๋กœ๋“œ + self.reload_plugin() + + return jsonify({'ret': 'success', 'msg': f"์—…๋ฐ์ดํŠธ ๋ฐ ๋ฆฌ๋กœ๋“œ ์™„๋ฃŒ!
{stdout}
", 'data': stdout}) + except Exception as e: + self.P.logger.error(f"์ž๊ฐ€ ์—…๋ฐ์ดํŠธ ์ค‘ ์˜ค๋ฅ˜: {str(e)}") + self.P.logger.error(traceback.format_exc()) + return jsonify({'ret': 'danger', 'msg': f"์—…๋ฐ์ดํŠธ ์‹คํŒจ: {str(e)}"}) + + elif sub == 'check_update': + force = req.form.get('force') == 'true' + return jsonify({'ret': 'success', 'data': self.get_update_info(force=force)}) + return jsonify({'ret': 'fail', 'log': f"Unknown sub: {sub}"}) except Exception as e: @@ -198,3 +233,92 @@ class AnimeModuleBase(PluginModuleBase): except Exception as e: return {'ret': 'fail', 'msg': str(e)} + def get_update_info(self, force=False): + """GitHub์—์„œ ์ตœ์‹  ๋ฒ„์ „ ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (์บ์‹ฑ ํ™œ์šฉ)""" + import requests + now = time.time() + + # ์‹ค์ œ ๋กœ์ปฌ ํŒŒ์ผ์—์„œ ํ˜„์žฌ ๋ฒ„์ „ ์ฝ๊ธฐ + current_version = self.P.plugin_info.get('version', '0.0.0') + try: + info_path = os.path.join(os.path.dirname(__file__), 'info.yaml') + if os.path.exists(info_path): + import yaml + with open(info_path, 'r', encoding='utf-8') as f: + local_info = yaml.safe_load(f) + current_version = str(local_info.get('version', current_version)) + except: pass + + # 1์‹œ๊ฐ„๋งˆ๋‹ค ์ฒดํฌ (force=True๋ฉด ์ฆ‰์‹œ) + if not force and AnimeModuleBase._latest_version and (now - AnimeModuleBase._last_update_check < 3600): + return { + 'current': current_version, + 'latest': AnimeModuleBase._latest_version, + 'has_update': self._is_newer(AnimeModuleBase._latest_version, current_version) + } + + try: + url = "https://raw.githubusercontent.com/projectdx75/anime_downloader/master/info.yaml" + res = requests.get(url, timeout=5) + if res.status_code == 200: + import yaml + data = yaml.safe_load(res.text) + AnimeModuleBase._latest_version = str(data.get('version', '')) + AnimeModuleBase._last_update_check = now + + return { + 'current': current_version, + 'latest': AnimeModuleBase._latest_version, + 'has_update': self._is_newer(AnimeModuleBase._latest_version, current_version) + } + except Exception as e: + self.P.logger.error(f"Update check failed: {e}") + + return { + 'current': current_version, + 'latest': AnimeModuleBase._latest_version or current_version, + 'has_update': False + } + + def _is_newer(self, latest, current): + """๋ฒ„์ „ ๋น„๊ต (0.7.8 vs 0.7.7)""" + if not latest or not current: return False + try: + l_parts = [int(p) for p in latest.split('.')] + c_parts = [int(p) for p in current.split('.')] + return l_parts > c_parts + except: + return latest != current + + def reload_plugin(self): + """ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ชจ๋“ˆ ํ•ซ ๋ฆฌ๋กœ๋“œ""" + import sys + import importlib + + try: + package_name = self.P.package_name + self.P.logger.info(f"ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ฆฌ๋กœ๋“œ ์‹œ์ž‘: {package_name}") + + # ๊ด€๋ จ ๋ชจ๋“ˆ ์ฐพ๊ธฐ ๋ฐ ๋ฆฌ๋กœ๋“œ + modules_to_reload = [] + for module_name in list(sys.modules.keys()): + if module_name.startswith(package_name): + modules_to_reload.append(module_name) + + # ์˜์กด์„ฑ ์—ญ์ˆœ์œผ๋กœ ์ •๋ ฌ (๊นŠ์€ ๋ชจ๋“ˆ ๋จผ์ €) + modules_to_reload.sort(key=lambda x: x.count('.'), reverse=True) + + for module_name in modules_to_reload: + try: + module = sys.modules[module_name] + importlib.reload(module) + self.P.logger.debug(f"Reloaded: {module_name}") + except Exception as e: + self.P.logger.warning(f"Failed to reload {module_name}: {e}") + + self.P.logger.info(f"ํ”Œ๋Ÿฌ๊ทธ์ธ ๋ชจ๋“ˆ [{package_name}] ๋ฆฌ๋กœ๋“œ ์™„๋ฃŒ") + return True + except Exception as e: + self.P.logger.error(f"๋ชจ๋“ˆ ๋ฆฌ๋กœ๋“œ ์ค‘ ์‹คํŒจ: {str(e)}") + self.P.logger.error(traceback.format_exc()) + return False diff --git a/templates/anime_downloader_anilife_setting.html b/templates/anime_downloader_anilife_setting.html index fa1a901..fc07c16 100644 --- a/templates/anime_downloader_anilife_setting.html +++ b/templates/anime_downloader_anilife_setting.html @@ -8,7 +8,12 @@

Anilife ์„ค์ •

- {{ macros.m_button_group([['globalSettingSaveBtn', '์„ค์ • ์ €์žฅ']])}} +
+ + {{ macros.m_button_group([['globalSettingSaveBtn', '์„ค์ • ์ €์žฅ']])}} +
{{ macros.m_row_start('5') }} @@ -709,6 +714,37 @@ function getDragAfterElement(container, x) { return [...container.querySelectorAll('.tag-chip:not(.dragging)')].reduce((c, el) => { var box = el.getBoundingClientRect(); var offset = x - box.left - box.width/2; return (offset < 0 && offset > c.offset) ? {offset, element: el} : c; }, {offset: Number.NEGATIVE_INFINITY}).element; } +// ====================================== +// ์ž๊ฐ€ ์—…๋ฐ์ดํŠธ ๊ธฐ๋Šฅ +// ====================================== +$('#btn-self-update').on('click', function() { + if (!confirm('์ตœ์‹  ์ฝ”๋“œ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•˜๊ณ  ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ๋ฆฌ๋กœ๋“œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?')) return; + + var btn = $(this); + var originalHTML = btn.html(); + btn.prop('disabled', true).html(' ์—…๋ฐ์ดํŠธ ์ค‘...'); + + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/self_update', + type: 'POST', + dataType: 'json', + success: function(ret) { + if (ret.ret === 'success') { + $.notify('์—…๋ฐ์ดํŠธ ์™„๋ฃŒ! ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•ฉ๋‹ˆ๋‹ค.', {type: 'success'}); + setTimeout(function() { location.reload(); }, 1500); + } else { + $.notify('์—…๋ฐ์ดํŠธ ์‹คํŒจ: ' + ret.msg + '', {type: 'danger'}); + } + }, + error: function() { + $.notify('์—…๋ฐ์ดํŠธ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ', {type: 'danger'}); + }, + complete: function() { + btn.prop('disabled', false).html(originalHTML); + } + }); +}); +