commit ce86032aa907e725b5a537554459596c973bec8d Author: projectdx Date: Sat Oct 29 17:21:14 2022 +0900 2 commit diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ff51500 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38389e3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyo +*.pyc \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..76e21fd --- /dev/null +++ b/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# @Time : 2022/02/08 2:55 PM +# @Author : yommi +# @Site : +# @File : __init__ +# @Software: PyCharm +# 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/bin/Darwin/chromedriver b/bin/Darwin/chromedriver new file mode 100755 index 0000000..94707e5 Binary files /dev/null and b/bin/Darwin/chromedriver differ diff --git a/bin/Linux/chromedriver b/bin/Linux/chromedriver new file mode 100755 index 0000000..696aabc Binary files /dev/null and b/bin/Linux/chromedriver differ diff --git a/info.yaml b/info.yaml new file mode 100644 index 0000000..1a27243 --- /dev/null +++ b/info.yaml @@ -0,0 +1,7 @@ +title: "애니 다운로더" +version: "0.1.0" +package_name: "anime_downloader" +developer: "projectdx" +description: "anime downloader" +home: "https://github.com/flaskfarm/ffmpeg" +more: "https://raw.githubusercontent.com/flaskfarm/ffmpeg/main/README.md" diff --git a/lib/_ffmpeg_queue.py b/lib/_ffmpeg_queue.py new file mode 100644 index 0000000..d6224f0 --- /dev/null +++ b/lib/_ffmpeg_queue.py @@ -0,0 +1,365 @@ +import abc +import os +import queue +import threading +import time +import traceback +from datetime import datetime + +import requests +# from flaskfarm.lib.plugin import get_model_setting +from flaskfarm.lib.support.expand.ffmpeg import SupportFfmpeg +# from flaskfarm.lib.system.setup import SystemModelSetting +from flaskfarm.lib.tool import ToolUtil +# from flaskfarm.lib.system.setup import P as SM +# from flaskfarm.lib.system.mod_setting import ModuleSetting as SM + +from ..setup import * + + +class FfmpegQueueEntity(abc.ABCMeta('ABC', (object,), {'__slots__': ()})): + + def __init__(self, P, module_logic, info): + self.P = P + # SupportFfmpeg.initialize() + self.module_logic = module_logic + self.entity_id = -1 # FfmpegQueueEntity.static_index + self.info = info + self.url = None + self.ffmpeg_status = -1 + self.ffmpeg_status_kor = u'대기중' + self.ffmpeg_percent = 0 + self.ffmpeg_arg = None + self.cancel = False + self.created_time = datetime.now().strftime('%m-%d %H:%M:%S') + self.savepath = None + self.filename = None + self.filepath = None + self.quality = None + self.headers = None + # FfmpegQueueEntity.static_index += 1 + # FfmpegQueueEntity.entity_list.append(self) + + def get_video_url(self): + return self.url + + def get_video_filepath(self): + return self.filepath + + @abc.abstractmethod + def refresh_status(self): + pass + + @abc.abstractmethod + def info_dict(self, tmp): + pass + + def download_completed(self): + pass + + def as_dict(self): + tmp = {} + tmp['entity_id'] = self.entity_id + tmp['url'] = self.url + tmp['ffmpeg_status'] = self.ffmpeg_status + tmp['ffmpeg_status_kor'] = self.ffmpeg_status_kor + tmp['ffmpeg_percent'] = self.ffmpeg_percent + tmp['ffmpeg_arg'] = self.ffmpeg_arg + tmp['cancel'] = self.cancel + tmp['created_time'] = self.created_time # .strftime('%m-%d %H:%M:%S') + tmp['savepath'] = self.savepath + tmp['filename'] = self.filename + tmp['filepath'] = self.filepath + tmp['quality'] = self.quality + # tmp['current_speed'] = self.ffmpeg_arg['current_speed'] if self.ffmpeg_arg is not None else '' + tmp = self.info_dict(tmp) + return tmp + + +class FfmpegQueue(object): + + def __init__(self, P, max_ffmpeg_count): + + self.P = P + self.static_index = 1 + self.entity_list = [] + self.current_ffmpeg_count = 0 + self.download_queue = None + self.download_thread = None + self.max_ffmpeg_count = max_ffmpeg_count + if self.max_ffmpeg_count is None or self.max_ffmpeg_count == '': + self.max_ffmpeg_count = 1 + + def queue_start(self): + try: + if self.download_queue is None: + self.download_queue = queue.Queue() + if self.download_thread is None: + self.download_thread = threading.Thread(target=self.download_thread_function, args=()) + self.download_thread.daemon = True + self.download_thread.start() + except Exception as exception: + self.P.logger.error('Exception:%s', exception) + self.P.logger.error(traceback.format_exc()) + + def download_thread_function(self): + while True: + try: + while True: + try: + if self.current_ffmpeg_count < self.max_ffmpeg_count: + break + time.sleep(5) + except Exception as exception: + self.P.logger.error('Exception:%s', exception) + self.P.logger.error(traceback.format_exc()) + self.P.logger.error('current_ffmpeg_count : %s', self.current_ffmpeg_count) + self.P.logger.error('max_ffmpeg_count : %s', self.max_ffmpeg_count) + break + entity = self.download_queue.get() + if entity.cancel: + continue + + # from .logic_ani24 import LogicAni24 + # entity.url = LogicAni24.get_video_url(entity.info['code']) + video_url = entity.get_video_url() + if video_url is None: + entity.ffmpeg_status_kor = 'URL실패' + entity.refresh_status() + # plugin.socketio_list_refresh() + continue + + # import ffmpeg + + # max_pf_count = 0 + # save_path = ModelSetting.get('download_path') + # if ModelSetting.get('auto_make_folder') == 'True': + # program_path = os.path.join(save_path, entity.info['filename'].split('.')[0]) + # save_path = program_path + # try: + # if not os.path.exists(save_path): + # os.makedirs(save_path) + # except: + # logger.debug('program path make fail!!') + # 파일 존재여부 체크 + print(entity.info) + filepath = entity.get_video_filepath() + if os.path.exists(filepath): + entity.ffmpeg_status_kor = '파일 있음' + entity.ffmpeg_percent = 100 + entity.refresh_status() + # plugin.socketio_list_refresh() + continue + dirname = os.path.dirname(filepath) + # filename = os.path.f + if not os.path.exists(dirname): + os.makedirs(dirname) + # f = ffmpeg.Ffmpeg(video_url, os.path.basename(filepath), plugin_id=entity.entity_id, listener=self.ffmpeg_listener, call_plugin=self.P.package_name, save_path=dirname, headers=entity.headers) + # print(filepath) + # print(os.path.basename(filepath)) + # print(dirname) + # aa_sm = get_model_setting("system", P.logger) + P.logger.debug(P) + # P.logger.debug(P.system_setting.get("port")) + + ffmpeg = SupportFfmpeg(video_url, os.path.basename(filepath), callback_function=self.callback_function, + max_pf_count=0, save_path=ToolUtil.make_path(dirname), timeout_minute=60, + ) + # + ffmpeg.start() + self.current_ffmpeg_count += 1 + self.download_queue.task_done() + + except Exception as exception: + self.P.logger.error('Exception:%s', exception) + self.P.logger.error(traceback.format_exc()) + + def callback_function(self, **args): + refresh_type = None + if args['type'] == 'status_change': + if args['status'] == SupportFfmpeg.Status.DOWNLOADING: + refresh_type = 'status_change' + elif args['status'] == SupportFfmpeg.Status.COMPLETED: + refresh_type = 'status_change' + elif args['status'] == SupportFfmpeg.Status.READY: + data = {'type': 'info', + 'msg': '다운로드중 Duration(%s)' % args['data']['duration_str'] + '
' + args['data'][ + 'save_fullpath'], 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['type'] == 'last': + if args['status'] == SupportFfmpeg.Status.WRONG_URL: + data = {'type': 'warning', 'msg': '잘못된 URL입니다'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['status'] == SupportFfmpeg.Status.WRONG_DIRECTORY: + data = {'type': 'warning', 'msg': '잘못된 디렉토리입니다.
' + args['data']['save_fullpath']} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['status'] == SupportFfmpeg.Status.ERROR or args['status'] == SupportFfmpeg.Status.EXCEPTION: + data = {'type': 'warning', 'msg': '다운로드 시작 실패.
' + args['data']['save_fullpath']} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['status'] == SupportFfmpeg.Status.USER_STOP: + data = {'type': 'warning', 'msg': '다운로드가 중지 되었습니다.
' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.COMPLETED: + data = {'type': 'success', 'msg': '다운로드가 완료 되었습니다.
' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.TIME_OVER: + data = {'type': 'warning', 'msg': '시간초과로 중단 되었습니다.
' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.PF_STOP: + data = {'type': 'warning', 'msg': 'PF초과로 중단 되었습니다.
' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.FORCE_STOP: + data = {'type': 'warning', 'msg': '강제 중단 되었습니다.
' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.HTTP_FORBIDDEN: + data = {'type': 'warning', 'msg': '403에러로 중단 되었습니다.
' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.ALREADY_DOWNLOADING: + data = {'type': 'warning', 'msg': '임시파일폴더에 파일이 있습니다.
' + args['data']['temp_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['type'] == 'normal': + if args['status'] == SupportFfmpeg.Status.DOWNLOADING: + refresh_type = 'status' + # P.logger.info(refresh_type) + self.socketio_callback(refresh_type, args['data']) + + def ffmpeg_listener(self, **arg): + import ffmpeg + entity = self.get_entity_by_entity_id(arg['plugin_id']) + if entity is None: + return + if arg['type'] == 'status_change': + if arg['status'] == ffmpeg.Status.DOWNLOADING: + pass + elif arg['status'] == ffmpeg.Status.COMPLETED: + entity.donwload_completed() + elif arg['status'] == ffmpeg.Status.READY: + pass + elif arg['type'] == 'last': + self.current_ffmpeg_count += -1 + elif arg['type'] == 'log': + pass + elif arg['type'] == 'normal': + pass + + entity.ffmpeg_arg = arg + entity.ffmpeg_status = int(arg['status']) + entity.ffmpeg_status_kor = str(arg['status']) + entity.ffmpeg_percent = arg['data']['percent'] + entity.ffmpeg_arg['status'] = str(arg['status']) + # self.P.logger.debug(arg) + # import plugin + # arg['status'] = str(arg['status']) + # plugin.socketio_callback('status', arg) + entity.refresh_status() + + # FfmpegQueueEntity.static_index += 1 + # FfmpegQueueEntity.entity_list.append(self) + + def add_queue(self, entity): + try: + # entity = QueueEntity.create(info) + # if entity is not None: + # LogicQueue.download_queue.put(entity) + # return True + entity.entity_id = self.static_index + self.static_index += 1 + self.entity_list.append(entity) + self.download_queue.put(entity) + return True + except Exception as exception: + self.P.logger.error('Exception:%s', exception) + self.P.logger.error(traceback.format_exc()) + return False + + def set_max_ffmpeg_count(self, max_ffmpeg_count): + self.max_ffmpeg_count = max_ffmpeg_count + + def get_max_ffmpeg_count(self): + return self.max_ffmpeg_count + + def command(self, cmd, entity_id): + self.P.logger.debug('command :%s %s', cmd, entity_id) + ret = {} + try: + if cmd == 'cancel': + self.P.logger.debug('command :%s %s', cmd, entity_id) + entity = self.get_entity_by_entity_id(entity_id) + if entity is not None: + if entity.ffmpeg_status == -1: + entity.cancel = True + entity.ffmpeg_status_kor = "취소" + # entity.refresh_status() + ret['ret'] = 'refresh' + elif entity.ffmpeg_status != 5: + ret['ret'] = 'notify' + ret['log'] = '다운로드중 상태가 아닙니다.' + else: + idx = entity.ffmpeg_arg['data']['idx'] + import ffmpeg + ffmpeg.Ffmpeg.stop_by_idx(idx) + entity.refresh_status() + ret['ret'] = 'refresh' + elif cmd == 'reset': + if self.download_queue is not None: + with self.download_queue.mutex: + self.download_queue.queue.clear() + for _ in self.entity_list: + if _.ffmpeg_status == 5: + import ffmpeg + idx = _.ffmpeg_arg['data']['idx'] + ffmpeg.Ffmpeg.stop_by_idx(idx) + self.entity_list = [] + ret['ret'] = 'refresh' + elif cmd == 'delete_completed': + new_list = [] + for _ in self.entity_list: + if _.ffmpeg_status_kor in [u'파일 있음', u'취소', u'사용자중지']: + continue + if _.ffmpeg_status != 7: + new_list.append(_) + self.entity_list = new_list + ret['ret'] = 'refresh' + elif cmd == 'remove': + new_list = [] + for _ in self.entity_list: + if _.entity_id == entity_id: + continue + new_list.append(_) + self.entity_list = new_list + ret['ret'] = 'refresh' + return ret + except Exception as exception: + self.P.logger.error('Exception:%s', exception) + self.P.logger.error(traceback.format_exc()) + + def get_entity_by_entity_id(self, entity_id): + for _ in self.entity_list: + if _.entity_id == entity_id: + return _ + return None + + def get_entity_list(self): + ret = [] + for x in self.entity_list: + tmp = x.as_dict() + ret.append(tmp) + return ret diff --git a/lib/util.py b/lib/util.py new file mode 100644 index 0000000..c25fcc1 --- /dev/null +++ b/lib/util.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +######################################################### +# python +import os +import re +import json +import traceback +import platform +import subprocess +# third-party +from sqlalchemy.ext.declarative import DeclarativeMeta +# sjva 공용 +from framework import app, logger + + +######################################################### + +def download(url, file_name): + try: + import requests + with open(file_name, "wb") as file_is: # open in binary mode + response = requests.get(url) # get request + file_is.write(response.content) # write to file + except Exception as exception: + logger.debug('Exception:%s', exception) + logger.debug(traceback.format_exc()) + + +def read_file(filename): + try: + import codecs + ifp = codecs.open(filename, 'r', encoding='utf8') + data = ifp.read() + ifp.close() + return data + except Exception as exception: + logger.error('Exception:%s', exception) + logger.error(traceback.format_exc()) + + +class Util(object): + @staticmethod + def change_text_for_use_filename(text): + # text = text.replace('/', '') + # 2021-07-31 X:X + # text = text.replace(':', ' ') + text = re.sub('[\\/:*?\"<>|]', ' ', text).strip() + text = re.sub("\s{2,}", ' ', text) + return text + + @staticmethod + def write_file(data, filename): + try: + import codecs + ofp = codecs.open(filename, 'w', encoding='utf8') + ofp.write(data) + ofp.close() + except Exception as exception: + logger.debug('Exception:%s', exception) + logger.debug(traceback.format_exc()) diff --git a/mod_anilife.py b/mod_anilife.py new file mode 100644 index 0000000..bf9a99d --- /dev/null +++ b/mod_anilife.py @@ -0,0 +1,1494 @@ +import os +import sys +import threading +import traceback +import json +from datetime import datetime +import hashlib +import re +import asyncio +import platform + +import lxml.etree + +# third-party +import requests +from lxml import html +from urllib import parse +import urllib + +packages = [ + "beautifulsoup4", + "requests-cache", + "cloudscraper", + "selenium_stealth", + "webdriver_manager", +] +for package in packages: + try: + import package + + except ImportError: + # main(["install", package]) + os.system(f"pip install {package}") + +from bs4 import BeautifulSoup +import cloudscraper + +# third-party +from flask import request, render_template, jsonify +from sqlalchemy import or_, and_, func, not_, desc + +# sjva 공용 +from framework import db, scheduler, path_data, socketio +from framework.util import Util +from framework import F +from plugin import ( + PluginModuleBase +) +from flaskfarm.lib.plugin._ffmpeg_queue import FfmpegQueueEntity, FfmpegQueue + +# from tool_base import d + +# 패키지 +# from .plugin import P +from .setup import * + +logger = P.logger + + +# =================================================================# + + +# 패키지 +class LogicAniLife(PluginModuleBase): + db_default = { + "anilife_db_version": "1", + "anilife_url": "https://anilife.live", + "anilife_download_path": os.path.join(path_data, P.package_name, "ohli24"), + "anilife_auto_make_folder": "True", + "anilife_auto_make_season_folder": "True", + "anilife_finished_insert": "[완결]", + "anilife_max_ffmpeg_process_count": "1", + "anilife_order_desc": "False", + "anilife_auto_start": "False", + "anilife_interval": "* 5 * * *", + "anilife_auto_mode_all": "False", + "anilife_auto_code_list": "all", + "anilife_current_code": "", + "anilife_uncompleted_auto_enqueue": "False", + "anilife_image_url_prefix_series": "https://www.jetcloud.cc/series/", + "anilife_image_url_prefix_episode": "https://www.jetcloud-list.cc/thumbnail/", + } + + current_headers = None + current_data = None + referer = None + origin_url = None + episode_url = None + cookies = None + + os_platform = platform.system() + + session = requests.Session() + headers = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", + "Referer": "", + "Cookie": "SPSI=ef307b8c976fac3363cdf420c9ca40a9; SPSE=+PhK0/uGUBMCZIgXplNjzqW3K2kXLybiElDTtOOiboHiBXO7Tp/9roMW7FplGZuGCUo3i4Fwx5VIUG57Zj6VVw==; anilife_csrf=b1eb92529839d7486169cd91e4e60cd2; UTGv2=h45f897818578a5664b31004b95a9992d273; _ga=GA1.1.281412913.1662803695; _ga_56VYJJ7FTM=GS1.1.1662803695.1.0.1662803707.0.0.0; DCST=pE9; DSR=w2XdPUpwLWDqkLpWXfs/5TiO4mtNv5O3hqNhEr7GP1kFoRBBzbFRpR+xsJd9A+E29M+we7qIvJxQmHQTjDNLuQ==; DCSS=696763EB4EA5A67C4E39CFA510FE36F19B0912C; DGCC=RgP; spcsrf=8a6b943005d711258f2f145a8404d873; sp_lit=F9PWLXyxvZbOyk3eVmtTlg==; PRLST=wW; adOtr=70fbCc39867" + # "Cookie": "" + # "Cookie": "_ga=GA1.1.578607927.1660813724; __gads=ID=10abb8b98b6828ae-2281c943a9d500fd:T=1660813741:RT=1660813741:S=ALNI_MYU_iB2lBgSrEQUBwhKpNsToaqQ8A; SL_G_WPT_TO=ko; SL_GWPT_Show_Hide_tmp=1; SL_wptGlobTipTmp=1; SPSI=944c237cdd8606d80e5e330a0f332d03; SPSE=itZcXMDuso0ktWnDkV2G0HVwWEctCgDjrcFMlEQ5C745wqvp1pEEddrsAsjPUBjl6/8+9Njpq1IG3wt/tVag7w==; sbtsck=jav9aILa6Ofn0dEQr5DhDq5rpbd1JUoNgKwxBpZrqYd+CM=; anilife_csrf=54ee9d15c87864ee5e2538a63d894ad6; UTGv2=h46b326af644f4ac5d0eb1502881136b3750; DCST=pE9; __gpi=UID=000008ba227e99e0:T=1660813741:RT=1661170429:S=ALNI_MaJHIVJIGpQ5nTE9lvypKQxJnn10A; DSR=GWyTLTvSMF/lQD77ojQkGyl+7JvTudkSwV1GKeNVUcWEBa/msln9zzsBj7lj+89ywSRBM34Ol73AKf+KHZ9bZA==; DCSS=9D44115EC4CE12CADB88A005DC65A3CD74A211E; DGCC=zdV; spcsrf=fba136251afc6b5283109fc920322c70; sp_lit=kw0Xkp66eQ7bV0f0tNClhg==; PRLST=gt; adOtr=2C4H9c4d78d; _ga_56VYJJ7FTM=GS1.1.1661168661.18.1.1661173389.0.0.0", + } + useragent = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, " + "like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" + } + + def __init__(self, P): + super(LogicAniLife, self).__init__(P, "setting", scheduler_desc="애니라이프 자동 다운로드") + self.name = "anilife" + self.queue = None + default_route_socketio_module(self, attach='/search') + + @staticmethod + def get_html(url: str, referer: str = None, stream: bool = False, timeout: int = 5): + data = "" + try: + print("cloudflare protection bypass ==================") + # return LogicAniLife.get_html_cloudflare(url) + return LogicAniLife.get_html_selenium(url, referer) + # return LogicAniLife.get_html_playwright(url) + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + return data + + @staticmethod + def get_html_requests( + url: str, referer: str = None, stream: str = False, timeout: int = 5 + ) -> str: + data = "" + try: + print("get_html_requests ==================") + + # cj = browser_cookie3.chrome(domain_name="anilife.live") + referer = "https://anilife.live/" + + if LogicAniLife.session is None: + LogicAniLife.session = requests.session() + + # logger.debug('get_html :%s', url) + LogicAniLife.headers["Referer"] = "" if referer is None else referer + LogicAniLife.headers[ + "Cookie" + ] = "_ga=GA1.1.578607927.1660813724; __gads=ID=10abb8b98b6828ae-2281c943a9d500fd:T=1660813741:RT=1660813741:S=ALNI_MYU_iB2lBgSrEQUBwhKpNsToaqQ8A; sbtsck=javuwDzcOJqUyweM1OQeNGzHbjoHp7Cgw44XnPdM738c3E=; SPSI=e48379959d54a6a62cc7abdcafdb2761; SPSE=h5HfMGLJzLqzNafMD3YaOvHSC9xfh77CcWdKvexp/z5N5OsTkIiYSCudQhFffEfk/0pcOTVf0DpeV0RoNopzig==; anilife_csrf=b93b9f25a12a51cf185805ec4de7cf9d; UTGv2=h46b326af644f4ac5d0eb1502881136b3750; __gpi=UID=000008ba227e99e0:T=1660813741:RT=1660912282:S=ALNI_MaJHIVJIGpQ5nTE9lvypKQxJnn10A; DSR=SXPX8ELcRgh6N/9rNgjpQoNfaX2DRceeKYR0/ul7qTI9gApWQpZxr8jgymf/r0HsUT551vtOv2CMWpIn0Hd26A==; DCSS=89508000A76BBD939F6DDACE5BD9EB902D2212A; DGCC=Wdm; adOtr=7L4Xe58995d; spcsrf=6554fa003bf6a46dd9b7417acfacc20a; _ga_56VYJJ7FTM=GS1.1.1660912281.10.1.1660912576.0.0.0; PRLST=EO" + + LogicAniLife.headers["Referer"] = referer + + page_content = LogicAniLife.session.get( + url, headers=headers, timeout=timeout, allow_redirects=True + ) + data = page_content.text + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + return data + + @staticmethod + async def get_html_playwright( + url: str, + headless: bool = False, + referer: str = None, + engine: str = "chrome", + stealth: bool = False, + ) -> str: + try: + from playwright.sync_api import sync_playwright + from playwright.async_api import async_playwright + from playwright_stealth import stealth_sync, stealth_async + + import time + + cookie = None + browser_args = [ + "--window-size=1300,570", + "--window-position=000,000", + "--disable-dev-shm-usage", + "--no-sandbox", + "--disable-web-security", + "--disable-features=site-per-process", + "--disable-setuid-sandbox", + "--disable-accelerated-2d-canvas", + "--no-first-run", + "--no-zygote", + # '--single-process', + "--disable-gpu", + "--use-gl=egl", + "--disable-blink-features=AutomationControlled", + "--disable-background-networking", + "--enable-features=NetworkService,NetworkServiceInProcess", + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-breakpad", + "--disable-client-side-phishing-detection", + "--disable-component-extensions-with-background-pages", + "--disable-default-apps", + "--disable-extensions", + "--disable-features=Translate", + "--disable-hang-monitor", + "--disable-ipc-flooding-protection", + "--disable-popup-blocking", + "--disable-prompt-on-repost", + "--disable-renderer-backgrounding", + "--disable-sync", + "--force-color-profile=srgb", + "--metrics-recording-only", + "--enable-automation", + "--password-store=basic", + "--use-mock-keychain", + "--hide-scrollbars", + "--mute-audio", + ] + # scraper = cloudscraper.create_scraper( + # browser={"browser": "chrome", "platform": "windows", "desktop": True}, + # debug=False, + # # sess=LogicAniLife.session, + # delay=10, + # ) + # + # cookie_value, user_agent = scraper.get_cookie_string(url) + # + # logger.debug(f"cookie_value:: {cookie_value}") + + start = time.time() + ua = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/69.0.3497.100 Safari/537.36" + ) + + # from playwright_stealth import stealth_sync + + def set_cookie(req): + nonlocal cookie + if "cookie" in req.headers: + cookie = req.headers["cookie"] + + async with async_playwright() as p: + try: + if engine == "chrome": + browser = await p.chromium.launch( + channel="chrome", args=browser_args, headless=headless + ) + elif engine == "webkit": + browser = await p.webkit.launch( + headless=headless, + args=browser_args, + ) + else: + browser = await p.firefox.launch( + headless=headless, + args=browser_args, + ) + # context = browser.new_context( + # user_agent=ua, + # ) + + LogicAniLife.headers[ + "Referer" + ] = "https://anilife.live/detail/id/471" + # print(LogicAniLife.headers) + + LogicAniLife.headers["Referer"] = LogicAniLife.episode_url + + if referer is not None: + LogicAniLife.headers["Referer"] = referer + + logger.debug(f"LogicAniLife.headers::: {LogicAniLife.headers}") + context = await browser.new_context( + extra_http_headers=LogicAniLife.headers + ) + await context.add_cookies(LogicAniLife.cookies) + + # LogicAniLife.headers["Cookie"] = cookie_value + + # context.set_extra_http_headers(LogicAniLife.headers) + + page = await context.new_page() + + # page.set_extra_http_headers(LogicAniLife.headers) + + if stealth: + await stealth_async(page) + + # page.on("request", set_cookie) + # stealth_sync(page) + print(LogicAniLife.headers["Referer"]) + + page.on("request", set_cookie) + + print(f'Referer:: {LogicAniLife.headers["Referer"]}') + # await page.set_extra_http_headers(LogicAniLife.headers) + + await page.goto( + url, wait_until="load", referer=LogicAniLife.headers["Referer"] + ) + # page.wait_for_timeout(10000) + await asyncio.sleep(2.9) + + # await page.reload() + + # time.sleep(10) + # cookies = context.cookies + # print(cookies) + + print(f"page.url:: {page.url}") + LogicAniLife.origin_url = page.url + + # print(page.content()) + + print(f"run at {time.time() - start} sec") + + return await page.content() + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + finally: + await browser.close() + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + finally: + # browser.close() + pass + + @staticmethod + async def get_vod_url_v1( + url, headless=False, referer=None, engine="chrome", stealth=False + ): + from playwright.sync_api import sync_playwright + from playwright.async_api import async_playwright + from playwright_har_tracer import HarTracer + from playwright_stealth import stealth_sync, stealth_async + + import time + + # scraper = cloudscraper.create_scraper( + # browser={"browser": "chrome", "platform": "windows", "desktop": True}, + # debug=False, + # # sess=LogicAniLife.session, + # delay=10, + # ) + # + # cookie_value, user_agent = scraper.get_cookie_string(url) + # + # logger.debug(f"cookie_value:: {cookie_value}") + + start = time.time() + ua = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/69.0.3497.100 Safari/537.36" + ) + # from playwright_stealth import stealth_sync + cookie = None + + def set_cookie(req): + nonlocal cookie + if "cookie" in req.headers: + cookie = req.headers["cookie"] + + async with async_playwright() as p: + if engine == "chrome": + browser = await p.chromium.launch(channel="chrome", headless=headless) + elif engine == "webkit": + browser = await p.webkit.launch(headless=headless) + else: + browser = await p.firefox.launch(headless=headless) + + LogicAniLife.headers["Referer"] = "https://anilife.live/detail/id/471" + # print(LogicAniLife.headers) + + LogicAniLife.headers["Referer"] = LogicAniLife.episode_url + + if referer is not None: + LogicAniLife.headers["Referer"] = referer + + logger.debug(f"LogicAniLife.headers::: {LogicAniLife.headers}") + context = await browser.new_context(extra_http_headers=LogicAniLife.headers) + await context.add_cookies(LogicAniLife.cookies) + + # LogicAniLife.headers["Cookie"] = cookie_value + + # context.set_extra_http_headers(LogicAniLife.headers) + tracer = HarTracer(context=context, browser_name=p.webkit.name) + + page = await context.new_page() + + # page.set_extra_http_headers(LogicAniLife.headers) + + if stealth: + await stealth_async(page) + + # page.on("request", set_cookie) + # stealth_sync(page) + print(LogicAniLife.headers["Referer"]) + + page.on("request", set_cookie) + + print(f'Referer:: {LogicAniLife.headers["Referer"]}') + # await page.set_extra_http_headers(LogicAniLife.headers) + + await page.goto( + url, wait_until="load", referer=LogicAniLife.headers["Referer"] + ) + + har = await tracer.flush() + # page.wait_for_timeout(10000) + await asyncio.sleep(10) + + # await page.reload() + + # time.sleep(10) + # cookies = context.cookies + # print(cookies) + + print(f"page.url:: {page.url}") + LogicAniLife.origin_url = page.url + + # print(page.content()) + + print(f"run at {time.time() - start} sec") + + return await page.content() + + @staticmethod + async def get_vod_url(url: str, headless: bool = False) -> str: + from playwright.sync_api import sync_playwright + from playwright.async_api import async_playwright + from playwright_stealth import stealth_async + import html_to_json + from playwright_har_tracer import HarTracer + import time + + # scraper = cloudscraper.create_scraper( + # browser={"browser": "chrome", "platform": "windows", "desktop": True}, + # debug=False, + # # sess=LogicAniLife.session, + # delay=10, + # ) + # + # cookie_value, user_agent = scraper.get_cookie_string(url) + # + # logger.debug(f"cookie_value:: {cookie_value}") + browser_args = [ + "--window-size=1300,570", + "--window-position=000,000", + "--disable-dev-shm-usage", + "--no-sandbox", + "--disable-web-security", + "--disable-features=site-per-process", + "--disable-setuid-sandbox", + "--disable-accelerated-2d-canvas", + "--no-first-run", + "--no-zygote", + # "--single-process", + "--disable-gpu", + "--use-gl=egl", + "--disable-blink-features=AutomationControlled", + "--disable-background-networking", + "--enable-features=NetworkService,NetworkServiceInProcess", + "--disable-background-timer-throttling", + "--disable-backgrounding-occluded-windows", + "--disable-breakpad", + "--disable-client-side-phishing-detection", + "--disable-component-extensions-with-background-pages", + "--disable-default-apps", + "--disable-extensions", + "--disable-features=Translate", + "--disable-hang-monitor", + "--disable-ipc-flooding-protection", + "--disable-popup-blocking", + "--disable-prompt-on-repost", + "--disable-renderer-backgrounding", + "--disable-sync", + "--force-color-profile=srgb", + "--metrics-recording-only", + "--enable-automation", + "--password-store=basic", + "--use-mock-keychain", + "--hide-scrollbars", + "--mute-audio", + ] + + start = time.time() + ua = ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/69.0.3497.100 Safari/537.36" + ) + # from playwright_stealth import stealth_sync + + async with async_playwright() as p: + try: + # browser = await p.chromium.launch(headless=headless, args=browser_args) + browser = await p.chromium.launch(headless=headless, args=browser_args) + + # browser = await p.webkit.launch(headless=headless) + # context = browser.new_context( + # user_agent=ua, + # ) + + LogicAniLife.headers[ + "Referer" + ] = "https://anilife.live/g/l?id=14344143-040a-4e40-9399-a7d22d94554b" + # print(LogicAniLife.headers) + + # context = await browser.new_context(extra_http_headers=LogicAniLife.headers) + context = await browser.new_context() + await context.set_extra_http_headers(LogicAniLife.headers) + + # await context.add_cookies(LogicAniLife.cookies) + + # tracer = HarTracer(context=context, browser_name=p.chromium.name) + tracer = HarTracer(context=context, browser_name=p.webkit.name) + + # LogicAniLife.headers["Cookie"] = cookie_value + + # context.set_extra_http_headers(LogicAniLife.headers) + + page = await context.new_page() + + # await page.set_extra_http_headers(LogicAniLife.headers) + + # await stealth_async(page) + # logger.debug(url) + + # page.on("request", set_cookie) + # stealth_sync(page) + # await page.goto( + # url, wait_until="load", referer=LogicAniLife.headers["Referer"] + # ) + # await page.goto(url, wait_until="load") + await page.goto(url, wait_until="domcontentloaded") + + har = await tracer.flush() + + # page.wait_for_timeout(10000) + await asyncio.sleep(2) + + # logger.debug(har) + # page.reload() + + # time.sleep(10) + # cookies = context.cookies + # print(cookies) + + # print(page.content()) + # vod_url = page.evaluate( + # """() => { + # return console.log(vodUrl_1080p) }""" + # ) + + # vod_url = page.evaluate( + # """async () =>{ + # return _0x55265f(0x99) + alJson[_0x55265f(0x91)] + # }""" + # ) + result_har_json = har.to_json() + result_har_dict = har.to_dict() + # logger.debug(result_har_dict) + + tmp_video_url = [] + for i, elem in enumerate(result_har_dict["log"]["entries"]): + if "m3u8" in elem["request"]["url"]: + logger.debug(elem["request"]["url"]) + tmp_video_url.append(elem["request"]["url"]) + + vod_url = tmp_video_url[-1] + + logger.debug(f"vod_url:: {vod_url}") + + logger.debug(f"run at {time.time() - start} sec") + + return vod_url + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + finally: + await browser.close() + + @staticmethod + def get_html_selenium(url: str, referer: str) -> bytes: + from selenium.webdriver.common.by import By + from selenium import webdriver + from selenium_stealth import stealth + from webdriver_manager.chrome import ChromeDriverManager + import time + + options = webdriver.ChromeOptions() + # 크롬드라이버 헤더 옵션추가 (리눅스에서 실행시 필수) + options.add_argument("start-maximized") + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("window-size=1920x1080") + options.add_argument("disable-gpu") + # options.add_argument('--no-sandbox') + options.add_argument("--disable-dev-shm-usage") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option("useAutomationExtension", False) + + if LogicAniLife.os_platform == "Darwin": + # 크롬드라이버 경로 + driver_path = "./bin/Darwin/chromedriver" + # driver = webdriver.Chrome(executable_path=driver_path, chrome_options=options) + driver = webdriver.Chrome( + ChromeDriverManager().install(), chrome_options=options + ) + else: + driver_bin_path = os.path.join( + os.path.dirname(__file__), "bin", f"{LogicAniLife.os_platform}" + ) + driver_path = f"{driver_bin_path}/chromedriver" + driver = webdriver.Chrome( + executable_path=driver_path, chrome_options=options + ) + + stealth( + driver, + languages=["ko-KR", "ko"], + vendor="Google Inc.", + platform="Win32", + webgl_vendor="Intel Inc.", + renderer="Intel Iris OpenGL Engine", + fix_hairline=True, + ) + driver.get(url) + + driver.refresh() + logger.debug(f"current_url:: {driver.current_url}") + # logger.debug(f"current_cookie:: {driver.get_cookies()}") + cookies_list = driver.get_cookies() + + cookies_dict = {} + for cookie in cookies_list: + cookies_dict[cookie["name"]] = cookie["value"] + + # logger.debug(cookies_dict) + LogicAniLife.cookies = cookies_list + # LogicAniLife.headers["Cookie"] = driver.get_cookies() + LogicAniLife.episode_url = driver.current_url + time.sleep(1) + elem = driver.find_element(By.XPATH, "//*") + source_code = elem.get_attribute("outerHTML") + + driver.close() + + return source_code.encode("utf-8") + + # Create a request interceptor + @staticmethod + def interceptor(request): + del request.headers["Referer"] # Delete the header first + request.headers[ + "Referer" + ] = "https://anilife.live/g/l?id=0a36917f-39cc-43ea-b0c6-0c86d27c2408" + + @staticmethod + def get_html_seleniumwire(url, referer, wired=False): + from selenium import webdriver + from selenium.webdriver.common.by import By + from seleniumwire import webdriver as wired_webdriver + from selenium_stealth import stealth + import time + + options = webdriver.ChromeOptions() + # 크롬드라이버 헤더 옵션추가 (리눅스에서 실행시 필수) + options.add_argument("start-maximized") + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_experimental_option("excludeSwitches", ["enable-automation"]) + options.add_experimental_option("useAutomationExtension", False) + # 크롬드라이버 경로 + driver_path = "./bin/Darwin/chromedriver" + if wired: + driver = wired_webdriver.Chrome( + executable_path=driver_path, chrome_options=options + ) + else: + driver = webdriver.Chrome( + executable_path=driver_path, chrome_options=options + ) + + # stealth ====================================== + # stealth( + # driver, + # languages=["en-US", "en"], + # vendor="Google Inc.", + # platform="Win32", + # webgl_vendor="Intel Inc.", + # renderer="Intel Iris OpenGL Engine", + # fix_hairline=True, + # ) + if wired: + driver.request_interceptor = LogicAniLife.interceptor + driver.get(url) + driver.refresh() + time.sleep(1) + elem = driver.find_element(By.XPATH, "//*") + source_code = elem.get_attribute("outerHTML") + + return source_code.encode("utf-8") + + @staticmethod + def get_html_cloudflare(url, cached=False): + # scraper = cloudscraper.create_scraper( + # # disableCloudflareV1=True, + # # captcha={"provider": "return_response"}, + # delay=10, + # browser="chrome", + # ) + # scraper = cfscrape.create_scraper( + # browser={"browser": "chrome", "platform": "android", "desktop": False} + # ) + + # scraper = cloudscraper.create_scraper( + # browser={"browser": "chrome", "platform": "windows", "mobile": False}, + # debug=True, + # ) + + # LogicAniLife.headers["referer"] = LogicAniLife.referer + LogicAniLife.headers["Referer"] = "https://anilife.live/" + LogicAniLife.headers[ + "Cookie" + ] = "_ga=GA1.1.578607927.1660813724; __gads=ID=10abb8b98b6828ae-2281c943a9d500fd:T=1660813741:RT=1660813741:S=ALNI_MYU_iB2lBgSrEQUBwhKpNsToaqQ8A; sbtsck=javuwDzcOJqUyweM1OQeNGzHbjoHp7Cgw44XnPdM738c3E=; SPSI=e48379959d54a6a62cc7abdcafdb2761; SPSE=h5HfMGLJzLqzNafMD3YaOvHSC9xfh77CcWdKvexp/z5N5OsTkIiYSCudQhFffEfk/0pcOTVf0DpeV0RoNopzig==; anilife_csrf=b93b9f25a12a51cf185805ec4de7cf9d; UTGv2=h46b326af644f4ac5d0eb1502881136b3750; __gpi=UID=000008ba227e99e0:T=1660813741:RT=1660912282:S=ALNI_MaJHIVJIGpQ5nTE9lvypKQxJnn10A; DSR=SXPX8ELcRgh6N/9rNgjpQoNfaX2DRceeKYR0/ul7qTI9gApWQpZxr8jgymf/r0HsUT551vtOv2CMWpIn0Hd26A==; DCSS=89508000A76BBD939F6DDACE5BD9EB902D2212A; DGCC=Wdm; adOtr=7L4Xe58995d; spcsrf=6554fa003bf6a46dd9b7417acfacc20a; _ga_56VYJJ7FTM=GS1.1.1660912281.10.1.1660912576.0.0.0; PRLST=EO" + # logger.debug(f"headers:: {LogicAniLife.headers}") + + if LogicAniLife.session is None: + LogicAniLife.session = requests.Session() + LogicAniLife.session.headers = LogicAniLife.headers + + # LogicAniLife.session = requests.Session() + + sess = cloudscraper.create_scraper( + browser={"browser": "firefox", "platform": "windows", "desktop": True}, + debug=False, + sess=LogicAniLife.session, + delay=10, + ) + + # print(scraper.get(url, headers=LogicAniLife.headers).content) + # print(scraper.get(url).content) + # return scraper.get(url, headers=LogicAniLife.headers).content + # print(LogicAniLife.headers) + return sess.get( + url, headers=LogicAniLife.session.headers, timeout=10, allow_redirects=True + ).content.decode("utf8", errors="replace") + + @staticmethod + def db_init(): + pass + + def process_menu(self, sub, req): + arg = P.ModelSetting.to_dict() + arg["sub"] = self.name + if sub in ["setting", "queue", "list", "category", "request"]: + if sub == "request" and req.args.get("content_code") is not None: + arg["anilife_current_code"] = req.args.get("content_code") + if sub == "setting": + job_id = "%s_%s" % (self.P.package_name, self.name) + arg["scheduler"] = str(scheduler.is_include(job_id)) + arg["is_running"] = str(scheduler.is_running(job_id)) + return render_template( + "{package_name}_{module_name}_{sub}.html".format( + package_name=P.package_name, module_name=self.name, sub=sub + ), + arg=arg, + ) + return render_template("sample.html", title="%s - %s" % (P.package_name, sub)) + + def process_ajax(self, sub, req): + try: + if sub == "analysis": + # code = req.form['code'] + logger.debug(req) + code = request.form["code"] + + wr_id = request.form.get("wr_id", None) + bo_table = request.form.get("bo_table", None) + data = [] + + # logger.info("code::: %s", code) + P.ModelSetting.set("anilife_current_code", code) + data = self.get_series_info(code) + self.current_data = data + return jsonify({"ret": "success", "data": data, "code": code}) + elif sub == "anime_list": + data = [] + cate = request.form["type"] + page = request.form["page"] + + data = self.get_anime_info(cate, page) + # self.current_data = data + return jsonify( + {"ret": "success", "cate": cate, "page": page, "data": data} + ) + elif sub == "complete_list": + data = [] + + cate = request.form["type"] + logger.debug("cate:: %s", cate) + page = request.form["page"] + + data = self.get_anime_info(cate, page) + # self.current_data = data + return jsonify( + {"ret": "success", "cate": cate, "page": page, "data": data} + ) + elif sub == "search": + data = [] + # cate = request.form["type"] + # page = request.form["page"] + cate = request.form["type"] + query = request.form["query"] + page = request.form["page"] + + data = self.get_search_result(query, page, cate) + # self.current_data = data + return jsonify( + { + "ret": "success", + "cate": cate, + "page": page, + "query": query, + "data": data, + } + ) + elif sub == "add_queue": + logger.debug(f"add_queue routine ===============") + ret = {} + info = json.loads(request.form["data"]) + logger.info(f"info:: {info}") + ret["ret"] = self.add(info) + return jsonify(ret) + elif sub == "entity_list": + return jsonify(self.queue.get_entity_list()) + elif sub == "queue_command": + ret = self.queue.command( + req.form["command"], int(req.form["entity_id"]) + ) + return jsonify(ret) + elif sub == "add_queue_checked_list": + data = json.loads(request.form["data"]) + + def func(): + count = 0 + for tmp in data: + add_ret = self.add(tmp) + if add_ret.startswith("enqueue"): + self.socketio_callback("list_refresh", "") + count += 1 + notify = { + "type": "success", + "msg": "%s 개의 에피소드를 큐에 추가 하였습니다." % count, + } + socketio.emit( + "notify", notify, namespace="/framework", broadcast=True + ) + + thread = threading.Thread(target=func, args=()) + thread.daemon = True + thread.start() + return jsonify("") + elif sub == "web_list": + return jsonify(ModelAniLifeItem.web_list(request)) + elif sub == "db_remove": + return jsonify(ModelAniLifeItem.delete_by_id(req.form["id"])) + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + + @staticmethod + def add_whitelist(*args): + ret = {} + + logger.debug(f"args: {args}") + try: + + if len(args) == 0: + code = str(LogicAniLife.current_data["code"]) + else: + code = str(args[0]) + + print(code) + + whitelist_program = P.ModelSetting.get("anilife_auto_code_list") + # whitelist_programs = [ + # str(x.strip().replace(" ", "")) + # for x in whitelist_program.replace("\n", "|").split("|") + # ] + whitelist_programs = [ + str(x.strip()) 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(P.ModelSetting) + .filter_by(key="anilife_auto_code_list") + .with_for_update() + .first() + ) + entity.value = whitelist_program + db.session.commit() + ret["ret"] = True + ret["code"] = code + if len(args) == 0: + return LogicAniLife.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 + + def setting_save_after(self, change_list): + if self.queue.get_max_ffmpeg_count() != P.ModelSetting.get_int( + "anilife_max_ffmpeg_process_count" + ): + self.queue.set_max_ffmpeg_count( + P.ModelSetting.get_int("anilife_max_ffmpeg_process_count") + ) + + def scheduler_function(self): + logger.debug(f"ohli24 scheduler_function::=========================") + + content_code_list = P.ModelSetting.get_list("ohli24_auto_code_list", "|") + url = f'{P.ModelSetting.get("anilife_url")}/dailyani' + if "all" in content_code_list: + ret_data = LogicAniLife.get_auto_anime_info(self, url=url) + + def plugin_load(self): + self.queue = FfmpegQueue( + P, P.ModelSetting.get_int("anilife_max_ffmpeg_process_count") + ) + self.current_data = None + self.queue.queue_start() + + def reset_db(self): + db.session.query(ModelAniLifeItem).delete() + db.session.commit() + return True + + # 시리즈 정보를 가져오는 함수 + def get_series_info(self, code): + try: + if code.isdigit(): + url = P.ModelSetting.get("anilife_url") + "/detail/id/" + code + else: + url = P.ModelSetting.get("anilife_url") + "/g/l?id=" + code + + logger.debug("url::: > %s", url) + response_data = LogicAniLife.get_html(url, timeout=10) + tree = html.fromstring(response_data) + # logger.debug(response_data) + main_title = tree.xpath('//div[@class="infox"]/h1/text()')[0] + image = tree.xpath('//div[@class="thumb"]/img/@src')[0] + des_items = tree.xpath( + '//div[@class="info-content"]/div[@class="spe"]/span' + ) + des_items1 = ( + tree.xpath('//div[@class="info-content"]/div[@class="spe"]')[0] + .text_content() + .strip() + ) + + des = {} + des_key = [ + "_otit", + "_dir", + "_pub", + "_tag", + "_classifi", + "_country", + "_season", + "_grade", + "_total_chapter", + "_show_time", + "_release_year", + "_recent_date", + "_air_date", + ] + description_dict = { + "상태": "_status", + "원제": "_otit", + "원작": "_org", + "감독": "_dir", + "각본": "_scr", + "시즌": "_season", + "캐릭터 디자인": "_character_design", + "음악": "_sound", + "제작사": "_pub", + "장르": "_tag", + "분류": "_classifi", + "제작국가": "_country", + "방영일": "_date", + "등급": "_grade", + "유형": "_type", + "에피소드": "_total_chapter", + "상영시간": "_show_time", + "공식 방영일": "_release_date", + "방영 시작일": "_air_date", + "최근 방영일": "_recent_date", + "개봉년도": "_release_year", + } + # print(main_title) + # print(image) + # print(des_items) + + list_body_li = tree.xpath('//div[@class="eplister"]/ul/li') + # logger.debug(f"list_body_li:: {list_body_li}") + + episodes = [] + vi = None + + for li in list_body_li: + # logger.debug(li) + ep_num = li.xpath('.//a/div[@class="epl-num"]/text()')[0].strip() + title = li.xpath('.//a/div[@class="epl-title"]/text()')[0].strip() + thumbnail = image + link = li.xpath(".//a/@href")[0] + date = "" + m = hashlib.md5(title.encode("utf-8")) + _vi = m.hexdigest() + episodes.append( + { + "ep_num": ep_num, + "title": f"{main_title} {ep_num}화 - {title}", + "link": link, + "thumbnail": image, + "date": date, + "day": date, + "_id": title, + "va": link, + "_vi": _vi, + "content_code": code, + "ep_url": url, + } + ) + + # print(lxml.etree.tostring(des_items, method="text")) + # + # for idx, item in enumerate(des_items): + # span = item.xpath(".//b/text()") + # logger.info(f"0: {span[0]}") + # key = description_dict[span[0].replace(":", "")] + # logger.debug(f"key:: {key}") + # try: + # print(item.xpath(".//text()")[1].strip()) + # des[key] = item.xpath(".//text()")[1].strip() + # except IndexError: + # if item.xpath(".//a"): + # des[key] = item.xpath(".//a")[0] + # des[key] = "" + + ser_description = "작품 설명 부분" + des = "" + des1 = "" + data = { + "title": main_title, + "image": image, + "date": "2022.01.11 00:30 (화)", + "ser_description": ser_description, + # "des": des, + "des1": des_items1, + "episode": episodes, + } + + return data + + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + return {"ret": "exception", "log": str(e)} + + @staticmethod + def get_real_link(url): + response = requests.get(url) + if response.history: + print("Request was redirected") + for resp in response.history: + print(resp.status_code, resp.url) + print("Final destination:") + print(response.status_code, response.url) + return response.url + else: + print("Request was not redirected") + + def get_anime_info(self, cate, page): + logger.debug(f"get_anime_info() routine") + logger.debug(f"cate:: {cate}") + wrapper_xpath = '//div[@class="bsx"]' + try: + if cate == "ing": + url = P.ModelSetting.get("anilife_url") + wrapper_xpath = ( + '//div[contains(@class, "listupd")]/*/*/div[@class="bsx"]' + ) + elif cate == "theater": + url = ( + P.ModelSetting.get("anilife_url") + + "/vodtype/categorize/Movie/" + + page + ) + wrapper_xpath = '//div[@class="bsx"]' + else: + url = ( + P.ModelSetting.get("anilife_url") + + "/vodtype/categorize/Movie/" + + page + ) + # cate == "complete": + logger.info("url:::> %s", url) + data = {} + response_data = LogicAniLife.get_html(url, timeout=10) + # logger.debug(response_data) + + # logger.debug(f"wrapper_xath:: {wrapper_xpath}") + tree = html.fromstring(response_data) + tmp_items = tree.xpath(wrapper_xpath) + data["anime_count"] = len(tmp_items) + data["anime_list"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + # logger.debug(entity["link"]) + p = re.compile(r"^[http?s://]+[a-zA-Z0-9-]+/[a-zA-Z0-9-_.?=]+$") + + # print(p.match(entity["link"]) != None) + if p.match(entity["link"]) is None: + entity["link"] = P.ModelSetting.get("anilife_url") + entity["link"] + # real_url = LogicAniLife.get_real_link(url=entity["link"]) + + # logger.debug(entity["link"]) + + entity["code"] = entity["link"].split("/")[-1] + entity["title"] = item.xpath(".//div[@class='tt']/text()")[0].strip() + entity["image_link"] = item.xpath(".//div[@class='limit']/img/@src")[ + 0 + ].replace("..", P.ModelSetting.get("anilife_url")) + data["ret"] = "success" + data["anime_list"].append(entity) + + return data + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + return {"ret": "exception", "log": str(e)} + + ######################################################### + def add(self, episode_info): + if self.is_exist(episode_info): + return "queue_exist" + else: + db_entity = ModelAniLifeItem.get_by_anilife_id(episode_info["_id"]) + + logger.debug(f"db_entity():: => {db_entity}") + + if db_entity is None: + logger.debug(f"episode_info:: {episode_info}") + entity = AniLifeQueueEntity(P, self, episode_info) + logger.debug("entity:::> %s", entity.as_dict()) + ModelAniLifeItem.append(entity.as_dict()) + + self.queue.add_queue(entity) + + return "enqueue_db_append" + elif db_entity.status != "completed": + entity = AniLifeQueueEntity(P, self, episode_info) + + self.queue.add_queue(entity) + return "enqueue_db_exist" + else: + return "db_completed" + + def is_exist(self, info): + for e in self.queue.entity_list: + if e.info["_id"] == info["_id"]: + return True + return False + + +class AniLifeQueueEntity(FfmpegQueueEntity): + def __init__(self, P, module_logic, info): + super(AniLifeQueueEntity, self).__init__(P, module_logic, info) + self._vi = None + self.url = None + self.epi_queue = None + self.filepath = None + self.savepath = None + self.quality = None + self.filename = None + self.vtt = None + self.season = 1 + self.content_title = None + self.srt_url = None + self.headers = None + # Todo::: 임시 주석 처리 + self.make_episode_info() + + def refresh_status(self): + self.module_logic.socketio_callback("status", self.as_dict()) + + def info_dict(self, tmp): + # logger.debug("self.info::> %s", self.info) + for key, value in self.info.items(): + tmp[key] = value + tmp["vtt"] = self.vtt + tmp["season"] = self.season + tmp["content_title"] = self.content_title + tmp["anilife_info"] = self.info + tmp["epi_queue"] = self.epi_queue + return tmp + + def donwload_completed(self): + db_entity = ModelAniLifeItem.get_by_anilife_id(self.info["_id"]) + if db_entity is not None: + db_entity.status = "completed" + db_entity.complated_time = datetime.now() + db_entity.save() + + def make_episode_info(self): + logger.debug("make_episode_info() routine ==========") + try: + # 다운로드 추가 + base_url = "https://anilife.live" + iframe_url = "" + + url = self.info["va"] + logger.debug(f"url:: {url}") + + ourls = parse.urlparse(url) + + self.headers = { + "Referer": f"{ourls.scheme}://{ourls.netloc}", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36", + } + + headers["Referer"] = "https://anilife.live/detail/id/471" + headers["Referer"] = LogicAniLife.episode_url + + logger.debug("make_episode_info()::url==> %s", url) + logger.info(f"self.info:::> {self.info}") + + referer = "https://anilife.live/g/l?id=13fd4d28-ff18-4764-9968-7e7ea7347c51" + referer = LogicAniLife.episode_url + + # text = requests.get(url, headers=headers).text + # text = LogicAniLife.get_html_seleniumwire(url, referer=referer, wired=True) + # https://anilife.live/ani/provider/10f60832-20d1-4918-be62-0f508bf5460c + referer_url = ( + "https://anilife.live/g/l?id=d4be1e0e-301b-403b-be1b-cf19f3ccfd23" + ) + referer_url = LogicAniLife.episode_url + + logger.debug(f"LogicAniLife.episode_url:: {LogicAniLife.episode_url}") + text = asyncio.run( + LogicAniLife.get_html_playwright( + url, + headless=True, + referer=referer_url, + engine="chrome", + stealth=True, + ) + ) + + # vod_1080p_url = text + + # logger.debug(text) + soup = BeautifulSoup(text, "lxml") + + all_scripts = soup.find_all("script") + # print(all_scripts) + + regex = r"(?Phttp?s:\/\/.*=jawcloud)" + match = re.compile(regex).search(text) + + jawcloud_url = None + # print(match) + if match: + jawcloud_url = match.group("jawcloud_url") + + logger.debug(f"jawcloud_url:: {jawcloud_url}") + + # loop = asyncio.new_event_loop() + # asyncio.set_event_loop(loop) + # + logger.info(self.info) + + match = re.compile( + r"(?P.*?)\s*((?P<season>\d+)%s)?\s*((?P<epi_no>\d+)%s)" + % ("기", "화") + ).search(self.info["title"]) + + # epi_no 초기값 + epi_no = 1 + self.quality = "1080P" + + if match: + self.content_title = match.group("title").strip() + if "season" in match.groupdict() and match.group("season") is not None: + self.season = int(match.group("season")) + + # epi_no = 1 + epi_no = int(match.group("epi_no")) + ret = "%s.S%sE%s.%s-AL.mp4" % ( + self.content_title, + "0%s" % self.season if self.season < 10 else self.season, + "0%s" % epi_no if epi_no < 10 else epi_no, + self.quality, + ) + else: + self.content_title = self.info["title"] + P.logger.debug("NOT MATCH") + ret = "%s.720p-AL.mp4" % self.info["title"] + + # logger.info('self.content_title:: %s', self.content_title) + self.epi_queue = epi_no + + self.filename = Util.change_text_for_use_filename(ret) + logger.info(f"self.filename::> {self.filename}") + self.savepath = P.ModelSetting.get("ohli24_download_path") + logger.info(f"self.savepath::> {self.savepath}") + + if P.ModelSetting.get_bool("ohli24_auto_make_folder"): + if self.info["day"].find("완결") != -1: + folder_name = "%s %s" % ( + P.ModelSetting.get("ohli24_finished_insert"), + self.content_title, + ) + else: + folder_name = self.content_title + folder_name = Util.change_text_for_use_filename(folder_name.strip()) + self.savepath = os.path.join(self.savepath, folder_name) + if P.ModelSetting.get_bool("ohli24_auto_make_season_folder"): + self.savepath = os.path.join( + self.savepath, "Season %s" % int(self.season) + ) + self.filepath = os.path.join(self.savepath, self.filename) + if not os.path.exists(self.savepath): + os.makedirs(self.savepath) + + vod_1080p_url = asyncio.run( + LogicAniLife.get_vod_url(jawcloud_url, headless=True) + ) + print(f"vod_1080p_url:: {vod_1080p_url}") + self.url = vod_1080p_url + + logger.info(self.url) + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + + +class ModelAniLifeItem(db.Model): + __tablename__ = "{package_name}_anilife_item".format(package_name=P.package_name) + __table_args__ = {"mysql_collate": "utf8_general_ci"} + __bind_key__ = P.package_name + id = db.Column(db.Integer, primary_key=True) + created_time = db.Column(db.DateTime) + completed_time = db.Column(db.DateTime) + reserved = db.Column(db.JSON) + content_code = db.Column(db.String) + season = db.Column(db.Integer) + episode_no = db.Column(db.Integer) + title = db.Column(db.String) + episode_title = db.Column(db.String) + anilife_va = db.Column(db.String) + anilife_vi = db.Column(db.String) + anilife_id = db.Column(db.String) + quality = db.Column(db.String) + filepath = db.Column(db.String) + filename = db.Column(db.String) + savepath = db.Column(db.String) + video_url = db.Column(db.String) + vtt_url = db.Column(db.String) + thumbnail = db.Column(db.String) + status = db.Column(db.String) + anilife_info = db.Column(db.JSON) + + def __init__(self): + self.created_time = datetime.now() + + def __repr__(self): + 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 save(self): + db.session.add(self) + db.session.commit() + + @classmethod + def get_by_id(cls, idx): + return db.session.query(cls).filter_by(id=idx).first() + + @classmethod + def get_by_anilife_id(cls, anilife_id): + return db.session.query(cls).filter_by(anilife_id=anilife_id).first() + + @classmethod + def delete_by_id(cls, idx): + db.session.query(cls).filter_by(id=idx).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) + count = query.count() + query = query.limit(page_size).offset((page - 1) * page_size) + lists = query.all() + ret["list"] = [item.as_dict() for item in lists] + ret["paging"] = Util.get_paging_info(count, page, page_size) + return ret + + @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 + + @classmethod + def get_list_uncompleted(cls): + return db.session.query(cls).filter(cls.status != "completed").all() + + @classmethod + def append(cls, q): + item = ModelAniLifeItem() + item.content_code = q["content_code"] + item.season = q["season"] + item.episode_no = q["epi_queue"] + item.title = q["content_title"] + item.episode_title = q["title"] + item.ohli24_va = q["va"] + item.ohli24_vi = q["_vi"] + item.ohli24_id = q["_id"] + item.quality = q["quality"] + item.filepath = q["filepath"] + item.filename = q["filename"] + item.savepath = q["savepath"] + item.video_url = q["url"] + item.vtt_url = q["vtt"] + item.thumbnail = q["thumbnail"] + item.status = "wait" + item.ohli24_info = q["anilife_info"] + item.save() diff --git a/mod_linkkf.py b/mod_linkkf.py new file mode 100644 index 0000000..d76d43f --- /dev/null +++ b/mod_linkkf.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2022/02/08 3:44 PM +# @Author : yommi +# @Site : +# @File : logic_linkkf +# @Software: PyCharm +import os, sys, traceback, re, json, threading +from datetime import datetime +import copy + +# third-party +import requests + +# third-party +from flask import request, render_template, jsonify +from sqlalchemy import or_, and_, func, not_, desc + +# sjva 공용 +from framework import db, scheduler, path_data, socketio +from framework.util import Util +from framework import F +from plugin import ( + PluginModuleBase +) +from flaskfarm.lib.plugin._ffmpeg_queue import FfmpegQueueEntity, FfmpegQueue + +# from tool_base import d + +# 패키지 +# from .plugin import P +from .setup import * + +logger = P.logger + + +class LogicLinkkf(PluginModuleBase): + db_default = { + "linkkf_db_version": "1", + "linkkf_url": "https://linkkf.app", + "linkkf_download_path": os.path.join(path_data, P.package_name, "linkkf"), + "linkkf_auto_make_folder": "True", + "linkkf_auto_make_season_folder": "True", + "linkkf_finished_insert": "[완결]", + "linkkf_max_ffmpeg_process_count": "1", + "linkkf_order_desc": "False", + "linkkf_auto_start": "False", + "linkkf_interval": "* 5 * * *", + "linkkf_auto_mode_all": "False", + "linkkf_auto_code_list": "all", + "linkkf_current_code": "", + "linkkf_uncompleted_auto_enqueue": "False", + "linkkf_image_url_prefix_series": "", + "linkkf_image_url_prefix_episode": "", + "linkkf_discord_notify": "True", + } + current_headers = None + current_data = None + + session = requests.Session() + 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": "", + } + useragent = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, " + "like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" + } + + def __init__(self, P): + super(LogicLinkkf, self).__init__(P, "setting", scheduler_desc="linkkf 자동 다운로드") + self.name = "linkkf" + # default_route_socketio(P, self) + default_route_socketio_module(self, attach='/setting') + + def process_menu(self, sub, req): + arg = P.ModelSetting.to_dict() + arg["sub"] = self.name + if sub in ["setting", "queue", "category", "list", "request"]: + if sub == "request" and req.args.get("content_code") is not None: + arg["linkkf_current_code"] = req.args.get("content_code") + if sub == "setting": + job_id = "%s_%s" % (self.P.package_name, self.name) + arg["scheduler"] = str(scheduler.is_include(job_id)) + arg["is_running"] = str(scheduler.is_running(job_id)) + return render_template( + "{package_name}_{module_name}_{sub}.html".format( + package_name=P.package_name, module_name=self.name, sub=sub + ), + arg=arg, + ) + return render_template("sample.html", title="%s - %s" % (P.package_name, sub)) + + def process_ajax(self, sub, req): + try: + if sub == "analysis": + pass + elif sub == "anime_list": + pass + elif sub == "complete_list": + pass + elif sub == "search": + pass + elif sub == "add_queue": + pass + elif sub == "entity_list": + pass + elif sub == "queue_command": + pass + elif sub == "add_queue_checked_list": + pass + elif sub == "web_list": + pass + elif sub == "db_remove": + pass + elif sub == "add_whitelist": + pass + + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + + pass + + +class ModelLinkkfItem(db.Model): + __tablename__ = "{package_name}_linkkf_item".format(package_name=P.package_name) + __table_args__ = {"mysql_collate": "utf8_general_ci"} + __bind_key__ = P.package_name + id = db.Column(db.Integer, primary_key=True) + created_time = db.Column(db.DateTime) + completed_time = db.Column(db.DateTime) + reserved = db.Column(db.JSON) + content_code = db.Column(db.String) + season = db.Column(db.Integer) + episode_no = db.Column(db.Integer) + title = db.Column(db.String) + episode_title = db.Column(db.String) + linkkf_va = db.Column(db.String) + linkkf_vi = db.Column(db.String) + linkkf_id = db.Column(db.String) + quality = db.Column(db.String) + filepath = db.Column(db.String) + filename = db.Column(db.String) + savepath = db.Column(db.String) + video_url = db.Column(db.String) + vtt_url = db.Column(db.String) + thumbnail = db.Column(db.String) + status = db.Column(db.String) + linkkf_info = db.Column(db.JSON) + + def __int__(self): + self.created_time == datetime.now() + + def __repr__(self): + 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 save(self): + db.session.add(self) + db.session.commit() + + @classmethod + def get_by_id(cls, idx): + return db.session.query(cls).filter_by(id=idx).first() diff --git a/mod_ohli24.py b/mod_ohli24.py new file mode 100644 index 0000000..adefb7e --- /dev/null +++ b/mod_ohli24.py @@ -0,0 +1,1260 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time : 2022/02/08 3:44 PM +# @Author : yommi +# @Site : +# @File : logic_ohli24 +# @Software: PyCharm + +import os, sys, traceback, re, json, threading +from datetime import datetime, date +import copy +import hashlib +import subprocess + +import PIL.Image +# third-party +import requests +from lxml import html +from urllib import parse +import urllib +import asyncio +import importlib + +# import aiohttp + +# third-party +from flask import request, render_template, jsonify +from sqlalchemy import or_, and_, func, not_, desc +from pip._internal import main + +pkgs = ["bs4", "jsbeautifier", "aiohttp"] +for pkg in pkgs: + try: + importlib.import_module(pkg) + # except ImportError: + except ImportError: + subprocess.check_call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'pip']) + # main(["install", pkg]) + subprocess.check_call([sys.executable, '-m', 'pip', 'install', pkg]) + importlib.import_module(pkg) + +import aiohttp + +from bs4 import BeautifulSoup +import jsbeautifier + +# sjva 공용 +from framework import db, scheduler, path_data, socketio +from framework.util import Util +# from framework.common.util import headers +from framework import F +from plugin import ( + PluginModuleBase +) +from .lib._ffmpeg_queue import FfmpegQueueEntity, FfmpegQueue +from support.expand.ffmpeg import SupportFfmpeg + +from .lib.util import Util +# from tool_base import d + +# 패키지 +# from .plugin import P +from .setup import * + +logger = P.logger + + +######################################################### + + +class LogicOhli24(PluginModuleBase): + db_default = { + "ohli24_db_version": "1", + "ohli24_url": "https://ohli24.net", + "ohli24_download_path": os.path.join(path_data, P.package_name, "ohli24"), + "ohli24_auto_make_folder": "True", + "ohli24_auto_make_season_folder": "True", + "ohli24_finished_insert": "[완결]", + "ohli24_max_ffmpeg_process_count": "1", + "ohli24_order_desc": "False", + "ohli24_auto_start": "False", + "ohli24_interval": "* 5 * * *", + "ohli24_auto_mode_all": "False", + "ohli24_auto_code_list": "all", + "ohli24_current_code": "", + "ohli24_uncompleted_auto_enqueue": "False", + "ohli24_image_url_prefix_series": "https://www.jetcloud.cc/series/", + "ohli24_image_url_prefix_episode": "https://www.jetcloud-list.cc/thumbnail/", + "ohli24_discord_notify": "True", + } + current_headers = None + current_data = None + + session = requests.Session() + # 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", + # "authority": "ndoodle.xyz", + # "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": "https://ndoodle.xyz/video/8a66cd1b3045b820efd42dbf18eb28e1", + # "Referer": "https://ndoodle.xyz/video/8a66cd1b3045b820efd42dbf18eb28e1", + # } + headers = { + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.114 Whale/3.17.145.12 Safari/537.36', + 'authority': 'ndoodle.xyz', + 'accept': '*/*', + 'accept-language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', + 'cache-control': 'no-cache', + 'pragma': 'no-cache', + 'referer': 'https://ndoodle.xyz/video/e6e31529675d0ef99d777d729c423382' + + } + useragent = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, " + "like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" + } + + def __init__(self, P): + super(LogicOhli24, self).__init__(P, "setting", scheduler_desc="ohli24 자동 다운로드") + self.name = "ohli24" + self.queue = None + # default_route_socketio(P, self) + default_route_socketio_module(self, attach='/search') + + @staticmethod + def db_init(): + pass + # try: + # for key, value in P.Logic.db_default.items(): + # if db.session.query(ModelSetting).filter_by(key=key).count() == 0: + # db.session.add(ModelSetting(key, value)) + # db.session.commit() + # except Exception as e: + # logger.error('Exception:%s', e) + # logger.error(traceback.format_exc()) + + def process_menu(self, sub, req): + arg = P.ModelSetting.to_dict() + arg["sub"] = self.name + if sub in ["setting", "queue", "list", "category", "request", "search"]: + if sub == "request" and req.args.get("content_code") is not None: + arg["ohli24_current_code"] = req.args.get("content_code") + if sub == "setting": + job_id = "%s_%s" % (self.P.package_name, self.name) + arg["scheduler"] = str(scheduler.is_include(job_id)) + arg["is_running"] = str(scheduler.is_running(job_id)) + return render_template( + "{package_name}_{module_name}_{sub}.html".format( + package_name=P.package_name, module_name=self.name, sub=sub + ), + arg=arg, + ) + return render_template("sample.html", title="%s - %s" % (P.package_name, sub)) + + # @staticmethod + def process_ajax(self, sub, req): + try: + if sub == "analysis": + # code = req.form['code'] + code = request.form["code"] + + wr_id = request.form.get("wr_id", None) + bo_table = request.form.get("bo_table", None) + data = [] + # print(code) + # logger.info("code::: %s", code) + P.ModelSetting.set("ohli24_current_code", code) + data = self.get_series_info(code, wr_id, bo_table) + self.current_data = data + return jsonify({"ret": "success", "data": data, "code": code}) + elif sub == "anime_list": + data = [] + cate = request.form["type"] + page = request.form["page"] + + data = self.get_anime_info(cate, page) + # self.current_data = data + return jsonify( + {"ret": "success", "cate": cate, "page": page, "data": data} + ) + elif sub == "complete_list": + data = [] + + cate = request.form["type"] + logger.debug("cate:: %s", cate) + page = request.form["page"] + + data = self.get_anime_info(cate, page) + # self.current_data = data + return jsonify( + {"ret": "success", "cate": cate, "page": page, "data": data} + ) + elif sub == "search": + data = [] + # cate = request.form["type"] + # page = request.form["page"] + cate = request.form["type"] + query = request.form["query"] + page = request.form["page"] + + data = self.get_search_result(query, page, cate) + # self.current_data = data + return jsonify( + { + "ret": "success", + "cate": cate, + "page": page, + "query": query, + "data": data, + } + ) + elif sub == "add_queue": + ret = {} + info = json.loads(request.form["data"]) + logger.info(f"info:: {info}") + ret["ret"] = self.add(info) + return jsonify(ret) + elif sub == "entity_list": + return jsonify(self.queue.get_entity_list()) + elif sub == "queue_command": + ret = self.queue.command( + req.form["command"], int(req.form["entity_id"]) + ) + return jsonify(ret) + elif sub == "add_queue_checked_list": + data = json.loads(request.form["data"]) + + def func(): + count = 0 + for tmp in data: + add_ret = self.add(tmp) + if add_ret.startswith("enqueue"): + self.socketio_callback("list_refresh", "") + count += 1 + notify = { + "type": "success", + "msg": "%s 개의 에피소드를 큐에 추가 하였습니다." % count, + } + socketio.emit( + "notify", notify, namespace="/framework", broadcast=True + ) + + thread = threading.Thread(target=func, args=()) + thread.daemon = True + thread.start() + return jsonify("") + elif sub == "web_list": + return jsonify(ModelOhli24Item.web_list(request)) + elif sub == "db_remove": + return jsonify(ModelOhli24Item.delete_by_id(req.form["id"])) + 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(f"params:: {params}") + if params is not None: + code = params["data_code"] + logger.debug(f"params: {code}") + ret = LogicOhli24.add_whitelist(code) + else: + ret = LogicOhli24.add_whitelist() + return jsonify(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + + @staticmethod + def add_whitelist(*args): + ret = {} + + logger.debug(f"args: {args}") + try: + + if len(args) == 0: + code = str(LogicOhli24.current_data["code"]) + else: + code = str(args[0]) + + print(code) + + whitelist_program = P.ModelSetting.get("ohli24_auto_code_list") + # whitelist_programs = [ + # str(x.strip().replace(" ", "")) + # for x in whitelist_program.replace("\n", "|").split("|") + # ] + whitelist_programs = [ + str(x.strip()) 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(P.ModelSetting) + .filter_by(key="ohli24_auto_code_list") + .with_for_update() + .first() + ) + entity.value = whitelist_program + db.session.commit() + ret["ret"] = True + ret["code"] = code + if len(args) == 0: + return LogicOhli24.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 + + def setting_save_after(self, change_list): + if self.queue.get_max_ffmpeg_count() != P.ModelSetting.get_int( + "ohli24_max_ffmpeg_process_count" + ): + self.queue.set_max_ffmpeg_count( + P.ModelSetting.get_int("ohli24_max_ffmpeg_process_count") + ) + + def scheduler_function(self): + # Todo: 스케쥴링 함수 미구현 + logger.debug(f"ohli24 scheduler_function::=========================") + + content_code_list = P.ModelSetting.get_list("ohli24_auto_code_list", "|") + logger.debug(f"content_code_list::: {content_code_list}") + url_list = ["https://www.naver.com/", "https://www.daum.net/"] + + week = ["월요일", "화요일", "수요일", "목요일", "금요일", "토요일", "일요일"] + today = date.today() + print(today) + print() + print(today.weekday()) + + url = f'{P.ModelSetting.get("ohli24_url")}/bbs/board.php?bo_table=ing&sca={week[today.weekday()]}' + + # print(url) + + if "all" in content_code_list: + ret_data = LogicOhli24.get_auto_anime_info(self, url=url) + + logger.debug(f"today_info:: {ret_data}") + + for item in ret_data["anime_list"]: + # wr_id = request.form.get("wr_id", None) + # bo_table = request.form.get("bo_table", None) + wr_id = None + bo_table = None + data = [] + # print(code) + # logger.info("code::: %s", code) + # logger.debug(item) + + # 잠시 중지 + # data = self.get_series_info(item["code"], wr_id, bo_table) + # logger.debug(data) + + # result = asyncio.run(LogicOhli24.main(url_list)) + # logger.debug(f"result:: {result}") + + elif len(content_code_list) > 0: + for item in content_code_list: + url = P.ModelSetting.get("ohli24_url") + "/c/" + item + print("scheduling url: %s", url) + # ret_data = LogicOhli24.get_auto_anime_info(self, url=url) + content_info = self.get_series_info(item, "", "") + + for episode_info in content_info["episode"]: + add_ret = self.add(episode_info) + if add_ret.startswith("enqueue"): + self.socketio_callback("list_refresh", "") + # logger.debug(f"data: {data}") + # self.current_data = data + # db에서 다운로드 완료 유무 체크 + + @staticmethod + async def get_data(url) -> str: + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + content = await response.text() + # print(response) + return content + + @staticmethod + async def main(url_list: list): + input_coroutines = [LogicOhli24.get_data(url_) for url_ in url_list] + res = await asyncio.gather(*input_coroutines) + return res + + def get_series_info(self, code, wr_id, bo_table): + code_type = "c" + + try: + if ( + self.current_data is not None + and "code" in self.current_data + and self.current_data["code"] == code + ): + return self.current_data + + if code.startswith("http"): + + # if code.split('c/')[1] is not None: + # code = code.split('c/')[1] + # code_type = 'c' + # elif code.split('e/')[1] is not None: + # code_type = 'e' + # code = code.split('e/')[1] + if "/c/" in code: + code = code.split("c/")[1] + code_type = "c" + elif "/e/" in code: + code = code.split("e/")[1] + code_type = "e" + + logger.info(f"code:::: {code}") + + if code_type == "c": + url = P.ModelSetting.get("ohli24_url") + "/c/" + code + elif code_type == "e": + url = P.ModelSetting.get("ohli24_url") + "/e/" + code + else: + url = P.ModelSetting.get("ohli24_url") + "/e/" + code + + if wr_id is not None: + # print(len(wr_id)) + if len(wr_id) > 0: + url = ( + P.ModelSetting.get("ohli24_url") + + "/bbs/board.php?bo_table=" + + bo_table + + "&wr_id=" + + wr_id + ) + else: + pass + + logger.debug("url:::> %s", url) + + # self.current_headers = { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) + # AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36', + # 'Referer': url } + + response_data = LogicOhli24.get_html(url, timeout=10) + tree = html.fromstring(response_data) + title = tree.xpath('//div[@class="view-title"]/h1/text()')[0] + # image = tree.xpath('//div[@class="view-info"]/div[@class="image"]/div/img')[0]['src'] + image = tree.xpath('//div[@class="image"]/div/img/@src')[0] + image = image.replace("..", P.ModelSetting.get("ohli24_url")) + des_items = tree.xpath('//div[@class="list"]/p') + des = {} + des_key = [ + "_otit", + "_dir", + "_pub", + "_tag", + "_classifi", + "_country", + "_grade", + "_total_chapter", + "_show_time", + "_release_year", + ] + description_dict = { + "원제": "_otit", + "원작": "_org", + "감독": "_dir", + "각본": "_scr", + "캐릭터 디자인": "_character_design", + "음악": "_sound", + "제작사": "_pub", + "장르": "_tag", + "분류": "_classifi", + "제작국가": "_country", + "방영일": "_date", + "등급": "_grade", + "총화수": "_total_chapter", + "상영시간": "_show_time", + "상영일": "_release_date", + "개봉년도": "_release_year", + "개봉일": "_opening_date", + "런타임": "_run_time", + } + + list_body_li = tree.xpath('//ul[@class="list-body"]/li') + # logger.debug(f"list_body_li:: {list_body_li}") + episodes = [] + vi = None + for li in list_body_li: + # logger.debug(li) + title = li.xpath(".//a/text()")[0].strip() + thumbnail = image + # logger.info(li.xpath('//a[@class="item-subject"]/@href')) + link = ( + P.ModelSetting.get("ohli24_url") + + li.xpath('.//a[@class="item-subject"]/@href')[0] + ) + # logger.debug(f"link:: {link}") + date = li.xpath('.//div[@class="wr-date"]/text()')[0] + m = hashlib.md5(title.encode("utf-8")) + # _vi = hashlib.md5(title.encode('utf-8').hexdigest()) + # logger.info(m.hexdigest()) + _vi = m.hexdigest() + episodes.append( + { + "title": title, + "link": link, + "thumbnail": image, + "date": date, + "day": date, + "_id": title, + "va": link, + "_vi": _vi, + "content_code": code, + } + ) + + logger.info("des_items length:: %s", len(des_items)) + for idx, item in enumerate(des_items): + # key = des_key[idx] + span = item.xpath(".//span//text()") + # logger.info(span) + key = description_dict[span[0]] + try: + des[key] = item.xpath(".//span/text()")[1] + except IndexError: + des[key] = "" + + # logger.info(f"des::>> {des}") + image = image.replace("..", P.ModelSetting.get("ohli24_url")) + # logger.info("images:: %s", image) + logger.info("title:: %s", title) + + ser_description = tree.xpath( + '//div[@class="view-stocon"]/div[@class="c"]/text()' + ) + + data = { + "title": title, + "image": image, + "date": "2022.01.11 00:30 (화)", + "ser_description": ser_description, + "des": des, + "episode": episodes, + } + + if P.ModelSetting.get_bool("ohli24_order_desc"): + data["episode"] = list(reversed(data["episode"])) + data["list_order"] = "desc" + + return data + # logger.info(response_text) + + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + return {"ret": "exception", "log": str(e)} + + def get_anime_info(self, cate, page): + try: + if cate == "ing": + url = ( + P.ModelSetting.get("ohli24_url") + + "/bbs/board.php?bo_table=" + + cate + + "&page=" + + page + ) + elif cate == "movie": + url = ( + P.ModelSetting.get("ohli24_url") + + "/bbs/board.php?bo_table=" + + cate + + "&page=" + + page + ) + else: + url = ( + P.ModelSetting.get("ohli24_url") + + "/bbs/board.php?bo_table=" + + cate + + "&page=" + + page + ) + # cate == "complete": + logger.info("url:::> %s", url) + data = {} + response_data = LogicOhli24.get_html(url, timeout=10) + tree = html.fromstring(response_data) + tmp_items = tree.xpath('//div[@class="list-row"]') + data["anime_count"] = len(tmp_items) + data["anime_list"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = entity["link"].split("/")[-1] + entity["title"] = item.xpath(".//div[@class='post-title']/text()")[ + 0 + ].strip() + entity["image_link"] = item.xpath(".//div[@class='img-item']/img/@src")[ + 0 + ].replace("..", P.ModelSetting.get("ohli24_url")) + data["ret"] = "success" + data["anime_list"].append(entity) + + return data + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + return {"ret": "exception", "log": str(e)} + + def get_auto_anime_info(self, url: str = ""): + try: + + logger.info("url:::> %s", url) + data = {} + response_data = LogicOhli24.get_html(url, timeout=10) + tree = html.fromstring(response_data) + tmp_items = tree.xpath('//div[@class="list-row"]') + data["anime_count"] = len(tmp_items) + data["anime_list"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + entity["code"] = entity["link"].split("/")[-1] + entity["title"] = item.xpath(".//div[@class='post-title']/text()")[ + 0 + ].strip() + entity["image_link"] = item.xpath(".//div[@class='img-item']/img/@src")[ + 0 + ].replace("..", P.ModelSetting.get("ohli24_url")) + data["ret"] = "success" + data["anime_list"].append(entity) + + return data + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + return {"ret": "exception", "log": str(e)} + + # @staticmethod + def get_search_result(self, query, page, cate): + try: + _query = urllib.parse.quote(query) + url = ( + P.ModelSetting.get("ohli24_url") + + "/bbs/search.php?srows=24&gr_id=&sfl=wr_subject&stx=" + + _query + + "&page=" + + page + ) + + logger.info("get_search_result()::url> %s", url) + data = {} + response_data = LogicOhli24.get_html(url, timeout=10) + tree = html.fromstring(response_data) + tmp_items = tree.xpath('//div[@class="list-row"]') + data["anime_count"] = len(tmp_items) + data["anime_list"] = [] + + for item in tmp_items: + entity = {} + entity["link"] = item.xpath(".//a/@href")[0] + # entity["code"] = entity["link"].split("/")[-1] + entity["wr_id"] = entity["link"].split("=")[-1] + # logger.debug(item.xpath(".//div[@class='post-title']/text()").join()) + entity["title"] = "".join( + item.xpath(".//div[@class='post-title']/text()") + ).strip() + entity["image_link"] = item.xpath(".//div[@class='img-item']/img/@src")[ + 0 + ].replace("..", P.ModelSetting.get("ohli24_url")) + + entity["code"] = item.xpath(".//div[@class='img-item']/img/@alt")[0] + + data["ret"] = "success" + data["anime_list"].append(entity) + + return data + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + return {"ret": "exception", "log": str(e)} + + # @staticmethod + def plugin_load(self): + try: + # ffmpeg_modelsetting = get_model_setting("ffmpeg", logger) + # SupportFfmpeg.initialize(P.ModelSetting.get('ffmpeg_path'), os.path.join(F.config['path_data'], 'tmp'), + # self.callback_function, P.ModelSetting.get_int('max_pf_count')) + # P.logger.debug(ffmpeg_modelsetting.get('ffmpeg_path')) + P.logger.debug(F.config['path_data']) + + # SupportFfmpeg.initialize(ffmpeg_modelsetting.get('ffmpeg_path'), os.path.join(F.config['path_data'], 'tmp'), + # self.callback_function, ffmpeg_modelsetting.get_int('max_pf_count')) + + SupportFfmpeg.initialize("ffmpeg", os.path.join(F.config['path_data'], 'tmp'), + self.callback_function, 1) + + logger.debug("%s plugin_load", P.package_name) + self.queue = FfmpegQueue( + P, P.ModelSetting.get_int("ohli24_max_ffmpeg_process_count") + ) + self.current_data = None + self.queue.queue_start() + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + # @staticmethod + def plugin_unload(self): + try: + logger.debug("%s plugin_unload", P.package_name) + scheduler.remove_job("%s_recent" % P.package_name) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + @staticmethod + def reset_db() -> bool: + db.session.query(ModelOhli24Item).delete() + db.session.commit() + return True + + @staticmethod + def get_html(url, referer=None, stream=False, timeout=5): + data = "" + headers = { + "referer": f"https://ohli24.net", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" + "Mozilla/5.0 (Macintosh; Intel " + "Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 " + "Whale/3.12.129.46 Safari/537.36", + "X-Requested-With": "XMLHttpRequest", + } + try: + + if LogicOhli24.session is None: + LogicOhli24.session = requests.session() + + # logger.debug('get_html :%s', url) + headers["Referer"] = "" if referer is None else referer + page_content = LogicOhli24.session.get( + url, headers=headers, timeout=timeout + ) + data = page_content.text + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + return data + + ######################################################### + def add(self, episode_info): + if self.is_exist(episode_info): + return "queue_exist" + else: + db_entity = ModelOhli24Item.get_by_ohli24_id(episode_info["_id"]) + # logger.debug("db_entity:::> %s", db_entity) + if db_entity is None: + entity = Ohli24QueueEntity(P, self, episode_info) + logger.debug("entity:::> %s", entity.as_dict()) + ModelOhli24Item.append(entity.as_dict()) + # # logger.debug("entity:: type >> %s", type(entity)) + # + self.queue.add_queue(entity) + + # P.logger.debug(F.config['path_data']) + # P.logger.debug(self.headers) + + # filename = os.path.basename(entity.filepath) + # ffmpeg = SupportFfmpeg(entity.url, entity.filename, callback_function=self.callback_function, + # max_pf_count=0, + # save_path=entity.savepath, timeout_minute=60, headers=self.headers) + # ret = {'ret': 'success'} + # ret['json'] = ffmpeg.start() + return "enqueue_db_append" + elif db_entity.status != "completed": + entity = Ohli24QueueEntity(P, self, episode_info) + + logger.debug("entity:::> %s", entity.as_dict()) + + P.logger.debug(F.config['path_data']) + P.logger.debug(self.headers) + + filename = os.path.basename(entity.filepath) + ffmpeg = SupportFfmpeg(entity.url, entity.filename, callback_function=self.callback_function, + max_pf_count=0, save_path=entity.savepath, timeout_minute=60, + headers=self.headers) + ret = {'ret': 'success'} + ret['json'] = ffmpeg.start() + + # self.queue.add_queue(entity) + return "enqueue_db_exist" + else: + return "db_completed" + + def is_exist(self, info): + # for en in self.queue.entity_list: + # if en.info["_id"] == info["_id"]: + # return True + return False + + def callback_function(self, **args): + refresh_type = None + if args['type'] == 'status_change': + if args['status'] == SupportFfmpeg.Status.DOWNLOADING: + refresh_type = 'status_change' + elif args['status'] == SupportFfmpeg.Status.COMPLETED: + refresh_type = 'status_change' + elif args['status'] == SupportFfmpeg.Status.READY: + data = {'type': 'info', + 'msg': '다운로드중 Duration(%s)' % args['data']['duration_str'] + '<br>' + args['data'][ + 'save_fullpath'], 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['type'] == 'last': + if args['status'] == SupportFfmpeg.Status.WRONG_URL: + data = {'type': 'warning', 'msg': '잘못된 URL입니다'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['status'] == SupportFfmpeg.Status.WRONG_DIRECTORY: + data = {'type': 'warning', 'msg': '잘못된 디렉토리입니다.<br>' + args['data']['save_fullpath']} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['status'] == SupportFfmpeg.Status.ERROR or args['status'] == SupportFfmpeg.Status.EXCEPTION: + data = {'type': 'warning', 'msg': '다운로드 시작 실패.<br>' + args['data']['save_fullpath']} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'add' + elif args['status'] == SupportFfmpeg.Status.USER_STOP: + data = {'type': 'warning', 'msg': '다운로드가 중지 되었습니다.<br>' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.COMPLETED: + data = {'type': 'success', 'msg': '다운로드가 완료 되었습니다.<br>' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.TIME_OVER: + data = {'type': 'warning', 'msg': '시간초과로 중단 되었습니다.<br>' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.PF_STOP: + data = {'type': 'warning', 'msg': 'PF초과로 중단 되었습니다.<br>' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.FORCE_STOP: + data = {'type': 'warning', 'msg': '강제 중단 되었습니다.<br>' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.HTTP_FORBIDDEN: + data = {'type': 'warning', 'msg': '403에러로 중단 되었습니다.<br>' + args['data']['save_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['status'] == SupportFfmpeg.Status.ALREADY_DOWNLOADING: + data = {'type': 'warning', 'msg': '임시파일폴더에 파일이 있습니다.<br>' + args['data']['temp_fullpath'], + 'url': '/ffmpeg/download/list'} + socketio.emit("notify", data, namespace='/framework', broadcast=True) + refresh_type = 'last' + elif args['type'] == 'normal': + if args['status'] == SupportFfmpeg.Status.DOWNLOADING: + refresh_type = 'status' + # P.logger.info(refresh_type) + self.socketio_callback(refresh_type, args['data']) + + +class Ohli24QueueEntity(FfmpegQueueEntity): + def __init__(self, P, module_logic, info): + super(Ohli24QueueEntity, self).__init__(P, module_logic, info) + self._vi = None + self.url = None + self.epi_queue = None + self.filepath = None + self.savepath = None + self.quality = None + self.filename = None + self.vtt = None + self.season = 1 + self.content_title = None + self.srt_url = None + self.headers = None + # Todo::: 임시 주석 처리 + self.make_episode_info() + + def refresh_status(self): + self.module_logic.socketio_callback("status", self.as_dict()) + + def info_dict(self, tmp): + # logger.debug('self.info::> %s', self.info) + for key, value in self.info.items(): + tmp[key] = value + tmp["vtt"] = self.vtt + tmp["season"] = self.season + tmp["content_title"] = self.content_title + tmp["ohli24_info"] = self.info + tmp["epi_queue"] = self.epi_queue + return tmp + + def donwload_completed(self): + db_entity = ModelOhli24Item.get_by_ohli24_id(self.info["_id"]) + if db_entity is not None: + db_entity.status = "completed" + db_entity.complated_time = datetime.now() + db_entity.save() + + # Get episode info from OHLI24 site + + def make_episode_info(self): + try: + # url = 'https://ohli24.net/e/' + self.info['va'] + base_url = "https://ohli24.net" + iframe_url = "" + + # https://ohli24.net/e/%EB%85%B9%EC%9D%84%20%EB%A8%B9%EB%8A%94%20%EB%B9%84%EC%8A%A4%EC%BD%94%206%ED%99%94 + url = self.info["va"] + + ourls = parse.urlparse(url) + + headers = { + "referer": f"{ourls.scheme}://{ourls.netloc}", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36", + } + logger.debug("make_episode_info()::url==> %s", url) + logger.info(f"self.info:::> {self.info}") + + text = requests.get(url, headers=headers).text + # logger.debug(text) + soup1 = BeautifulSoup(text, "lxml") + pattern = re.compile(r"url : \"\.\.(.*)\"") + script = soup1.find("script", text=pattern) + + if script: + match = pattern.search(script.text) + if match: + iframe_url = match.group(1) + logger.info("iframe_url::> %s", iframe_url) + + logger.debug(soup1.find("iframe")) + + iframe_url = soup1.find("iframe")["src"] + logger.info("iframe_url::> %s", iframe_url) + + print(base_url) + print(iframe_url) + # exit() + + # resp = requests.get(iframe_url, headers=headers, timeout=20).text + # soup2 = BeautifulSoup(resp, "lxml") + # iframe_src = soup2.find("iframe")["src"] + iframe_src = iframe_url + # print(resp1) + + logger.debug(f"iframe_src:::> {iframe_src}") + + resp1 = requests.get(iframe_src, headers=headers, timeout=600).text + # logger.info('resp1::>> %s', resp1) + soup3 = BeautifulSoup(resp1, "lxml") + # packed_pattern = re.compile(r'\\{*(eval.+)*\\}', re.MULTILINE | re.DOTALL) + s_pattern = re.compile(r"(eval.+)", re.MULTILINE | re.DOTALL) + packed_pattern = re.compile( + r"if?.([^{}]+)\{.*(eval.+)\}.+else?.{.(eval.+)\}", re.DOTALL + ) + packed_script = soup3.find("script", text=s_pattern) + # packed_script = soup3.find('script') + # logger.info('packed_script>>> %s', packed_script.text) + unpack_script = None + if packed_script is not None: + # logger.debug('zzzzzzzzzzzz') + match = packed_pattern.search(packed_script.text) + # match = re.search(packed_pattern, packed_script.text) + # logger.debug("match::: %s", match.group()) + unpack_script = jsbeautifier.beautify(match.group(3)) + + # logger.info('match groups:: %s', match.groups()) + # logger.info('match group3:: %s', match.group(3)) + # print('packed_script==>', packed_script) + # logger.debug(unpack_script) + + p1 = re.compile(r"(\"tracks\".*\])\,\"captions\"", re.MULTILINE | re.DOTALL) + m2 = re.search( + r"(\"tracks\".*\]).*\"captions\"", + unpack_script, + flags=re.MULTILINE | re.DOTALL, + ) + # print(m2.group(1)) + dict_string = "{" + m2.group(1) + "}" + + logger.info(f"dict_string::> {dict_string}") + tracks = json.loads(dict_string) + self.srt_url = tracks["tracks"][0]["file"] + + logger.debug(f'srt_url::: {tracks["tracks"][0]["file"]}') + + video_hash = iframe_src.split("/") + video_hashcode = re.sub(r"index\.php\?data=", "", video_hash[-1]) + self._vi = video_hashcode + video_info_url = f"{video_hash[0]}//{video_hash[2]}/player/index.php?data={video_hashcode}&do=getVideo" + # print('hash:::', video_hash) + logger.debug(f"video_info_url::: {video_info_url}") + + headers = { + "referer": f"{iframe_src}", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" + "Mozilla/5.0 (Macintosh; Intel " + "Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 " + "Whale/3.12.129.46 Safari/537.36", + "X-Requested-With": "XMLHttpRequest", + } + # print(headers) + payload = { + "hash": video_hash[-1], + } + resp2 = requests.post( + video_info_url, headers=headers, data=payload, timeout=20 + ).json() + + logger.debug("resp2::> %s", resp2) + + hls_url = resp2["videoSource"] + logger.debug(f"video_url::> {hls_url}") + + resp3 = requests.get(hls_url, headers=headers).text + # logger.debug(resp3) + + # stream_url = hls_url.split('\n')[-1].strip() + stream_info = resp3.split("\n")[-2:] + # logger.debug('stream_url:: %s', stream_url) + logger.debug(f"stream_info:: {stream_info}") + self.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": "https://ndoodle.xyz/video/03a3655fff3e9bdea48de9f49e938e32", + } + + self.url = stream_info[1].strip() + match = re.compile(r'NAME="(?P<quality>.*?)"').search(stream_info[0]) + self.quality = "720P" + if match is not None: + self.quality = match.group("quality") + logger.info(self.quality) + + match = re.compile( + r"(?P<title>.*?)\s*((?P<season>\d+)%s)?\s*((?P<epi_no>\d+)%s)" + % ("기", "화") + ).search(self.info["title"]) + + # epi_no 초기값 + epi_no = 1 + + if match: + self.content_title = match.group("title").strip() + if "season" in match.groupdict() and match.group("season") is not None: + self.season = int(match.group("season")) + + # epi_no = 1 + epi_no = int(match.group("epi_no")) + ret = "%s.S%sE%s.%s-OHNI24.mp4" % ( + self.content_title, + "0%s" % self.season if self.season < 10 else self.season, + "0%s" % epi_no if epi_no < 10 else epi_no, + self.quality, + ) + else: + self.content_title = self.info["title"] + P.logger.debug("NOT MATCH") + ret = "%s.720p-OHNI24.mp4" % self.info["title"] + + # logger.info('self.content_title:: %s', self.content_title) + self.epi_queue = epi_no + self.filename = Util.change_text_for_use_filename(ret) + logger.info(f"self.filename::> {self.filename}") + self.savepath = P.ModelSetting.get("ohli24_download_path") + logger.info(f"self.savepath::> {self.savepath}") + + # TODO: 완결 처리 + + if P.ModelSetting.get_bool("ohli24_auto_make_folder"): + if self.info["day"].find("완결") != -1: + folder_name = "%s %s" % ( + P.ModelSetting.get("ohli24_finished_insert"), + self.content_title, + ) + else: + folder_name = self.content_title + folder_name = Util.change_text_for_use_filename(folder_name.strip()) + self.savepath = os.path.join(self.savepath, folder_name) + if P.ModelSetting.get_bool("ohli24_auto_make_season_folder"): + self.savepath = os.path.join( + self.savepath, "Season %s" % int(self.season) + ) + self.filepath = os.path.join(self.savepath, self.filename) + if not os.path.exists(self.savepath): + os.makedirs(self.savepath) + + # from .lib.util import write_file, convert_vtt_to_srt + + srt_filepath = os.path.join( + self.savepath, self.filename.replace(".mp4", ".ko.srt") + ) + + if self.srt_url is not None and not os.path.exists(srt_filepath): + if requests.get(self.srt_url, headers=headers).status_code == 200: + srt_data = requests.get(self.srt_url, headers=headers).text + Util.write_file(srt_data, srt_filepath) + + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + + +class ModelOhli24Item(db.Model): + __tablename__ = "{package_name}_ohli24_item".format(package_name=P.package_name) + __table_args__ = {"mysql_collate": "utf8_general_ci"} + __bind_key__ = P.package_name + id = db.Column(db.Integer, primary_key=True) + created_time = db.Column(db.DateTime) + completed_time = db.Column(db.DateTime) + reserved = db.Column(db.JSON) + content_code = db.Column(db.String) + season = db.Column(db.Integer) + episode_no = db.Column(db.Integer) + title = db.Column(db.String) + episode_title = db.Column(db.String) + ohli24_va = db.Column(db.String) + ohli24_vi = db.Column(db.String) + ohli24_id = db.Column(db.String) + quality = db.Column(db.String) + filepath = db.Column(db.String) + filename = db.Column(db.String) + savepath = db.Column(db.String) + video_url = db.Column(db.String) + vtt_url = db.Column(db.String) + thumbnail = db.Column(db.String) + status = db.Column(db.String) + ohli24_info = db.Column(db.JSON) + + def __init__(self): + self.created_time = datetime.now() + + def __repr__(self): + 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 save(self): + db.session.add(self) + db.session.commit() + + @classmethod + def get_by_id(cls, idx): + return db.session.query(cls).filter_by(id=idx).first() + + @classmethod + def get_by_ohli24_id(cls, ohli24_id): + return db.session.query(cls).filter_by(ohli24_id=ohli24_id).first() + + @classmethod + def delete_by_id(cls, idx): + db.session.query(cls).filter_by(id=idx).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) + count = query.count() + query = query.limit(page_size).offset((page - 1) * page_size) + lists = query.all() + ret["list"] = [item.as_dict() for item in lists] + ret["paging"] = Util.get_paging_info(count, page, page_size) + return ret + + @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 + + @classmethod + def get_list_uncompleted(cls): + return db.session.query(cls).filter(cls.status != "completed").all() + + @classmethod + def append(cls, q): + item = ModelOhli24Item() + item.content_code = q["content_code"] + item.season = q["season"] + item.episode_no = q["epi_queue"] + item.title = q["content_title"] + item.episode_title = q["title"] + item.ohli24_va = q["va"] + item.ohli24_vi = q["_vi"] + item.ohli24_id = q["_id"] + item.quality = q["quality"] + item.filepath = q["filepath"] + item.filename = q["filename"] + item.savepath = q["savepath"] + item.video_url = q["url"] + item.vtt_url = q["vtt"] + item.thumbnail = q["thumbnail"] + item.status = "wait" + item.ohli24_info = q["ohli24_info"] + item.save() diff --git a/plugin.py.old b/plugin.py.old new file mode 100644 index 0000000..d34c197 --- /dev/null +++ b/plugin.py.old @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# @Time : 2022/02/08 2:57 PM +# @Author : yommi +# @Site : +# @File : plugin +# @Software: PyCharm + +import os, traceback + +# third-party +from flask import Blueprint + +# sjva 공용 +from framework.logger import get_logger +from framework import app, path_data +from plugin import get_model_setting, Logic, default_route, PluginUtil + + +####################################################################### + + +class P(object): + 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="static", + ) + menu = { + "main": [package_name, "애니 다운로드"], + "sub": [ + ["ohli24", "OHLI24"], + ["linkkf", "LINKKF"], + ["anilife", "애니라이프"], + ["log", "로그"], + ], + "category": "vod", + "sub2": { + "ohli24": [ + ["setting", "설정"], + ["request", "요청"], + ["queue", "큐"], + ["category", "검색"], + ["list", "목록"], + ], + "linkkf": [ + ["setting", "설정"], + ["request", "요청"], + ["queue", "큐"], + ["category", "검색"], + ["list", "목록"], + ], + "anilife": [ + ["setting", "설정"], + ["request", "요청"], + ["category", "검색"], + ["queue", "큐"], + ["list", "목록"], + ], + }, + } + plugin_info = { + "version": "0.1.2.0", + "name": "anime_downloader", + "category_name": "vod", + "icon": "", + "developer": "soju6jan && projectdx", + "description": "비디오 다운로드", + "home": "http://yommi.duckdns.org:20080/projectdx/anime-downloader", + "more": "", + } + ModelSetting = get_model_setting(package_name, logger) + logic = None + module_list = None + home_module = "ohli24" + + +# 초기화 함수 +def initialize(): + try: + app.config["SQLALCHEMY_BINDS"][P.package_name] = "sqlite:///%s" % ( + os.path.join( + path_data, "db", "{package_name}.db".format(package_name=P.package_name) + ) + ) + PluginUtil.make_info_json(P.plugin_info, __file__) + from .logic_ohli24 import LogicOhli24 + from .logic_anilife import LogicAniLife + + # P.module_list = [LogicOhli24(P), LogicLinkkf(P)] + P.module_list = [LogicOhli24(P), LogicAniLife(P)] + P.logic = Logic(P) + default_route(P) + except Exception as e: + P.logger.error("Exception:%s", e) + P.logger.error(traceback.format_exc()) + + +initialize() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..98781d1 --- /dev/null +++ b/setup.py @@ -0,0 +1,135 @@ +__menu = { + 'uri': __package__, + 'name': '애니 다운로더', + 'list': [ + { + 'uri': 'ohli24', + 'name': '애니24', + 'list': [ + { + 'uri': 'setting', + 'name': '설정' + }, + { + 'uri': 'request', + 'name': '요청' + }, + { + 'uri': 'queue', + 'name': '큐' + }, + { + 'uri': 'search', + 'name': '검색', + }, + { + 'uri': 'list', + 'name': '목록', + } + + ] + }, + { + 'uri': 'anilife', + 'name': '애니라이프', + 'list': [ + { + 'uri': 'setting', + 'name': '설정' + }, + { + 'uri': 'request', + 'name': '요청' + }, + { + 'uri': 'queue', + 'name': '큐' + }, + { + 'uri': 'search', + 'name': '검색', + }, + { + 'uri': 'list', + 'name': '목록', + } + + ] + }, + { + 'uri': 'linkkf', + 'name': '링크애니', + 'list': [ + { + 'uri': 'setting', + 'name': '설정' + }, + { + 'uri': 'request', + 'name': '요청' + }, + { + 'uri': 'queue', + 'name': '큐' + }, + { + 'uri': 'search', + 'name': '검색', + }, + { + 'uri': 'list', + 'name': '목록', + } + + ] + }, + { + 'uri': 'manual', + 'name': '매뉴얼', + 'list': [ + { + 'uri': 'README.md', + 'name': 'README', + }, + ] + }, + { + 'uri': 'log', + 'name': '로그', + }, + ] +} + +setting = { + 'filepath': __file__, + 'use_db': True, + 'use_default_setting': True, + 'home_module': 'ohli24', + 'menu': __menu, + # 'setting_menu': None, + 'default_route': 'normal', +} + +from plugin import * + +DEFINE_DEV = True + +P = create_plugin_instance(setting) +try: + if DEFINE_DEV: + from .mod_ohli24 import LogicOhli24 + from .mod_anilife import LogicAniLife + from .mod_linkkf import LogicLinkkf + else: + from support import SupportSC + + ModuleOhli24 = SupportSC.load_module_P(P, 'mod_ohli24').LogicOhli24 + ModuleAnilife = SupportSC.load_module_P(P, 'mod_anilife').LogicAnilife + ModuleLinkkf = SupportSC.load_module_P(P, 'mod_linkkf').LogicLinkkf + P.set_module_list([ModuleOhli24, ModuleAnilife, ModuleLinkkf]) + + P.set_module_list([LogicOhli24, LogicAniLife, LogicLinkkf]) + +except Exception as e: + P.logger.error(f'Exception:{str(e)}') + P.logger.error(traceback.format_exc()) 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/sjva_ui14.js b/static/js/sjva_ui14.js new file mode 100644 index 0000000..b8931cc --- /dev/null +++ b/static/js/sjva_ui14.js @@ -0,0 +1,183 @@ + + +function m_row_start(padding='10', align='center') { + var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">'; + return str; +} +function m_row_start_hover(padding='10', align='center') { + var str = '<div class="row my_hover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">'; + return str; +} +function m_row_start_top(padding='10') { + return m_row_start(padding, 'top'); +} +function m_row_start_color(padding='10', align='center', color='') { + var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+'; background-color:'+color+'">'; + return str; +} +function m_row_start_color2(padding='10', align='center') { + var str = '<div class="row bg-dark text-white" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">'; + return str; +} + +function m_row_end() { + var str = '</div>'; + return str; +} + +//border +function m_col(w, h, align='left') { + var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">'; + str += h + str += '</div>'; + return str +} + +function m_col2(w, h, align='left') { + var str = '<div class="col-sm-' + w + ' " style="padding:5px; margin:0px; text-align: '+align+'; word-break:break-all;">'; + str += h + str += '</div>'; + return str +} + + +function m_button_group(h) { + var str = '<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">'; + str += h + str += '</div>'; + return str; +} + +function m_button(id, text, data) { + var str = '<button id="'+id+'" name="'+id+'" class="btn btn-sm btn-outline-success" ' + for ( var i in data) { + str += ' data-' + data[i].key + '="' + data[i].value+ '" ' + } + str += '>' + text + '</button>'; + return str; +} + +function m_button2(id, text, data, outline_color) { + var str = '<button id="'+id+'" name="'+id+'" class="btn btn-sm btn-outline-'+outline_color+'" ' + for ( var i in data) { + str += ' data-' + data[i].key + '="' + data[i].value+ '" ' + } + str += '>' + text + '</button>'; + return str; +} + + + + +function m_hr(margin='5') { + var str = '<hr style="width: 100%; margin:'+margin+'px;" />'; + return str; +} + + +function m_hr_black() { + var str = '<hr style="width: 100%; color: black; height: 2px; background-color:black;" />'; + return str; +} +// 체크박스는 자바로 하면 on/off 스크립트가 안먹힘. + + +function m_modal(data='EMPTY', title='JSON', json=true) { + document.getElementById("modal_title").innerHTML = title; + if (json) { + data = JSON.stringify(data, null, 2); + } + document.getElementById("modal_body").innerHTML = "<pre>"+ data + "</pre>";; + $("#large_modal").modal(); +} + +function m_tab_head(name, active) { + if (active) { + var str = '<a class="nav-item nav-link active" id="id_'+name+'" data-toggle="tab" href="#'+name+'" role="tab">'+name+'</a>'; + } else { + var str = '<a class="nav-item nav-link" id="id_'+name+'" data-toggle="tab" href="#'+name+'" role="tab">'+name+'</a>'; + } + return str; +} + +function m_tab_content(name, content, active) { + if (active) { + var str = '<div class="tab-pane fade show active" id="'+name+'" role="tabpanel" >'; + } else { + var str = '<div class="tab-pane fade show" id="'+name+'" role="tabpanel" >'; + } + str += content; + str += '</div>' + return str; +} + +function m_progress(id, width, label) { + var str = ''; + str += '<div class="progress" style="height: 25px;">' + str += '<div id="'+id+'" class="progress-bar" style="background-color:yellow;width:'+width+'%"></div>'; + str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin-top:2px">'+label+'</div>'; + str += '</div>' + return str; +} + + +function m_progress2(id, width, label) { + var str = ''; + str += '<div class="progress" style="height: 25px;">' + str += '<div id="'+id+'" class="progress-bar" style="background-color:yellow;width:'+width+'%"></div>'; + str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin:0px; margin-top:2px">'+label+'</div>'; + str += '</div>' + return str; +} + + + +function make_page_html(data) { + str = ' \ + <div class="d-inline-block"></div> \ + <div class="row mb-3"> \ + <div class="col-sm-12"> \ + <div class="btn-toolbar" style="justify-content: center;" role="toolbar" aria-label="Toolbar with button groups" > \ + <div class="btn-group btn-group-sm mr-2" role="group" aria-label="First group">' + if (data.prev_page) { + str += '<button id="page" data-page="' + (data.start_page-1) + '" type="button" class="btn btn-secondary">«</button>' + } + + for (var i = data.start_page ; i <= data.last_page ; i++) { + str += '<button id="page" data-page="' + i +'" type="button" class="btn btn-secondary" '; + if (i == data.current_page) { + str += 'disabled'; + } + str += '>'+i+'</button>'; + } + if (data.next_page) { + str += '<button id="page" data-page="' + (data.last_page+1) + '" type="button" class="btn btn-secondary">»</button>' + } + + str += '</div> \ + </div> \ + </div> \ + </div> \ + ' + document.getElementById("page1").innerHTML = str; + document.getElementById("page2").innerHTML = str; +} + +function use_collapse(div, reverse=false) { + var ret = $('#' + div).prop('checked'); + if (reverse) { + if (ret) { + $('#' + div + '_div').collapse('hide') + } else { + $('#' + div + '_div').collapse('show') + } + } else { + if (ret) { + $('#' + div + '_div').collapse('show') + } else { + $('#' + div + '_div').collapse('hide') + } + } +} + + diff --git a/static/ohli24/img_loader_x200.svg b/static/ohli24/img_loader_x200.svg new file mode 100644 index 0000000..05f8d6f --- /dev/null +++ b/static/ohli24/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/templates/anime_downloader_anilife_category.html b/templates/anime_downloader_anilife_category.html new file mode 100644 index 0000000..13091c2 --- /dev/null +++ b/templates/anime_downloader_anilife_category.html @@ -0,0 +1,813 @@ +{% extends "base.html" %} {% block content %} +<div id="preloader"> + <div class='demo'> + <!-- <div class="loader-inner">--> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <!-- </div>--> + </div> +</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-primary"> + search + </button> +</div> + +<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="theater" type="button" class="btn btn-primary">극장판</button> + <button id="complete_anilist" type="button" class="btn btn-dark"> + 완결 + </button> + <button id="top20" type="button" class="btn btn-grey"> + Top20 + </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> +<!--전체--> + +<script + type="text/javascript" + src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js" +></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 type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const anilife_url = "{{arg['anilife_url']}}"; + let current_data = null; + let page = 1; + let next_page = Number + let current_cate = '' + let current_query = '' + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + const loader = document.getElementById("preloader"); + + const dismissLoadingScreen = async function () { + console.log("Before the delay") + // await delay(2.5); + loader.style.display = "none"; + }; + + + const get_anime_list = (type, page) => { + console.log(`type: ${type}, page: ${page}`) + let url = '' + let data = {"page": page, "type": type} + + switch (type) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + case 'top20': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'top20' + break + default: + break; + } + + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret + console.log('ret::>', ret) + + if (current_screen_movie_data !== '') { + if (type === "ing") { + make_airing_list(ret.data, page) + observer.observe(); + } else if (type === "fin") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else if (type === "theater") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else { + make_screen_movie_list(ret.data, page) + } + div_visible = true + console.log(div_visible) + } + dismissLoadingScreen() + next_page = page + 1 + } + }) + } + + function make_airing_list(data, page) { + let str = '' + let tmp = '' + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + // tmp += '<img class="lozad" data-src="' + data.anime_list[i].image_link + '" />'; + tmp += '<img class="lazyload" src="../static/img_loader_x200.svg" data-original="' + data.anime_list[i].image_link + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + data.anime_list[i].code + '\'"/>'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + 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: 10, + effect: "fadeIn", + }); + + } + + function make_search_result_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + if (data.anime_list[i].wr_id !== '') { + const re = /bo_table=([^&]+)/ + const bo_table = data.anime_list[i].link.match(re) + // console.log(bo_table) + request_url = './request?code=' + data.anime_list[i].code + '&wr_id=' + data.anime_list[i].wr_id + '&bo_table=' + bo_table[1] + } else { + request_url = './request?code=' + data.anime_list[i].code + } + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="' + request_url + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + 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; + } + + } + + function make_screen_movie_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + 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: 10, + effect: "fadeIn", + }); + + } + + + $(document).ready(function () { + + // if ( "{{arg['anilife_current_code']}}" !== "" ) { + // document.getElementById("code").value = "{{arg['anilife_current_code']}}"; + // // 값이 공백이 아니면 분석 버튼 계속 누름 + // document.getElementById("analysis_btn").click(); + // } + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("엔터키를 눌렀습니다."); + $("#btn_search").trigger("click"); + } + }) + + get_anime_list("ing", 1) + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + }); + + $("body").on("click", "#btn_search", function (e) { + e.preventDefault(); + let query = $("#input_search").val(); + console.log(query); + current_cate = "search" + current_query = query + + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } + + $.ajax({ + url: "/" + package_name + "/ajax/" + sub + "/search", + type: "POST", + cache: false, + data: {query: query, type: current_cate, page: page}, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + console.log('ret:::', ret) + make_search_result_list(ret.data, 1); + next_page = page + 1 + } else { + $.notify("<strong>분석 실패</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + }); + + $('#anime_category #ing').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("ing", 1) + }) + + $('#anime_category #complete_anilist').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("fin", 1) + }) + + $('#anime_category #theater').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("theater", 1) + }) + + $('#anime_category #top20').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("top20", 1) + }) + + // 분석 버튼 클릭시 호출 + $("body").on('click', '#analysis_btn', function (e) { + e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // console.log(ret.code) + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_anilife_btn', function (e) { + e.preventDefault(); + window.open("{{arg['anilife_url']}}", "_blank"); + }); + + $("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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</strong>', {type: 'warning'}); + } else { + $.notify('<strong>추가 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + // const observer = lozad(); + // const el = document.querySelector('img'); + // const observer = lozad(el); // passing a `NodeList` (e.g. `document.querySelectorAll()`) is also valid + // observer.observe(); + const loadNextAnimes = (cate, page) => { + spinner.style.display = "block"; + let data = {type: cate, page: String(page)}; + let url = '' + switch (cate) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + case 'search': + url = "/" + package_name + "/ajax/" + sub + "/search" + current_cate = 'search' + data.query = current_query + break; + default: + break; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), + }) + .then((res) => res.json()) + .then((response) => { + // console.log("Success:", JSON.stringify(response)); + // {#imagesContainer.appendChild()#} + console.log("return page:::> ", String(response.page)); + // {#page = response.page#} + if (current_cate === 'search') { + make_search_result_list(response.data, response.page); + + } else { + make_screen_movie_list(response.data, response.page); + } + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); + }; + + + const onScroll = (e) => { + console.dir(e.target.scrollingElement.scrollHeight); + const {scrollTop, scrollHeight, clientHeight} = e.target.scrollingElement; + if (Math.round(scrollHeight - scrollTop) <= clientHeight) { + document.getElementById("spinner").style.display = "block"; + console.log("loading"); + console.log("now page::> ", page); + console.log("next_page::> ", String(next_page)); + loadNextAnimes(current_cate, next_page); + } + }; + + const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; + }; + + document.addEventListener("scroll", debounce(onScroll, 300)); +</script> +<style> + button.code-button { + min-width: 82px !important; + } + + .tooltip { + position: relative; + display: block; + } + + @media (min-width: 576px) { + .container { + max-width: 100%; + } + } + + [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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + .card-body { + padding: 0 !important; + } + + .card-title { + padding: 1rem !important; + } + + button#add_whitelist { + float: right; + } + + button.btn-favorite { + background-color: #e0ff42; + } + + body { + font-family: NanumSquareNeo, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + } + + body { + background-image: linear-gradient(90deg, #233f48, #6c6fa2, #768dae); + } + + .demo { + width: 100px; + height: 102px; + border-radius: 100%; + position: absolute; + top: 45%; + left: calc(50% - 50px); + } + + .circle { + width: 100%; + height: 100%; + position: absolute; + } + + .circle .inner { + width: 100%; + height: 100%; + border-radius: 100%; + border: 5px solid rgba(0, 255, 170, 0.7); + border-right: none; + border-top: none; + backgroudn-clip: padding; + box-shadow: inset 0px 0px 10px rgba(0, 255, 170, 0.15); + } + + @-webkit-keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + .circle:nth-of-type(0) { + transform: rotate(0deg); + } + + .circle:nth-of-type(0) .inner { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + + .circle:nth-of-type(1) { + transform: rotate(70deg); + } + + .circle:nth-of-type(1) .inner { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + + .circle:nth-of-type(2) { + transform: rotate(140deg); + } + + .circle:nth-of-type(2) .inner { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + + .demo { + -webkit-animation: spin 5s infinite linear; + animation: spin 5s infinite linear; + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + /*position: fixed;*/ + right: 0; + /*top: 0;*/ + z-index: 99999; + opacity: 0.5; + margin: 0 auto; + transform: translate(-50%, -50%); + position: absolute; + top: 50%; + + } + + .loader-inner { + bottom: 0; + height: 60px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + #preloader { + /*background-color: green;*/ + /*color: white;*/ + /*height: 100vh;*/ + /*width: 100%;*/ + /*position: fixed;*/ + /*z-index: 100;*/ + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + opacity: 0.5; + } +</style> +{% endblock %} diff --git a/templates/anime_downloader_anilife_list.html b/templates/anime_downloader_anilife_list.html new file mode 100644 index 0000000..6414ac7 --- /dev/null +++ b/templates/anime_downloader_anilife_list.html @@ -0,0 +1,172 @@ +{% 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> + +<script type="text/javascript"> +var package_name = "{{arg['package_name']}}"; +var sub = "{{arg['sub']}}"; +var current_data = null; + +$(document).ready(function(){ + global_sub_request_search('1'); +}); + +$("#search").click(function(e) { + e.preventDefault(); + global_sub_request_search('1'); +}); + +$("body").on('click', '#page', function(e){ + e.preventDefault(); + global_sub_request_search($(this).data('page')); +}); + +$("#reset_btn").click(function(e) { + e.preventDefault(); + document.getElementById("order").value = 'desc'; + document.getElementById("option").value = 'all'; + document.getElementById("search_word").value = ''; + global_sub_request_search('1') +}); + + +$("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(); + var search_word = $(this).data('title'); + document.getElementById("search_word").value = search_word; + global_sub_request_search('1') +}); + +$("body").on('click', '#remove_btn', function(e) { + e.preventDefault(); + id = $(this).data('id'); + $.ajax({ + url: '/'+package_name+'/ajax/'+sub+ '/db_remove', + type: "POST", + cache: false, + data: {id:id}, + dataType: "json", + success: function (data) { + if (data) { + $.notify('<strong>삭제되었습니다.</strong>', { + type: 'success' + }); + global_sub_request_search(current_data.paging.current_page, false) + } else { + $.notify('<strong>삭제 실패</strong>', { + type: 'warning' + }); + } + } + }); +}); + +$("body").on('click', '#request_btn', function(e){ + e.preventDefault(); + var content_code = $(this).data('content_code'); + $(location).attr('href', '/' + package_name + '/' + sub + '/request?content_code=' + content_code) +}); + + + +function make_list(data) { + //console.log(data) + str = ''; + for (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 + '(추가)'; + if (data[i].completed_time != null) + tmp += data[i].completed_time + '(완료)'; + str += m_col(3, tmp) + tmp = data[i].savepath + '<br>' + data[i].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].content_code}]); + tmp2 += m_button('self_search_btn', '목록 검색', [{'key':'title', 'value':data[i].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; +} + + + + +</script> +<style> +body { + width: 100%; + /*height: 100vh;*/ + /*display: flex;*/ + align-items: center; + justify-content: center; + background-size: 300% 300%; + background-image: linear-gradient( + -45deg, + rgba(59,173,227,1) 0%, + rgba(87,111,230,1) 25%, + rgba(152,68,183,1) 51%, + rgba(255,53,127,1) 100% + ); + animation: AnimateBG 20s ease infinite; +} +#main_container { + background-color: white; +} + +@keyframes AnimateBG { + 0%{background-position:0% 50%} + 50%{background-position:100% 50%} + 100%{background-position:0% 50%} +} + +</style> +{% endblock %} \ No newline at end of file diff --git a/templates/anime_downloader_anilife_queue.html b/templates/anime_downloader_anilife_queue.html new file mode 100644 index 0000000..6ec4add --- /dev/null +++ b/templates/anime_downloader_anilife_queue.html @@ -0,0 +1,130 @@ +{% extends "base.html" %} +{% block content %} + +<div> + {{ 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')) }} + {{ macros.m_col(3, macros.m_strong('Status')) }} + {{ macros.m_col(2, macros.m_strong('Action')) }} + {{ macros.m_row_end() }} + {{ macros.m_hr_head_bottom() }} + <div id="download_list_div"></div> +</div> <!--전체--> + +<script type="text/javascript"> +var package_name = "{{arg['package_name'] }}"; +var sub = "{{arg['sub'] }}"; +var current_data = null; +socket = io.connect(window.location.protocol + "//" + document.domain + ":" + location.port + "/" + package_name + '/' + sub); + +$(document).ready(function(){ +}); + +socket.on('start', function(data){ + on_start(); +}); +socket.on('list_refresh', function(data){ + on_start() +}); + +socket.on('status', function(data){ + on_status(data) +}); + + +function on_start() { + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/entity_list', + type: "POST", + cache: false, + data: {}, + dataType: "json", + success: function (data) { + make_download_list(data) + } + }); +} + + +function on_status(data) { + //console.log(data) + tmp = document.getElementById("progress_"+data.entity_id) + if (tmp != null) { + document.getElementById("progress_"+data.entity_id).style.width = data.ffmpeg_percent+ '%'; + document.getElementById("progress_"+data.entity_id+"_label").innerHTML = data.ffmpeg_status_kor + "(" + data.ffmpeg_percent + "%)" + ' ' + ((data.ffmpeg_arg != null)?data.ffmpeg_arg.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); + str += m_col(4, (data[i].filename != null) ? data[i].filename : ''); + + 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(2, 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} + queue_command(send_data) +}); + +$("body").on('click', '#reset_btn', function(e){ + e.preventDefault(); + entity_id = $(this).data('id') + send_data = {'command':'reset', 'entity_id':-1} + queue_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} + queue_command(send_data) +}); + +function queue_command(data) { + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/queue_command', + type: "POST", + cache: false, + data: data, + dataType: "json", + success: function (ret) { + if (ret.ret == 'notify') { + $.notify('<strong>'+ ret.log +'</strong>', {type: 'warning'}); + } + on_start(); + } + }); +} + +$("body").on('click', '#go_ffmpeg_btn', function(e){ + e.preventDefault(); + $(location).attr('href', '/ffmpeg') +}); + +</script> +{% endblock %} \ No newline at end of file diff --git a/templates/anime_downloader_anilife_request.html b/templates/anime_downloader_anilife_request.html new file mode 100644 index 0000000..db05839 --- /dev/null +++ b/templates/anime_downloader_anilife_request.html @@ -0,0 +1,569 @@ +{% extends "base.html" %} {% block content %} + + +<div> + <div id="preloader" class="loader"> + <div class="loader-inner"> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + </div> + </div> + <form id="program_list"> + {{ macros.setting_input_text_and_buttons('code', '작품 Code', + [['analysis_btn', '분석'], ['go_anilife_btn', 'Go 애니라이프']], desc='예) + "https://anilife.live/g/l?id=f6e83ec6-bd25-4d6c-9428-c10522687604" 이나 "f6e83ec6-bd25-4d6c-9428-c10522687604"') + }} + </form> + <form id="program_auto_form"> + <div id="episode_list"></div> + </form> +</div> +<!--전체--> + +<script type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const anilife_url = "{{arg['anilife_url']}}"; + let current_data = null; + + const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), + }) + + + function findGetParameter(parameterName) { + let result = null, + tmp = []; + const items = location.search.substr(1).split("&"); + for (let index = 0; index < items.length; index++) { + tmp = items[index].split("="); + if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); + } + return result; + } + + const loader = document.getElementById("preloader"); + + const dismissLoadingScreen = async function () { + console.log("Before the delay") + // await delay(2.5); + loader.style.display = "none"; + }; + + const wait_seconds = function () { + // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp + let result = setTimeout(dismissLoadingScreen, 2500); + console.log(result) + }; + const init = function () { + + } + + function delay(n) { + return new Promise(function (resolve) { + setTimeout(resolve, n * 1000); + }); + } + + async function myAsyncFunction() { + //Do what you want here + console.log("Before the delay") + + await delay(2.5); + + console.log("After the delay") + //Do what you want here too + + } + + // myAsyncFunction(); + // window.addEventListener("DOMContentLoaded", dismissLoadingScreen); + + // window.addEventListener("DOMContentLoaded", wait_seconds); + + function analyze(wr_id, bo_table) { + // e.preventDefault(); + // e.stopPropagation() + // e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code, wr_id: wr_id, bo_table: bo_table}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // {#console.log(ret.code)#} + console.log(ret.data) + make_program(ret.data) + $("#loader").css("display", 'none') + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + } + + function make_program(data) { + current_data = data; + // console.log("current_data::", current_data) + str = ''; + 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 += m_button('apply_new_title_btn', '저장폴더명, 파일명 제목 변경', []); + tmp += m_button('search_tvdb_btn', 'TVDB', []); + tmp = m_button_group(tmp) + */ + str += tmp + // program + str += m_hr_black(); + str += m_row_start(0); + tmp = '' + if (data.image != null) + tmp = '<img src="' + data.image + '" class="img-fluid">'; + str += m_col(3, tmp) + tmp = '' + tmp += m_row_start(2) + m_col(3, '제목', 'right') + m_col(9, data.title) + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '제작사', 'right') + m_col(9, data.des._pub) + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '감독', 'right') + m_col(9, data.des._dir) + m_row_end(); + // + // tmp += m_row_start(2) + m_col(3, '원작', 'right') + m_col(9, data.des._otit) + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '장르', 'right') + m_col(9, data.des._tag) + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '분류', 'right') + m_col(9, data.des._classifi) + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '공식 방영일', 'right') + m_col(9, data.date+'('+data.day+')') + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '에피소드', 'right') + m_col(9, data.des._total_chapter ? data.des._total_chapter : '') + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '등급', 'right') + m_col(9, data.des._grade) + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '최근 방영일', 'right') + m_col(9, data.des._recent_date ? data.des._recent_date : '') + m_row_end(); + // tmp += m_row_start(2) + m_col(3, '줄거리', 'right') + m_col(9, data.ser_description) + m_row_end(); + + tmp += "<div>" + data.des1 + "</div>" + str += m_col(9, tmp) + str += m_row_end(); + + str += m_hr_black(); + for (i in data.episode) { + str += m_row_start(); + tmp = ''; + if (data.episode[i].thumbnail) + tmp = '<img src="' + data.episode[i].thumbnail + '" class="img-fluid">' + str += m_col(3, tmp) + tmp = '<strong>' + data.episode[i].ep_num + '화. ' + data.episode[i].title + '</strong>'; + tmp += '<br>'; + tmp += data.episode[i].date + '<br>'; + + tmp += '<div class="form-inline">' + tmp += '<input id="checkbox_' + i + '" name="checkbox_' + i + '" 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': 'idx', 'value': i}]) + tmp += '</div>' + str += m_col(9, tmp) + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle() + } + + $(function () { + console.log(params.wr_id) + console.log(findGetParameter('wr_id')) + console.log(params.code) + if (params.code === '') { + + } else { + document.getElementById("code").value = params.code + document.getElementById("analysis_btn").click(); + } + + if ("{{arg['anilife_current_code']}}" !== "") { + if (params.code === null) { + console.log('params.code === null') + document.getElementById("code").value = "{{arg['anilife_current_code']}}"; + + } else if (params.code === '') { + document.getElementById("code").value = "{{arg['anilife_current_code']}}"; + } else { + + console.log('params code exist') + console.log(params.code) + document.getElementById("code").value = params.code + + analyze(params.wr_id, params.bo_table) + // document.getElementById("analysis_btn").click(); + // $('#analysis_btn').trigger('click') + } + // 값이 공백이 아니면 분석 버튼 계속 누름 + // {#document.getElementById("analysis_btn").click();#} + } else { + + } + + }) + + $(document).ready(function () { + $("#loader").css("display", 'none') + // console.log('wr_id::', params.wr_id) + + + }); + + $("#analysis_btn").unbind("click").bind('click', function (e) { + e.preventDefault(); + e.stopPropagation() + $("#loader").css("display", 'block') + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + $("#loader").css("display", 'none') + if (ret.ret === 'success' && ret.data != null) { + // {#console.log(ret.code)#} + console.log(ret.data) + + make_program(ret.data) + dismissLoadingScreen() + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_anilife_btn', function (e) { + e.preventDefault(); + window.open("{{arg['anilife_url']}}", "_blank"); + }); + + $("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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + console.log('#add_queue_btn::data >>', data) + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</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_"]'); + let data = []; + let idx; + for (let i in all) { + if (all[i].checked) { + idx = parseInt(all[i].id.split('_')[1]) + data.push(current_data.episode[idx]); + } + } + if (data.length == 0) { + $.notify('<strong>선택하세요.</strong>', {type: 'warning'}); + return; + } + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue_checked_list', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + $.notify('<strong>백그라운드로 작업을 추가합니다.</strong>', {type: 'success'}); + } + }); + }); +</script> +<style> + 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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + #preloader { + /*background-color: green;*/ + /*color: white;*/ + /*height: 100vh;*/ + /*width: 100%;*/ + /*position: fixed;*/ + /*z-index: 100;*/ + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + opacity: 0.5; + } + + .loader { + background: rgb(0, 0, 0, 0.8); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + } + + .loader-inner { + bottom: 0; + height: 60px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap { + animation: spin 2000ms cubic-bezier(.175, .885, .32, 1.275) infinite; + box-sizing: border-box; + height: 50px; + left: 0; + overflow: hidden; + position: absolute; + top: 0; + transform-origin: 50% 100%; + width: 100px; + } + + .loader-line { + border: 4px solid transparent; + border-radius: 100%; + box-sizing: border-box; + height: 100px; + left: 0; + margin: 0 auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap:nth-child(1) { + animation-delay: -50ms; + } + + .loader-line-wrap:nth-child(2) { + animation-delay: -100ms; + } + + .loader-line-wrap:nth-child(3) { + animation-delay: -150ms; + } + + .loader-line-wrap:nth-child(4) { + animation-delay: -200ms; + } + + .loader-line-wrap:nth-child(5) { + animation-delay: -250ms; + } + + .loader-line-wrap:nth-child(1) .loader-line { + border-color: hsl(0, 80%, 60%); + height: 90px; + width: 90px; + top: 7px; + } + + .loader-line-wrap:nth-child(2) .loader-line { + border-color: hsl(60, 80%, 60%); + height: 76px; + width: 76px; + top: 14px; + } + + .loader-line-wrap:nth-child(3) .loader-line { + border-color: hsl(120, 80%, 60%); + height: 62px; + width: 62px; + top: 21px; + } + + .loader-line-wrap:nth-child(4) .loader-line { + border-color: hsl(180, 80%, 60%); + height: 48px; + width: 48px; + top: 28px; + } + + .loader-line-wrap:nth-child(5) .loader-line { + border-color: hsl(240, 80%, 60%); + height: 34px; + width: 34px; + top: 35px; + } + + @keyframes spin { + 0%, 15% { + transform: rotate(0); + } + 100% { + transform: rotate(360deg); + } + } +</style> +{% endblock %} diff --git a/templates/anime_downloader_anilife_setting.html b/templates/anime_downloader_anilife_setting.html new file mode 100644 index 0000000..a3c9d6a --- /dev/null +++ b/templates/anime_downloader_anilife_setting.html @@ -0,0 +1,68 @@ +{% extends "base.html" %} +{% block content %} +<div> + {{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}} + {{ macros.m_row_start('5') }} + {{ macros.m_row_end() }} + <nav> + {{ macros.m_tab_head_start() }} + {{ macros.m_tab_head('normal', '일반', true) }} + {{ macros.m_tab_head('auto', '홈화면 자동', false) }} + {{ macros.m_tab_head('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('anilife_url', '애니라이프 URL', [['go_btn', 'GO']], value=arg['anilife_url']) }} + {{ macros.setting_input_text('anilife_download_path', '저장 폴더', value=arg['anilife_download_path'], desc='정상적으로 다운 완료 된 파일이 이동할 폴더 입니다. ') }} + {{ macros.setting_input_int('anilife_max_ffmpeg_process_count', '동시 다운로드 수', value=arg['anilife_max_ffmpeg_process_count'], desc='동시에 다운로드 할 에피소드 갯수입니다.') }} + {{ macros.setting_checkbox('anilife_order_desc', '요청 화면 최신순 정렬', value=arg['anilife_order_desc'], desc='On : 최신화부터, Off : 1화부터') }} + {{ macros.setting_checkbox('anilife_auto_make_folder', '제목 폴더 생성', value=arg['anilife_auto_make_folder'], desc='제목으로 폴더를 생성하고 폴더 안에 다운로드합니다.') }} + <div id="anilife_auto_make_folder_div" class="collapse"> + {{ macros.setting_input_text('anilife_finished_insert', '완결 표시', col='3', value=arg['anilife_finished_insert'], desc=['완결된 컨텐츠 폴더명 앞에 넣을 문구입니다.']) }} + {{ macros.setting_checkbox('anilife_auto_make_season_folder', '시즌 폴더 생성', value=arg['anilife_auto_make_season_folder'], desc=['On : Season 번호 폴더를 만듭니다.']) }} + </div> + {{ macros.setting_checkbox('anilife_uncompleted_auto_enqueue', '자동으로 다시 받기', value=arg['anilife_uncompleted_auto_enqueue'], desc=['On : 플러그인 로딩시 미완료인 항목은 자동으로 다시 받습니다.']) }} + {{ 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('anilife_interval', '스케쥴링 실행 정보', value=arg['anilife_interval'], col='3', desc=['Inverval(minute 단위)이나 Cron 설정']) }} + {{ macros.setting_checkbox('anilife_auto_start', '시작시 자동실행', value=arg['anilife_auto_start'], desc='On : 시작시 자동으로 스케쥴러에 등록됩니다.') }} + {{ macros.setting_input_textarea('anilife_auto_code_list', '자동 다운로드할 작품 코드', desc=['all 입력시 모두 받기', '구분자 | 또는 엔터'], value=arg['anilife_auto_code_list'], row='10') }} + {{ macros.setting_checkbox('anilife_auto_mode_all', '에피소드 모두 받기', value=arg['anilife_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() }} + + </div><!--tab-content--> + </form> +</div> <!--전체--> + +<script type="text/javascript"> +var package_name = "{{arg['package_name'] }}"; +var sub = "{{arg['sub'] }}"; +var current_data = null; + + +$(document).ready(function(){ + use_collapse('anilife_auto_make_folder'); +}); + +$('#ani365_auto_make_folder').change(function() { + use_collapse('anilife_auto_make_folder'); +}); + + +$("body").on('click', '#go_btn', function(e){ + e.preventDefault(); + let url = document.getElementById("anilife_url").value + window.open(url, "_blank"); +}); + +</script> +{% endblock %} \ No newline at end of file diff --git a/templates/anime_downloader_linkkf_category.html b/templates/anime_downloader_linkkf_category.html new file mode 100644 index 0000000..f279139 --- /dev/null +++ b/templates/anime_downloader_linkkf_category.html @@ -0,0 +1,734 @@ +{% extends "base.html" %} {% block content %} + +<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> + <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="theater" 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> +<!--전체--> + +<script + type="text/javascript" + src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js" +></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 type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const linkkf_url = "{{arg['linkkf_url']}}"; + let current_data = null; + let page = 1; + let next_page = Number + let current_cate = '' + let current_query = '' + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + + const get_anime_list = (type, page) => { + console.log(`type: ${type}, page: ${page}`) + let url = '' + let data = {"page": page, "type": type} + + switch (type) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + default: + break; + } + + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret + console.log('ret::>', ret) + + if (current_screen_movie_data !== '') { + if (type === "ing") { + make_airing_list(ret.data, page) + observer.observe(); + } else if (type === "fin") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else if (type === "theater") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else { + make_screen_movie_list(ret.data, page) + } + div_visible = true + console.log(div_visible) + } + next_page = page + 1 + } + }) + } + + function make_airing_list(data, page) { + let str = '' + let tmp = '' + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + // tmp += '<img class="lozad" data-src="' + data.anime_list[i].image_link + '" />'; + tmp += '<img class="lazyload" src="../static/img_loader_x200.svg" data-original="' + data.anime_list[i].image_link + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + data.anime_list[i].code + '\'"/>'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + data.anime_list[i].code + + '"><i class="bi bi-heart-fill"></i></button></p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + // tmp += + // '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + // data.anime_list[i].code + + // '"><i class="bi bi-heart-fill"></i></button>'; + tmp += '</div><!-- .card -->'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + function make_search_result_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + if (data.anime_list[i].wr_id !== '') { + const re = /bo_table=([^&]+)/ + const bo_table = data.anime_list[i].link.match(re) + console.log(bo_table) + + request_url = './request?code=' + data.anime_list[i].code + '&wr_id=' + data.anime_list[i].wr_id + '&bo_table=' + bo_table[1] + } else { + request_url = './request?code=' + data.anime_list[i].code + } + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="' + request_url + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + str += '</div><!-- .card-columns -->'; + 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; + } + + } + + function make_screen_movie_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + + $(document).ready(function () { + + // if ( "{{arg['linkkf_current_code']}}" !== "" ) { + // document.getElementById("code").value = "{{arg['linkkf_current_code']}}"; + // // 값이 공백이 아니면 분석 버튼 계속 누름 + // document.getElementById("analysis_btn").click(); + // } + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("엔터키를 눌렀습니다."); + $("#btn_search").trigger("click"); + } + }) + + get_anime_list("ing", 1) + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + }); + + $("body").on("click", "#btn_search", function (e) { + e.preventDefault(); + let query = $("#input_search").val(); + console.log(query); + current_cate = "search" + current_query = query + + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } + + $.ajax({ + url: "/" + package_name + "/ajax/" + sub + "/search", + type: "POST", + cache: false, + data: {query: query, type: current_cate, page: page}, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + console.log('ret:::', ret) + make_search_result_list(ret.data, 1); + next_page = page + 1 + } else { + $.notify("<strong>분석 실패</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + }); + + $('#anime_category #ing').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("ing", 1) + }) + + $('#anime_category #complete_anilist').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("fin", 1) + }) + + $('#anime_category #theater').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("theater", 1) + }) + + // 분석 버튼 클릭시 호출 + $("body").on('click', '#analysis_btn', function (e) { + e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // console.log(ret.code) + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_linkkf_btn', function (e) { + e.preventDefault(); + window.open("{{arg['linkkf_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/"+sub+"/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', '#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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</strong>', {type: 'warning'}); + } else { + $.notify('<strong>추가 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + // const observer = lozad(); + // const el = document.querySelector('img'); + // const observer = lozad(el); // passing a `NodeList` (e.g. `document.querySelectorAll()`) is also valid + // observer.observe(); + const loadNextAnimes = (cate, page) => { + spinner.style.display = "block"; + let data = {type: cate, page: String(page)}; + let url = '' + switch (cate) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + case 'search': + url = "/" + package_name + "/ajax/" + sub + "/search" + current_cate = 'search' + data.query = current_query + break; + default: + break; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), + }) + .then((res) => res.json()) + .then((response) => { + // console.log("Success:", JSON.stringify(response)); + // {#imagesContainer.appendChild()#} + console.log("return page:::> ", String(response.page)); + // {#page = response.page#} + if (current_cate === 'search') { + make_search_result_list(response.data, response.page); + + } else { + make_screen_movie_list(response.data, response.page); + } + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); + }; + + + const onScroll = (e) => { + console.dir(e.target.scrollingElement.scrollHeight); + const {scrollTop, scrollHeight, clientHeight} = e.target.scrollingElement; + if (Math.round(scrollHeight - scrollTop) <= clientHeight) { + document.getElementById("spinner").style.display = "block"; + console.log("loading"); + console.log("now page::> ", page); + console.log("next_page::> ", String(next_page)); + loadNextAnimes(current_cate, next_page); + } + }; + + const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; + }; + + document.addEventListener("scroll", debounce(onScroll, 300)); +</script> +<style> + 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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + .card-body { + padding: 0 !important; + } + + .card-title { + padding: 1rem !important; + } + + button#add_whitelist { + float: right; + } + + button.btn-favorite { + background-color: #e0ff42; + } + /*.card-columns {*/ + /* @include media-breakpoint-only(lg) {*/ + /* column-count: 4;*/ + /* }*/ + /* @include media-breakpoint-only(xl) {*/ + /* column-count: 5;*/ + /* }*/ + /*}*/ + @media (min-width: 576px) { + .container { + max-width: 100%; + } + .card-columns { + column-count: 2; + column-gap: 1.25rem; + } + .card-columns .card { + display: inline-block; + } + } + + + @media (min-width: 768px) { + .card-columns {column-count: 3;} + } + + /* Large devices (desktops, 992px and up) */ + @media (min-width: 992px) { + .card-columns {column-count: 3;} + } + + /* Extra large devices (large desktops, 1200px and up) */ + @media (min-width: 1200px) { + .card-columns { + column-count: 5; + } + } + .card { + margin-bottom: 0.75rem; + } + .card-columns .card { + margin-bottom: 0.75rem; + } + .card-columns .card img{ + width: 100%; + } + button#add_whitelist { + /*top: -70px;*/ + position: relative; + } + body { + font-family: NanumSquareNeo,system-ui,-apple-system,Segoe UI,Roboto,Helvetica Neue,Noto Sans,Liberation Sans,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji; + } + body { + background-image: linear-gradient(90deg, #233f48, #6c6fa2, #768dae); + } + +</style> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css"> +{% endblock %} diff --git a/templates/anime_downloader_linkkf_list.html b/templates/anime_downloader_linkkf_list.html new file mode 100644 index 0000000..797a6c1 --- /dev/null +++ b/templates/anime_downloader_linkkf_list.html @@ -0,0 +1,144 @@ +{% 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> + +<script type="text/javascript"> +var package_name = "{{arg['package_name']}}"; +var sub = "{{arg['sub']}}"; +var current_data = null; + +$(document).ready(function(){ + global_sub_request_search('1'); +}); + +$("#search").click(function(e) { + e.preventDefault(); + global_sub_request_search('1'); +}); + +$("body").on('click', '#page', function(e){ + e.preventDefault(); + global_sub_request_search($(this).data('page')); +}); + +$("#reset_btn").click(function(e) { + e.preventDefault(); + document.getElementById("order").value = 'desc'; + document.getElementById("option").value = 'all'; + document.getElementById("search_word").value = ''; + global_sub_request_search('1') +}); + + +$("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(); + var search_word = $(this).data('title'); + document.getElementById("search_word").value = search_word; + global_sub_request_search('1') +}); + +$("body").on('click', '#remove_btn', function(e) { + e.preventDefault(); + id = $(this).data('id'); + $.ajax({ + url: '/'+package_name+'/ajax/'+sub+ '/db_remove', + type: "POST", + cache: false, + data: {id:id}, + dataType: "json", + success: function (data) { + if (data) { + $.notify('<strong>삭제되었습니다.</strong>', { + type: 'success' + }); + global_sub_request_search(current_data.paging.current_page, false) + } else { + $.notify('<strong>삭제 실패</strong>', { + type: 'warning' + }); + } + } + }); +}); + +$("body").on('click', '#request_btn', function(e){ + e.preventDefault(); + var content_code = $(this).data('content_code'); + $(location).attr('href', '/' + package_name + '/' + sub + '/request?content_code=' + content_code) +}); + + + +function make_list(data) { + //console.log(data) + str = ''; + for (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 + '(추가)'; + if (data[i].completed_time != null) + tmp += data[i].completed_time + '(완료)'; + str += m_col(3, tmp) + tmp = data[i].savepath + '<br>' + data[i].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].content_code}]); + tmp2 += m_button('self_search_btn', '목록 검색', [{'key':'title', 'value':data[i].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; +} + + + + +</script> +{% endblock %} \ No newline at end of file diff --git a/templates/anime_downloader_linkkf_search.html b/templates/anime_downloader_linkkf_search.html new file mode 100644 index 0000000..abdeb66 --- /dev/null +++ b/templates/anime_downloader_linkkf_search.html @@ -0,0 +1,924 @@ +{% extends "base.html" %} {% block content %} +<!--<div id="preloader"></div>--> +<div id="preloader" class="loader"> + <div class="loader-inner"> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + </div> +</div> +<div id="yommi_wrapper"> + <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-primary"> + search + </button> + </div> + + <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="theater" 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> +</div> +<!--전체--> + +<script + type="text/javascript" + src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js" +></script> +<script src="{{ url_for('.static', filename='js/sjva_ui14.js') }}"></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 type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const anilife_url = "{{arg['anilife_url']}}"; + // let current_data = null; + let page = 1; + let next_page = Number + let current_cate = '' + let current_query = '' + + const loader = document.getElementById("preloader"); + + const dismissLoadingScreen = function () { + loader.style.display = "none"; + }; + + const wait3seconds = function () { + // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp + const result = setTimeout(dismissLoadingScreen, 2000); + }; + + window.addEventListener("load", wait3seconds); + // window.addEventListener("load", dismissLoadingScreen); + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + + const get_anime_list = (type, page) => { + console.log(`type: ${type}, page: ${page}`) + let url = '' + let data = {"page": page, "type": type} + + switch (type) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + default: + break; + } + + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret + console.log('ret::>', ret) + + if (current_screen_movie_data !== '') { + if (type === "ing") { + make_airing_list(ret.data, page) + observer.observe(); + } else if (type === "fin") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else if (type === "theater") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else { + make_screen_movie_list(ret.data, page) + } + div_visible = true + console.log(div_visible) + } + next_page = page + 1 + } + }) + } + + function make_airing_list(data, page) { + let str = '' + let tmp = '' + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + // tmp += '<img class="lozad" data-src="' + data.anime_list[i].image_link + '" />'; + tmp += '<img class="lazyload" src="../static/img_loader_x200.svg" data-original="' + data.anime_list[i].image_link + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + data.anime_list[i].code + '\'"/>'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + data.anime_list[i].code + + '"><i class="bi bi-heart-fill"></i></button></p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + // tmp += + // '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + // data.anime_list[i].code + + // '"><i class="bi bi-heart-fill"></i></button>'; + tmp += '</div><!-- .card -->'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + function make_search_result_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + if (data.anime_list[i].wr_id !== '') { + const re = /bo_table=([^&]+)/ + const bo_table = data.anime_list[i].link.match(re) + console.log(bo_table) + if (bo_table != null) { + request_url = './request?code=' + data.anime_list[i].code + '&wr_id=' + data.anime_list[i].wr_id + '&bo_table=' + bo_table[1] + } else { + request_url = './request?code=' + data.anime_list[i].code + } + } else { + request_url = './request?code=' + data.anime_list[i].code + } + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="' + request_url + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + str += '</div><!-- .card-columns -->'; + 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; + } + + } + + function make_screen_movie_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + + $(document).ready(function () { + + // if ( "{{arg['anilife_current_code']}}" !== "" ) { + // document.getElementById("code").value = "{{arg['anilife_current_code']}}"; + // // 값이 공백이 아니면 분석 버튼 계속 누름 + // document.getElementById("analysis_btn").click(); + // } + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("엔터키를 눌렀습니다."); + $("#btn_search").trigger("click"); + } + }) + + get_anime_list("ing", 1) + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + }); + + $("body").on("click", "#btn_search", function (e) { + e.preventDefault(); + let query = $("#input_search").val(); + console.log(query); + current_cate = "search" + current_query = query + + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } + + $.ajax({ + url: "/" + package_name + "/ajax/" + sub + "/search", + type: "POST", + cache: false, + data: {query: query, type: current_cate, page: page}, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + console.log('ret:::', ret) + make_search_result_list(ret.data, 1); + next_page = page + 1 + } else { + $.notify("<strong>분석 실패</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + }); + + $('#anime_category #ing').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("ing", 1) + }) + + $('#anime_category #complete_anilist').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("fin", 1) + }) + + $('#anime_category #theater').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("theater", 1) + }) + + // 분석 버튼 클릭시 호출 + $("body").on('click', '#analysis_btn', function (e) { + e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // console.log(ret.code) + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_anilife_btn', function (e) { + e.preventDefault(); + window.open("{{arg['anilife_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/" + sub + "/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', '#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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</strong>', {type: 'warning'}); + } else { + $.notify('<strong>추가 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + // const observer = lozad(); + // const el = document.querySelector('img'); + // const observer = lozad(el); // passing a `NodeList` (e.g. `document.querySelectorAll()`) is also valid + // observer.observe(); + const loadNextAnimes = (cate, page) => { + spinner.style.display = "block"; + let data = {type: cate, page: String(page)}; + let url = '' + switch (cate) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + case 'search': + url = "/" + package_name + "/ajax/" + sub + "/search" + current_cate = 'search' + data.query = current_query + break; + default: + break; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), + }) + .then((res) => res.json()) + .then((response) => { + // console.log("Success:", JSON.stringify(response)); + // {#imagesContainer.appendChild()#} + console.log("return page:::> ", String(response.page)); + // {#page = response.page#} + if (current_cate === 'search') { + make_search_result_list(response.data, response.page); + + } else { + make_screen_movie_list(response.data, response.page); + } + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); + }; + + + const onScroll = (e) => { + console.dir(e.target.scrollingElement.scrollHeight); + const {scrollTop, scrollHeight, clientHeight} = e.target.scrollingElement; + if (Math.round(scrollHeight - scrollTop) <= clientHeight) { + document.getElementById("spinner").style.display = "block"; + console.log("loading"); + console.log("now page::> ", page); + console.log("next_page::> ", String(next_page)); + loadNextAnimes(current_cate, next_page); + } + }; + + const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; + }; + + document.addEventListener("scroll", debounce(onScroll, 300)); +</script> +<style> + 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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + .card-body { + padding: 0 !important; + } + + .card-title { + padding: 1rem !important; + } + + button#add_whitelist { + float: right; + } + + button.btn-favorite { + background-color: #e0ff42; + } + + /*.card-columns {*/ + /* @include media-breakpoint-only(lg) {*/ + /* column-count: 4;*/ + /* }*/ + /* @include media-breakpoint-only(xl) {*/ + /* column-count: 5;*/ + /* }*/ + /*}*/ + @media (min-width: 576px) { + .container { + max-width: 100%; + } + + .card-columns { + column-count: 2; + column-gap: 1.25rem; + } + + .card-columns .card { + display: inline-block; + } + } + + + @media (min-width: 768px) { + .card-columns { + column-count: 3; + } + } + + /* Large devices (desktops, 992px and up) */ + @media (min-width: 992px) { + .card-columns { + column-count: 3; + } + } + + /* Extra large devices (large desktops, 1200px and up) */ + @media (min-width: 1200px) { + .card-columns { + column-count: 5; + } + + #yommi_wrapper { + max-width: 80%; + margin: 0 auto; + } + } + + .card { + margin-bottom: 0.75rem; + } + + .card-columns .card { + margin-bottom: 0.75rem; + } + + .card-columns .card img { + width: 100%; + } + + button#add_whitelist { + /*top: -70px;*/ + position: relative; + } + + body { + font-family: NanumSquareNeo, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + } + + body { + background-image: linear-gradient(90deg, #233f48, #6c6fa2, #768dae); + } + + #preloader { + /*background-color: green;*/ + /*color: white;*/ + /*height: 100vh;*/ + /*width: 100%;*/ + /*position: fixed;*/ + /*z-index: 100;*/ + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + opacity: 0.5; + } + + /*.loader {*/ + /* background: rgb(0, 0, 0, 0.8);*/ + /* background: radial-gradient(#222, #000);*/ + /* bottom: 0;*/ + /* left: 0;*/ + /* overflow: hidden;*/ + /* position: fixed;*/ + /* right: 0;*/ + /* top: 0;*/ + /* z-index: 99999;*/ + /*}*/ + + .loader-inner { + bottom: 0; + height: 60px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap { + animation: spin 2000ms cubic-bezier(.175, .885, .32, 1.275) infinite; + box-sizing: border-box; + height: 50px; + left: 0; + overflow: hidden; + position: absolute; + top: 0; + transform-origin: 50% 100%; + width: 100px; + } + + .loader-line { + border: 4px solid transparent; + border-radius: 100%; + box-sizing: border-box; + height: 100px; + left: 0; + margin: 0 auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap:nth-child(1) { + animation-delay: -50ms; + } + + .loader-line-wrap:nth-child(2) { + animation-delay: -100ms; + } + + .loader-line-wrap:nth-child(3) { + animation-delay: -150ms; + } + + .loader-line-wrap:nth-child(4) { + animation-delay: -200ms; + } + + .loader-line-wrap:nth-child(5) { + animation-delay: -250ms; + } + + .loader-line-wrap:nth-child(1) .loader-line { + border-color: hsl(0, 80%, 60%); + height: 90px; + width: 90px; + top: 7px; + } + + .loader-line-wrap:nth-child(2) .loader-line { + border-color: hsl(60, 80%, 60%); + height: 76px; + width: 76px; + top: 14px; + } + + .loader-line-wrap:nth-child(3) .loader-line { + border-color: hsl(120, 80%, 60%); + height: 62px; + width: 62px; + top: 21px; + } + + .loader-line-wrap:nth-child(4) .loader-line { + border-color: hsl(180, 80%, 60%); + height: 48px; + width: 48px; + top: 28px; + } + + .loader-line-wrap:nth-child(5) .loader-line { + border-color: hsl(240, 80%, 60%); + height: 34px; + width: 34px; + top: 35px; + } + + @keyframes spin { + 0%, 15% { + transform: rotate(0); + } + 100% { + transform: rotate(360deg); + } + } + + +</style> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css"> +{% endblock %} diff --git a/templates/anime_downloader_ohli24_category.html b/templates/anime_downloader_ohli24_category.html new file mode 100644 index 0000000..7c48ac8 --- /dev/null +++ b/templates/anime_downloader_ohli24_category.html @@ -0,0 +1,923 @@ +{% extends "base.html" %} {% block content %} +<!--<div id="preloader"></div>--> +<div id="preloader" class="loader"> + <div class="loader-inner"> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + </div> +</div> +<div id="yommi_wrapper"> + <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-primary"> + search + </button> + </div> + + <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="theater" 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> +</div> +<!--전체--> + +<script + type="text/javascript" + src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js" +></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 type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const anilife_url = "{{arg['anilife_url']}}"; + let current_data = null; + let page = 1; + let next_page = Number + let current_cate = '' + let current_query = '' + + const loader = document.getElementById("preloader"); + + const dismissLoadingScreen = function () { + loader.style.display = "none"; + }; + + const wait3seconds = function () { + // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp + const result = setTimeout(dismissLoadingScreen, 2000); + }; + + window.addEventListener("load", wait3seconds); + // window.addEventListener("load", dismissLoadingScreen); + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + + const get_anime_list = (type, page) => { + console.log(`type: ${type}, page: ${page}`) + let url = '' + let data = {"page": page, "type": type} + + switch (type) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + default: + break; + } + + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret + console.log('ret::>', ret) + + if (current_screen_movie_data !== '') { + if (type === "ing") { + make_airing_list(ret.data, page) + observer.observe(); + } else if (type === "fin") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else if (type === "theater") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else { + make_screen_movie_list(ret.data, page) + } + div_visible = true + console.log(div_visible) + } + next_page = page + 1 + } + }) + } + + function make_airing_list(data, page) { + let str = '' + let tmp = '' + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + // tmp += '<img class="lozad" data-src="' + data.anime_list[i].image_link + '" />'; + tmp += '<img class="lazyload" src="../static/img_loader_x200.svg" data-original="' + data.anime_list[i].image_link + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + data.anime_list[i].code + '\'"/>'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + data.anime_list[i].code + + '"><i class="bi bi-heart-fill"></i></button></p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + // tmp += + // '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + // data.anime_list[i].code + + // '"><i class="bi bi-heart-fill"></i></button>'; + tmp += '</div><!-- .card -->'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + function make_search_result_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + if (data.anime_list[i].wr_id !== '') { + const re = /bo_table=([^&]+)/ + const bo_table = data.anime_list[i].link.match(re) + console.log(bo_table) + if (bo_table != null) { + request_url = './request?code=' + data.anime_list[i].code + '&wr_id=' + data.anime_list[i].wr_id + '&bo_table=' + bo_table[1] + } else { + request_url = './request?code=' + data.anime_list[i].code + } + } else { + request_url = './request?code=' + data.anime_list[i].code + } + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="' + request_url + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + str += '</div><!-- .card-columns -->'; + 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; + } + + } + + function make_screen_movie_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + + $(document).ready(function () { + + // if ( "{{arg['anilife_current_code']}}" !== "" ) { + // document.getElementById("code").value = "{{arg['anilife_current_code']}}"; + // // 값이 공백이 아니면 분석 버튼 계속 누름 + // document.getElementById("analysis_btn").click(); + // } + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("엔터키를 눌렀습니다."); + $("#btn_search").trigger("click"); + } + }) + + get_anime_list("ing", 1) + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + }); + + $("body").on("click", "#btn_search", function (e) { + e.preventDefault(); + let query = $("#input_search").val(); + console.log(query); + current_cate = "search" + current_query = query + + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } + + $.ajax({ + url: "/" + package_name + "/ajax/" + sub + "/search", + type: "POST", + cache: false, + data: {query: query, type: current_cate, page: page}, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + console.log('ret:::', ret) + make_search_result_list(ret.data, 1); + next_page = page + 1 + } else { + $.notify("<strong>분석 실패</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + }); + + $('#anime_category #ing').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("ing", 1) + }) + + $('#anime_category #complete_anilist').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("fin", 1) + }) + + $('#anime_category #theater').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("theater", 1) + }) + + // 분석 버튼 클릭시 호출 + $("body").on('click', '#analysis_btn', function (e) { + e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // console.log(ret.code) + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_anilife_btn', function (e) { + e.preventDefault(); + window.open("{{arg['anilife_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/" + sub + "/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', '#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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</strong>', {type: 'warning'}); + } else { + $.notify('<strong>추가 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + // const observer = lozad(); + // const el = document.querySelector('img'); + // const observer = lozad(el); // passing a `NodeList` (e.g. `document.querySelectorAll()`) is also valid + // observer.observe(); + const loadNextAnimes = (cate, page) => { + spinner.style.display = "block"; + let data = {type: cate, page: String(page)}; + let url = '' + switch (cate) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + case 'search': + url = "/" + package_name + "/ajax/" + sub + "/search" + current_cate = 'search' + data.query = current_query + break; + default: + break; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), + }) + .then((res) => res.json()) + .then((response) => { + // console.log("Success:", JSON.stringify(response)); + // {#imagesContainer.appendChild()#} + console.log("return page:::> ", String(response.page)); + // {#page = response.page#} + if (current_cate === 'search') { + make_search_result_list(response.data, response.page); + + } else { + make_screen_movie_list(response.data, response.page); + } + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); + }; + + + const onScroll = (e) => { + console.dir(e.target.scrollingElement.scrollHeight); + const {scrollTop, scrollHeight, clientHeight} = e.target.scrollingElement; + if (Math.round(scrollHeight - scrollTop) <= clientHeight) { + document.getElementById("spinner").style.display = "block"; + console.log("loading"); + console.log("now page::> ", page); + console.log("next_page::> ", String(next_page)); + loadNextAnimes(current_cate, next_page); + } + }; + + const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; + }; + + document.addEventListener("scroll", debounce(onScroll, 300)); +</script> +<style> + 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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + .card-body { + padding: 0 !important; + } + + .card-title { + padding: 1rem !important; + } + + button#add_whitelist { + float: right; + } + + button.btn-favorite { + background-color: #e0ff42; + } + + /*.card-columns {*/ + /* @include media-breakpoint-only(lg) {*/ + /* column-count: 4;*/ + /* }*/ + /* @include media-breakpoint-only(xl) {*/ + /* column-count: 5;*/ + /* }*/ + /*}*/ + @media (min-width: 576px) { + .container { + max-width: 100%; + } + + .card-columns { + column-count: 2; + column-gap: 1.25rem; + } + + .card-columns .card { + display: inline-block; + } + } + + + @media (min-width: 768px) { + .card-columns { + column-count: 3; + } + } + + /* Large devices (desktops, 992px and up) */ + @media (min-width: 992px) { + .card-columns { + column-count: 3; + } + } + + /* Extra large devices (large desktops, 1200px and up) */ + @media (min-width: 1200px) { + .card-columns { + column-count: 5; + } + + #yommi_wrapper { + max-width: 80%; + margin: 0 auto; + } + } + + .card { + margin-bottom: 0.75rem; + } + + .card-columns .card { + margin-bottom: 0.75rem; + } + + .card-columns .card img { + width: 100%; + } + + button#add_whitelist { + /*top: -70px;*/ + position: relative; + } + + body { + font-family: NanumSquareNeo, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + } + + body { + background-image: linear-gradient(90deg, #233f48, #6c6fa2, #768dae); + } + + #preloader { + /*background-color: green;*/ + /*color: white;*/ + /*height: 100vh;*/ + /*width: 100%;*/ + /*position: fixed;*/ + /*z-index: 100;*/ + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + opacity: 0.5; + } + + /*.loader {*/ + /* background: rgb(0, 0, 0, 0.8);*/ + /* background: radial-gradient(#222, #000);*/ + /* bottom: 0;*/ + /* left: 0;*/ + /* overflow: hidden;*/ + /* position: fixed;*/ + /* right: 0;*/ + /* top: 0;*/ + /* z-index: 99999;*/ + /*}*/ + + .loader-inner { + bottom: 0; + height: 60px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap { + animation: spin 2000ms cubic-bezier(.175, .885, .32, 1.275) infinite; + box-sizing: border-box; + height: 50px; + left: 0; + overflow: hidden; + position: absolute; + top: 0; + transform-origin: 50% 100%; + width: 100px; + } + + .loader-line { + border: 4px solid transparent; + border-radius: 100%; + box-sizing: border-box; + height: 100px; + left: 0; + margin: 0 auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap:nth-child(1) { + animation-delay: -50ms; + } + + .loader-line-wrap:nth-child(2) { + animation-delay: -100ms; + } + + .loader-line-wrap:nth-child(3) { + animation-delay: -150ms; + } + + .loader-line-wrap:nth-child(4) { + animation-delay: -200ms; + } + + .loader-line-wrap:nth-child(5) { + animation-delay: -250ms; + } + + .loader-line-wrap:nth-child(1) .loader-line { + border-color: hsl(0, 80%, 60%); + height: 90px; + width: 90px; + top: 7px; + } + + .loader-line-wrap:nth-child(2) .loader-line { + border-color: hsl(60, 80%, 60%); + height: 76px; + width: 76px; + top: 14px; + } + + .loader-line-wrap:nth-child(3) .loader-line { + border-color: hsl(120, 80%, 60%); + height: 62px; + width: 62px; + top: 21px; + } + + .loader-line-wrap:nth-child(4) .loader-line { + border-color: hsl(180, 80%, 60%); + height: 48px; + width: 48px; + top: 28px; + } + + .loader-line-wrap:nth-child(5) .loader-line { + border-color: hsl(240, 80%, 60%); + height: 34px; + width: 34px; + top: 35px; + } + + @keyframes spin { + 0%, 15% { + transform: rotate(0); + } + 100% { + transform: rotate(360deg); + } + } + + +</style> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css"> +{% endblock %} diff --git a/templates/anime_downloader_ohli24_list.html b/templates/anime_downloader_ohli24_list.html new file mode 100644 index 0000000..797a6c1 --- /dev/null +++ b/templates/anime_downloader_ohli24_list.html @@ -0,0 +1,144 @@ +{% 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> + +<script type="text/javascript"> +var package_name = "{{arg['package_name']}}"; +var sub = "{{arg['sub']}}"; +var current_data = null; + +$(document).ready(function(){ + global_sub_request_search('1'); +}); + +$("#search").click(function(e) { + e.preventDefault(); + global_sub_request_search('1'); +}); + +$("body").on('click', '#page', function(e){ + e.preventDefault(); + global_sub_request_search($(this).data('page')); +}); + +$("#reset_btn").click(function(e) { + e.preventDefault(); + document.getElementById("order").value = 'desc'; + document.getElementById("option").value = 'all'; + document.getElementById("search_word").value = ''; + global_sub_request_search('1') +}); + + +$("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(); + var search_word = $(this).data('title'); + document.getElementById("search_word").value = search_word; + global_sub_request_search('1') +}); + +$("body").on('click', '#remove_btn', function(e) { + e.preventDefault(); + id = $(this).data('id'); + $.ajax({ + url: '/'+package_name+'/ajax/'+sub+ '/db_remove', + type: "POST", + cache: false, + data: {id:id}, + dataType: "json", + success: function (data) { + if (data) { + $.notify('<strong>삭제되었습니다.</strong>', { + type: 'success' + }); + global_sub_request_search(current_data.paging.current_page, false) + } else { + $.notify('<strong>삭제 실패</strong>', { + type: 'warning' + }); + } + } + }); +}); + +$("body").on('click', '#request_btn', function(e){ + e.preventDefault(); + var content_code = $(this).data('content_code'); + $(location).attr('href', '/' + package_name + '/' + sub + '/request?content_code=' + content_code) +}); + + + +function make_list(data) { + //console.log(data) + str = ''; + for (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 + '(추가)'; + if (data[i].completed_time != null) + tmp += data[i].completed_time + '(완료)'; + str += m_col(3, tmp) + tmp = data[i].savepath + '<br>' + data[i].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].content_code}]); + tmp2 += m_button('self_search_btn', '목록 검색', [{'key':'title', 'value':data[i].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; +} + + + + +</script> +{% endblock %} \ No newline at end of file diff --git a/templates/anime_downloader_ohli24_queue.html b/templates/anime_downloader_ohli24_queue.html new file mode 100644 index 0000000..e9917cf --- /dev/null +++ b/templates/anime_downloader_ohli24_queue.html @@ -0,0 +1,131 @@ +{% extends "base.html" %} +{% block content %} + +<div> + {{ 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')) }} + {{ macros.m_col(3, macros.m_strong('Status')) }} + {{ macros.m_col(2, macros.m_strong('Action')) }} + {{ macros.m_row_end() }} + {{ macros.m_hr_head_bottom() }} + <div id="download_list_div"></div> +</div> <!--전체--> + +<script type="text/javascript"> +var package_name = "{{arg['package_name'] }}"; +var sub = "{{arg['sub'] }}"; +var current_data = null; +socket = io.connect(window.location.protocol + "//" + document.domain + ":" + location.port + "/" + package_name + '/' + sub); + +$(document).ready(function(){ +}); + +socket.on('start', function(data){ + on_start(); +}); +socket.on('list_refresh', function(data){ + on_start() +}); + +socket.on('status', function(data){ + console.log(data); + on_status(data) +}); + + +function on_start() { + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/entity_list', + type: "POST", + cache: false, + data: {}, + dataType: "json", + success: function (data) { + make_download_list(data) + } + }); +} + + +function on_status(data) { + //console.log(data) + tmp = document.getElementById("progress_"+data.entity_id) + if (tmp != null) { + document.getElementById("progress_"+data.entity_id).style.width = data.ffmpeg_percent+ '%'; + document.getElementById("progress_"+data.entity_id+"_label").innerHTML = data.ffmpeg_status_kor + "(" + data.ffmpeg_percent + "%)" + ' ' + ((data.ffmpeg_arg != null)?data.ffmpeg_arg.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); + str += m_col(4, (data[i].filename != null) ? data[i].filename : ''); + + 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(2, 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} + queue_command(send_data) +}); + +$("body").on('click', '#reset_btn', function(e){ + e.preventDefault(); + entity_id = $(this).data('id') + send_data = {'command':'reset', 'entity_id':-1} + queue_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} + queue_command(send_data) +}); + +function queue_command(data) { + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/queue_command', + type: "POST", + cache: false, + data: data, + dataType: "json", + success: function (ret) { + if (ret.ret == 'notify') { + $.notify('<strong>'+ ret.log +'</strong>', {type: 'warning'}); + } + on_start(); + } + }); +} + +$("body").on('click', '#go_ffmpeg_btn', function(e){ + e.preventDefault(); + $(location).attr('href', '/ffmpeg') +}); + +</script> +{% endblock %} \ No newline at end of file diff --git a/templates/anime_downloader_ohli24_request.html b/templates/anime_downloader_ohli24_request.html new file mode 100644 index 0000000..101e310 --- /dev/null +++ b/templates/anime_downloader_ohli24_request.html @@ -0,0 +1,548 @@ +{% extends "base.html" %} {% block content %} +<div id="preloader"> + <div class='demo'> + <!-- <div class="loader-inner">--> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <div class='circle'> + <div class='inner'></div> + </div> + <!-- </div>--> + </div> +</div> +<div> + <form id="program_list"> + {{ macros.setting_input_text_and_buttons('code', '작품 Code', + [['analysis_btn', '분석'], ['go_ohli24_btn', 'Go OHLI24']], desc='예) + "https://ohli24.net/c/녹을 먹는 비스코" 이나 "녹을 먹는 비스코"') }} + </form> + <form id="program_auto_form"> + <div id="episode_list"></div> + </form> +</div> +<!--전체--> +<script src="{{ url_for('.static', filename='js/sjva_ui14.js') }}"></script> + +<script type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const ohli24_url = "{{arg['ohli24_url']}}"; + {#let current_data = '';#} + + const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), + }) + + const loader = document.getElementById("preloader"); + + const dismissLoadingScreen = function () { + loader.style.display = "none"; + $('.demo').css("display", "none") + }; + + const wait3seconds = function () { + // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp + const result = setTimeout(dismissLoadingScreen, 2000); + }; + + window.addEventListener("load", wait3seconds); + + + function findGetParameter(parameterName) { + let result = null, + tmp = []; + const items = location.search.substr(1).split("&"); + for (let index = 0; index < items.length; index++) { + tmp = items[index].split("="); + if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); + } + return result; + } + + function analyze(wr_id, bo_table) { + // e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code, wr_id: wr_id, bo_table: bo_table}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // {#console.log(ret.code)#} + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + } + + function make_program(data) { + current_data = data; + console.log("current_data::", current_data) + str = ''; + 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 += m_button('apply_new_title_btn', '저장폴더명, 파일명 제목 변경', []); + tmp += m_button('search_tvdb_btn', 'TVDB', []); + tmp = m_button_group(tmp) + */ + str += tmp + // program + str += m_hr_black(); + str += m_row_start(0); + tmp = '' + if (data.image != null) + tmp = '<img src="' + data.image + '" class="img-fluid">'; + str += m_col(3, tmp) + tmp = '' + tmp += m_row_start(2) + m_col(3, '제목', 'right') + m_col(9, data.title) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '원제', 'right') + m_col(9, data.des._otit) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '감독', 'right') + m_col(9, data.des._dir) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '제작사', 'right') + m_col(9, data.des._pub) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '장르', 'right') + m_col(9, data.des._tag) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '분류', 'right') + m_col(9, data.des._classifi) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '방영일', 'right') + m_col(9, data.date + '(' + data.day + ')') + m_row_end(); + tmp += m_row_start(2) + m_col(3, '등급', 'right') + m_col(9, data.des._grade) + m_row_end(); + tmp += m_row_start(2) + m_col(3, '총화수', 'right') + m_col(9, data.des._total_chapter ? data.des._total_chapter : '') + m_row_end(); + tmp += m_row_start(2) + m_col(3, '상영시간', 'right') + m_col(9, data.des._show_time ? data.des._show_time : '') + m_row_end(); + tmp += m_row_start(2) + m_col(3, '줄거리', 'right') + m_col(9, data.ser_description) + 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 = ''; + if (data.episode[i].thumbnail) + tmp = '<img src="' + data.episode[i].thumbnail + '" class="img-fluid">' + str += m_col(3, tmp) + tmp = '<strong>' + data.episode[i].title + '</strong>'; + tmp += '<br>'; + tmp += data.episode[i].date + '<br>'; + + tmp += '<div class="form-inline">' + tmp += '<input id="checkbox_' + i + '" name="checkbox_' + i + '" 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': 'idx', 'value': i}]) + tmp += '</div>' + str += m_col(9, tmp) + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle() + } + + $(function () { + console.log(params.wr_id) + console.log(findGetParameter('wr_id')) + console.log(params.code) + if (params.code === '') { + + } else { + document.getElementById("code").value = params.code + // {#document.getElementById("analysis_btn").click();#} + } + + if ("{{arg['ohli24_current_code']}}" !== "") { + if (params.code === null) { + console.log('params.code === null') + document.getElementById("code").value = "{{arg['ohli24_current_code']}}"; + + } else if (params.code === '') { + document.getElementById("code").value = "{{arg['ohli24_current_code']}}"; + } else { + + console.log('params code exist') + console.log(params.code) + document.getElementById("code").value = params.code + + analyze(params.wr_id, params.bo_table) + // document.getElementById("analysis_btn").click(); + // $('#analysis_btn').trigger('click') + } + // 값이 공백이 아니면 분석 버튼 계속 누름 + // {#document.getElementById("analysis_btn").click();#} + } else { + + } + + }) + + $(document).ready(function () { + + console.log('wr_id::', params.wr_id) + + }); + + $("#analysis_btn").unbind("click").bind('click', function (e) { + e.preventDefault(); + e.stopPropagation() + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // {#console.log(ret.code)#} + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_ohli24_btn', function (e) { + e.preventDefault(); + window.open("{{arg['ohli24_url']}}", "_blank"); + }); + + $("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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + console.log('#add_queue_btn::data >>', data) + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</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_"]'); + let data = []; + let idx; + for (let i in all) { + if (all[i].checked) { + idx = parseInt(all[i].id.split('_')[1]) + data.push(current_data.episode[idx]); + } + } + if (data.length == 0) { + $.notify('<strong>선택하세요.</strong>', {type: 'warning'}); + return; + } + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue_checked_list', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + $.notify('<strong>백그라운드로 작업을 추가합니다.</strong>', {type: 'success'}); + } + }); + }); +</script> +<style> + 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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + /*@import url(https://fonts.googleapis.com/css?family=Lato);*/ + /*a {*/ + /* position: fixed;*/ + /* bottom: 2%;*/ + /* display: block;*/ + /* text-align: center;*/ + /* color: #0fa;*/ + /* font-family: "Lato", sans-serif;*/ + /* text-decoration: none !important;*/ + /* width: 100%;*/ + /*}*/ + + /*body, html {*/ + /* width: 100%;*/ + /* height: 100%;*/ + /* overflow: hidden;*/ + /*}*/ + + /*body {*/ + /* background: linear-gradient(90deg, #00b377, #00d68f);*/ + /* box-shadow: inset 0px 0px 90px rgba(0, 0, 0, 0.5);*/ + /* margin: 0px;*/ + /* padding: 0px;*/ + /*}*/ + + .demo { + width: 100px; + height: 102px; + border-radius: 100%; + position: absolute; + top: 45%; + left: calc(50% - 50px); + } + + .circle { + width: 100%; + height: 100%; + position: absolute; + } + + .circle .inner { + width: 100%; + height: 100%; + border-radius: 100%; + border: 5px solid rgba(0, 255, 170, 0.7); + border-right: none; + border-top: none; + backgroudn-clip: padding; + box-shadow: inset 0px 0px 10px rgba(0, 255, 170, 0.15); + } + + @-webkit-keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + .circle:nth-of-type(0) { + transform: rotate(0deg); + } + + .circle:nth-of-type(0) .inner { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + + .circle:nth-of-type(1) { + transform: rotate(70deg); + } + + .circle:nth-of-type(1) .inner { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + + .circle:nth-of-type(2) { + transform: rotate(140deg); + } + + .circle:nth-of-type(2) .inner { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear; + } + + .demo { + -webkit-animation: spin 5s infinite linear; + animation: spin 5s infinite linear; + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + /*position: fixed;*/ + right: 0; + /*top: 0;*/ + z-index: 99999; + opacity: 0.5; + margin: 0 auto; + transform: translate(-50%, -50%); + position: absolute; + top: 50%; + + } + + .loader-inner { + bottom: 0; + height: 60px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + #preloader { + /*background-color: green;*/ + /*color: white;*/ + /*height: 100vh;*/ + /*width: 100%;*/ + /*position: fixed;*/ + /*z-index: 100;*/ + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + opacity: 0.5; + } +</style> +{% endblock %} diff --git a/templates/anime_downloader_ohli24_search.html b/templates/anime_downloader_ohli24_search.html new file mode 100644 index 0000000..abdeb66 --- /dev/null +++ b/templates/anime_downloader_ohli24_search.html @@ -0,0 +1,924 @@ +{% extends "base.html" %} {% block content %} +<!--<div id="preloader"></div>--> +<div id="preloader" class="loader"> + <div class="loader-inner"> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + <div class="loader-line-wrap"> + <div class="loader-line"></div> + </div> + </div> +</div> +<div id="yommi_wrapper"> + <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-primary"> + search + </button> + </div> + + <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="theater" 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> +</div> +<!--전체--> + +<script + type="text/javascript" + src="https://cdn.jsdelivr.net/npm/lozad/dist/lozad.min.js" +></script> +<script src="{{ url_for('.static', filename='js/sjva_ui14.js') }}"></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 type="text/javascript"> + const package_name = "{{arg['package_name'] }}"; + const sub = "{{arg['sub'] }}"; + const anilife_url = "{{arg['anilife_url']}}"; + // let current_data = null; + let page = 1; + let next_page = Number + let current_cate = '' + let current_query = '' + + const loader = document.getElementById("preloader"); + + const dismissLoadingScreen = function () { + loader.style.display = "none"; + }; + + const wait3seconds = function () { + // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp + const result = setTimeout(dismissLoadingScreen, 2000); + }; + + window.addEventListener("load", wait3seconds); + // window.addEventListener("load", dismissLoadingScreen); + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + + const get_anime_list = (type, page) => { + console.log(`type: ${type}, page: ${page}`) + let url = '' + let data = {"page": page, "type": type} + + switch (type) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + default: + break; + } + + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret + console.log('ret::>', ret) + + if (current_screen_movie_data !== '') { + if (type === "ing") { + make_airing_list(ret.data, page) + observer.observe(); + } else if (type === "fin") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else if (type === "theater") { + make_screen_movie_list(ret.data, page) + observer.observe(); + } else { + make_screen_movie_list(ret.data, page) + } + div_visible = true + console.log(div_visible) + } + next_page = page + 1 + } + }) + } + + function make_airing_list(data, page) { + let str = '' + let tmp = '' + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + // tmp += '<img class="lozad" data-src="' + data.anime_list[i].image_link + '" />'; + tmp += '<img class="lazyload" src="../static/img_loader_x200.svg" data-original="' + data.anime_list[i].image_link + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + data.anime_list[i].code + '\'"/>'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + data.anime_list[i].code + + '"><i class="bi bi-heart-fill"></i></button></p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + // tmp += + // '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + // data.anime_list[i].code + + // '"><i class="bi bi-heart-fill"></i></button>'; + tmp += '</div><!-- .card -->'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + function make_search_result_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + if (data.anime_list[i].wr_id !== '') { + const re = /bo_table=([^&]+)/ + const bo_table = data.anime_list[i].link.match(re) + console.log(bo_table) + if (bo_table != null) { + request_url = './request?code=' + data.anime_list[i].code + '&wr_id=' + data.anime_list[i].wr_id + '&bo_table=' + bo_table[1] + } else { + request_url = './request?code=' + data.anime_list[i].code + } + } else { + request_url = './request?code=' + data.anime_list[i].code + } + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + // {#tmp += '<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>';#} + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="' + request_url + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + str += '</div><!-- .card-columns -->'; + 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; + } + + } + + function make_screen_movie_list(data, page) { + let str = '' + let tmp = '' + + console.log(data.anime_list, page) + + str += '<div>'; + str += '<button type="button" class="btn btn-info">Page <span class="badge bg-warning">' + page + '</span></button>'; + str += '</div>'; + // str += '<div class="card-columns">' + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.anime_list) { + + tmp = '<div class="col-sm-4">'; + tmp += '<div class="card">'; + tmp += '<img class="card-img-top" src="' + data.anime_list[i].image_link + '" />'; + tmp += '<div class="card-body">' + tmp += '<h5 class="card-title">' + data.anime_list[i].title + '</h5>'; + tmp += '<p class="card-text">' + data.anime_list[i].code + '</p>'; + tmp += '<a href="./request?code=' + data.anime_list[i].code + '" class="btn btn-primary cut-text">' + data.anime_list[i].title + '</a>'; + tmp += '</div>'; + tmp += '</div>'; + tmp += '</div>'; + str += tmp + + } + str += '</div>'; + // str += '</div><!-- .card-columns -->'; + 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: 10, + effect: "fadeIn", + }); + + } + + + $(document).ready(function () { + + // if ( "{{arg['anilife_current_code']}}" !== "" ) { + // document.getElementById("code").value = "{{arg['anilife_current_code']}}"; + // // 값이 공백이 아니면 분석 버튼 계속 누름 + // document.getElementById("analysis_btn").click(); + // } + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("엔터키를 눌렀습니다."); + $("#btn_search").trigger("click"); + } + }) + + get_anime_list("ing", 1) + + + const observer = lozad('.lozad', { + rootMargin: '10px 0px', // syntax similar to that of CSS Margin + threshold: 0.1, // ratio of element convergence + enableAutoReload: true // it will reload the new image when validating attributes changes + }); + observer.observe(); + + }); + + $("body").on("click", "#btn_search", function (e) { + e.preventDefault(); + let query = $("#input_search").val(); + console.log(query); + current_cate = "search" + current_query = query + + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } + + $.ajax({ + url: "/" + package_name + "/ajax/" + sub + "/search", + type: "POST", + cache: false, + data: {query: query, type: current_cate, page: page}, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + console.log('ret:::', ret) + make_search_result_list(ret.data, 1); + next_page = page + 1 + } else { + $.notify("<strong>분석 실패</strong><br>" + ret.log, { + type: "warning", + }); + } + }, + }); + }); + + $('#anime_category #ing').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("ing", 1) + }) + + $('#anime_category #complete_anilist').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("fin", 1) + }) + + $('#anime_category #theater').on("click", function () { + // {#console.log(this.id)#} + let spinner = document.getElementById('spinner'); + spinner.style.visibility = 'visible'; + get_anime_list("theater", 1) + }) + + // 분석 버튼 클릭시 호출 + $("body").on('click', '#analysis_btn', function (e) { + e.preventDefault(); + const code = document.getElementById("code").value + console.log(code) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/analysis', + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (ret) { + if (ret.ret === 'success' && ret.data != null) { + // console.log(ret.code) + console.log(ret.data) + make_program(ret.data) + } else { + $.notify('<strong>분석 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + + + $("body").on('click', '#go_anilife_btn', function (e) { + e.preventDefault(); + window.open("{{arg['anilife_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/" + sub + "/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', '#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', '#add_queue_btn', function (e) { + e.preventDefault(); + data = current_data.episode[$(this).data('idx')]; + console.log('data:::>', data) + $.ajax({ + url: '/' + package_name + '/ajax/' + sub + '/add_queue', + type: "POST", + cache: false, + data: {data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + if (data.ret == 'enqueue_db_append' || data.ret == 'enqueue_db_exist') { + $.notify('<strong>다운로드 작업을 추가 하였습니다.</strong>', {type: 'success'}); + } else if (data.ret == 'queue_exist') { + $.notify('<strong>이미 큐에 있습니다. 삭제 후 추가하세요.</strong>', {type: 'warning'}); + } else if (data.ret == 'db_completed') { + $.notify('<strong>DB에 완료 기록이 있습니다.</strong>', {type: 'warning'}); + } else { + $.notify('<strong>추가 실패</strong><br>' + ret.log, {type: 'warning'}); + } + } + }); + }); + // const observer = lozad(); + // const el = document.querySelector('img'); + // const observer = lozad(el); // passing a `NodeList` (e.g. `document.querySelectorAll()`) is also valid + // observer.observe(); + const loadNextAnimes = (cate, page) => { + spinner.style.display = "block"; + let data = {type: cate, page: String(page)}; + let url = '' + switch (cate) { + case 'ing': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'ing' + break; + case 'movie': + url = '/' + package_name + '/ajax/' + sub + '/screen_movie_list' + current_cate = 'movie' + break; + case 'theater': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'theater' + break; + case 'fin': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'fin' + break + case 'search': + url = "/" + package_name + "/ajax/" + sub + "/search" + current_cate = 'search' + data.query = current_query + break; + default: + break; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), + }) + .then((res) => res.json()) + .then((response) => { + // console.log("Success:", JSON.stringify(response)); + // {#imagesContainer.appendChild()#} + console.log("return page:::> ", String(response.page)); + // {#page = response.page#} + if (current_cate === 'search') { + make_search_result_list(response.data, response.page); + + } else { + make_screen_movie_list(response.data, response.page); + } + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); + }; + + + const onScroll = (e) => { + console.dir(e.target.scrollingElement.scrollHeight); + const {scrollTop, scrollHeight, clientHeight} = e.target.scrollingElement; + if (Math.round(scrollHeight - scrollTop) <= clientHeight) { + document.getElementById("spinner").style.display = "block"; + console.log("loading"); + console.log("now page::> ", page); + console.log("next_page::> ", String(next_page)); + loadNextAnimes(current_cate, next_page); + } + }; + + const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; + }; + + document.addEventListener("scroll", debounce(onScroll, 300)); +</script> +<style> + 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%; + } + + #screen_movie_list { + margin-top: 10px; + } + + .card-body { + padding: 0 !important; + } + + .card-title { + padding: 1rem !important; + } + + button#add_whitelist { + float: right; + } + + button.btn-favorite { + background-color: #e0ff42; + } + + /*.card-columns {*/ + /* @include media-breakpoint-only(lg) {*/ + /* column-count: 4;*/ + /* }*/ + /* @include media-breakpoint-only(xl) {*/ + /* column-count: 5;*/ + /* }*/ + /*}*/ + @media (min-width: 576px) { + .container { + max-width: 100%; + } + + .card-columns { + column-count: 2; + column-gap: 1.25rem; + } + + .card-columns .card { + display: inline-block; + } + } + + + @media (min-width: 768px) { + .card-columns { + column-count: 3; + } + } + + /* Large devices (desktops, 992px and up) */ + @media (min-width: 992px) { + .card-columns { + column-count: 3; + } + } + + /* Extra large devices (large desktops, 1200px and up) */ + @media (min-width: 1200px) { + .card-columns { + column-count: 5; + } + + #yommi_wrapper { + max-width: 80%; + margin: 0 auto; + } + } + + .card { + margin-bottom: 0.75rem; + } + + .card-columns .card { + margin-bottom: 0.75rem; + } + + .card-columns .card img { + width: 100%; + } + + button#add_whitelist { + /*top: -70px;*/ + position: relative; + } + + body { + font-family: NanumSquareNeo, system-ui, -apple-system, Segoe UI, Roboto, Helvetica Neue, Noto Sans, Liberation Sans, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + } + + body { + background-image: linear-gradient(90deg, #233f48, #6c6fa2, #768dae); + } + + #preloader { + /*background-color: green;*/ + /*color: white;*/ + /*height: 100vh;*/ + /*width: 100%;*/ + /*position: fixed;*/ + /*z-index: 100;*/ + background: rgba(0, 0, 0, 0.2); + background: radial-gradient(#222, #000); + bottom: 0; + left: 0; + overflow: hidden; + position: fixed; + right: 0; + top: 0; + z-index: 99999; + opacity: 0.5; + } + + /*.loader {*/ + /* background: rgb(0, 0, 0, 0.8);*/ + /* background: radial-gradient(#222, #000);*/ + /* bottom: 0;*/ + /* left: 0;*/ + /* overflow: hidden;*/ + /* position: fixed;*/ + /* right: 0;*/ + /* top: 0;*/ + /* z-index: 99999;*/ + /*}*/ + + .loader-inner { + bottom: 0; + height: 60px; + left: 0; + margin: auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap { + animation: spin 2000ms cubic-bezier(.175, .885, .32, 1.275) infinite; + box-sizing: border-box; + height: 50px; + left: 0; + overflow: hidden; + position: absolute; + top: 0; + transform-origin: 50% 100%; + width: 100px; + } + + .loader-line { + border: 4px solid transparent; + border-radius: 100%; + box-sizing: border-box; + height: 100px; + left: 0; + margin: 0 auto; + position: absolute; + right: 0; + top: 0; + width: 100px; + } + + .loader-line-wrap:nth-child(1) { + animation-delay: -50ms; + } + + .loader-line-wrap:nth-child(2) { + animation-delay: -100ms; + } + + .loader-line-wrap:nth-child(3) { + animation-delay: -150ms; + } + + .loader-line-wrap:nth-child(4) { + animation-delay: -200ms; + } + + .loader-line-wrap:nth-child(5) { + animation-delay: -250ms; + } + + .loader-line-wrap:nth-child(1) .loader-line { + border-color: hsl(0, 80%, 60%); + height: 90px; + width: 90px; + top: 7px; + } + + .loader-line-wrap:nth-child(2) .loader-line { + border-color: hsl(60, 80%, 60%); + height: 76px; + width: 76px; + top: 14px; + } + + .loader-line-wrap:nth-child(3) .loader-line { + border-color: hsl(120, 80%, 60%); + height: 62px; + width: 62px; + top: 21px; + } + + .loader-line-wrap:nth-child(4) .loader-line { + border-color: hsl(180, 80%, 60%); + height: 48px; + width: 48px; + top: 28px; + } + + .loader-line-wrap:nth-child(5) .loader-line { + border-color: hsl(240, 80%, 60%); + height: 34px; + width: 34px; + top: 35px; + } + + @keyframes spin { + 0%, 15% { + transform: rotate(0); + } + 100% { + transform: rotate(360deg); + } + } + + +</style> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css"> +{% endblock %} diff --git a/templates/anime_downloader_ohli24_setting.html b/templates/anime_downloader_ohli24_setting.html new file mode 100644 index 0000000..6660746 --- /dev/null +++ b/templates/anime_downloader_ohli24_setting.html @@ -0,0 +1,68 @@ +{% extends "base.html" %} +{% block content %} +<div> + {{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}} + {{ macros.m_row_start('5') }} + {{ macros.m_row_end() }} + <nav> + {{ macros.m_tab_head_start() }} + {{ macros.m_tab_head('normal', '일반', true) }} + {{ macros.m_tab_head('auto', '홈화면 자동', false) }} + {{ macros.m_tab_head('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('ohli24_url', 'ohli24 URL', [['go_btn', 'GO']], value=arg['ohli24_url']) }} + {{ macros.setting_input_text('ohli24_download_path', '저장 폴더', value=arg['ohli24_download_path'], desc='정상적으로 다운 완료 된 파일이 이동할 폴더 입니다. ') }} + {{ macros.setting_input_int('ohli24_max_ffmpeg_process_count', '동시 다운로드 수', value=arg['ohli24_max_ffmpeg_process_count'], desc='동시에 다운로드 할 에피소드 갯수입니다.') }} + {{ macros.setting_checkbox('ohli24_order_desc', '요청 화면 최신순 정렬', value=arg['ohli24_order_desc'], desc='On : 최신화부터, Off : 1화부터') }} + {{ macros.setting_checkbox('ohli24_auto_make_folder', '제목 폴더 생성', value=arg['ohli24_auto_make_folder'], desc='제목으로 폴더를 생성하고 폴더 안에 다운로드합니다.') }} + <div id="ohli24_auto_make_folder_div" class="collapse"> + {{ macros.setting_input_text('ohli24_finished_insert', '완결 표시', col='3', value=arg['ohli24_finished_insert'], desc=['완결된 컨텐츠 폴더명 앞에 넣을 문구입니다.']) }} + {{ macros.setting_checkbox('ohli24_auto_make_season_folder', '시즌 폴더 생성', value=arg['ohli24_auto_make_season_folder'], desc=['On : Season 번호 폴더를 만듭니다.']) }} + </div> + {{ macros.setting_checkbox('ohli24_uncompleted_auto_enqueue', '자동으로 다시 받기', value=arg['ohli24_uncompleted_auto_enqueue'], desc=['On : 플러그인 로딩시 미완료인 항목은 자동으로 다시 받습니다.']) }} + {{ 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('ohli24_interval', '스케쥴링 실행 정보', value=arg['ohli24_interval'], col='3', desc=['Inverval(minute 단위)이나 Cron 설정']) }} + {{ macros.setting_checkbox('ohli24_auto_start', '시작시 자동실행', value=arg['ohli24_auto_start'], desc='On : 시작시 자동으로 스케쥴러에 등록됩니다.') }} + {{ macros.setting_input_textarea('ohli24_auto_code_list', '자동 다운로드할 작품 코드', desc=['all 입력시 모두 받기', '구분자 | 또는 엔터'], value=arg['ohli24_auto_code_list'], row='10') }} + {{ macros.setting_checkbox('ohli24_auto_mode_all', '에피소드 모두 받기', value=arg['ohli24_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() }} + + </div><!--tab-content--> + </form> +</div> <!--전체--> + +<script type="text/javascript"> +var package_name = "{{arg['package_name'] }}"; +var sub = "{{arg['sub'] }}"; +var current_data = null; + + +$(document).ready(function(){ + use_collapse('ohli24_auto_make_folder'); +}); + +$('#ani365_auto_make_folder').change(function() { + use_collapse('ohli24_auto_make_folder'); +}); + + +$("body").on('click', '#go_btn', function(e){ + e.preventDefault(); + let url = document.getElementById("ohli24_url").value + window.open(url, "_blank"); +}); + +</script> +{% endblock %} \ No newline at end of file diff --git a/test.ipynb b/test.ipynb new file mode 100644 index 0000000..4d1aa16 --- /dev/null +++ b/test.ipynb @@ -0,0 +1,234 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<class 'datetime.date'>\n" + ] + } + ], + "source": [ + "from datetime import date, datetime\n", + "\n", + "print(date)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2022-06-07\n" + ] + } + ], + "source": [ + "print(date.today())" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 4, + "outputs": [ + { + "ename": "RuntimeError", + "evalue": "asyncio.run() cannot be called from a running event loop", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mRuntimeError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_40530/2714877972.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 52\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 53\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 54\u001B[0;31m \u001B[0masyncio\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/runners.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(main, debug)\u001B[0m\n\u001B[1;32m 31\u001B[0m \"\"\"\n\u001B[1;32m 32\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mevents\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_get_running_loop\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mis\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 33\u001B[0;31m raise RuntimeError(\n\u001B[0m\u001B[1;32m 34\u001B[0m \"asyncio.run() cannot be called from a running event loop\")\n\u001B[1;32m 35\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mRuntimeError\u001B[0m: asyncio.run() cannot be called from a running event loop" + ] + } + ], + "source": [ + "from playwright.sync_api import sync_playwright\n", + "from playwright.async_api import async_playwright\n", + "# from playwright_stealth import stealth_sync\n", + "import asyncio\n", + "\n", + "async def run(pw):\n", + "\n", + " browser = await pw.chromium.launch(headless=False)\n", + " # context = browser.new_context(\n", + " # user_agent=ua,\n", + " # )\n", + "\n", + "\n", + " url = 'https://anilife.live/h/live?p=5aaf4907-da62-4b98-ba8f-59066a0dc0f4&a=none&player=jawcloud'\n", + " #\n", + " # if referer is not None:\n", + " # LogicAniLife.headers[\"Referer\"] = referer\n", + "\n", + " # context = browser.new_context(extra_http_headers=LogicAniLife.headers)\n", + " context = await browser.new_context()\n", + " # LogicAniLife.headers[\"Cookie\"] = cookie_value\n", + "\n", + " # context.set_extra_http_headers(LogicAniLife.headers)\n", + "\n", + " page = await context.new_page()\n", + "\n", + "\n", + "\n", + " # page.on(\"request\", set_cookie)\n", + " # stealth_sync(page)\n", + " page.goto(\n", + " url, wait_until=\"networkidle\"\n", + " )\n", + " page.wait_for_timeout(10000)\n", + " # time.sleep(1)\n", + " # page.reload()\n", + "\n", + " # time.sleep(10)\n", + " cookies = context.cookies\n", + " # print(cookies)\n", + "\n", + " # print(page.content())\n", + " vod_url = page.evaluate(\n", + " \"\"\"() => {\n", + " return console.log(vodUrl_1080p) }\"\"\"\n", + " )\n", + " print(vod_url)\n", + "\n", + "async def main():\n", + " async with async_playwright() as p:\n", + " await main_run(p)\n", + "\n", + "\n", + "asyncio.run(main())" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": 5, + "outputs": [ + { + "ename": "Error", + "evalue": "Executable doesn't exist at /Users/yommi/Library/Caches/ms-playwright/chromium-939194/chrome-mac/Chromium.app/Contents/MacOS/Chromium\n╔═════════════════════════════════════════════════════════════════════════╗\n║ Looks like Playwright Test or Playwright was just installed or updated. ║\n║ Please run the following command to download new browsers: ║\n║ ║\n║ playwright install ║\n║ ║\n║ <3 Playwright Team ║\n╚═════════════════════════════════════════════════════════════════════════╝", + "output_type": "error", + "traceback": [ + "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m", + "\u001B[0;31mError\u001B[0m Traceback (most recent call last)", + "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_67283/2756312828.py\u001B[0m in \u001B[0;36m<module>\u001B[0;34m\u001B[0m\n\u001B[1;32m 57\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 58\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 59\u001B[0;31m \u001B[0masyncio\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/nest_asyncio.py\u001B[0m in \u001B[0;36mrun\u001B[0;34m(future, debug)\u001B[0m\n\u001B[1;32m 30\u001B[0m \u001B[0mloop\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0masyncio\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mget_event_loop\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 31\u001B[0m \u001B[0mloop\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mset_debug\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdebug\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 32\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mloop\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mrun_until_complete\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mfuture\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 33\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 34\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0msys\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mversion_info\u001B[0m \u001B[0;34m>=\u001B[0m \u001B[0;34m(\u001B[0m\u001B[0;36m3\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;36m6\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;36m0\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/nest_asyncio.py\u001B[0m in \u001B[0;36mrun_until_complete\u001B[0;34m(self, future)\u001B[0m\n\u001B[1;32m 68\u001B[0m raise RuntimeError(\n\u001B[1;32m 69\u001B[0m 'Event loop stopped before Future completed.')\n\u001B[0;32m---> 70\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mf\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mresult\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 71\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 72\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0m_run_once\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/futures.py\u001B[0m in \u001B[0;36mresult\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 176\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__log_traceback\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 177\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m \u001B[0;32mis\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 178\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 179\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_result\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 180\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/tasks.py\u001B[0m in \u001B[0;36m__step\u001B[0;34m(***failed resolving arguments***)\u001B[0m\n\u001B[1;32m 278\u001B[0m \u001B[0;31m# We use the `send` method directly, because coroutines\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 279\u001B[0m \u001B[0;31m# don't have `__iter__` and `__next__` methods.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 280\u001B[0;31m \u001B[0mresult\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcoro\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msend\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 281\u001B[0m \u001B[0;32melse\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 282\u001B[0m \u001B[0mresult\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mcoro\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mthrow\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mexc\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_67283/2756312828.py\u001B[0m in \u001B[0;36mmain\u001B[0;34m()\u001B[0m\n\u001B[1;32m 54\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mmain\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 55\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mwith\u001B[0m \u001B[0masync_playwright\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;32mas\u001B[0m \u001B[0mp\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 56\u001B[0;31m \u001B[0;32mawait\u001B[0m \u001B[0mmain_run\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mp\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 57\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 58\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/var/folders/9l/5sls926d02g2kzqb70_0c7rc0000gn/T/ipykernel_67283/2756312828.py\u001B[0m in \u001B[0;36mmain_run\u001B[0;34m(playwright)\u001B[0m\n\u001B[1;32m 11\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0mmain_run\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mplaywright\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 12\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 13\u001B[0;31m \u001B[0mbrowser\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mawait\u001B[0m \u001B[0mplaywright\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mchromium\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mlaunch\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mheadless\u001B[0m\u001B[0;34m=\u001B[0m\u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 14\u001B[0m \u001B[0;31m# context = browser.new_context(\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 15\u001B[0m \u001B[0;31m# user_agent=ua,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/async_api/_generated.py\u001B[0m in \u001B[0;36mlaunch\u001B[0;34m(self, executable_path, channel, args, ignore_default_args, handle_sigint, handle_sigterm, handle_sighup, timeout, env, headless, devtools, proxy, downloads_path, slow_mo, traces_dir, chromium_sandbox, firefox_user_prefs)\u001B[0m\n\u001B[1;32m 11600\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 11601\u001B[0m return mapping.from_impl(\n\u001B[0;32m> 11602\u001B[0;31m await self._async(\n\u001B[0m\u001B[1;32m 11603\u001B[0m \u001B[0;34m\"browser_type.launch\"\u001B[0m\u001B[0;34m,\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 11604\u001B[0m self._impl_obj.launch(\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/_impl/_browser_type.py\u001B[0m in \u001B[0;36mlaunch\u001B[0;34m(self, executablePath, channel, args, ignoreDefaultArgs, handleSIGINT, handleSIGTERM, handleSIGHUP, timeout, env, headless, devtools, proxy, downloadsPath, slowMo, tracesDir, chromiumSandbox, firefoxUserPrefs)\u001B[0m\n\u001B[1;32m 86\u001B[0m \u001B[0mparams\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mlocals_to_params\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mlocals\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 87\u001B[0m \u001B[0mnormalize_launch_params\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mparams\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 88\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0mfrom_channel\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;32mawait\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_channel\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0msend\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m\"launch\"\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 89\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 90\u001B[0m async def launch_persistent_context(\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/_impl/_connection.py\u001B[0m in \u001B[0;36msend\u001B[0;34m(self, method, params)\u001B[0m\n\u001B[1;32m 37\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 38\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0msend\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmethod\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mstr\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mDict\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m->\u001B[0m \u001B[0mAny\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 39\u001B[0;31m \u001B[0;32mreturn\u001B[0m \u001B[0;32mawait\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0minner_send\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mmethod\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 40\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 41\u001B[0m \u001B[0;32masync\u001B[0m \u001B[0;32mdef\u001B[0m \u001B[0msend_return_as_dict\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mself\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mmethod\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mstr\u001B[0m\u001B[0;34m,\u001B[0m \u001B[0mparams\u001B[0m\u001B[0;34m:\u001B[0m \u001B[0mDict\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m)\u001B[0m \u001B[0;34m->\u001B[0m \u001B[0mAny\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/site-packages/playwright/_impl/_connection.py\u001B[0m in \u001B[0;36minner_send\u001B[0;34m(self, method, params, return_as_dict)\u001B[0m\n\u001B[1;32m 61\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0mcallback\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfuture\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mdone\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 62\u001B[0m \u001B[0mcallback\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mfuture\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mcancel\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m---> 63\u001B[0;31m \u001B[0mresult\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0mnext\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0miter\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0mdone\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0mresult\u001B[0m\u001B[0;34m(\u001B[0m\u001B[0;34m)\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 64\u001B[0m \u001B[0;31m# Protocol now has named return values, assume result is one level deeper unless\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 65\u001B[0m \u001B[0;31m# there is explicit ambiguity.\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;32m/Volumes/WD/Users/yommi/miniforge3/envs/ani-downloader/lib/python3.8/asyncio/futures.py\u001B[0m in \u001B[0;36mresult\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 176\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m__log_traceback\u001B[0m \u001B[0;34m=\u001B[0m \u001B[0;32mFalse\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 177\u001B[0m \u001B[0;32mif\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m \u001B[0;32mis\u001B[0m \u001B[0;32mnot\u001B[0m \u001B[0;32mNone\u001B[0m\u001B[0;34m:\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0;32m--> 178\u001B[0;31m \u001B[0;32mraise\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_exception\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[0m\u001B[1;32m 179\u001B[0m \u001B[0;32mreturn\u001B[0m \u001B[0mself\u001B[0m\u001B[0;34m.\u001B[0m\u001B[0m_result\u001B[0m\u001B[0;34m\u001B[0m\u001B[0;34m\u001B[0m\u001B[0m\n\u001B[1;32m 180\u001B[0m \u001B[0;34m\u001B[0m\u001B[0m\n", + "\u001B[0;31mError\u001B[0m: Executable doesn't exist at /Users/yommi/Library/Caches/ms-playwright/chromium-939194/chrome-mac/Chromium.app/Contents/MacOS/Chromium\n╔═════════════════════════════════════════════════════════════════════════╗\n║ Looks like Playwright Test or Playwright was just installed or updated. ║\n║ Please run the following command to download new browsers: ║\n║ ║\n║ playwright install ║\n║ ║\n║ <3 Playwright Team ║\n╚═════════════════════════════════════════════════════════════════════════╝" + ] + } + ], + "source": [ + "# !playwright install\n", + "\n", + "from playwright.sync_api import sync_playwright\n", + "from playwright.async_api import async_playwright\n", + "# from playwright_stealth import stealth_sync\n", + "import asyncio\n", + "import nest_asyncio\n", + "nest_asyncio.apply()\n", + "\n", + "\n", + "async def main_run(playwright):\n", + "\n", + " browser = await playwright.chromium.launch(headless=False)\n", + " # context = browser.new_context(\n", + " # user_agent=ua,\n", + " # )\n", + "\n", + "\n", + " url = 'https://sir.kr'\n", + " #\n", + " # if referer is not None:\n", + " # LogicAniLife.headers[\"Referer\"] = referer\n", + "\n", + " # context = browser.new_context(extra_http_headers=LogicAniLife.headers)\n", + " context = await browser.new_context()\n", + " # LogicAniLife.headers[\"Cookie\"] = cookie_value\n", + "\n", + " # context.set_extra_http_headers(LogicAniLife.headers)\n", + "\n", + " page = await context.new_page()\n", + "\n", + "\n", + "\n", + " # page.on(\"request\", set_cookie)\n", + " # stealth_sync(page)\n", + " page.goto(\n", + " url, wait_until=\"networkidle\"\n", + " )\n", + " page.wait_for_timeout(10000)\n", + " # time.sleep(1)\n", + " # page.reload()\n", + "\n", + " # time.sleep(10)\n", + " cookies = context.cookies\n", + " # print(cookies)\n", + "\n", + " # print(page.content())\n", + " vod_url = page.evaluate(\n", + " \"\"\"() => {\n", + " return console.log(vodUrl_1080p) }\"\"\"\n", + " )\n", + " print(vod_url)\n", + "\n", + "async def main():\n", + " async with async_playwright() as p:\n", + " await main_run(p)\n", + "\n", + "\n", + "asyncio.run(main())" + ], + "metadata": { + "collapsed": false + } + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/test.py b/test.py new file mode 100644 index 0000000..b6f4fc7 --- /dev/null +++ b/test.py @@ -0,0 +1,94 @@ +from playwright.sync_api import sync_playwright +from playwright.async_api import async_playwright + +# from playwright_stealth import stealth_sync +import asyncio +import html_to_json + + +async def run(playwright): + + headers = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7", + "Referer": "https://anilife.live/", + # "Cookie": "SPSI=ef307b8c976fac3363cdf420c9ca40a9; SPSE=+PhK0/uGUBMCZIgXplNjzqW3K2kXLybiElDTtOOiboHiBXO7Tp/9roMW7FplGZuGCUo3i4Fwx5VIUG57Zj6VVw==; anilife_csrf=b1eb92529839d7486169cd91e4e60cd2; UTGv2=h45f897818578a5664b31004b95a9992d273; _ga=GA1.1.281412913.1662803695; _ga_56VYJJ7FTM=GS1.1.1662803695.1.0.1662803707.0.0.0; DCST=pE9; DSR=w2XdPUpwLWDqkLpWXfs/5TiO4mtNv5O3hqNhEr7GP1kFoRBBzbFRpR+xsJd9A+E29M+we7qIvJxQmHQTjDNLuQ==; DCSS=696763EB4EA5A67C4E39CFA510FE36F19B0912C; DGCC=RgP; spcsrf=8a6b943005d711258f2f145a8404d873; sp_lit=F9PWLXyxvZbOyk3eVmtTlg==; PRLST=wW; adOtr=70fbCc39867" + # "Cookie": "" + # "Cookie": "_ga=GA1.1.578607927.1660813724; __gads=ID=10abb8b98b6828ae-2281c943a9d500fd:T=1660813741:RT=1660813741:S=ALNI_MYU_iB2lBgSrEQUBwhKpNsToaqQ8A; SL_G_WPT_TO=ko; SL_GWPT_Show_Hide_tmp=1; SL_wptGlobTipTmp=1; SPSI=944c237cdd8606d80e5e330a0f332d03; SPSE=itZcXMDuso0ktWnDkV2G0HVwWEctCgDjrcFMlEQ5C745wqvp1pEEddrsAsjPUBjl6/8+9Njpq1IG3wt/tVag7w==; sbtsck=jav9aILa6Ofn0dEQr5DhDq5rpbd1JUoNgKwxBpZrqYd+CM=; anilife_csrf=54ee9d15c87864ee5e2538a63d894ad6; UTGv2=h46b326af644f4ac5d0eb1502881136b3750; DCST=pE9; __gpi=UID=000008ba227e99e0:T=1660813741:RT=1661170429:S=ALNI_MaJHIVJIGpQ5nTE9lvypKQxJnn10A; DSR=GWyTLTvSMF/lQD77ojQkGyl+7JvTudkSwV1GKeNVUcWEBa/msln9zzsBj7lj+89ywSRBM34Ol73AKf+KHZ9bZA==; DCSS=9D44115EC4CE12CADB88A005DC65A3CD74A211E; DGCC=zdV; spcsrf=fba136251afc6b5283109fc920322c70; sp_lit=kw0Xkp66eQ7bV0f0tNClhg==; PRLST=gt; adOtr=2C4H9c4d78d; _ga_56VYJJ7FTM=GS1.1.1661168661.18.1.1661173389.0.0.0", + } + useragent = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, " + "like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" + } + + browser = await playwright.webkit.launch(headless=False) + # context = browser.new_context( + # user_agent=ua, + # ) + + # url = "https://anilife.live/h/live?p=7ccd9e49-9f59-4976-b5d8-25725d6a6188&a=none&player=jawcloud" + url = "https://api-svr-01.anilife.live/m3u8/st/MDk5NzUyNjY3NTkwZTc3ZmYwMGRmNGIyMzk3MGZiNzU1YjBkNjk2YTFiMzJiZTVhZWZjYjg3NGY4YmE3NTkyMDRkNTU1Y2RjMDhkZTkwNWZiMzZiMTI3ZjE5Zjk0YzQ3MjgzYmUxMTIzZTM2OTllMWZlMzZjM2I1OTIxMmNkNmZmODUxOWZhY2JiMzUxYmE4ZjVjOTMyNzFiYzA0YWI1OTNjZWU0NzMwOTJmYTA4NGU1ZDM1YTlkODA5NzljOTMxNTVhYjlmMmQwMWIwOGMyMTg1N2UyOWJjYjZjN2UwNzJkNjBiOGQzNzc4NTZlZjlkNTQwMDQ5MjgyOGQzYjQxN2M1YmIzYmZiYWYwNGQ0M2U5YmIwMjc4NjgyN2I4M2M1ZDFjOWUxMjM3MjViZDJlZDM3MGI0ZmJkNDE2MThhYTY2N2JlZDllNjQwNTg4MGIxZjBmYTYzMTU4ZTJlZmI1Zg==/dKtKWqgJFnmS-1XShKtsaJWn_OMY1F_HdGDxH2w38mQ/1662826054" + # + # if referer is not None: + # LogicAniLife.headers["Referer"] = referer + + # context = browser.new_context(extra_http_headers=LogicAniLife.headers) + # context = await browser.new_context() + context = await browser.new_context(extra_http_headers=headers) + # LogicAniLife.headers["Cookie"] = cookie_value + + # context.set_extra_http_headers(LogicAniLife.headers) + + page = await context.new_page() + + # page.on("request", set_cookie) + # stealth_sync(page) + await page.goto(url, wait_until="networkidle") + await page.wait_for_timeout(2000) + # time.sleep(1) + # page.reload() + + # time.sleep(10) + cookies = context.cookies + # print(cookies) + + # print(page.content()) + # vod_url = await page.evaluate( + # """() => { + # return console.log(vodUrl_1080p) }""" + # ) + # vod_url1 = await page.evaluate( + # """async () =>{ + # return _0x55265f(0x99) + alJson[_0x55265f(0x91)] + # }""" + # ) + # print(vod_url) + # print(vod_url1) + html_content = await page.content() + # print(await page.content()) + # print(f"html_content:: {html_content}") + output_json = html_to_json.convert(html_content) + print(output_json) + print(f"output_json:: {output_json['html'][0]['body'][0]['_value']}") + + +async def main(): + async with async_playwright() as p: + await run(p) + + +from loguru import logger +import snoop + + +class Calc: + @staticmethod + # @logger.catch() + @snoop + def add(a, b): + return a + b + + +cal = Calc() +cal.add(1, 2) # return 3 diff --git a/test_sir.py b/test_sir.py new file mode 100644 index 0000000..2c1c568 --- /dev/null +++ b/test_sir.py @@ -0,0 +1,38 @@ +import asyncio +from playwright.async_api import Playwright, async_playwright + + +async def run(playwright: Playwright) -> None: + browser = await playwright.chromium.launch(headless=False) + context = await browser.new_context() + # Open new page + page = await context.new_page() + # Go to https://sir.kr/ + await page.goto("https://sir.kr/") + await asyncio.sleep(1) + # Click [placeholder="아이디"] + await page.locator('[placeholder="아이디"]').click() + # Fill [placeholder="아이디"] + await page.locator('[placeholder="아이디"]').fill("tongki77") + # Press Tab + await page.locator('[placeholder="아이디"]').press("Tab") + # Fill [placeholder="비밀번호"] + await page.locator('[placeholder="비밀번호"]').fill("sir98766") + # Click input:has-text("로그인") + await page.locator('input:has-text("로그인")').click() + # await expect(page).to_have_url("https://sir.kr/") + # Click text=출석 2 + await asyncio.sleep(2) + await page.locator("text=출석 2").click() + await asyncio.sleep(2) + # --------------------- + await context.close() + await browser.close() + + +async def main() -> None: + async with async_playwright() as playwright: + await run(playwright) + + +asyncio.run(main())