From 4cd21e759ce11fff8bdfcdc4a2285e4675cd03ec Mon Sep 17 00:00:00 2001 From: projectdx Date: Sat, 30 Apr 2022 16:45:26 +0900 Subject: [PATCH] first commit --- .gitignore | 5 + README.md | 6 + __init__.py | 11 + __pycache__/__init__.cpython-38.pyc | Bin 0 -> 263 bytes __pycache__/logic.cpython-38.pyc | Bin 0 -> 3969 bytes __pycache__/logic_queue.cpython-38.pyc | Bin 0 -> 8600 bytes __pycache__/model.cpython-38.pyc | Bin 0 -> 7090 bytes __pycache__/plugin.cpython-38.pyc | Bin 0 -> 8288 bytes info.json | 11 + logic.py | 143 +++++ logic_inflearn.py | 822 +++++++++++++++++++++++++ logic_queue.py | 449 ++++++++++++++ model.py | 223 +++++++ plugin.py | 433 +++++++++++++ static/css/inflearn_category.css | 201 ++++++ static/css/inflearn_list.css | 192 ++++++ static/css/inflearn_request.css | 117 ++++ static/img_loader_x200.svg | 23 + static/js/inflearn_category.js | 791 ++++++++++++++++++++++++ static/js/inflearn_list.js | 155 +++++ static/js/inflearn_request.js | 432 +++++++++++++ templates/inflearn_category.html | 74 +++ templates/inflearn_list.html | 64 ++ templates/inflearn_queue.html | 138 +++++ templates/inflearn_request.html | 63 ++ templates/inflearn_setting.html | 81 +++ 26 files changed, 4434 insertions(+) create mode 100644 .gitignore create mode 100755 README.md create mode 100755 __init__.py create mode 100644 __pycache__/__init__.cpython-38.pyc create mode 100644 __pycache__/logic.cpython-38.pyc create mode 100644 __pycache__/logic_queue.cpython-38.pyc create mode 100644 __pycache__/model.cpython-38.pyc create mode 100644 __pycache__/plugin.cpython-38.pyc create mode 100755 info.json create mode 100755 logic.py create mode 100755 logic_inflearn.py create mode 100755 logic_queue.py create mode 100755 model.py create mode 100755 plugin.py create mode 100644 static/css/inflearn_category.css create mode 100644 static/css/inflearn_list.css create mode 100755 static/css/inflearn_request.css create mode 100644 static/img_loader_x200.svg create mode 100644 static/js/inflearn_category.js create mode 100644 static/js/inflearn_list.js create mode 100755 static/js/inflearn_request.js create mode 100755 templates/inflearn_category.html create mode 100644 templates/inflearn_list.html create mode 100755 templates/inflearn_queue.html create mode 100755 templates/inflearn_request.html create mode 100755 templates/inflearn_setting.html 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 0000000000000000000000000000000000000000..873a7e81d165c4d01da818bf45ad805f4c368ce5 GIT binary patch literal 263 zcmYj~KWhR(5XEQjAE)8IL)vRxLRt}RQ`m?pwpmVgm4p3rtha%DBs;-xv#pi2U8>Ar z2oAg-^Jd_^ZPQEvr@9<=X9jR3^E?@vCF=N!P@tFr%LKET$ZRfhTZqDzqGX`54Yc_U zGoUTgWM`rtLhr^@SnXnZ8B9F%PWEo^|BdmTO3mLVI07F#0f%8 zJK^Vz2k%F|`S@JDbJdLbdnQ8oi)`I|_aQz#T*! literal 0 HcmV?d00001 diff --git a/__pycache__/logic.cpython-38.pyc b/__pycache__/logic.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c2d207c1a7095576f590218492a79932f00ca9fe GIT binary patch literal 3969 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 literal 0 HcmV?d00001 diff --git a/__pycache__/logic_queue.cpython-38.pyc b/__pycache__/logic_queue.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e03e4b8557491070acfe6c36013b48bbabec0961 GIT binary patch literal 8600 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 literal 0 HcmV?d00001 diff --git a/__pycache__/plugin.cpython-38.pyc b/__pycache__/plugin.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5ef30db3581d3841f36a5637a2c8d0cc5f78fd9 GIT binary patch literal 8288 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 λ°©λ¬Έν•΄μ„œ 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 %}