commit 4cd21e759ce11fff8bdfcdc4a2285e4675cd03ec Author: projectdx Date: Sat Apr 30 16:45:26 2022 +0900 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d7c45d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.ipynb +test.ipynb +.idea +.vscode \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..c33a082 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +# inflearn_sjva + +## πŸš€ changelog +- **ν”ŒλŸ¬κ·ΈμΈ μ‚­μ œ 버그 μˆ˜μ •** 0.1.4.1 +- **μŠ€μΌ€μ₯΄λŸ¬ μ˜€λ™μž‘ 버그 μˆ˜μ •** 0.1.4.3 +- **λ°©μ˜μ€‘ λ²„νŠΌ, 버그 μˆ˜μ •** 0.1.4.7 diff --git a/__init__.py b/__init__.py new file mode 100755 index 0000000..b1fa0c4 --- /dev/null +++ b/__init__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +from .plugin import blueprint, menu, plugin_info, plugin_load, plugin_unload + +# from .plugin import blueprint, menu, plugin_load, plugin_unload, plugin_info +# from .plugin import blueprint, menu, plugin_load, plugin_unload, plugin_info +# from .plugin import P +# blueprint = P.blueprint +# menu = P.menu +# plugin_load = P.logic.plugin_load +# plugin_unload = P.logic.plugin_unload +# plugin_info = P.plugin_info diff --git a/__pycache__/__init__.cpython-38.pyc b/__pycache__/__init__.cpython-38.pyc new file mode 100644 index 0000000..873a7e8 Binary files /dev/null and b/__pycache__/__init__.cpython-38.pyc differ diff --git a/__pycache__/logic.cpython-38.pyc b/__pycache__/logic.cpython-38.pyc new file mode 100644 index 0000000..c2d207c Binary files /dev/null and b/__pycache__/logic.cpython-38.pyc differ diff --git a/__pycache__/logic_queue.cpython-38.pyc b/__pycache__/logic_queue.cpython-38.pyc new file mode 100644 index 0000000..e03e4b8 Binary files /dev/null and b/__pycache__/logic_queue.cpython-38.pyc differ diff --git a/__pycache__/model.cpython-38.pyc b/__pycache__/model.cpython-38.pyc new file mode 100644 index 0000000..fb3a379 Binary files /dev/null and b/__pycache__/model.cpython-38.pyc differ diff --git a/__pycache__/plugin.cpython-38.pyc b/__pycache__/plugin.cpython-38.pyc new file mode 100644 index 0000000..a5ef30d Binary files /dev/null and b/__pycache__/plugin.cpython-38.pyc differ diff --git a/info.json b/info.json new file mode 100755 index 0000000..024c50f --- /dev/null +++ b/info.json @@ -0,0 +1,11 @@ +{ + "version": "0.3.1.0", + "name": "linkkf-yommi", + "category_name": "vod", + "icon": "", + "developer": "projectdx && persuade", + "description": "linkkf μ‚¬μ΄νŠΈμ—μ„œ μ• λ‹ˆ λ‹€μš΄λ‘œλ“œ", + "home": "https://github.com/projectdx75/linkkf-yommi", + "more": "", + "category": "vod" +} \ No newline at end of file diff --git a/logic.py b/logic.py new file mode 100755 index 0000000..097e23a --- /dev/null +++ b/logic.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +######################################################### +# python +import os +import sys +import traceback +import logging +import threading +import time + +# third-party +from sqlalchemy import desc + +# sjva 곡용 +from framework import db, scheduler, path_data +from framework.job import Job +from framework.util import Util +from framework.logger import get_logger + +# νŒ¨ν‚€μ§€ +# from .plugin import package_name, logger +from .model import ModelSetting +from .logic_queue import LogicQueue +from .logic_inflearn import LogicInflearn + +######################################################### +package_name = __name__.split(".")[0] +logger = get_logger(package_name) + + +class Logic(object): + db_default = { + "inflearn_url": "https://www.inflearn.com/", + "download_path": os.path.join(path_data, "inflearn"), + "inflearn_auto_make_folder": "True", + "inflearn_auto_make_season_folder": "True", + "inflearn_finished_insert": "[μ™„κ²°]", + "include_date": "False", + "date_option": "0", # 0:YYMMDD, 1:YYYY-MM-DD + "auto_make_folder": "True", + "max_ffmpeg_process_count": "4", + "auto_interval": "* 20 * * *", + "auto_start": "False", + "whitelist_program": "", + } + + @staticmethod + def db_init(): + try: + logger.debug(Logic.db_default.items()) + for key, value in Logic.db_default.items(): + logger.debug(f"{key}: {value}") + if db.session.query(ModelSetting).filter_by(key=key).count() == 0: + db.session.add(ModelSetting(key, value)) + db.session.commit() + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def plugin_load(): + try: + logger.debug("%s plugin_load", package_name) + # DB μ΄ˆκΈ°ν™” + Logic.db_init() + + if ModelSetting.get("auto_start") == "True": + Logic.scheduler_start() + + # 편의λ₯Ό μœ„ν•΄ json 파일 생성 + from .plugin import plugin_info + + Util.save_from_dict_to_json( + plugin_info, os.path.join(os.path.dirname(__file__), "info.json") + ) + LogicQueue.queue_start() + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def plugin_unload(): + try: + logger.debug("%s plugin_unload", package_name) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def scheduler_start(): + try: + interval = ModelSetting.get("auto_interval") + job = Job( + package_name, + package_name, + interval, + Logic.scheduler_function, + "μΈν”„λŸ° λ‹€μš΄λ‘œλ“œ", + True, + ) + scheduler.add_job_instance(job) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def scheduler_stop(): + try: + scheduler.remove_job(package_name) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def setting_save(req): + try: + for key, value in req.form.items(): + logger.debug("Key:%s Value:%s", key, value) + entity = ( + db.session.query(ModelSetting) + .filter_by(key=key) + .with_for_update() + .first() + ) + entity.value = value + db.session.commit() + return True + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + logger.error("key:%s value:%s", key, value) + return False + + @staticmethod + def scheduler_function(): + try: + LogicLinkkfYommi.scheduler_function() + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + # κΈ°λ³Έ ꡬ쑰 End + ################################################################## diff --git a/logic_inflearn.py b/logic_inflearn.py new file mode 100755 index 0000000..4d0887c --- /dev/null +++ b/logic_inflearn.py @@ -0,0 +1,822 @@ +# -*- coding: utf-8 -*- +######################################################### +# python +import os +import sys +import traceback +import logging +import threading +import time +import re +import random +import urllib + +# import pip + +# import urlparse +from urllib.parse import urlparse +import json + +packages = ["beautifulsoup4"] +for package in packages: + try: + import package + + except ImportError: + # main(["install", package]) + os.system(f"pip install {package}") + +# third-party +import requests +from lxml import html, etree +from bs4 import BeautifulSoup + +# import snoop +# from snoop import spy + +# sjva 곡용 +from framework import db, scheduler, path_data +from framework.job import Job +from framework.util import Util +from framework.logger import get_logger + +# νŒ¨ν‚€μ§€ +# from .plugin import package_name, logger +# from anime_downloader.logic_ohli24 import ModelOhli24Item +from .model import ModelSetting, ModelLinkkf, ModelLinkkfProgram +from .logic_queue import LogicQueue + +######################################################### +package_name = __name__.split(".")[0] +logger = get_logger(package_name) + + +class LogicInflearn(object): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98" + "Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", + } + + session = None + referer = None + current_data = None + + @staticmethod + def get_html(url): + + try: + if LogicInflearn.session is None: + LogicInflearn.session = requests.Session() + LogicInflearn.headers["referer"] = LogicInflearn.referer + LogicInflearn.referer = url + # logger.debug( + # f"get_html()::LogicLinkkfYommi.referer = {LogicLinkkfYommi.referer}" + # ) + page = LogicInflearn.session.get(url, headers=LogicInflearn.headers) + # logger.info("page", page) + + return page.content.decode("utf8", errors="replace") + # return page.text + # return page.content + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_video_url_from_url(url, url2): + video_url = None + referer_url = None + vtt_url = None + LogicInflearn.referer = url2 + # logger.info("dx download url : %s , url2 : %s" % (url, url2)) + # logger.debug(LogicLinkkfYommi.referer) + + try: + if "kfani" in url2: + # kfani 계열 처리 => λ°©λ¬Έν•΄μ„œ m3u8을 λ°›μ•„μ˜¨λ‹€. + logger.debug("kfani routine") + LogicInflearn.referer = url2 + # logger.debug(f"url2: {url2}") + data = LogicInflearn.get_html(url2) + # logger.info("dx: data", data) + regex2 = r'"([^\"]*m3u8)"|]+src=\"([^"]+)' + + temp_url = re.findall(regex2, data)[0] + video_url = "" + ref = "https://kfani.me" + for i in temp_url: + if i is None: + continue + video_url = i + # video_url = '{1} -headers \'Referer: "{0}"\' -user_agent "Mozilla/5.0 (Windows NT 10.0; Win64; + # x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3554.0 Safari/537.36"'.format(ref, + # video_url) + + match = re.compile(r".*?.vtt)").search(data) + # logger.info("match group: %s", match.group('vtt_url')) + vtt_url = match.group("vtt_url") + # logger.info("vtt_url: %s", vtt_url) + # logger.debug(f"LogicLinkkfYommi.referer: {LogicLinkkfYommi.referer}") + referer_url = url2 + + else: + logger.error("μƒˆλ‘œμš΄ μœ ν˜•μ˜ url λ°œμƒ! %s %s" % (url, url2)) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + # logger.debug(f"referer_url: {referer_url}") + # logger.debug(f"LogicLinkkfYommi.referer: {LogicLinkkfYommi.referer}") + + return [video_url, referer_url, vtt_url] + + @staticmethod + def get_video_url(episode_url): + try: + # url = urlparse.urljoin(ModelSetting.get('inflearn_url'), episode_id) + url = episode_url + # logger.info("url: %s" % url) + data = LogicInflearn.get_html(url) + # logger.info(data) + tree = html.fromstring(data) + url2s = [ + tag.attrib["value"] + for tag in tree.xpath('//*[@id="body"]/div/span/center/select/option') + ] + + # logger.info('dx: url', url) + # logger.info('dx: urls2', url2s) + + video_url = None + referer_url = None # dx + + for url2 in url2s: + try: + + if video_url is not None: + continue + logger.debug(f"url: {url}, url2: {url2}") + ret = LogicInflearn.get_video_url_from_url(url, url2) + print(f"ret::::> {ret}") + + if ret is not None: + video_url = ret + referer_url = url2 + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + # logger.info(video_url) + + # return [video_url, referer_url] + return video_url + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def apply_new_title(new_title): + try: + ret = {} + if LogicInflearn.current_data is not None: + program = ( + db.session.query(ModelLinkkfProgram) + .filter_by(programcode=LogicInflearn.current_data["code"]) + .first() + ) + new_title = Util.change_text_for_use_filename(new_title) + LogicInflearn.current_data["save_folder"] = new_title + program.save_folder = new_title + db.session.commit() + + for entity in LogicInflearn.current_data["episode"]: + entity["save_folder"] = new_title + entity["filename"] = LogicInflearn.get_filename( + LogicInflearn.current_data["save_folder"], + LogicInflearn.current_data["season"], + entity["title"], + ) + # tmp = data['filename'].split('.') + # tmp[0] = new_title + # data['filename'] = '.'.join(tmp) + return LogicInflearn.current_data + else: + ret["ret"] = False + ret["log"] = "No current data!!" + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + ret["ret"] = False + ret["log"] = str(e) + return ret + + @staticmethod + def apply_new_season(new_season): + try: + ret = {} + season = int(new_season) + if LogicInflearn.current_data is not None: + program = ( + db.session.query(ModelLinkkfProgram) + .filter_by(programcode=LogicInflearn.current_data["code"]) + .first() + ) + LogicInflearn.current_data["season"] = season + program.season = season + db.session.commit() + + for entity in LogicInflearn.current_data["episode"]: + entity["filename"] = LogicInflearn.get_filename( + LogicInflearn.current_data["save_folder"], + LogicInflearn.current_data["season"], + entity["title"], + ) + return LogicInflearn.current_data + else: + ret["ret"] = False + ret["log"] = "No current data!!" + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + ret["ret"] = False + ret["log"] = str(e) + return ret + + @staticmethod + def add_whitelist(*args): + ret = {} + + logger.debug(f"args: {args}") + try: + + if len(args) == 0: + code = str(LogicInflearn.current_data["code"]) + else: + code = str(args[0]) + + whitelist_program = ModelSetting.get("whitelist_program") + whitelist_programs = [ + str(x.strip().replace(" ", "")) + for x in whitelist_program.replace("\n", ",").split(",") + ] + if code not in whitelist_programs: + whitelist_programs.append(code) + whitelist_programs = filter( + lambda x: x != "", whitelist_programs + ) # remove blank code + whitelist_program = ",".join(whitelist_programs) + entity = ( + db.session.query(ModelSetting) + .filter_by(key="whitelist_program") + .with_for_update() + .first() + ) + entity.value = whitelist_program + db.session.commit() + ret["ret"] = True + ret["code"] = code + if len(args) == 0: + return LogicInflearn.current_data + else: + return ret + else: + ret["ret"] = False + ret["log"] = "이미 μΆ”κ°€λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€." + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + ret["ret"] = False + ret["log"] = str(e) + return ret + + @staticmethod + def get_airing_info(): + try: + url = f"{ModelSetting.get('inflearn_url')}/airing" + html_content = LogicInflearn.get_html(url) + download_path = ModelSetting.get("download_path") + tree = html.fromstring(html_content) + tmp_items = tree.xpath('//div[@class="item"]') + # logger.info('tmp_items:::', tmp_items) + + data = {"ret": "success"} + + # logger.debug(tree.xpath('//*[@id="wp_page"]//text()')) + if tree.xpath('//*[@id="wp_page"]//text()'): + data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] + else: + data["total_page"] = 0 + + data["episode_count"] = len(tmp_items) + data["episode"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = re.search(r"[0-9]+", entity["link"]).group() + entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ + 0 + ].strip() + entity["image_link"] = item.xpath( + './/img[@class="photo"]/@data-lazy-src' + )[0] + entity["chapter"] = item.xpath(".//a/button/span//text()")[0] + # logger.info('entity:::', entity['title']) + data["episode"].append(entity) + + json_file_path = os.path.join(download_path, "airing_list.json") + logger.debug("json_file_path:: %s", json_file_path) + + with open(json_file_path, "w") as outfile: + json.dump(data, outfile) + + return data + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_search_result(query): + try: + # query = query.encode("utf-8") + _query = urllib.parse.quote(query) + url = f"{ModelSetting.get('inflearn_url')}/?s={_query}" + logger.debug("search url::> %s", url) + html_content = LogicInflearn.get_html(url) + download_path = ModelSetting.get("download_path") + tree = html.fromstring(html_content) + tmp_items = tree.xpath('//div[@class="item"]') + # logger.info('tmp_items:::', tmp_items) + + data = {"ret": "success", "query": query} + + # data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] + if tree.xpath('//*[@id="wp_page"]//text()'): + data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] + else: + data["total_page"] = 0 + + data["episode_count"] = len(tmp_items) + data["episode"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = re.search(r"[0-9]+", entity["link"]).group() + entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ + 0 + ].strip() + entity["image_link"] = item.xpath('.//img[@class="photo"]/@src')[0] + # logger.info('entity:::', entity['title']) + data["episode"].append(entity) + + json_file_path = os.path.join(download_path, "airing_list.json") + logger.debug("json_file_path:: %s", json_file_path) + + with open(json_file_path, "w") as outfile: + json.dump(data, outfile) + + return data + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_anime_list_info(cate, page): + try: + if cate == "ing": + url = f"{ModelSetting.get('inflearn_url')}/airing/page/{page}" + elif cate == "complete": + url = f"{ModelSetting.get('inflearn_url')}/anime-list/page/{page}" + logger.debug(f"get_anime_list_info():url >> {url}") + + html_content = LogicInflearn.get_html(url) + download_path = ModelSetting.get("download_path") + tree = html.fromstring(html_content) + tmp_items = tree.xpath('//div[@class="item"]') + # logger.info('tmp_items:::', tmp_items) + + data = {"ret": "success", "page": page} + + data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] + data["episode_count"] = len(tmp_items) + data["episode"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = re.search(r"[0-9]+", entity["link"]).group() + entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ + 0 + ].strip() + entity["image_link"] = item.xpath( + './/img[@class="photo"]/@data-lazy-src' + )[0] + entity["chapter"] = item.xpath(".//a/button/span//text()")[0] + # logger.info('entity:::', entity['title']) + data["episode"].append(entity) + + json_file_path = os.path.join(download_path, "airing_list.json") + logger.debug("json_file_path:: %s", json_file_path) + + with open(json_file_path, "w") as outfile: + json.dump(data, outfile) + + return data + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_screen_movie_info(page): + try: + url = f"{ModelSetting.get('inflearn_url')}/ani/page/{page}" + + html_content = LogicInflearn.get_html(url) + download_path = ModelSetting.get("download_path") + tree = html.fromstring(html_content) + tmp_items = tree.xpath('//div[@class="item"]') + # logger.info('tmp_items:::', tmp_items) + + data = {"ret": "success", "page": page} + + data["episode_count"] = len(tmp_items) + data["episode"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = re.search(r"[0-9]+", entity["link"]).group() + entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ + 0 + ].strip() + entity["image_link"] = item.xpath( + './/img[@class="photo"]/@data-lazy-src' + )[0] + # logger.info('entity:::', entity['title']) + data["episode"].append(entity) + + json_file_path = os.path.join(download_path, "airing_list.json") + logger.debug("json_file_path:: %s", json_file_path) + + with open(json_file_path, "w") as outfile: + json.dump(data, outfile) + + return data + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_complete_anilist_info(page): + try: + url = f"{ModelSetting.get('inflearn_url')}/anime-list/page/{page}" + + html_content = LogicInflearn.get_html(url) + download_path = ModelSetting.get("download_path") + tree = html.fromstring(html_content) + tmp_items = tree.xpath('//div[@class="item"]') + # logger.info('tmp_items:::', tmp_items) + + data = {"ret": "success", "page": page} + + data["episode_count"] = len(tmp_items) + data["episode"] = [] + + if tree.xpath('//*[@id="wp_page"]//text()'): + data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] + else: + data["total_page"] = 0 + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = re.search(r"[0-9]+", entity["link"]).group() + entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ + 0 + ].strip() + entity["image_link"] = item.xpath( + './/img[@class="photo"]/@data-lazy-src' + )[0] + # logger.info('entity:::', entity['title']) + data["episode"].append(entity) + + json_file_path = os.path.join(download_path, "airing_list.json") + logger.debug("json_file_path:: %s", json_file_path) + + with open(json_file_path, "w") as outfile: + json.dump(data, outfile) + + return data + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_title_info(code): + try: + if ( + LogicInflearn.current_data is not None + and LogicInflearn.current_data["code"] == code + and LogicInflearn.current_data["ret"] + ): + return LogicInflearn.current_data + url = "%s/%s" % (ModelSetting.get("inflearn_url"), code) + # logger.info(url) + html_content = LogicInflearn.get_html(url) + sys.setrecursionlimit(10**7) + # logger.info(html_content) + tree = html.fromstring(html_content) + # tree = etree.fromstring( + # html_content, parser=etree.XMLParser(huge_tree=True) + # ) + # tree1 = BeautifulSoup(html_content, "lxml") + + soup = BeautifulSoup(html_content, "html.parser") + # tree = etree.HTML(str(soup)) + # logger.info(tree) + + data = {"code": code, "ret": False} + # //*[@id="body"]/div/div[1]/article/center/strong + # tmp = tree.xpath('/html/body/div[2]/div/div/article/center/strong' + # )[0].text_content().strip().encode('utf8') + # tmp = tree.xpath('//*[@id="body"]/div/div[1]/article/center/strong')[0].text_content().strip() + # logger.info('tmp::>', tree.xpath('//div[@class="hrecipe"]/article/center/strong')) + # tmp1 = tree.xpath("//div[contains(@id, 'related')]/ul/a") + # tmp = tree1.find_element(By.Xpath, "//ul/a") + tmp = soup.select("ul > a") + + # logger.debug(f"tmp1 size:=> {str(len(tmp))}") + + try: + tmp = ( + tree.xpath('//div[@class="hrecipe"]/article/center/strong')[0] + .text_content() + .strip() + ) + except IndexError: + tmp = tree.xpath("//article/center/strong")[0].text_content().strip() + + # print(tmp) + # logger.info(tmp) + match = re.compile(r"(?P\d+)κΈ°").search(tmp) + if match: + data["season"] = match.group("season") + else: + data["season"] = "1" + + # replace_str = f'({data["season"]}κΈ°)' + # logger.info(replace_str) + data["_id"] = str(code) + data["title"] = tmp.replace(data["season"] + "κΈ°", "").strip() + data["title"] = data["title"].replace("()", "").strip() + data["title"] = ( + Util.change_text_for_use_filename(data["title"]) + .replace("OVA", "") + .strip() + ) + # logger.info(f"title:: {data['title']}") + try: + # data['poster_url'] = tree.xpath( + # '//*[@id="body"]/div/div/div[1]/center/img' + # )[0].attrib['data-src'] + + data["poster_url"] = tree.xpath( + '//*[@id="body"]/div/div[1]/div[1]/center/img' + )[0].attrib["data-lazy-src"] + data["detail"] = [ + { + "info": tree.xpath("/html/body/div[2]/div/div[1]/div[1]")[0] + .text_content() + .strip() + } + ] + except Exception as e: + data["detail"] = [{"μ •λ³΄μ—†μŒ": ""}] + data["poster_url"] = None + + data["rate"] = tree.xpath('span[@class="tag-score"]') + # tag_score = tree.xpath('//span[@class="taq-score"]').text_content().strip() + tag_score = tree.xpath('//span[@class="taq-score"]')[0].text_content() + # logger.debug(tag_score) + tag_count = ( + tree.xpath('//span[contains(@class, "taq-count")]')[0] + .text_content() + .strip() + ) + data_rate = tree.xpath('//div[@class="rating"]/div/@data-rate') + # logger.debug("data_rate::> %s", data_rate) + # tmp = tree.xpath('//*[@id="relatedpost"]/ul/li') + # tmp = tree.xpath('//article/a') + # μˆ˜μ •λœ + # tmp = tree.xpath("//ul/a") + tmp = soup.select("ul > a") + + # logger.debug(f"tmp size:=> {str(len(tmp))}") + # logger.info(tmp) + if tmp is not None: + data["episode_count"] = str(len(tmp)) + else: + data["episode_count"] = "0" + + data["episode"] = [] + # tags = tree.xpath( + # '//*[@id="syno-nsc-ext-gen3"]/article/div[1]/article/a') + # tags = tree.xpath("//ul/a") + tags = soup.select("ul > a") + + # logger.info("tags", tags) + # re1 = re.compile(r'\/(?P\d+)') + re1 = re.compile(r"\-([^-])+\.") + + data["save_folder"] = data["title"] + # logger.debug(f"save_folder::> {data['save_folder']}") + + program = ( + db.session.query(ModelLinkkfProgram).filter_by(programcode=code).first() + ) + + if program is None: + program = ModelLinkkfProgram(data) + db.session.add(program) + db.session.commit() + else: + data["save_folder"] = program.save_folder + data["season"] = program.season + + idx = 1 + for t in tags: + entity = { + "_id": data["code"], + "program_code": data["code"], + "program_title": data["title"], + "save_folder": Util.change_text_for_use_filename( + data["save_folder"] + ), + "title": t.text.strip(), + # "title": t.text_content().strip(), + } + # entity['code'] = re1.search(t.attrib['href']).group('code') + + # logger.debug(f"title ::>{entity['title']}") + + # 고유idμž„μ„ μ•Œμˆ˜ μ—†λŠ” 말도 μ•ˆλ¨.. + # μ—ν”Όμ†Œλ“œ μ½”λ“œκ°€ κ³ μœ ν•΄μ•Ό μƒνƒœκ°’ 갱신이 μ œλŒ€λ‘œ 된 값에 넣어짐 + p = re.compile(r"([0-9]+)ν™”?") + m_obj = p.match(entity["title"]) + # logger.info(m_obj.group()) + # entity['code'] = data['code'] + '_' +str(idx) + + episode_code = None + # logger.debug(f"m_obj::> {m_obj}") + if m_obj is not None: + episode_code = m_obj.group(1) + entity["code"] = data["code"] + episode_code.zfill(4) + else: + entity["code"] = data["code"] + + # logger.info('episode_code', episode_code) + # entity["url"] = t.attrib["href"] + entity["url"] = t["href"] + entity["season"] = data["season"] + + # μ €μž₯경둜 μ €μž₯ + tmp_save_path = ModelSetting.get("download_path") + if ModelSetting.get("auto_make_folder") == "True": + program_path = os.path.join(tmp_save_path, entity["save_folder"]) + entity["save_path"] = program_path + if ModelSetting.get("inflearn_auto_make_season_folder"): + entity["save_path"] = os.path.join( + entity["save_path"], "Season %s" % int(entity["season"]) + ) + + data["episode"].append(entity) + entity["image"] = data["poster_url"] + + # entity['title'] = t.text_content().strip().encode('utf8') + + # entity['season'] = data['season'] + # logger.debug(f"save_folder::2> {data['save_folder']}") + entity["filename"] = LogicInflearn.get_filename( + data["save_folder"], data["season"], entity["title"] + ) + idx = idx + 1 + data["ret"] = True + # logger.info('data', data) + LogicInflearn.current_data = data + + # srt 파일 처리 + + return data + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + data["log"] = str(e) + data["ret"] = "error" + return data + except IndexError as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + data["log"] = str(e) + data["ret"] = "error" + return data + + @staticmethod + def get_filename(maintitle, season, title): + try: + # logger.debug("get_filename()===") + # logger.info("title:: %s", title) + # logger.info("maintitle:: %s", maintitle) + match = re.compile( + r"(?P.*?)\s?((?P<season>\d+)κΈ°)?\s?((?P<epi_no>\d+)ν™”?)" + ).search(title) + if match: + epi_no = int(match.group("epi_no")) + if epi_no < 10: + epi_no = "0%s" % epi_no + else: + epi_no = "%s" % epi_no + + if int(season) < 10: + season = "0%s" % season + else: + season = "%s" % season + + # title_part = match.group('title').strip() + # ret = '%s.S%sE%s%s.720p-SA.mp4' % (maintitle, season, epi_no, date_str) + ret = "%s.S%sE%s.720p-LK.mp4" % (maintitle, season, epi_no) + else: + logger.debug("NOT MATCH") + ret = "%s.720p-SA.mp4" % maintitle + + return Util.change_text_for_use_filename(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def get_info_by_code(code): + logger.error("get_info_by_code: %s", code) + + try: + if LogicInflearn.current_data is not None: + for t in LogicInflearn.current_data["episode"]: + if t["code"] == code: + return t + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def scheduler_function(): + try: + logger.debug("Linkkf scheduler_function start..") + + whitelist_program = ModelSetting.get("whitelist_program") + whitelist_programs = [ + x.strip().replace(" ", "") + for x in whitelist_program.replace("\n", ",").split(",") + ] + + logger.debug(f"whitelist_programs: {whitelist_programs}") + + for code in whitelist_programs: + logger.info("auto download start : %s", code) + downloaded = ( + db.session.query(ModelLinkkf) + .filter(ModelLinkkf.completed.is_(True)) + .filter_by(programcode=code) + .with_for_update() + .all() + ) + # logger.debug(f"downloaded:: {downloaded}") + dl_codes = [dl.episodecode for dl in downloaded] + # logger.debug('dl_codes:: ', dl_codes) + logger.info("downloaded codes :%s", dl_codes) + + # if len(dl_codes) > 0: + data = LogicInflearn.get_title_info(code) + + for episode in data["episode"]: + e_code = episode["code"] + if e_code not in dl_codes: + logger.info("Logic Queue added :%s", e_code) + LogicQueue.add_queue(episode) + + logger.debug("========================================") + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def reset_db() -> bool: + db.session.query(ModelLinkkf).delete() + db.session.commit() + return True diff --git a/logic_queue.py b/logic_queue.py new file mode 100755 index 0000000..7acfc99 --- /dev/null +++ b/logic_queue.py @@ -0,0 +1,449 @@ +# -*- coding: utf-8 -*- +######################################################### +# python +import os +import traceback +import sys +import logging +import threading +import queue + +# import Queue +# from .logic_queue import LogicQueue +import json +import time +from datetime import datetime +import requests + +# third-party + +# sjva 곡용 +from framework import db, scheduler, path_data +from framework.job import Job +from framework.util import Util +from framework.logger import get_logger + +# νŒ¨ν‚€μ§€ +# from .plugin import package_name, logger +import system +from .model import ModelSetting, ModelLinkkf + +# from plugin import LogicModuleBase, FfmpegQueueEntity, FfmpegQueue, default_route_socketio + +######################################################### +package_name = __name__.split(".")[0] +logger = get_logger(package_name) + + +class QueueEntity: + static_index = 1 + entity_list = [] + + def __init__(self, info): + # logger.info('info:::::>> %s', info) + self.entity_id = info["code"] + self.info = info + self.url = None + self.ffmpeg_status = -1 + self.ffmpeg_status_kor = "λŒ€κΈ°μ€‘" + self.ffmpeg_percent = 0 + self.ffmpeg_arg = None + self.cancel = False + self.created_time = datetime.now().strftime("%m-%d %H:%M:%S") + self.status = None + QueueEntity.static_index += 1 + QueueEntity.entity_list.append(self) + + @staticmethod + def create(info): + for e in QueueEntity.entity_list: + if e.info["code"] == info["code"]: + return + ret = QueueEntity(info) + return ret + + @staticmethod + def get_entity_by_entity_id(entity_id): + # logger.debug('entity_list::> %s', QueueEntity.entity_list) + for _ in QueueEntity.entity_list: + # logger.debug('entity::>> %s', _.entity_id) + if _.entity_id == entity_id: + return _ + return None + + +class LogicQueue(object): + download_queue = None + download_thread = None + current_ffmpeg_count = 0 + + def refresh_status(self): + self.module_logic.socketio_callback("status", self.as_dict()) + + @staticmethod + def queue_start(): + try: + if LogicQueue.download_queue is None: + LogicQueue.download_queue = queue.Queue() + # LogicQueue.download_queue = Queue.Queue() + if LogicQueue.download_thread is None: + LogicQueue.download_thread = threading.Thread( + target=LogicQueue.download_thread_function, args=() + ) + LogicQueue.download_thread.daemon = True + LogicQueue.download_thread.start() + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + # @staticmethod + # def download_thread_function(): + # while True: + # try: + # entity = LogicQueue.download_queue.get() + # logger.debug( + # "Queue receive item:%s %s", entity.title_id, entity.episode_id + # ) + # # LogicAni.process(entity) + # LogicQueue.download_queue.task_done() + # except Exception as e: + # logger.error("Exception:%s", e) + # logger.error(traceback.format_exc()) + + @staticmethod + def download_thread_function(): + headers = None + from . import plugin + + # import plugin + while True: + try: + while True: + if LogicQueue.current_ffmpeg_count < int( + ModelSetting.get("max_ffmpeg_process_count") + ): + break + # logger.debug(LogicQueue.current_ffmpeg_count) + time.sleep(5) + entity = LogicQueue.download_queue.get() + + # Todo: κ³ μ°° + # if entity.cancel: + # continue + + # logger.debug( + # "download_thread_function()::entity.info['code'] >> %s", entity + # ) + + if entity is None: + continue + + # db에 ν•΄λ‹Ή μ—ν”Όμ†Œλ“œκ°€ μ‘΄μž¬ν•˜λŠ” 확인 + db_entity = ModelLinkkf.get_by_inflearn_id(entity.info["code"]) + if db_entity is None: + episode = ModelLinkkf("auto", info=entity.info) + db.session.add(episode) + db.session.commit() + else: + # episode = ModelLinkkf("auto", info=entity.info) + # query = db.session.query(ModelLinkkf).filter_by(episodecode=entity.info.episodecode).with_for_update().first() + pass + + from .logic_inflearn import LogicInflearn + + # entity.url = LogicLinkkfYommi.get_video_url( + # entity.info['code']) + # logger.debug(f"entity.info[url] = {entity.info['url']}") + entity.url = LogicInflearn.get_video_url(entity.info["url"]) + + # logger.info('entity.info: %s', entity.info['url']) + logger.debug(f"entity.url: {entity.url}") + # logger.info('url1: %s', entity.url[0]) + # print(entity) + # logger.info('entity: %s', entity.__dict__) + + # logger.info('entity.url:::> %s', entity.url) + if entity.url[0] is None: + self.ffmpeg_status_kor = "URLμ‹€νŒ¨" + plugin.socketio_list_refresh() + continue + + import ffmpeg + + max_pf_count = 0 + referer = None + save_path = ModelSetting.get("download_path") + if ModelSetting.get("auto_make_folder") == "True": + program_path = os.path.join(save_path, entity.info["save_folder"]) + save_path = program_path + if ModelSetting.get("inflearn_auto_make_season_folder"): + save_path = os.path.join( + save_path, "Season %s" % int(entity.info["season"]) + ) + try: + if not os.path.exists(save_path): + os.makedirs(save_path) + except: + logger.debug("program path make fail!!") + + # 파일 μ‘΄μž¬μ—¬λΆ€ 체크 + if entity.url[1] is not None: + referer = entity.url[1] + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36", + # 'Accept': + # 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + # 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', + "Referer": f"{referer}", + } + # logger.info('referer: %s', referer) + + # logger.info('filename::::>>>> %s', entity.info['filename']) + # logger.info('파일체크::::>', os.path.join(save_path, entity.info['filename'])) + if os.path.exists(os.path.join(save_path, entity.info["filename"])): + entity.ffmpeg_status_kor = "파일 있음" + entity.ffmpeg_percent = 100 + plugin.socketio_list_refresh() + continue + + headers = { + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/71.0.3554.0 Safari/537.36Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3554.0 Safari/537.36", + "Referer": f"{referer}", + } + # logger.debug(f"referer: {referer}") + + f = ffmpeg.Ffmpeg( + entity.url[0], + entity.info["filename"], + plugin_id=entity.entity_id, + listener=LogicQueue.ffmpeg_listener, + max_pf_count=max_pf_count, + # referer=referer, + call_plugin=package_name, + save_path=save_path, + headers=headers, + ) + f.start() + + LogicQueue.current_ffmpeg_count += 1 + LogicQueue.download_queue.task_done() + + # vtt file to srt file + from framework.common.util import write_file, convert_vtt_to_srt + from urllib import parse + + ourls = parse.urlparse(entity.url[1]) + # print(ourls) + # logger.info('ourls:::>', ourls) + base_url = f"{ourls.scheme}://{ourls.netloc}" + # logger.info('base_url:::>', base_url) + + # Todo: μž„μ‹œ 컀밋 둜직 ν•΄κ²°ν•˜λ©΄ λ‹€μ‹œ 처리 + # if "linkkf.app" in base_url: + # base_url = f"{ourls.scheme}://kfani.me" + + vtt_url = base_url + entity.url[2] + logger.debug(f"srt:url => {vtt_url}") + srt_filepath = os.path.join( + save_path, entity.info["filename"].replace(".mp4", ".ko.srt") + ) + # logger.info('srt_filepath::: %s', srt_filepath) + if entity.url[2] is not None and not os.path.exists(srt_filepath): + vtt_data = requests.get(vtt_url, headers=headers).text + srt_data = convert_vtt_to_srt(vtt_data) + write_file(srt_data, srt_filepath) + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def ffmpeg_listener(**arg): + # logger.debug(arg) + # logger.debug(arg["plugin_id"]) + import ffmpeg + + refresh_type = None + if arg["type"] == "status_change": + if arg["status"] == ffmpeg.Status.DOWNLOADING: + episode = ( + db.session.query(ModelLinkkf) + .filter_by(episodecode=arg["plugin_id"]) + .with_for_update() + .first() + ) + if episode: + episode.ffmpeg_status = int(arg["status"]) + episode.duration = arg["data"]["duration"] + db.session.commit() + elif arg["status"] == ffmpeg.Status.COMPLETED: + pass + elif arg["status"] == ffmpeg.Status.READY: + pass + elif arg["type"] == "last": + LogicQueue.current_ffmpeg_count += -1 + episode = ( + db.session.query(ModelLinkkf) + .filter_by(episodecode=arg["plugin_id"]) + .with_for_update() + .first() + ) + if ( + arg["status"] == ffmpeg.Status.WRONG_URL + or arg["status"] == ffmpeg.Status.WRONG_DIRECTORY + or arg["status"] == ffmpeg.Status.ERROR + or arg["status"] == ffmpeg.Status.EXCEPTION + ): + episode.etc_abort = 1 + elif arg["status"] == ffmpeg.Status.USER_STOP: + episode.user_abort = True + logger.debug("Status.USER_STOP received..") + if arg["status"] == ffmpeg.Status.COMPLETED: + episode.completed = True + episode.end_time = datetime.now() + episode.download_time = (episode.end_time - episode.start_time).seconds + episode.completed_time = episode.end_time + episode.filesize = arg["data"]["filesize"] + episode.filesize_str = arg["data"]["filesize_str"] + episode.download_speed = arg["data"]["download_speed"] + episode.status = "completed" + logger.debug("Status.COMPLETED received..") + elif arg["status"] == ffmpeg.Status.TIME_OVER: + episode.etc_abort = 2 + elif arg["status"] == ffmpeg.Status.PF_STOP: + episode.pf = int(arg["data"]["current_pf_count"]) + episode.pf_abort = 1 + elif arg["status"] == ffmpeg.Status.FORCE_STOP: + episode.etc_abort = 3 + elif arg["status"] == ffmpeg.Status.HTTP_FORBIDDEN: + episode.etc_abort = 4 + db.session.commit() + logger.debug("LAST commit %s", arg["status"]) + elif arg["type"] == "log": + pass + elif arg["type"] == "normal": + pass + if refresh_type is not None: + pass + + entity = QueueEntity.get_entity_by_entity_id(arg["plugin_id"]) + if entity is None: + return + entity.ffmpeg_arg = arg + entity.ffmpeg_status = int(arg["status"]) + entity.ffmpeg_status_kor = str(arg["status"]) + entity.ffmpeg_percent = arg["data"]["percent"] + from . import plugin + + arg["status"] = str(arg["status"]) + plugin.socketio_callback("status", arg) + + # @staticmethod + # def add_queue(info): + # try: + # entity = QueueEntity.create(info) + # if entity is not None: + # LogicQueue.download_queue.put(entity) + # return True + # except Exception as e: + # logger.error("Exception:%s", e) + # logger.error(traceback.format_exc()) + # return False + @staticmethod + def add_queue(info): + try: + + # Todo: + # if is_exist(info): + # return 'queue_exist' + # logger.debug("add_queue()::info >> %s", info) + # logger.debug("info::", info["_id"]) + + # episode[] code (episode_code) + db_entity = ModelLinkkf.get_by_inflearn_id(info["code"]) + # logger.debug("add_queue:: db_entity >> %s", db_entity) + + if db_entity is None: + entity = QueueEntity.create(info) + + # logger.debug("add_queue()::entity >> %s", entity) + LogicQueue.download_queue.put(entity) + return "enqueue_db_append" + elif db_entity.status != "completed": + entity = QueueEntity.create(info) + # return "Debugging" + LogicQueue.download_queue.put(entity) + + logger.debug("add_queue()::enqueue_db_exist") + return "enqueue_db_exist" + else: + return "db_completed" + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + return False + + @staticmethod + def program_auto_command(req): + try: + from . import plugin + + command = req.form["command"] + entity_id = int(req.form["entity_id"]) + logger.debug("command :%s %s", command, entity_id) + entity = QueueEntity.get_entity_by_entity_id(entity_id) + + logger.debug("entity::> %s", entity) + + # logger.info('logic_queue:: entity', entity) + + ret = {} + if command == "cancel": + if entity.status == -1: + entity.cancel = True + entity.status_kor = "μ·¨μ†Œ" + plugin.socketio_list_refresh() + ret["ret"] = "refresh" + elif entity.status != 5: + ret["ret"] = "notify" + ret["log"] = "λ‹€μš΄λ‘œλ“œ 쀑인 μƒνƒœκ°€ μ•„λ‹™λ‹ˆλ‹€." + else: + idx = entity.ffmpeg_arg["data"]["idx"] + import ffmpeg + + ffmpeg.Ffmpeg.stop_by_idx(idx) + plugin.socketio_list_refresh() + ret["ret"] = "refresh" + elif command == "reset": + if LogicQueue.download_queue is not None: + with LogicQueue.download_queue.mutex: + LogicQueue.download_queue.queue.clear() + for _ in QueueEntity.entity_list: + if _.ffmpeg_status == 5: + import ffmpeg + + idx = _.ffmpeg_arg["data"]["idx"] + ffmpeg.Ffmpeg.stop_by_idx(idx) + QueueEntity.entity_list = [] + plugin.socketio_list_refresh() + ret["ret"] = "refresh" + elif command == "delete_completed": + new_list = [] + for _ in QueueEntity.entity_list: + if _.ffmpeg_status_kor in ["파일 있음", "μ·¨μ†Œ"]: + continue + if _.ffmpeg_status != 7: + new_list.append(_) + QueueEntity.entity_list = new_list + plugin.socketio_list_refresh() + ret["ret"] = "refresh" + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + ret["ret"] = "notify" + ret["log"] = str(e) + return ret diff --git a/model.py b/model.py new file mode 100755 index 0000000..f58ea0a --- /dev/null +++ b/model.py @@ -0,0 +1,223 @@ +# -*- coding: utf-8 -*- +######################################################### +# python +import os +import traceback +import json +from datetime import datetime + +# third-party +from sqlalchemy import or_, and_, func, not_, desc + +# sjva 곡용 +from framework.logger import get_logger +from framework import db, app, path_app_root +from framework.util import Util + +# νŒ¨ν‚€μ§€ +# from .plugin import package_name, logger + +package_name = __name__.split(".")[0] +logger = get_logger(package_name) +db_file = os.path.join(path_app_root, "data", "db", "%s.db" % package_name) +app.config["SQLALCHEMY_BINDS"][package_name] = "sqlite:///%s" % (db_file) + + +class ModelSetting(db.Model): + __tablename__ = "plugin_%s_setting" % package_name + __table_args__ = {"mysql_collate": "utf8_general_ci"} + __bind_key__ = package_name + + id = db.Column(db.Integer, primary_key=True) + key = db.Column(db.String(100), unique=True, nullable=False) + value = db.Column(db.String, nullable=False) + + def __init__(self, key, value): + self.key = key + self.value = value + + def __repr__(self): + return repr(self.as_dict()) + + def as_dict(self): + return {x.name: getattr(self, x.name) for x in self.__table__.columns} + + @staticmethod + def get(key): + try: + return ( + db.session.query(ModelSetting).filter_by(key=key).first().value.strip() + ) + except Exception as e: + logger.error("Exception:%s %s", e, key) + logger.error(traceback.format_exc()) + + # @staticmethod + # def get_bool(key): + # try: + # return (ModelSetting.get(key) == 'True') + # except Exception as exception: + # logger.error('Exception:%s %s', exception, key) + # logger.error(traceback.format_exc()) + + +class ModelLinkkfProgram(db.Model): + __tablename__ = "plugin_%s_program" % package_name + __table_args__ = {"mysql_collate": "utf8_general_ci"} + __bind_key__ = package_name + + id = db.Column(db.Integer, primary_key=True) + contents_json = db.Column(db.JSON) + created_time = db.Column(db.DateTime) + + programcode = db.Column(db.String) + + save_folder = db.Column(db.String) + season = db.Column(db.Integer) + + def __init__(self, data): + self.created_time = datetime.now() + self.programcode = data["code"] + self.save_folder = data["title"] + self.season = data["season"] + + def __repr__(self): + # return "<Episode(id:%s, episode_code:%s, quality:%s)>" % (self.id, self.episode_code, self.quality) + return repr(self.as_dict()) + + def as_dict(self): + ret = {x.name: getattr(self, x.name) for x in self.__table__.columns} + return ret + + def set_info(self, data): + self.contents_json = data + self.programcode = data["code"] + self.save_folder = data["save_folder"] + self.season = data["season"] + + +class ModelLinkkf(db.Model): + __tablename__ = "plugin_%s_auto_episode" % package_name + __table_args__ = {"mysql_collate": "utf8_general_ci"} + __bind_key__ = package_name + + id = db.Column(db.Integer, primary_key=True) + contents_json = db.Column(db.JSON) + created_time = db.Column(db.DateTime) + completed_time = db.Column(db.DateTime) + + programcode = db.Column(db.String) + episodecode = db.Column(db.String) + filename = db.Column(db.String) + duration = db.Column(db.Integer) + start_time = db.Column(db.DateTime) + end_time = db.Column(db.DateTime) + download_time = db.Column(db.Integer) + completed = db.Column(db.Boolean) + user_abort = db.Column(db.Boolean) + pf_abort = db.Column(db.Boolean) + etc_abort = db.Column(db.Integer) # ffmpeg 원인 1, 채널, ν”„λ‘œκ·Έλž¨ + ffmpeg_status = db.Column(db.Integer) + temp_path = db.Column(db.String) + save_path = db.Column(db.String) + pf = db.Column(db.Integer) + retry = db.Column(db.Integer) + filesize = db.Column(db.Integer) + filesize_str = db.Column(db.String) + download_speed = db.Column(db.String) + call = db.Column(db.String) + # download_video μ°Έκ³  + status = db.Column(db.String) + inflearn_info = db.Column(db.JSON) + + def __init__(self, call, info): + self.created_time = datetime.now() + self.completed = False + self.start_time = datetime.now() + self.user_abort = False + self.pf_abort = False + self.etc_abort = 0 + self.ffmpeg_status = -1 + self.pf = 0 + self.retry = 0 + self.call = call + self.set_info(info) + # logger.info(str(self)) + + def __repr__(self): + # return "<Episode(id:%s, episode_code:%s, quality:%s)>" % (self.id, self.episode_code, self.quality) + return repr(self.as_dict()) + + def as_dict(self): + ret = {x.name: getattr(self, x.name) for x in self.__table__.columns} + ret["created_time"] = self.created_time.strftime("%Y-%m-%d %H:%M:%S") + ret["completed_time"] = ( + self.completed_time.strftime("%Y-%m-%d %H:%M:%S") + if self.completed_time is not None + else None + ) + return ret + + def set_info(self, data): + self.contents_json = data + self.programcode = data["program_code"] + self.episodecode = data["code"] + + @classmethod + def delete_by_id(cls, _id): + db.session.query(cls).filter_by(id=_id).delete() + db.session.commit() + return True + + @classmethod + def web_list(cls, req): + ret = {} + page = int(req.form["page"]) if "page" in req.form else 1 + page_size = 30 + job_id = "" + search = req.form["search_word"] if "search_word" in req.form else "" + option = req.form["option"] if "option" in req.form else "all" + order = req.form["order"] if "order" in req.form else "desc" + query = cls.make_query(search=search, order=order, option=option) + # logger.info(query) + count = query.count() + query = query.limit(page_size).offset((page - 1) * page_size) + lists = query.all() + # logger.info(lists) + ret["list"] = [item.as_dict() for item in lists] + ret["paging"] = Util.get_paging_info(count, page, page_size) + # logger.info(ret) + return ret + + @classmethod + def get_by_inflearn_id(cls, inflearn_id): + return db.session.query(cls).filter_by(episodecode=inflearn_id).first() + + @classmethod + def make_query(cls, search="", order="desc", option="all"): + query = db.session.query(cls) + if search is not None and search != "": + if search.find("|") != -1: + tmp = search.split("|") + conditions = [] + for tt in tmp: + if tt != "": + conditions.append(cls.filename.like("%" + tt.strip() + "%")) + query = query.filter(or_(*conditions)) + elif search.find(",") != -1: + tmp = search.split(",") + for tt in tmp: + if tt != "": + query = query.filter(cls.filename.like("%" + tt.strip() + "%")) + else: + query = query.filter(cls.filename.like("%" + search + "%")) + if option == "completed": + query = query.filter(cls.status == "completed") + + query = ( + query.order_by(desc(cls.id)) if order == "desc" else query.order_by(cls.id) + ) + return query + + +######################################################### diff --git a/plugin.py b/plugin.py new file mode 100755 index 0000000..b757656 --- /dev/null +++ b/plugin.py @@ -0,0 +1,433 @@ +# -*- coding: utf-8 -*- +######################################################### +# κ³ μ •μ˜μ—­ +######################################################### +# python +import os +import sys +import traceback +import json + +# third-party +from flask import ( + Blueprint, + request, + Response, + render_template, + redirect, + jsonify, + url_for, + send_from_directory, +) +from flask_login import login_required +from flask_socketio import SocketIO, emit, send + +# sjva 곡용 +from framework.logger import get_logger +from framework import app, db, scheduler, socketio, path_app_root +from framework.util import Util, AlchemyEncoder +from system.logic import SystemLogic + + +# νŒ¨ν‚€μ§€ + +from .logic import Logic +from .logic_inflearn import LogicInflearn +from .logic_queue import QueueEntity, LogicQueue +from .model import ModelSetting, ModelLinkkf + +# blueprint = Blueprint(package_name, +# package_name, +# url_prefix='/%s' % package_name, +# template_folder=os.path.join(os.path.dirname(__file__), +# 'templates')) + +package_name = __name__.split(".")[0] +logger = get_logger(package_name) + +######################################################### +# ν”ŒλŸ¬κ·ΈμΈ 곡용 +######################################################### +blueprint = Blueprint( + package_name, + package_name, + url_prefix="/%s" % package_name, + template_folder=os.path.join(os.path.dirname(__file__), "templates"), + static_folder=os.path.join(os.path.dirname(__file__), "static"), +) + + +def plugin_load(): + Logic.plugin_load() + + +def plugin_unload(): + Logic.plugin_unload() + + +# 메뉴 ꡬ성. +menu = { + "main": [package_name, "inflearn"], + "sub": [ + ["setting", "μ„€μ •"], + ["request", "μš”μ²­"], + ["category", "검색"], + ["queue", "큐"], + ["list", "λͺ©λ‘"], + ["log", "둜그"], + ], + "category": "vod", + # 'sub2': { + # 'linkkf-yommi': [ + # ['setting', u'μ„€μ •'], ['request', u'μš”μ²­'], ['queue', u'큐'], ['list', u'λͺ©λ‘'] + # ], + # } +} + +plugin_info = { + "version": "0.0.1.0", + "name": "inflearn", + "category_name": "vod", + "icon": "", + "developer": "projectdx", + "description": "μΈν”„λŸ° κ°•μ’Œ μ‚¬μ΄νŠΈ μ—μ„œ κ°•μ’Œ λ‹€μš΄λ‘œλ“œ", + "home": "https://github.com/projectdx75/linkkf-yommi", + "more": "", +} + + +######################################################### + + +# def plugin_load(): +# Logic.plugin_load() +# # +# # LogicQueue.queue_load() +# +# +# def plugin_unload(): +# Logic.plugin_unload() + + +######################################################### +# WEB Menu +######################################################### +@blueprint.route("/") +def home(): + # return redirect('/%s/setting' % package_name) + return redirect("/%s/category" % package_name) + + +@blueprint.route("/<sub>") +@login_required +def detail(sub): + # arg = { + # "package_name": package_name, + # "template_name": "%s_%s" % (package_name, sub), + # } + if sub == "setting": + setting_list = db.session.query(ModelSetting).all() + arg = Util.db_list_to_dict(setting_list) + arg["package_name"] = package_name + arg["sub"] = "setting" + arg["scheduler"] = str(scheduler.is_include(package_name)) + arg["is_running"] = str(scheduler.is_running(package_name)) + arg["template_name"] = "%s_%s" % (package_name, sub) + return render_template("%s_%s.html" % (package_name, sub), arg=arg) + elif sub in ["request", "queue", "list"]: + setting_list = db.session.query(ModelSetting).all() + arg = Util.db_list_to_dict(setting_list) + arg["package_name"] = package_name + arg["current_code"] = ( + LogicInflearn.current_data["code"] + if LogicInflearn.current_data is not None + else "" + ) + arg["template_name"] = "%s_%s" % (package_name, sub) + return render_template("%s_%s.html" % (package_name, sub), arg=arg) + elif sub == "category": + + setting_list = db.session.query(ModelSetting).all() + arg = Util.db_list_to_dict(setting_list) + arg["package_name"] = package_name + arg["template_name"] = "%s_%s" % (package_name, sub) + # logger.debug(f"arg:: {arg}") + return render_template("%s_%s.html" % (package_name, sub), arg=arg) + elif sub == "log": + return render_template("log.html", package=package_name) + return render_template("sample.html", title="%s - %s" % (package_name, sub)) + + +######################################################### +# For UI (보톡 μ›Ήμ—μ„œ μš”μ²­ν•˜λŠ” 정보에 λŒ€ν•œ κ²°κ³Όλ₯Ό λ¦¬ν„΄ν•œλ‹€.) +######################################################### +@blueprint.route("/ajax/<sub>", methods=["GET", "POST"]) +def ajax(sub): + logger.debug("AJAX %s %s", package_name, sub) + if sub == "setting_save": + try: + ret = Logic.setting_save(request) + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "scheduler": + go = request.form["scheduler"] + logger.debug("scheduler :%s", go) + if go == "true": + Logic.scheduler_start() + else: + Logic.scheduler_stop() + return jsonify(go) + # μš”μ²­ + elif sub == "analysis": + try: + + code = request.form["code"] + data = LogicInflearn.get_title_info(code) + # logger.debug("data::> %s", data) + # current_data = data + + # return jsonify(data) + if data["ret"] == "error": + return jsonify(data) + else: + return jsonify({"ret": "success", "data": data}) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + # except IndexError as e: + # logger.error("Exception:%s", e) + # logger.error(traceback.format_exc()) + return jsonify({"ret": "error", "log": e}) + elif sub == "search": + try: + query = request.form["query"] + logger.debug("query::>> %s", query) + data = LogicInflearn.get_search_result(str(query)) + + return jsonify(data) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "anime_list": + try: + # logger.debug(request.form) + page = request.form["page"] + cate = request.form["type"] + # data = LogicLinkkfYommi.get_screen_movie_info(page) + data = LogicInflearn.get_anime_list_info(cate, page) + dummy_data = {"ret": "success", "data": data} + return jsonify(data) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "airing_list": + try: + data = LogicInflearn.get_airing_info() + # dummy_data = {"ret": "success", "data": data} + logger.debug(f"airing_list:: {data}") + return jsonify(data) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + elif sub == "screen_movie_list": + try: + logger.debug("request:::> %s", request.form["page"]) + page = request.form["page"] + data = LogicInflearn.get_screen_movie_info(page) + dummy_data = {"ret": "success", "data": data} + return jsonify(data) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "complete_anilist": + try: + logger.debug("request:::> %s", request.form["page"]) + page = request.form["page"] + data = LogicInflearn.get_complete_anilist_info(page) + dummy_data = {"ret": "success", "data": data} + return jsonify(data) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "apply_new_title": + try: + new_title = request.form["new_title"] + ret = LogicInflearn.apply_new_title(new_title) + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "apply_new_season": + try: + new_season = request.form["new_season"] + ret = LogicInflearn.apply_new_season(new_season) + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "add_whitelist": + try: + # params = request.get_data() + # logger.debug(f"params: {params}") + # data_code = request.args.get("data_code") + params = request.get_json() + logger.debug(params) + if params is not None: + code = params["data_code"] + logger.debug(f"params: {code}") + ret = LogicInflearn.add_whitelist(code) + else: + ret = LogicInflearn.add_whitelist() + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + elif sub == "add_queue": + try: + ret = {} + # info = json.loads(request.form["data"]) + # logger.info("test::", info) + # logger.info("_id", info["_id"]) + # return False + + code = request.form["code"] + # ν•΄λ‹Ή code둜 dbμ‘°νšŒν›„ info λ³€μˆ˜μ— λ‹΄λŠ”λ‹€ + info = LogicInflearn.get_info_by_code(code) + # info = LogicLinkkfYommi.get_info_by_code(info) + # logger.debug(info) + # return False + + logger.debug(f"info::> {info}") + + # ret["ret"] = "debugging" + + if info is not None: + from .logic_queue import LogicQueue + + tmp = LogicQueue.add_queue(info) + logger.debug("add_queue : tmp >> %s", tmp) + # ret["ret"] = "success" if tmp else "fail" + ret["ret"] = tmp + else: + ret["ret"] = "no_data" + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + ret["ret"] = "fail" + ret["log"] = str(e) + return jsonify(ret) + elif sub == "add_queue_checked_list": + ret = {} + try: + from .logic_queue import LogicQueue + + code = request.form["code"] + code_list = code.split(",") + count = 0 + for c in code_list: + info = LogicInflearn.get_info_by_code(c) + if info is not None: + tmp = LogicQueue.add_queue(info) + count += 1 + ret["ret"] = "success" + ret["log"] = str(count) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + ret["ret"] = "fail" + ret["log"] = str(e) + return jsonify(ret) + # 큐 + elif sub == "program_auto_command": + try: + from .logic_queue import LogicQueue + + ret = LogicQueue.program_auto_command(request) + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + elif sub == "web_list": + try: + # # logger.info(request) + # data = [] + # logger.info("db :::>", ModelLinkkf.web_list(request)) + data = [{}] + dummy_data = {"ret": "success", "method": "web_list", "data": data} + return jsonify(ModelLinkkf.web_list(request)) + # return jsonify(dummy_data) + except Exception as e: + logger.error("Exception: %s", e) + logger.error(traceback.format_exc()) + elif sub == "db_remove": + return jsonify(ModelLinkkf.delete_by_id(request.form["id"])) + # reset_db + elif sub == "reset_db": + ret = {} + res = False + try: + res = LogicInflearn.reset_db() + if res: + ret["ret"] = "success" + + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + +######################################################### +# API +######################################################### +@blueprint.route("/api/<sub>", methods=["GET", "POST"]) +def api(sub): + logger.debug("api %s %s", package_name, sub) + + +######################################################### +# socketio +######################################################### +sid_list = [] + + +@socketio.on("connect", namespace="/%s" % package_name) +def connect(): + try: + sid_list.append(request.sid) + tmp = None + from .logic_queue import QueueEntity + + data = [_.__dict__ for _ in QueueEntity.entity_list] + tmp = json.dumps(data, cls=AlchemyEncoder) + tmp = json.loads(tmp) + emit("on_connect", tmp, namespace="/%s" % package_name) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + +@socketio.on("disconnect", namespace="/%s" % package_name) +def disconnect(): + try: + sid_list.remove(request.sid) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + +def socketio_callback(cmd, data): + if sid_list: + tmp = json.dumps(data, cls=AlchemyEncoder) + tmp = json.loads(tmp) + socketio.emit(cmd, tmp, namespace="/%s" % package_name, broadcast=True) + + +def socketio_list_refresh(): + data = [_.__dict__ for _ in QueueEntity.entity_list] + tmp = json.dumps(data, cls=AlchemyEncoder) + tmp = json.loads(tmp) + socketio_callback("list_refresh", tmp) diff --git a/static/css/inflearn_category.css b/static/css/inflearn_category.css new file mode 100644 index 0000000..2599583 --- /dev/null +++ b/static/css/inflearn_category.css @@ -0,0 +1,201 @@ +button.code-button { + min-width: 82px !important; +} +.tooltip { + position: relative; + display: block; +} + +[data-tooltip-text]:hover { + position: relative; +} + +[data-tooltip-text]:after { + -webkit-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + + background-color: rgba(0, 0, 0, 0.8); + + -webkit-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + -moz-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + color: #ffffff; + font-size: 12px; + margin-bottom: 10px; + padding: 7px 12px; + position: absolute; + width: auto; + min-width: 50px; + max-width: 300px; + word-wrap: break-word; + + z-index: 9999; + + opacity: 0; + left: -9999px; + top: 90%; + + content: attr(data-tooltip-text); +} + +[data-tooltip-text]:hover:after { + top: 230%; + left: 0; + opacity: 1; +} +[data-tooltip-text]:hover { + position: relative; +} + +[data-tooltip-text]:after { + -webkit-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + + background-color: rgba(0, 0, 0, 0.8); + + -webkit-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + -moz-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + color: #ffffff; + font-size: 12px; + margin-bottom: 10px; + padding: 7px 12px; + position: absolute; + width: auto; + min-width: 50px; + max-width: 300px; + word-wrap: break-word; + + z-index: 9999; + + opacity: 0; + left: -9999px; + top: -210% !important; + + content: attr(data-tooltip-text); +} + +[data-tooltip-text]:hover:after { + top: 130%; + left: 0; + opacity: 1; +} + +#airing_list { + display: none; +} + +.cut-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; +} + +@media (min-width: 576px) { + .container { + max-width: 100%; + } +} + +#screen_movie_list { + margin-top: 10px; +} +/* .spinner {*/ +/* width: 40px;*/ +/* height: 40px;*/ +/* background-color: #333;*/ + +/* margin: 100px auto;*/ +/* -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;*/ +/* animation: sk-rotateplane 1.2s infinite ease-in-out;*/ +/*}*/ + +/*@-webkit-keyframes sk-rotateplane {*/ +/* 0% { -webkit-transform: perspective(120px) }*/ +/* 50% { -webkit-transform: perspective(120px) rotateY(180deg) }*/ +/* 100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }*/ +/*}*/ + +/*@keyframes sk-rotateplane {*/ +/* 0% {*/ +/* transform: perspective(120px) rotateX(0deg) rotateY(0deg);*/ +/* -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)*/ +/* } 50% {*/ +/* transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);*/ +/* -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)*/ +/* } 100% {*/ +/* transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);*/ +/* -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);*/ +/* }*/ + +/*}*/ +.spinner { + width: 40px; + height: 40px; + + position: relative; + margin: 100px auto; +} + +.double-bounce1, +.double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #333; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + + -webkit-animation: sk-bounce 2s infinite ease-in-out; + animation: sk-bounce 2s infinite ease-in-out; +} + +.double-bounce2 { + -webkit-animation-delay: -1s; + animation-delay: -1s; +} + +@-webkit-keyframes sk-bounce { + 0%, + 100% { + -webkit-transform: scale(0); + } + 50% { + -webkit-transform: scale(1); + } +} + +@keyframes sk-bounce { + 0%, + 100% { + transform: scale(0); + -webkit-transform: scale(0); + } + 50% { + transform: scale(1); + -webkit-transform: scale(1); + } +} + +.badge-on-image { + position: absolute; + top: 2px; + /*bottom: 2px; !* position where you want it *!*/ + right: 2px; + padding: 5px 12px; +} diff --git a/static/css/inflearn_list.css b/static/css/inflearn_list.css new file mode 100644 index 0000000..3910d1c --- /dev/null +++ b/static/css/inflearn_list.css @@ -0,0 +1,192 @@ +{% extends "base.html" %} +{% block content %} +<!--{# <div>#}--> +<!--{# λ―Έκ΅¬ν˜„#}--> +<!--{# </div>#}--> +<div> + <form id="form_search" class="form-inline" style="text-align:left"> + <div class="container-fluid"> + <div class="row show-grid"> + <span class="col-md-4"> + <select id="order" name="order" class="form-control form-control-sm"> + <option value="desc">졜근순</option> + <option value="asc">였래된순</option> + </select> + <select id="option" name="option" class="form-control form-control-sm"> + <option value="all">전체</option> + <option value="completed">μ™„λ£Œ</option> + </select> + </span> + <span class="col-md-8"> + <input id="search_word" name="search_word" class="form-control form-control-sm w-75" type="text" placeholder="" aria-label="Search"> + <button id="search" class="btn btn-sm btn-outline-success">검색</button> + <button id="reset_btn" class="btn btn-sm btn-outline-success">리셋</button> + </span> + </div> + </div> + </form> + <div id='page1'></div> + {{ macros.m_hr_head_top() }} + {{ macros.m_row_start('0') }} + {{ macros.m_col(2, macros.m_strong('Poster')) }} + {{ macros.m_col(10, macros.m_strong('Info')) }} + {{ macros.m_row_end() }} + {{ macros.m_hr_head_bottom() }} + <div id="list_div"></div> + <div id='page2'></div> +</div> +<script type="text/javascript"> +const package_name = "{{arg['package_name']}}"; +// {#const sub = "{{arg['sub']}}";#} +// {#const sub = "list";#} +const sub = "" +let current_data = null; + +const get_list = (page, move_top=true) => { + let formData = get_formdata('#form_search') + // console.log(formData) + formData += '&page=' + page; + $.ajax({ + url: '/' + package_name + '/ajax/web_list', + type: "POST", + cache: false, + data: formData, + dataType: 'json', + success: (data) => { + current_data = data; + if (data) { + if (move_top) + window.scrollTo(0,0); + make_list(data.list) + // {#console.log(data)#} + // {#console.log(ret.data)#} + } else { + $.notify('<strong>뢄석 μ‹€νŒ¨</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); +} + +function sub_request_search(page, move_top=true) { + let formData = get_formdata('#form_search') + // console.log(formData) + formData += '&page=' + page; + $.ajax({ + url: '/' + package_name + '/ajax/web_list', + type: "POST", + cache: false, + data: formData, + dataType: "json", + success: function (data) { + current_data = data; + if (move_top) + window.scrollTo(0,0); + make_list(data.list) + make_page_html(data.paging) + } + }); +} + +$("body").on('click', '#remove_btn', function(e) { + e.preventDefault(); + let id = $(this).data('id'); + $.ajax({ + url: '/'+package_name+'/ajax/db_remove', + type: "POST", + cache: false, + data: {id:id}, + dataType: "json", + success: function (data) { + if (data) { + $.notify('<strong>μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.</strong>', { + type: 'success' + }); + sub_request_search(current_data.paging.current_page, false) + // get_list() + } else { + $.notify('<strong>μ‚­μ œ μ‹€νŒ¨</strong>', { + type: 'warning' + }); + } + } + }); +}); + +$(document).ready(function(){ + // {#global_sub_request_search('1');#} + get_list(1) +}); + +$("body").on('click', '#page', function(e){ + e.preventDefault(); + sub_request_search($(this).data('page')); +}); + +$("body").on('click', '#json_btn', function(e){ + e.preventDefault(); + var id = $(this).data('id'); + for (i in current_data.list) { + if (current_data.list[i].id == id) { + m_modal(current_data.list[i]) + } + } +}); + +$("body").on('click', '#self_search_btn', function(e){ + e.preventDefault(); + let search_word = $(this).data('title'); + document.getElementById("search_word").value = search_word; + sub_request_search('1') +}); + +$("body").on('click', '#request_btn', function(e){ + e.preventDefault(); + var content_code = $(this).data('content_code'); + $(location).attr('href', '/' + package_name + '/request?code=' + content_code) +}); + + +function make_list(data) { + //console.log(data) + let tmp, tmp2 = ''; + // console.log(data) + if (data.length > 0) { + let str = '' + for (let i in data) { + console.log(data[i]) + str += m_row_start(); + str += m_col(1, data[i].id); + tmp = (data[i].status == 'completed') ? 'μ™„λ£Œ' : 'λ―Έμ™„λ£Œ'; + str += m_col(1, tmp); + tmp = data[i].created_time + '(μΆ”κ°€)<br/>'; + if (data[i].completed_time != null) + tmp += data[i].completed_time + '(μ™„λ£Œ)'; + str += m_col(3, tmp) + tmp_save_path = (data[i].contents_json.save_path) ? (data[i].contents_json.save_path) : '' + tmp = tmp_save_path + '<br />' + data[i].contents_json.filename + '<br /><br />'; + tmp2 = m_button('json_btn', 'JSON', [{'key': 'id', 'value': data[i].id}]); + tmp2 += m_button('request_btn', 'μž‘ν’ˆ 검색', [{'key': 'content_code', 'value': data[i].contents_json.program_code}]); + tmp2 += m_button('self_search_btn', 'λͺ©λ‘ 검색', [{'key': 'title', 'value': data[i].contents_json.program_title}]); + tmp2 += m_button('remove_btn', 'μ‚­μ œ', [{'key': 'id', 'value': data[i].id}]); + tmp += m_button_group(tmp2) + str += m_col(7, tmp) + str += m_row_end(); + if (i != data.length - 1) str += m_hr(); + } + document.getElementById("list_div").innerHTML = str; + } else { + console.log('λͺ©λ‘μ—†μŠ΄') + return false; + } +} + + +</script> +<style> + @media (min-width: 576px) { + .container { + max-width: 98%; + } + } +</style> +{% endblock %} \ No newline at end of file diff --git a/static/css/inflearn_request.css b/static/css/inflearn_request.css new file mode 100755 index 0000000..8301317 --- /dev/null +++ b/static/css/inflearn_request.css @@ -0,0 +1,117 @@ +brg['current_code']}}";utton.code-button { + min-width: 82px !important; +} +.tooltip { + position: relative; + display: block; +} + +[data-tooltip-text]:hover { + position: relative; +} + +[data-tooltip-text]:after { + -webkit-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + + background-color: rgba(0, 0, 0, 0.8); + + -webkit-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + -moz-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + color: #ffffff; + font-size: 12px; + margin-bottom: 10px; + padding: 7px 12px; + position: absolute; + width: auto; + min-width: 50px; + max-width: 300px; + word-wrap: break-word; + + z-index: 9999; + + opacity: 0; + left: -9999px; + top: 90%; + + content: attr(data-tooltip-text); +} + +[data-tooltip-text]:hover:after { + top: 230%; + left: 0; + opacity: 1; +} +[data-tooltip-text]:hover { + position: relative; +} + +[data-tooltip-text]:after { + -webkit-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + + background-color: rgba(0, 0, 0, 0.8); + + -webkit-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + -moz-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + color: #ffffff; + font-size: 12px; + margin-bottom: 10px; + padding: 7px 12px; + position: absolute; + width: auto; + min-width: 50px; + max-width: 300px; + word-wrap: break-word; + + z-index: 9999; + + opacity: 0; + left: -9999px; + top: -210% !important; + + content: attr(data-tooltip-text); +} + +[data-tooltip-text]:hover:after { + top: 130%; + left: 0; + opacity: 1; +} + +#airing_list { + display: none; +} + +@media (min-width: 992px) { + .container { + max-width: 96%; + } +} +@media (min-width: 768px) { + .container { + max-width: 94%; + } +} +@media (min-width: 576px) { + .container { + max-width: 96%; + } + .form-inline .form-control { + width: 98%; + +} diff --git a/static/img_loader_x200.svg b/static/img_loader_x200.svg new file mode 100644 index 0000000..05f8d6f --- /dev/null +++ b/static/img_loader_x200.svg @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: rgb(241, 242, 243); display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"> +<g transform="translate(27.166666666666664,27.166666666666664)"> + <rect x="-18.5" y="-18.5" width="37" height="37" fill="#85a2b6"> + <animateTransform attributeName="transform" type="scale" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="1.1;1" begin="-0.3s"></animateTransform> + </rect> +</g> +<g transform="translate(72.83333333333333,27.166666666666664)"> + <rect x="-18.5" y="-18.5" width="37" height="37" fill="#bbcedd"> + <animateTransform attributeName="transform" type="scale" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="1.1;1" begin="-0.2s"></animateTransform> + </rect> +</g> +<g transform="translate(27.166666666666664,72.83333333333333)"> + <rect x="-18.5" y="-18.5" width="37" height="37" fill="#dce4eb"> + <animateTransform attributeName="transform" type="scale" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="1.1;1" begin="0s"></animateTransform> + </rect> +</g> +<g transform="translate(72.83333333333333,72.83333333333333)"> + <rect x="-18.5" y="-18.5" width="37" height="37" fill="#fdfdfd"> + <animateTransform attributeName="transform" type="scale" repeatCount="indefinite" dur="1s" keyTimes="0;1" values="1.1;1" begin="-0.1s"></animateTransform> + </rect> +</g> +<!-- [ldio] generated by https://loading.io/ --></svg> \ No newline at end of file diff --git a/static/js/inflearn_category.js b/static/js/inflearn_category.js new file mode 100644 index 0000000..93fd496 --- /dev/null +++ b/static/js/inflearn_category.js @@ -0,0 +1,791 @@ +// console.log("current_airing_data", current_airing_data); +let current_data = ""; +let current_airing_data = ""; +let current_screen_movie_data = null; +let code = ""; +let div_visible = false; +let page = 1; +let next_page = Number; +let current_cate = ""; +let total_page = ""; + +$("#anime_category #ing").on("click", function () { + let spinner = document.getElementById("spinner"); + spinner.style.display = "block"; + current_cate = "ing"; + get_anime_list(1, "ing"); +}); +$("#anime_category #movie").on("click", function () { + current_cate = "movie"; + get_anime_screen_movie(1); +}); +$("#anime_category #complete_anilist").on("click", function () { + current_cate = "complete"; + get_complete_anilist(1); +}); + +$("body").on("click", "#btn_search", function (e) { + e.preventDefault(); + let query = $("#input_search").val(); + // console.log(query); + + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } + + $.ajax({ + url: "/" + package_name + "/ajax/search", + type: "POST", + cache: false, + data: { query: query }, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + make_screen_movie_list(ret); + } else { + $.notify("<strong>뢄석 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +const get_airing_list = () => { + let spinner = document.getElementById("spinner"); + spinner.style.display = "block"; + $.ajax({ + url: "/" + package_name + "/ajax/airing_list", + type: "GET", + cache: false, + dataType: "json", + success: (ret) => { + current_airing_data = ret; + // console.log("ret::>", ret); + if (current_airing_data !== "") { + make_airing_list(ret); + div_visible = true; + spinner.style.display = "none"; + // console.log(div_visible); + } + }, + }); + + if (div_visible) { + // {#$('#airing_list').toggle()#} + } +}; + +const get_anime_list = (page, type) => { + let url = ""; + let data = { page: page, type: type }; + + switch (type) { + case "ing": + url = "/" + package_name + "/ajax/anime_list"; + current_cate = "ing"; + break; + case "movie": + url = "/" + package_name + "/ajax/screen_movie_list"; + current_cate = "movie"; + break; + case "complete": + url = "/" + package_name + "/ajax/screen_movie_list"; + current_cate = "complete"; + break; + default: + break; + } + + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret; + total_page = ret.total_page; + // console.log("ret::>", ret); + + if (current_screen_movie_data !== "") { + make_screen_movie_list(ret, page); + div_visible = true; + // console.log(div_visible); + // $("img.lazyload").lazyload({ + // threshold : 400, + // effect : "fadeIn", + // }); + } + next_page = page + 1; + }, + }); +}; + +const get_anime_screen_movie = (page) => { + let data = { page: page }; + $.ajax({ + url: "/" + package_name + "/ajax/screen_movie_list", + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret; + total_page = ret.total_page; + // console.log("ret::>", ret); + + if (current_screen_movie_data !== "") { + make_screen_movie_list(ret, page); + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); + div_visible = true; + } + next_page = page + 1; + }, + }); +}; + +const get_complete_anilist = (page) => { + let data = { page: page }; + $.ajax({ + url: "/" + package_name + "/ajax/complete_anilist", + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret; + console.log("get_complete_anilist():ret >", ret); + total_page = ret.total_page; + + if (current_screen_movie_data !== "") { + make_screen_movie_list(ret, page); + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); + div_visible = true; + } + next_page = page + 1; + }, + }); +}; + +// console.log(div_visible); + +$(document).on("click", "button.code-button", function (e) { + e.preventDefault(); + // console.log("click"); + // console.log("code to click:" + $(this).data("code")); + document.getElementById("code").value = $(this).data("code"); + $("#code").val($(this).data("code")); + $("#airing_list").toggle(); + code = document.getElementById("code").value; + document.getElementById("analysis_btn").click(); +}); +$(".code-button").tooltip(); + +$("body").on("click", "#analysis_btn", function (e) { + e.preventDefault(); + code = document.getElementById("code").value; + if (document.getElementById("code").value === "") { + console.log("code 값을 μž…λ ₯ ν•΄μ£Όμ„Έμš”."); + return; + } + + $.ajax({ + url: "/" + package_name + "/ajax/analysis", + type: "POST", + cache: false, + data: { code: code }, + dataType: "json", + success: function (ret) { + if (ret.ret) { + make_program(ret); + } else { + $.notify("<strong>뢄석 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#go_inflearn_btn", function (e) { + e.preventDefault(); + window.open(inflearn_url, "_blank"); +}); + +function make_airing_list(data) { + let str = ""; + let tmp = ""; + + tmp = + '<div id="exModal" class="form-inline" role="dialog" aria-hidden="true">'; + tmp += "</div>"; + str += m_hr_black(); + str += + '<div id="inner_airing" class="d-flex align-content-between flex-wrapd-flex align-content-between flex-wrap">'; + for (i in data.episode) { + tmp = + '<div class="mx-1 mb-1"><button id="code_button" data-code="' + + data.episode[i].code + + '" type="button" class="btn btn-primary code-button bootstrap-tooltip" data-toggle="button" data-tooltip="true" aria-pressed="true" autocomplete="off" data-placement="top">' + + '<span data-tooltip-text="' + + data.episode[i].title + + '">' + + data.episode[i].code + + "</span></button></div>"; + + str += tmp; + } + str += "</div>"; + str += m_hr_black(); + + document.getElementById("airing_list").innerHTML = str; + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); +} + +function make_screen_movie_list(data, page) { + let str = ""; + let tmp = ""; + + let page_elem = ""; + if (page === undefined) { + } else { + page_elem = '<span class="badge bg-warning">' + page + "</span>"; + } + + str += "<div>"; + str += + '<button type="button" class="btn btn-info">Page ' + + page_elem + + "</button>"; + str += "</div>"; + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.episode) { + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + // tmp += '<div class="card-header">'; + + // tmp += + // '<img class="card-img-top lazyload" src="./static/img_loader_x200.svg" data-original="' + data.episode[i].image_link + '" />'; + tmp += + '<img class="card-img-top lazy" src="./static/img_loader_x200.svg" data-lazy-src="' + + data.episode[i].image_link + + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + + data.episode[i].code + + "'\"/>"; + if (current_cate == "ing") { + tmp += + '<span class="badge badge-danger badge-on-image">' + + data.episode[i].chapter + + "</span>"; + } + tmp += '<div class="card-body">'; + tmp += '<h5 class="card-title">' + data.episode[i].title + "</h5>"; + tmp += + '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-success mb-1" data-code="' + + data.episode[i].code + + '"><p class="card-text">' + + data.episode[i].code + + " μžλ™ μΆ”κ°€</p></button>"; + tmp += + '<a href="./request?code=' + + data.episode[i].code + + '" class="btn btn-primary cut-text">' + + data.episode[i].title + + "</a>"; + tmp += "</div>"; + tmp += "</div>"; + // tmp += "</div>" + tmp += "</div>"; + str += tmp; + } + str += "</div>"; + str += m_hr_black(); + + if (page > 1) { + const temp = document.createElement("div"); + temp.innerHTML = str; + while (temp.firstChild) { + document.getElementById("screen_movie_list").appendChild(temp.firstChild); + } + page++; + } else { + document.getElementById("screen_movie_list").innerHTML = str; + } + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); +} + +const spinner = document.getElementById("spinner"); +const imagesContainer = document.getElementById("inner_screen_movie"); +const infiniteContainer = document.getElementById("screen_movie_list"); + +const loadNextAnimes = (cate, page) => { + spinner.style.display = "block"; + const data = { type: cate, page: String(page) }; + let url = ""; + + switch (cate) { + case "ing": + url = "/" + package_name + "/ajax/anime_list"; + current_cate = "ing"; + break; + case "movie": + url = "/" + package_name + "/ajax/screen_movie_list"; + current_cate = "movie"; + break; + case "complete": + url = "/" + package_name + "/ajax/anime_list"; + current_cate = "complete"; + break; + default: + break; + } + + // console.log('fetch_url::>', url) + console.log("cate::>", cate); + console.log("current_cate::>", current_cate); + + if (page > total_page) { + console.log("전체 νŽ˜μ΄μ§€ 초과"); + document.getElementById("spinner").style.display = "none"; + return false; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), + }) + .then((res) => { + document.getElementById("spinner").style.display = "block"; + return res.json(); + }) + .then((response) => { + // console.log("return page:::> ", response.page); + make_screen_movie_list(response, response.page); + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); +}; + +const onScroll = (e) => { + threshold = 50; + console.dir(e.target.scrollingElement.scrollHeight); + const { scrollTop, scrollHeight, clientHeight } = e.target.scrollingElement; + // if (Math.round(clientHeight + scrollTop) >= scrollHeight + threshold) { + if (clientHeight + scrollTop + threshold >= scrollHeight) { + document.getElementById("spinner").style.display = "block"; + // setTimeout() + console.log("loading"); + // console.log(current_cate) + // console.log("now page::> ", page); + // console.log("next_page::> ", next_page); + setTimeout(loadNextAnimes(current_cate, next_page), 1500); + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); + } +}; + +const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; +}; + +document.addEventListener("scroll", debounce(onScroll, 300)); + +function make_program(data) { + let str, + tmp = ""; + + tmp = '<div class="form-inline">'; + tmp += m_button("check_download_btn", "선택 λ‹€μš΄λ‘œλ“œ μΆ”κ°€", []); + tmp += m_button("all_check_on_btn", "전체 선택", []); + tmp += m_button("all_check_off_btn", "전체 ν•΄μ œ", []); + tmp += + '    <input id="new_title" name="new_title" class="form-control form-control-sm" value="' + + data.title + + '">'; + tmp += "</div>"; + tmp += '<div class="form-inline">'; + tmp += m_button("apply_new_title_btn", "μ €μž₯폴더λͺ… λ³€κ²½", []); + tmp += + '    <input id="new_season" name="new_season" class="form-control form-control-sm" value="' + + data.season + + '">'; + tmp += m_button("apply_new_season_btn", "μ‹œμ¦Œ λ³€κ²½ (숫자만 κ°€λŠ₯)", []); + tmp += m_button("search_tvdb_btn", "TVDB", []); + tmp += m_button("add_whitelist", "μŠ€μΌ€μ₯΄λ§ μΆ”κ°€", []); + tmp += "</div>"; + tmp = m_button_group(tmp); + str += tmp; + // program + str += m_hr_black(); + str += m_row_start(0); + tmp = ""; + if (data.poster_url != null) + tmp = '<img src="' + data.poster_url + '" class="img-fluid">'; + str += m_col(3, tmp); + tmp = ""; + tmp += m_row_start(0); + tmp += m_col(3, "제λͺ©", "right"); + tmp += m_col(9, data.title); + tmp += m_row_end(); + tmp += m_row_start(0); + tmp += m_col(3, "μ‹œμ¦Œ", "right"); + tmp += m_col(9, data.season); + tmp += m_row_end(); + for (i in data.detail) { + tmp += m_row_start(0); + key = Object.keys(data.detail[i])[0]; + value = data.detail[i][key]; + tmp += m_col(3, key, "right"); + tmp += m_col(9, value); + tmp += m_row_end(); + } + + str += m_col(9, tmp); + str += m_row_end(); + + str += m_hr_black(); + for (i in data.episode) { + str += m_row_start(); + // tmp = '<img src="' + data.episode[i].image + '" class="img-fluid">' + // str += m_col(3, tmp) + tmp = "<strong>" + data.episode[i].title + "</strong>"; + tmp += "<br>"; + tmp += data.episode[i].filename + "<br><p></p>"; + + tmp += '<div class="form-inline">'; + tmp += + '<input id="checkbox_' + + data.episode[i].code + + '" name="checkbox_' + + data.episode[i].code + + '" type="checkbox" checked data-toggle="toggle" data-on="μ„  택" data-off="-" data-onstyle="success" data-offstyle="danger" data-size="small">    '; + tmp += m_button("add_queue_btn", "λ‹€μš΄λ‘œλ“œ μΆ”κ°€", [ + { key: "code", value: data.episode[i].code }, + ]); + tmp += "</div>"; + str += m_col(12, tmp); + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle(); +} + +$("body").on("click", "#all_check_on_btn", function (e) { + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("on"); +}); + +$("body").on("click", "#all_check_off_btn", function (e) { + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("off"); +}); + +$("body").on("click", "#search_tvdb_btn", function (e) { + e.preventDefault(); + new_title = document.getElementById("new_title").value; + url = "https://www.thetvdb.com/search?query=" + new_title; + window.open(url, "_blank"); +}); + +$("body").on("click", "#add_whitelist", function (e) { + e.preventDefault(); + let data_code = $(this).attr("data-code"); + console.log(data_code); + $.ajax({ + url: "/" + package_name + "/ajax/add_whitelist", + type: "POST", + cache: false, + data: JSON.stringify({ data_code: data_code }), + contentType: "application/json;charset=UTF-8", + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("<strong>μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong><br>", { + type: "success", + }); + make_program(ret); + } else { + $.notify("<strong>μΆ”κ°€ μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#apply_new_title_btn", function (e) { + e.preventDefault(); + new_title = document.getElementById("new_title").value; + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_title", + type: "POST", + cache: false, + data: { new_title: new_title }, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("<strong>μ μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong><br>", { + type: "success", + }); + // console.log(ret); + make_program(ret); + } else { + $.notify("<strong>적용 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#apply_new_season_btn", function (e) { + e.preventDefault(); + new_season = document.getElementById("new_season").value; + if ($.isNumeric(new_season) == false) { + $.notify("<strong>μ‹œμ¦Œμ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.</strong><br>" + ret.log, { + type: "warning", + }); + } else { + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_season", + type: "POST", + cache: false, + data: { new_season: new_season }, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("<strong>μ μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong><br>", { + type: "success", + }); + make_program(ret); + } else { + $.notify("<strong>적용 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + } +}); + +// ν•˜λ‚˜μ”© λ‹€μš΄λ‘œλ“œ μΆ”κ°€ +$("body").on("click", "#add_queue_btn", function (e) { + e.preventDefault(); + code = $(this).data("code"); + $.ajax({ + url: "/" + package_name + "/ajax/add_queue", + type: "POST", + cache: false, + data: { code: code }, + dataType: "json", + success: function (data) { + if (data.ret === "success") { + $.notify("<strong>λ‹€μš΄λ‘œλ“œ μž‘μ—…μ„ μΆ”κ°€ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong>", { + type: "success", + }); + } else if (data.ret === "fail") { + $.notify("<strong>이미 큐에 μžˆμŠ΅λ‹ˆλ‹€. μ‚­μ œ ν›„ μΆ”κ°€ν•˜μ„Έμš”.</strong>", { + type: "warning", + }); + } else if (data.ret === "no_data") { + $.notify("<strong>잘λͺ»λœ μ½”λ“œμž…λ‹ˆλ‹€.</strong>", { + type: "warning", + }); + } else { + $.notify("<strong>μΆ”κ°€ μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#check_download_btn", function (e) { + e.preventDefault(); + all = $('input[id^="checkbox_"]'); + str = ""; + for (i in all) { + if (all[i].checked) { + code = all[i].id.split("_")[1]; + str += code + ","; + } + } + if (str == "") { + $.notify("<strong>μ„ νƒν•˜μ„Έμš”.</strong>", { + type: "warning", + }); + return; + } + $.ajax({ + url: "/" + package_name + "/ajax/add_queue_checked_list", + type: "POST", + cache: false, + data: { code: str }, + dataType: "json", + success: function (data) { + if (data.ret == "success") { + $.notify("<strong>" + data.log + "개λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong>", { + type: "success", + }); + } else { + $.notify("<strong>" + data.log + "</strong>", { + type: "warning", + }); + } + }, + }); +}); + +// $("#go_modal_airing").on("shown.bs.modal", function () { +// // {#get_airing_list()#} +// $("#exModal").trigger("focus"); +// }); + +// $("#go_modal_airing").click(function (e) { +// e.preventDefault(); +// console.log("open modal"); +// // $("#exModal").bootstrapToggle(); +// if (current_airing_data === "") { +// get_airing_list(); +// } +// $("#inner_airing").toggle(); +// $("#airing_list").toggle(); +// }); + +// $("#go_modal_airing").attr("class", "btn btn-primary"); + +$(document).ready(function () { + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("μ—”ν„°ν‚€λ₯Ό λˆŒλ €μŠ΅λ‹ˆλ‹€."); + $("#btn_search").trigger("click"); + } + }); + let spinner = document.getElementById("spinner"); + spinner.style.display = "block"; + get_anime_list(1, "ing"); + // $("img.lazyload").lazyload({ + // threshold : 200, + // effect : "fadeIn", + // }); +}); + +// <!--<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.7.0/dist/lazyload.min.js"></script>--> + +// <!--<script>--> +// <!-- lazyLoadInstance.update();--> +// <!--</script>--> +// <!--<script>--> +// <!-- const lazyLoadInstance = new LazyLoad({--> +// <!-- // Your custom settings go here--> +// <!--});--> +// <!-- lazyLoadInstance.update()--> +// <!--</script>--> +window.lazyLoadOptions = { + elements_selector: + "img[data-lazy-src],.rocket-lazyload,iframe[data-lazy-src]", + data_src: "lazy-src", + data_srcset: "lazy-srcset", + data_sizes: "lazy-sizes", + class_loading: "lazyloading", + class_loaded: "lazyloaded", + threshold: 50, + callback_loaded: function (element) { + if ( + element.tagName === "IFRAME" && + element.dataset.rocketLazyload == "fitvidscompatible" + ) { + if (element.classList.contains("lazyloaded")) { + if (typeof window.jQuery != "undefined") { + if (jQuery.fn.fitVids) { + jQuery(element).parent().fitVids(); + } + } + } + } + }, +}; +window.addEventListener( + "LazyLoad::Initialized", + function (e) { + var lazyLoadInstance = e.detail.instance; + if (window.MutationObserver) { + var observer = new MutationObserver(function (mutations) { + var image_count = 0; + var iframe_count = 0; + var rocketlazy_count = 0; + mutations.forEach(function (mutation) { + for (var i = 0; i < mutation.addedNodes.length; i++) { + if ( + typeof mutation.addedNodes[i].getElementsByTagName !== "function" + ) { + continue; + } + if ( + typeof mutation.addedNodes[i].getElementsByClassName !== + "function" + ) { + continue; + } + images = mutation.addedNodes[i].getElementsByTagName("img"); + is_image = mutation.addedNodes[i].tagName == "IMG"; + iframes = mutation.addedNodes[i].getElementsByTagName("iframe"); + is_iframe = mutation.addedNodes[i].tagName == "IFRAME"; + rocket_lazy = + mutation.addedNodes[i].getElementsByClassName("rocket-lazyload"); + image_count += images.length; + iframe_count += iframes.length; + rocketlazy_count += rocket_lazy.length; + if (is_image) { + image_count += 1; + } + if (is_iframe) { + iframe_count += 1; + } + } + }); + if (image_count > 0 || iframe_count > 0 || rocketlazy_count > 0) { + lazyLoadInstance.update(); + } + }); + var b = document.getElementsByTagName("body")[0]; + var config = { + childList: !0, + subtree: !0, + }; + observer.observe(b, config); + } + }, + !1 +); diff --git a/static/js/inflearn_list.js b/static/js/inflearn_list.js new file mode 100644 index 0000000..3d959dd --- /dev/null +++ b/static/js/inflearn_list.js @@ -0,0 +1,155 @@ +const sub = ""; +let current_data = null; + +const get_list = (page, move_top = true) => { + let formData = get_formdata("#form_search"); + // console.log(formData) + formData += "&page=" + page; + $.ajax({ + url: "/" + package_name + "/ajax/web_list", + type: "POST", + cache: false, + data: formData, + dataType: "json", + success: (data) => { + current_data = data; + if (data) { + if (move_top) window.scrollTo(0, 0); + make_list(data.list); + // {#console.log(data)#} + // {#console.log(ret.data)#} + } else { + $.notify("<strong>뢄석 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}; + +function sub_request_search(page, move_top = true) { + let formData = get_formdata("#form_search"); + // console.log(formData) + formData += "&page=" + page; + $.ajax({ + url: "/" + package_name + "/ajax/web_list", + type: "POST", + cache: false, + data: formData, + dataType: "json", + success: function (data) { + current_data = data; + if (move_top) window.scrollTo(0, 0); + make_list(data.list); + make_page_html(data.paging); + }, + }); +} + +$("body").on("click", "#remove_btn", function (e) { + e.preventDefault(); + let id = $(this).data("id"); + $.ajax({ + url: "/" + package_name + "/ajax/db_remove", + type: "POST", + cache: false, + data: { id: id }, + dataType: "json", + success: function (data) { + if (data) { + $.notify("<strong>μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.</strong>", { + type: "success", + }); + sub_request_search(current_data.paging.current_page, false); + // get_list() + } else { + $.notify("<strong>μ‚­μ œ μ‹€νŒ¨</strong>", { + type: "warning", + }); + } + }, + }); +}); + +$(document).ready(function () { + // {#global_sub_request_search('1');#} + get_list(1); +}); + +$("body").on("click", "#page", function (e) { + e.preventDefault(); + sub_request_search($(this).data("page")); +}); + +$("body").on("click", "#json_btn", function (e) { + e.preventDefault(); + var id = $(this).data("id"); + for (i in current_data.list) { + if (current_data.list[i].id == id) { + m_modal(current_data.list[i]); + } + } +}); + +$("body").on("click", "#self_search_btn", function (e) { + e.preventDefault(); + let search_word = $(this).data("title"); + document.getElementById("search_word").value = search_word; + sub_request_search("1"); +}); + +$("body").on("click", "#request_btn", function (e) { + e.preventDefault(); + var content_code = $(this).data("content_code"); + $(location).attr( + "href", + "/" + package_name + "/request?code=" + content_code + ); +}); + +function make_list(data) { + //console.log(data) + let tmp, + tmp2 = ""; + // console.log(data) + if (data.length > 0) { + let str = ""; + for (let i in data) { + console.log(data[i]); + str += m_row_start(); + str += m_col(1, data[i].id); + tmp = data[i].status == "completed" ? "μ™„λ£Œ" : "λ―Έμ™„λ£Œ"; + str += m_col(1, tmp); + tmp = data[i].created_time + "(μΆ”κ°€)<br/>"; + if (data[i].completed_time != null) + tmp += data[i].completed_time + "(μ™„λ£Œ)"; + str += m_col(3, tmp); + tmp_save_path = data[i].contents_json.save_path + ? data[i].contents_json.save_path + : ""; + tmp = + tmp_save_path + + "<br />" + + data[i].contents_json.filename + + "<br /><br />"; + tmp2 = m_button("json_btn", "JSON", [{ key: "id", value: data[i].id }]); + tmp2 += m_button("request_btn", "μž‘ν’ˆ 검색", [ + { key: "content_code", value: data[i].contents_json.program_code }, + ]); + tmp2 += m_button("self_search_btn", "λͺ©λ‘ 검색", [ + { key: "title", value: data[i].contents_json.program_title }, + ]); + tmp2 += m_button("remove_btn", "μ‚­μ œ", [ + { key: "id", value: data[i].id }, + ]); + tmp += m_button_group(tmp2); + str += m_col(7, tmp); + str += m_row_end(); + if (i != data.length - 1) str += m_hr(); + } + document.getElementById("list_div").innerHTML = str; + } else { + console.log("λͺ©λ‘μ—†μŠ΄"); + return false; + } +} diff --git a/static/js/inflearn_request.js b/static/js/inflearn_request.js new file mode 100755 index 0000000..3768873 --- /dev/null +++ b/static/js/inflearn_request.js @@ -0,0 +1,432 @@ +let current_data = ""; +let current_airing_data = ""; +let code = ""; +let div_visible = false; +let total_page = ""; + +const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), +}); + +// console.log('current_airing_data', current_airing_data); + +const get_airing_list = () => { + $.ajax({ + url: "/" + package_name + "/ajax/airing_list", + type: "GET", + cache: false, + dataType: "json", + success: (ret) => { + if (ret.ret == "success" && ret.episode != null) { + current_airing_data = ret; + total_page = ret.total_page; + // console.log(ret) + if (current_airing_data !== "") { + make_airing_list(ret); + div_visible = true; + // console.log(div_visible) + } + } else { + $.notify("<strong>뢄석 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + + if (div_visible) { + // {#$('#airing_list').toggle()#} + } +}; + +// console.log(div_visible) + +$(document).on("click", "button.code-button", function (e) { + e.preventDefault(); + // console.log('click') + // console.log('code to click:' + $(this).data("code")) + document.getElementById("code").value = $(this).data("code"); + $("#code").val($(this).data("code")); + $("#airing_list").toggle(); + code = document.getElementById("code").value; + document.getElementById("analysis_btn").click(); +}); +$(".code-button").tooltip(); + +$("body").on("click", "#analysis_btn", function (e) { + e.preventDefault(); + if (document.getElementById("code").value !== "") { + code = document.getElementById("code").value; + } + // console.log('#analysis_btn >>> code::', code) + if (code === "") { + console.log("code 값을 μž…λ ₯ν•΄μ£Όμ„Έμš”."); + $.notify("<strong>code 값을 μž…λ ₯ν•΄μ£Όμ„Έμš”.</strong><br>"); + return; + } + + $.ajax({ + url: "/" + package_name + "/ajax/analysis", + type: "POST", + cache: false, + data: { code: code }, + dataType: "json", + success: function (ret) { + if (ret.ret == "success" && ret.data != null) { + make_program(ret.data); + } else { + $.notify("<strong>뢄석 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#go_inflearn_btn", function (e) { + e.preventDefault(); + window.open(inflearn_url, "_blank"); +}); + +function make_airing_list(data) { + let str = ""; + let tmp = ""; + + tmp = + '<div id="exModal" class="form-inline" role="dialog" aria-hidden="true">'; + tmp += "</div>"; + str += m_hr_black(); + // {#str += m_row_start(0);#} + // {##} + // {#str += m_row_end();#} + // {#str += m_hr_black();#} + str += + '<div id="inner_airing" class="d-flex align-content-between flex-wrapd-flex align-content-between flex-wrap">'; + for (i in data.episode) { + // {#str += m_row_start();#} + // {#tmp = '<div class="col-sm"><strong>' + data.episode[i].title+ '</strong>';#} + // + // {#tmp += '<br />'#} + // {#tmp += '' + data.episode[i].code + '</div>';#} + // {#str += m_col(12, tmp)#} + tmp = + '<div class="mx-1 mb-1"><button id="code_button" data-code="' + + data.episode[i].code + + '" type="button" class="btn btn-primary code-button bootstrap-tooltip" data-toggle="button" data-tooltip="true" aria-pressed="true" autocomplete="off" data-placement="top">' + + '<span data-tooltip-text="' + + data.episode[i].title + + '">' + + data.episode[i].code + + "</span></button></div>"; + // {#if (i === 10) {#} + // {# tmp += '<div class="w-100"></div>'#} + str += tmp; + } + str += "</div>"; + str += m_hr_black(); + + document.getElementById("airing_list").innerHTML = str; +} + +function make_program(data) { + current_data = data; + // console.log('current_data:: ', data) + str = ""; + tmp = '<div class="form-inline w-100">'; + tmp += m_button("check_download_btn", "선택 λ‹€μš΄λ‘œλ“œ μΆ”κ°€", []); + tmp += m_button("all_check_on_btn", "전체 선택", []); + tmp += m_button("all_check_off_btn", "전체 ν•΄μ œ", []); + tmp += + '   <input id="new_title" name="new_title" class="form-control form-control-sm" value="' + + data.title + + '">'; + tmp += "</div>"; + tmp += '<div class="form-inline">'; + tmp += m_button("apply_new_title_btn", "μ €μž₯폴더λͺ… λ³€κ²½", []); + tmp += + '   <input id="new_season" name="new_season" class="form-control form-control-sm" value="' + + data.season + + '">'; + tmp += m_button("apply_new_season_btn", "μ‹œμ¦Œ λ³€κ²½ (숫자만 κ°€λŠ₯)", []); + tmp += m_button("search_tvdb_btn", "TVDB", []); + tmp += m_button("add_whitelist", "μŠ€μΌ€μ₯΄λ§ μΆ”κ°€", []); + tmp += "</div>"; + tmp = m_button_group(tmp); + str += tmp; + // program + str += m_hr_black(); + str += m_row_start(0); + tmp = ""; + if (data.poster_url != null) + tmp = '<img src="' + data.poster_url + '" class="img-fluid">'; + str += m_col(3, tmp); + tmp = ""; + tmp += m_row_start(0); + tmp += m_col(3, "제λͺ©", "right"); + tmp += m_col(9, data.title); + tmp += m_row_end(); + tmp += m_row_start(0); + tmp += m_col(3, "μ‹œμ¦Œ", "right"); + tmp += m_col(9, data.season); + tmp += m_row_end(); + for (i in data.detail) { + tmp += m_row_start(0); + key = Object.keys(data.detail[i])[0]; + value = data.detail[i][key]; + tmp += m_col(3, key, "right"); + tmp += m_col(9, value); + tmp += m_row_end(); + } + + str += m_col(9, tmp); + str += m_row_end(); + + str += m_hr_black(); + for (i in data.episode) { + str += m_row_start(); + // tmp = '<img src="' + data.episode[i].image + '" class="img-fluid">' + // str += m_col(3, tmp) + tmp = "<strong>" + data.episode[i].title + "</strong>"; + tmp += "<br>"; + tmp += data.episode[i].filename + "<br><p></p>"; + + tmp += '<div class="form-inline">'; + tmp += + '<input id="checkbox_' + + data.episode[i].code + + '" name="checkbox_' + + data.episode[i].code + + '" type="checkbox" checked data-toggle="toggle" data-on="μ„  택" data-off="-" data-onstyle="success" data-offstyle="danger" data-size="small">    '; + // tmp += m_button('add_queue_btn', 'λ‹€μš΄λ‘œλ“œ μΆ”κ°€', [{'key': 'code', 'value': data.episode[i].code}]) + tmp += m_button("add_queue_btn", "λ‹€μš΄λ‘œλ“œ μΆ”κ°€", [ + { key: "idx", value: i }, + ]); + tmp += "</div>"; + str += m_col(12, tmp); + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle(); +} + +$("body").on("click", "#all_check_on_btn", function (e) { + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("on"); +}); + +$("body").on("click", "#all_check_off_btn", function (e) { + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("off"); +}); + +$("body").on("click", "#search_tvdb_btn", function (e) { + e.preventDefault(); + new_title = document.getElementById("new_title").value; + url = "https://www.thetvdb.com/search?query=" + new_title; + window.open(url, "_blank"); +}); + +$("body").on("click", "#add_whitelist", function (e) { + e.preventDefault(); + $.ajax({ + url: "/" + package_name + "/ajax/add_whitelist", + type: "POST", + cache: false, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("<strong>μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong><br>", { + type: "success", + }); + make_program(ret); + } else { + $.notify("<strong>μΆ”κ°€ μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#apply_new_title_btn", function (e) { + e.preventDefault(); + new_title = document.getElementById("new_title").value; + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_title", + type: "POST", + cache: false, + data: { new_title: new_title }, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("<strong>μ μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong><br>", { + type: "success", + }); + // console.log(ret) + make_program(ret); + } else { + $.notify("<strong>적용 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#apply_new_season_btn", function (e) { + e.preventDefault(); + new_season = document.getElementById("new_season").value; + if ($.isNumeric(new_season) == false) { + $.notify("<strong>μ‹œμ¦Œμ€ μˆ«μžμ—¬μ•Ό ν•©λ‹ˆλ‹€.</strong><br>" + ret.log, { + type: "warning", + }); + } else { + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_season", + type: "POST", + cache: false, + data: { new_season: new_season }, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("<strong>μ μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong><br>", { + type: "success", + }); + make_program(ret); + } else { + $.notify("<strong>적용 μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + } +}); + +// ν•˜λ‚˜μ”© λ‹€μš΄λ‘œλ“œ μΆ”κ°€ +$("body").on("click", "#add_queue_btn", function (e) { + e.preventDefault(); + // code = $(this).data('code'); + code = current_data.episode[$(this).data("idx")].code; + // console.log('code:: ', code) + let data = current_data.episode[$(this).data("idx")]; + // console.log('data:: ', data) + $.ajax({ + url: "/" + package_name + "/ajax/add_queue", + type: "POST", + cache: false, + data: { code: code, data: JSON.stringify(data) }, + dataType: "json", + success: function (data) { + // console.log('#add_queue_btn::data >>', data) + if (data.ret === "enqueue_db_append") { + $.notify("<strong>λ‹€μš΄λ‘œλ“œ μž‘μ—…μ„ μΆ”κ°€ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong>", { + type: "success", + }); + } else if (data.ret === "enqueue_db_exist") { + $.notify("<strong>DB에 μ‘΄μž¬ν•˜λŠ” μ—ν”Όμ†Œλ“œμž…λ‹ˆλ‹€.</strong>", { + type: "warning", + }); + } else if (data.ret === "db_completed") { + $.notify("<strong>DB에 μ™„λ£Œ 기둝이 μžˆμŠ΅λ‹ˆλ‹€.</strong>", { + type: "warning", + }); + } else if (data.ret === "fail") { + $.notify("<strong>이미 큐에 μžˆμŠ΅λ‹ˆλ‹€. μ‚­μ œ ν›„ μΆ”κ°€ν•˜μ„Έμš”.</strong>", { + type: "warning", + }); + } else if (data.ret === "no_data") { + $.notify("<strong>잘λͺ»λœ μ½”λ“œμž…λ‹ˆλ‹€.</strong>", { + type: "warning", + }); + } else if (data.ret === "Debugging") { + $.notify("<strong>Debugging</strong>", { + type: "warning", + }); + } else { + $.notify("<strong>μΆ”κ°€ μ‹€νŒ¨</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#check_download_btn", function (e) { + e.preventDefault(); + all = $('input[id^="checkbox_"]'); + str = ""; + for (i in all) { + if (all[i].checked) { + code = all[i].id.split("_")[1]; + str += code + ","; + } + } + if (str == "") { + $.notify("<strong>μ„ νƒν•˜μ„Έμš”.</strong>", { + type: "warning", + }); + return; + } + $.ajax({ + url: "/" + package_name + "/ajax/add_queue_checked_list", + type: "POST", + cache: false, + data: { code: str }, + dataType: "json", + success: function (data) { + if (data.ret == "success") { + $.notify("<strong>" + data.log + "개λ₯Ό μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.</strong>", { + type: "success", + }); + } else { + $.notify("<strong>" + data.log + "</strong>", { + type: "warning", + }); + } + }, + }); +}); + +$("#go_modal_airing").on("shown.bs.modal", function () { + // {#get_airing_list()#} + $("#exModal").trigger("focus"); +}); + +$("#go_modal_airing").click(function (e) { + e.preventDefault(); + // console.log('open modal') + $("#exModal").bootstrapToggle(); + if (current_airing_data === "") { + get_airing_list(); + } + $("#inner_airing").toggle(); + $("#airing_list").toggle(); +}); + +$("#go_modal_airing").attr("class", "btn btn-primary"); + +// {#$(function () {#} +// {# $('[data-tooltip="true"]').tooltip()#} +// {#});#} + +// <!--{#--> +// <!--<script src="https://unpkg.com/@popperjs/core@2"></script>--> +// <!--#} {#--> +// <!--<script src="https://unpkg.com/tippy.js@6"></script>--> +// <!--#} {#--> +// <!--<script>--> +// <!-- // #}--> +// <!-- // {# tippy('.code-button',#}--> +// <!-- // {# { content: 'aaaa', // tooltip λ‚΄μš©#}--> +// <!-- // {# placement: 'top', // 상단 μœ„μΉ˜#}--> +// <!-- // {# trigger: 'hover', // :hoverκ°€ μ•„λ‹Œ clickμ‹œ λ…ΈμΆœ#}--> +// <!-- // {# hideOnClick : 'toggle', // clickμ‹œ, toggle() μ•‘μ…˜#}--> +// <!-- // {# animation: 'shift-away' // μ•„λž˜μ—μ„œ μœ„λ‘œ 올라였며 λ“±μž₯#}--> +// <!-- // {# })#}--> +// <!-- // {##}--> +// <!-- // {##}--> +// <!-- // {#--> +// <!--</script>--> +// <!--#}-- diff --git a/templates/inflearn_category.html b/templates/inflearn_category.html new file mode 100755 index 0000000..1fe1dc8 --- /dev/null +++ b/templates/inflearn_category.html @@ -0,0 +1,74 @@ +{% extends "base.html" %} {% block content %} + +<div> + <div class="input-group mb-2"> + <input + id="input_search" + type="search" + class="form-control rounded" + placeholder="Search" + aria-label="Search" + aria-describedby="search-addon" + /> + <button id="btn_search" type="button" class="btn btn-outline-primary"> + search + </button> + </div> + <div + id="anime_category" + class="btn-group" + role="group" + aria-label="Basic example" + > + <button id="ing" type="button" class="btn btn-success">λ°©μ˜μ€‘</button> + <button id="movie" type="button" class="btn btn-primary">κ·Ήμž₯판</button> + <button id="complete_anilist" type="button" class="btn btn-dark"> + μ™„κ²° + </button> + </div> + + <form id="airing_list_form"> + <div id="airing_list"></div> + </form> + <form id="screen_movie_list_form"> + <div id="screen_movie_list" class="container"></div> + </form> + <!-- <div class="text-center">--> + <!-- <div id="spinner" class="spinner-border" role="status">--> + <!-- <span class="sr-only">Loading...</span>--> + <!-- </div>--> + <!-- </div>--> + + <form id="program_auto_form"> + <div id="episode_list"></div> + </form> + <div class="spinner" id="spinner" style="display: none"> + <div class="double-bounce1"></div> + <div class="double-bounce2"></div> + </div> +</div> +<!--전체--> +<link + href="{{ url_for('.static', filename='css/%s.css' % arg['template_name']) }}" + type="text/css" + rel="stylesheet" +/> +<script> + "use strict"; + const package_name = '{{ arg["package_name"] }}'; + const inflearn_url = "{{arg['inflearn_url']}}"; +</script> + +<script + src="https://cdnjs.cloudflare.com/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js" + integrity="sha512-jNDtFf7qgU0eH/+Z42FG4fw3w7DM/9zbgNPe3wfJlCylVDTT3IgKW5r92Vy9IHa6U50vyMz5gRByIu4YIXFtaQ==" + crossorigin="anonymous" + referrerpolicy="no-referrer" +></script> + +<script src="{{ url_for('.static', filename='js/%s.js' % arg['template_name']) }}"></script> +<script + async + src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.7.0/dist/lazyload.min.js" +></script> +{% endblock %} diff --git a/templates/inflearn_list.html b/templates/inflearn_list.html new file mode 100644 index 0000000..5913519 --- /dev/null +++ b/templates/inflearn_list.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} {% block content %} + +<div> + <form id="form_search" class="form-inline" style="text-align: left"> + <div class="container-fluid"> + <div class="row show-grid"> + <span class="col-md-4"> + <select id="order" name="order" class="form-control form-control-sm"> + <option value="desc">졜근순</option> + <option value="asc">였래된순</option> + </select> + <select + id="option" + name="option" + class="form-control form-control-sm" + > + <option value="all">전체</option> + <option value="completed">μ™„λ£Œ</option> + </select> + </span> + <span class="col-md-8"> + <input + id="search_word" + name="search_word" + class="form-control form-control-sm w-75" + type="text" + placeholder="" + aria-label="Search" + /> + <button id="search" class="btn btn-sm btn-outline-success"> + 검색 + </button> + <button id="reset_btn" class="btn btn-sm btn-outline-success"> + 리셋 + </button> + </span> + </div> + </div> + </form> + <div id="page1"></div> + {{ macros.m_hr_head_top() }} {{ macros.m_row_start('0') }} {{ macros.m_col(2, + macros.m_strong('Poster')) }} {{ macros.m_col(10, macros.m_strong('Info')) }} + {{ macros.m_row_end() }} {{ macros.m_hr_head_bottom() }} + <div id="list_div"></div> + <div id="page2"></div> +</div> +<link + href="{{ url_for('.static', filename='css/%s.css' % arg['template_name']) +}}" + type="text/css" + rel="stylesheet" +/> +<script type="text/javascript"> + const package_name = "{{arg['package_name']}}"; +</script> +<script src="{{ url_for('.static', filename='js/%s.js' % arg['template_name']) }}"></script> +<style> + @media (min-width: 576px) { + .container { + max-width: 98%; + } + } +</style> +{% endblock %} diff --git a/templates/inflearn_queue.html b/templates/inflearn_queue.html new file mode 100755 index 0000000..5f8d04e --- /dev/null +++ b/templates/inflearn_queue.html @@ -0,0 +1,138 @@ +{% extends "base.html" %} +{% block content %} + +<div width="100%"> + {{ macros.m_button_group([['reset_btn', 'μ΄ˆκΈ°ν™”'], ['delete_completed_btn', 'μ™„λ£Œ λͺ©λ‘ μ‚­μ œ'], ['go_ffmpeg_btn', 'Go FFMPEG']])}} + + {{ macros.m_row_start('0') }} + {{ macros.m_row_end() }} + + {{ macros.m_hr_head_top() }} + {{ macros.m_row_start('0') }} + {{ macros.m_col(1, macros.m_strong('Idx')) }} + {{ macros.m_col(2, macros.m_strong('CreatedTime')) }} + {{ macros.m_col(4, macros.m_strong('Filename (Title)')) }} +<!-- {{ macros.m_col(3, macros.m_strong('ProgramTitle')) }}--> + {{ macros.m_col(3, macros.m_strong('Status')) }} + {{ macros.m_col(1, macros.m_strong('Action')) }} + {{ macros.m_row_end() }} + {{ macros.m_hr_head_bottom() }} + <div id="download_list_div"></div> +</div> <!--전체--> + + +<script type="text/javascript"> +const package_name = 'linkkf-yommi'; +let current_data = null; + +$(document).ready(function(){ + const protocol = window.location.protocol; + const socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/" + package_name); + + socket.on('on_connect', function(data){ + if (data != null) { + on_start(data); + } + }); + + socket.on('status', function(data){ + on_status(data) + }); + + socket.on('list_refresh', function(data){ + on_start(data) + }); +}); + +function on_start(data) { + make_download_list(data) +} + +function on_status(data) { + // console.log(data) + let tmp = document.getElementById("progress_"+data.plugin_id) + if (tmp != null) { + document.getElementById("progress_"+data.plugin_id).style.width = data.data.percent+ '%'; + document.getElementById("progress_"+data.plugin_id+"_label").innerHTML = data.status + "(" + data.data.percent + "%)" + ' x' + ((data.data != null)?data.data.current_speed:''); + } +} + +function make_download_list(data) { + str = ''; + for (i in data) { + str += m_row_start(); + str += m_col(1, data[i].entity_id); + str += m_col(2, data[i].created_time); + tmp_filename = '<b>' + data[i].info.program_title + '</b><br />' + data[i].info.filename + str += m_col(4, tmp_filename); + // str += m_col(3, data[i].info.program_title); + + label = data[i].ffmpeg_status_kor + if (data[i].ffmpeg_percent != 0) { + label += '(' + data[i].ffmpeg_percent + '%)' + } + tmp = m_progress('progress_'+data[i].entity_id, data[i].ffmpeg_percent, label) + str += m_col(3, tmp); + + tmp = m_button('program_cancel_btn', 'μ·¨μ†Œ', [{'key':'id', 'value':data[i].entity_id}]); + tmp = m_button_group(tmp) + str += m_col(1, tmp) + str += m_row_end(); + if (i != data.length -1) str += m_hr(0); + } + document.getElementById("download_list_div").innerHTML = str; +} + +$("body").on('click', '#program_cancel_btn', function(e){ + e.preventDefault(); + entity_id = $(this).data('id') + send_data = {'command':'cancel', 'entity_id':entity_id} + program_auto_command(send_data) +}); + +$("body").on('click', '#reset_btn', function(e){ + e.preventDefault(); + entity_id = $(this).data('id') + send_data = {'command':'reset', 'entity_id':-1} + program_auto_command(send_data) +}); + +$("body").on('click', '#delete_completed_btn', function(e){ + e.preventDefault(); + entity_id = $(this).data('id') + send_data = {'command':'delete_completed', 'entity_id':-1} + program_auto_command(send_data) +}); + + +function program_auto_command(data) { + $.ajax({ + url: '/' + package_name + '/ajax/program_auto_command', + type: "POST", + cache: false, + data: data, + dataType: "json", + success: function (ret) { + if (ret.ret == 'notify') { + $.notify('<strong>'+ ret.log +'</strong>', { + type: 'warning' + }); + } + } + }); +} + +$("body").on('click', '#go_ffmpeg_btn', function(e){ + e.preventDefault(); + $(location).attr('href', '/ffmpeg') +}); + +</script> +<style> + @media (min-width: 576px) { + .container { + max-width: 98%; + } + } +</style> +{% endblock %} diff --git a/templates/inflearn_request.html b/templates/inflearn_request.html new file mode 100755 index 0000000..c0ca8b5 --- /dev/null +++ b/templates/inflearn_request.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} {% block content %} + +<div> + <form id="program_list"> + {{ macros.setting_input_text_and_buttons('code', 'μž‘ν’ˆ Code', + [['analysis_btn', '뢄석'], ['go_inflearn_btn', 'Go Linkkf'], + ['go_modal_airing', 'λ°©μ˜μ€‘']], desc='URL: https://linkkf.app/[μ½”λ“œ]/ + ν˜•μ‹μ—μ„œ μ½”λ“œλΆ€λΆ„ 숫자 (예> 22005 μ›ν”ΌμŠ€)') }} + <!-- {#--> + <!-- <botton--> + <!-- id="go_modal_airing"--> + <!-- type="button"--> + <!-- class="btn btn-primary"--> + <!-- data-toggle="modal"--> + <!-- data-target="#exModal"--> + <!-- >λ°©μ˜μ€‘</botton--> + <!-- >#}--> + </form> + <form id="airing_list_form"> + <div id="airing_list"></div> + </form> + <form id="program_auto_form"> + <div id="episode_list"></div> + </form> +</div> +<!--전체--> +<link + href="{{ url_for('.static', filename='css/%s.css' % arg['template_name']) +}}" + type="text/css" + rel="stylesheet" +/> +<script type="text/javascript"> + const package_name = "linkkf-yommi"; + const inflearn_url = "{{arg['inflearn_url']}}"; + + $(document).ready(function () { + // console.log ('current_code::', {{arg['current_code']}}) + // {#console.log(params)#} + // {#code = params.code#} + // {#console.log('code:::> ', code)#} + + if ("{{arg['current_code']}}" !== "") { + code = "{{arg['current_code']}}"; + document.getElementById("code").value = "{{arg['current_code']}}"; + document.getElementById("analysis_btn").click(); + + // {#$('[data-tooltip="true"]').tooltip();#} + // {#$('.bootstrap-tooltip').tooltip();#} + } + + // console.log('params.code:::> ', params.code) + + if (params.code === "") { + } else { + document.getElementById("code").value = params.code; + document.getElementById("analysis_btn").click(); + } + // console.log('code:::' ,code) + }); +</script> +<script src="{{ url_for('.static', filename='js/%s.js' % arg['template_name']) }}"></script> +{% endblock %} diff --git a/templates/inflearn_setting.html b/templates/inflearn_setting.html new file mode 100755 index 0000000..d07cd66 --- /dev/null +++ b/templates/inflearn_setting.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% block content %} + +<div> + {{ macros.m_button_group([['global_setting_save_btn', 'μ„€μ • μ €μž₯']])}} + {{ macros.m_row_start('5') }} + {{ macros.m_row_end() }} + <nav> + {{ macros.m_tab_head_start() }} + {{ macros.m_tab_head2('normal', '일반', true) }} + {{ macros.m_tab_head2('auto', 'ν™ˆν™”λ©΄ μžλ™', false) }} + {{ macros.m_tab_head2('action', '기타', false) }} + {{ macros.m_tab_head_end() }} + </nav> + <form id="setting"> + <div class="tab-content" id="nav-tabContent"> + {{ macros.m_tab_content_start('normal', true) }} + {{ macros.setting_input_text_and_buttons('inflearn_url', 'Linkkf URL', [['go_btn', 'GO']], value=arg['inflearn_url'], desc=None) }} + {{ macros.setting_input_text('download_path', 'μ €μž₯ 폴더', value=arg['download_path'], desc='μ •μƒμ μœΌλ‘œ μ™„λ£Œλœ 파일이 이동할 폴더 μž…λ‹ˆλ‹€. ') }} + {{ macros.setting_checkbox('auto_make_folder', '제λͺ© 폴더 생성', value=arg['auto_make_folder'], desc='제λͺ©μœΌλ‘œ 폴더λ₯Ό μƒμ„±ν•˜κ³  폴더 μ•ˆμ— λ‹€μš΄λ‘œλ“œν•©λ‹ˆλ‹€.') }} + {{ macros.setting_input_int('max_ffmpeg_process_count', 'λ™μ‹œ λ‹€μš΄λ‘œλ“œ 수', value=arg['max_ffmpeg_process_count'], desc='λ™μ‹œμ— λ‹€μš΄λ‘œλ“œ ν•  μ—ν”Όμ†Œλ“œ κ°―μˆ˜μž…λ‹ˆλ‹€.') }} +{# {{ macros.setting_global_scheduler_sub_button(arg['scheduler'], arg['is_running']) }}#} + {{ macros.setting_global_scheduler_button(arg['scheduler'], arg['is_running']) }} +{# {{ macros.setting_input_int('auto_interval', 'μŠ€μΌ€μ₯΄λ§ μ‹€ν–‰ μ£ΌκΈ°', value=arg['auto_interval'], min='1', placeholder='10', desc='minute λ‹¨μœ„') }}#} + {{ macros.setting_input_text('auto_interval', 'μŠ€μΌ€μ₯΄λ§ μ‹€ν–‰ μ£ΌκΈ°', value=arg['auto_interval'], col='3', placeholder='10', desc=['Inverval(minute λ‹¨μœ„)μ΄λ‚˜ Cron μ„€μ •']) }} + {{ macros.setting_checkbox('auto_start', 'μ‹œμž‘μ‹œ μžλ™μ‹€ν–‰', value=arg['auto_start'], desc='On : μ‹œμž‘μ‹œ μžλ™μœΌλ‘œ μŠ€μΌ€μ₯΄λŸ¬μ— λ“±λ‘λ©λ‹ˆλ‹€.') }} + {{ macros.setting_input_textarea('whitelist_program', 'μžλ™ λ‹€μš΄λ‘œλ“œ codes', value=arg['whitelist_program'], desc=['이 곳에 μžˆλŠ” code듀을 μŠ€μΌ€μ₯΄λŸ¬ μΌμ •λ§ˆλ‹€ μžλ™μœΌλ‘œ λ‹€μš΄λ‘œλ“œν•©λ‹ˆλ‹€.', ', λ˜λŠ” Enter둜 ꡬ뢄']) }} + <!-- <div id="inflearn_auto_make_folder_div" class="collapse"> --> + {{ macros.setting_input_text('inflearn_finished_insert', 'μ™„κ²° ν‘œμ‹œ', col='3', value=arg['inflearn_finished_insert'], desc=['μ™„κ²°λœ 컨텐츠 폴더λͺ… μ•žμ— 넣을 λ¬Έκ΅¬μž…λ‹ˆλ‹€.']) }} + {{ macros.setting_checkbox('inflearn_auto_make_season_folder', 'μ‹œμ¦Œ 폴더 생성', value=arg['inflearn_auto_make_season_folder'], desc=['On : Season 번호 폴더λ₯Ό λ§Œλ“­λ‹ˆλ‹€.']) }} + <!-- </div> --> + {{ macros.m_tab_content_end() }} + {{ macros.m_tab_content_start('auto', false) }} +{# {{ macros.setting_global_scheduler_sub_button(arg['scheduler'], arg['is_running']) }}#} +{# {{ macros.setting_input_text('ani365_interval', 'μŠ€μΌ€μ₯΄λ§ μ‹€ν–‰ 정보', value=arg['ani365_interval'], col='3', desc=['Inverval(minute λ‹¨μœ„)μ΄λ‚˜ Cron μ„€μ •']) }}#} +{# {{ macros.setting_checkbox('ani365_auto_start', 'μ‹œμž‘μ‹œ μžλ™μ‹€ν–‰', value=arg['ani365_auto_start'], desc='On : μ‹œμž‘μ‹œ μžλ™μœΌλ‘œ μŠ€μΌ€μ₯΄λŸ¬μ— λ“±λ‘λ©λ‹ˆλ‹€.') }}#} +{# {{ macros.setting_input_textarea('ani365_auto_code_list', 'μžλ™ λ‹€μš΄λ‘œλ“œν•  μž‘ν’ˆ μ½”λ“œ', desc=['all μž…λ ₯μ‹œ λͺ¨λ‘ λ°›κΈ°', 'κ΅¬λΆ„μž | λ˜λŠ” μ—”ν„°'], value=arg['ani365_auto_code_list'], row='10') }}#} +{# {{ macros.setting_checkbox('ani365_auto_mode_all', 'μ—ν”Όμ†Œλ“œ λͺ¨λ‘ λ°›κΈ°', value=arg['ani365_auto_mode_all'], desc=['On : 이전 μ—ν”Όμ†Œλ“œλ₯Ό λͺ¨λ‘ λ°›μŠ΅λ‹ˆλ‹€.', 'Off : μ΅œμ‹  μ—ν”Όμ†Œλ“œλ§Œ λ°›μŠ΅λ‹ˆλ‹€.']) }}#} + {{ macros.m_tab_content_end() }} + {{ macros.m_tab_content_start('action', false) }} + {{ macros.setting_button([['global_one_execute_sub_btn', '1회 μ‹€ν–‰']], left='1회 μ‹€ν–‰' ) }} + {{ macros.setting_button([['global_reset_db_sub_btn', 'DB μ΄ˆκΈ°ν™”']], left='DB정리' ) }} + {{ macros.m_tab_content_end() }} + <!-- {{ macros.setting_button([['global_setting_save_btn', 'μ„€μ • μ €μž₯']]) }} --> + </div> + </form> +</div> <!--전체--> + + +<script type="text/javascript"> +{# {{ arg }}#} +const package_name = "{{ arg['package_name'] }}"; +const sub = "{{ arg['sub'] }}"; +const current_data = null; + +$(document).ready(function(){ + use_collapse('include_date'); + use_collapse('inflearn_auto_make_folder'); + +}); + +$('#include_date').change(function() { + use_collapse('include_date'); +}); + +$('#inflearn_auto_make_folder').change(function() { + use_collapse('inflearn_auto_make_folder'); +}); + +$("body").on('click', '#go_btn', function(e){ + e.preventDefault(); + url = document.getElementById("inflearn_url").value + window.open(url, "_blank"); +}); + +$('#auto_interval').change( function () { + console.log('change interval'); +}) + +</script> +{% endblock %}