diff --git a/mod_linkkf.py b/mod_linkkf.py index f9a63eb..366adaf 100644 --- a/mod_linkkf.py +++ b/mod_linkkf.py @@ -5,33 +5,34 @@ # @Site : # @File : logic_linkkf # @Software: PyCharm -import os, sys, traceback, re, json, threading +import os +import re +import sys +import traceback from datetime import datetime -import copy +import PIL.Image # third-party import requests -from lxml import html -from urllib import parse - +from bs4 import BeautifulSoup # third-party -from flask import request, render_template, jsonify -from sqlalchemy import or_, and_, func, not_, desc - +from flask import jsonify, render_template, request # 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 framework import db, path_data, scheduler +from lxml import html +from plugin import PluginModuleBase + +from anime_downloader.lib.util import Util +# 패키지 +# from .plugin import P +from anime_downloader.setup import * + +# from linkkf.model import ModelLinkkfProgram + +# from linkkf.model import ModelLinkkfProgram # from tool_base import d -# 패키지 -# from .plugin import P -from .setup import * logger = P.logger @@ -58,18 +59,19 @@ class LogicLinkkf(PluginModuleBase): } current_headers = None current_data = None + referer = 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", + "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" + "like Gecko) Chrome/96.0.4664.110 Whale/3.12.129.46 Safari/537.36" } def __init__(self, P): @@ -107,8 +109,8 @@ class LogicLinkkf(PluginModuleBase): 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) + P.ModelSetting.set("linkkf_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": @@ -164,58 +166,270 @@ class LogicLinkkf(PluginModuleBase): 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 - ) + url = f"{P.ModelSetting.get('linkkf_url')}/airing/page/{page}" + items_xpath = '//div[@class="myui-vodlist__box"]' + title_xpath = './/a[@class="text-fff"]//text()' 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": + url = f"{P.ModelSetting.get('linkkf_url')}/ani/page/{page}" + items_xpath = '//div[@class="myui-vodlist__box"]' + title_xpath = './/a[@class="text-fff"]//text()' + elif cate == "complete": + url = f"{P.ModelSetting.get('linkkf_url')}/anime-list/page/{page}" + items_xpath = '//div[@class="myui-vodlist__box"]' + title_xpath = './/a[@class="text-fff"]//text()' + elif cate == "top_view": + url = f"{P.ModelSetting.get('linkkf_url')}/topview/page/{page}" + items_xpath = '//div[@class="myui-vodlist__box"]' + title_xpath = './/a[@class="text-fff"]//text()' + logger.info("url:::> %s", url) - data = {} - response_data = LogicOhli24.get_html(url, timeout=10) + logger.info("test..........................") + logger.info("test..........................") + if self.referer is None: + self.referer = "https://linkkf.app" + + data = {"ret": "success", "page": page} + response_data = LogicLinkkf.get_html(url, timeout=10) + # P.logger.debug(response_data) + P.logger.debug("debug.....................") tree = html.fromstring(response_data) - tmp_items = tree.xpath('//div[@class="list-row"]') - data["anime_count"] = len(tmp_items) - data["anime_list"] = [] + tmp_items = tree.xpath(items_xpath) + + if tree.xpath('//div[@id="wp_page"]//text()'): + data["total_page"] = tree.xpath('//div[@id="wp_page"]//text()')[-1] + else: + data["total_page"] = 0 + data["episode_count"] = len(tmp_items) + data["episode"] = [] for item in tmp_items: - entity = {} + entity = dict() 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) + entity["code"] = re.search(r"[0-9]+", entity["link"]).group() + entity["title"] = item.xpath(title_xpath)[0].strip() + entity["image_link"] = item.xpath("./a/@data-original")[0] + entity["chapter"] = ( + item.xpath("./a/span//text()")[0].strip() + if len(item.xpath("./a/span//text()")) > 0 + else "" + ) + # logger.info('entity:::', entity['title']) + data["episode"].append(entity) + + # logger.debug(data) 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_series_info(self, code): + data = {"code": code, "ret": False} + try: + # 이전 데이터가 있다면, 리턴 (# If you have previous data, return) + if ( + LogicLinkkf.current_data is not None + and LogicLinkkf.current_data["code"] == code + and LogicLinkkf.current_data["ret"] + ): + return LogicLinkkf.current_data + url = "%s/%s" % (P.ModelSetting.get("linkkf_url"), code) + logger.info(url) + + logger.debug(LogicLinkkf.headers) + html_content = LogicLinkkf.get_html(url, cached=False) + # html_content = LogicLinkkf.get_html_playwright(url) + # html_content = LogicLinkkf.get_html_cloudflare(url, cached=False) + + sys.setrecursionlimit(10 ** 7) + # logger.info(html_content) + tree = html.fromstring(html_content) + # tree = etree.fromstring( + # html_content, parser=etree.XMLParser(huge_tree=True) + # ) + # tree1 = BeautifulSoup(html_content, "lxml") + + soup = BeautifulSoup(html_content, "html.parser") + # tree = etree.HTML(str(soup)) + # logger.info(tree) + + tmp2 = soup.select("ul > a") + if len(tmp2) == 0: + tmp = soup.select("u > a") + else: + tmp = soup.select("ul > a") + + # logger.debug(f"tmp1 size:=> {str(len(tmp))}") + + try: + tmp = ( + tree.xpath('//div[@class="hrecipe"]/article/center/strong')[0] + .text_content() + .strip() + ) + except IndexError: + tmp = tree.xpath("//article/center/strong")[0].text_content().strip() + + # logger.info(tmp) + match = re.compile(r"(?P\d+)기").search(tmp) + if match: + data["season"] = match.group("season") + else: + data["season"] = "1" + + data["_id"] = str(code) + data["title"] = tmp.replace(data["season"] + "기", "").strip() + data["title"] = data["title"].replace("()", "").strip() + data["title"] = ( + Util.change_text_for_use_filename(data["title"]) + .replace("OVA", "") + .strip() + ) + + try: + data["poster_url"] = tree.xpath( + '//div[@class="myui-content__thumb"]/a/@data-original' + ) + # print(tree.xpath('//div[@class="myui-content__detail"]/text()')) + if len(tree.xpath('//div[@class="myui-content__detail"]/text()')) > 3: + data["detail"] = [ + { + "info": str(tree.xpath( + "//div[@class='myui-content__detail']/text()" + )[3]) + } + ] + else: + data["detail"] = [{"정보없음": ""}] + except Exception as e: + logger.error(e) + data["detail"] = [{"정보없음": ""}] + data["poster_url"] = None + + data["rate"] = tree.xpath('span[@class="tag-score"]') + + tag_score = tree.xpath('//span[@class="taq-score"]')[0].text_content() + # logger.debug(tag_score) + tag_count = ( + tree.xpath('//span[contains(@class, "taq-count")]')[0] + .text_content() + .strip() + ) + data_rate = tree.xpath('//div[@class="rating"]/div/@data-rate') + + tmp2 = soup.select("ul > a") + if len(tmp) == 0: + tmp = soup.select("u > a") + else: + tmp = soup.select("ul > a") + + if tmp is not None: + data["episode_count"] = str(len(tmp)) + else: + data["episode_count"] = "0" + + data["episode"] = [] + # tags = tree.xpath( + # '//*[@id="syno-nsc-ext-gen3"]/article/div[1]/article/a') + # tags = tree.xpath("//ul/a") + tags = soup.select("ul > u > a") + if len(tags) > 0: + pass + else: + tags = soup.select("ul > a") + + logger.debug(len(tags)) + + # logger.info("tags", tags) + # re1 = re.compile(r'\/(?P\d+)') + re1 = re.compile(r"\-([^-])+\.") + + data["save_folder"] = data["title"] + # logger.debug(f"save_folder::> {data['save_folder']}") + + # program = ( + # db.session.query(ModelLinkkfProgram).filter_by(programcode=code).first() + # ) + + idx = 1 + for t in tags: + entity = { + "_id": data["code"], + "program_code": data["code"], + "program_title": data["title"], + "save_folder": Util.change_text_for_use_filename( + data["save_folder"] + ), + "title": t.text.strip(), + # "title": t.text_content().strip(), + } + # entity['code'] = re1.search(t.attrib['href']).group('code') + + # logger.debug(f"title ::>{entity['title']}") + + # 고유id임을 알수 없는 말도 안됨.. + # 에피소드 코드가 고유해야 상태값 갱신이 제대로 된 값에 넣어짐 + p = re.compile(r"([0-9]+)화?") + m_obj = p.match(entity["title"]) + # logger.info(m_obj.group()) + # entity['code'] = data['code'] + '_' +str(idx) + + episode_code = None + # logger.debug(f"m_obj::> {m_obj}") + if m_obj is not None: + episode_code = m_obj.group(1) + entity["code"] = data["code"] + episode_code.zfill(4) + else: + entity["code"] = data["code"] + + aa = t["href"] + if "/player" in aa: + entity["url"] = "https://linkkf.app" + t["href"] + else: + entity["url"] = t["href"] + entity["season"] = data["season"] + + # 저장 경로 저장 + # Todo: db + tmp_save_path = P.ModelSetting.get(f"linkkf_download_path") + if P.ModelSetting.get("linkkf_auto_make_folder") == "True": + program_path = os.path.join(tmp_save_path, entity["save_folder"]) + entity["save_path"] = program_path + if P.ModelSetting.get("linkkf_auto_make_season_folder"): + entity["save_path"] = os.path.join( + entity["save_path"], "Season %s" % int(entity["season"]) + ) + + entity["image"] = data["poster_url"] + + entity["filename"] = LogicLinkkf.get_filename( + data["save_folder"], data["season"], entity["title"] + ) + data["episode"].append(entity) + idx = idx + 1 + + data["ret"] = True + # logger.info('data', data) + self.current_data = data + + return data + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + data["log"] = str(e) + data["ret"] = "error" + return data + except IndexError as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + data["log"] = str(e) + data["ret"] = "error" + return data + @staticmethod - def get_html(url, referer=None, stream=False, timeout=5): + def get_html(url: str, referer: str = None, cached: bool = False, stream: bool = False, timeout: int = 5): data = "" headers = { "referer": f"https://linkkf.app", @@ -241,6 +455,42 @@ class LogicLinkkf(PluginModuleBase): logger.error("Exception:%s", e) logger.error(traceback.format_exc()) return data + + @staticmethod + def get_filename(maintitle, season, title): + try: + # logger.debug("get_filename()===") + # logger.info("title:: %s", title) + # logger.info("maintitle:: %s", maintitle) + match = re.compile( + r"(?P.*?)\s?((?P<season>\d+)기)?\s?((?P<epi_no>\d+)화?)" + ).search(title) + if match: + epi_no = int(match.group("epi_no")) + if epi_no < 10: + epi_no = "0%s" % epi_no + else: + epi_no = "%s" % epi_no + + if int(season) < 10: + season = "0%s" % season + else: + season = "%s" % season + + # title_part = match.group('title').strip() + # ret = '%s.S%sE%s%s.720p-SA.mp4' % (maintitle, season, epi_no, date_str) + ret = "%s.S%sE%s.720p-LK.mp4" % (maintitle, season, epi_no) + else: + logger.debug("NOT MATCH") + ret = "%s.720p-SA.mp4" % maintitle + + return Util.change_text_for_use_filename(ret) + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + + + class ModelLinkkfItem(db.Model): __tablename__ = "{package_name}_linkkf_item".format(package_name=P.package_name) __table_args__ = {"mysql_collate": "utf8_general_ci"} diff --git a/templates/anime_downloader_linkkf_request.html b/templates/anime_downloader_linkkf_request.html new file mode 100644 index 0000000..bf48f2a --- /dev/null +++ b/templates/anime_downloader_linkkf_request.html @@ -0,0 +1,575 @@ +{% 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:: ', data) + str = ""; + tmp = '<div class="form-inline w-100">'; + tmp += m_button("check_download_btn", "선택 다운로드 추가", []); + tmp += m_button("all_check_on_btn", "전체 선택", []); + tmp += m_button("all_check_off_btn", "전체 해제", []); + tmp += m_button("down_subtitle_btn", "자막만 전체 받기", []) + tmp += + '   <input id="new_title" name="new_title" class="form-control form-control-sm" value="' + + data.title + + '">'; + tmp += "</div>"; + tmp += '<div class="form-inline">'; + tmp += m_button("apply_new_title_btn", "저장폴더명 변경", []); + tmp += + '   <input id="new_season" name="new_season" class="form-control form-control-sm" value="' + + data.season + + '">'; + tmp += m_button("apply_new_season_btn", "시즌 변경 (숫자만 가능)", []); + tmp += m_button("search_tvdb_btn", "TVDB", []); + tmp += m_button("add_whitelist", "스케쥴링 추가", []); + + tmp += "</div>"; + tmp = m_button_group(tmp); + str += tmp; + // program + // str += m_hr_black(); + str += "<div class='card p-lg-5 mt-md-3 p-md-3 border-light'>" + + str += m_row_start(0); + tmp = ""; + if (data.poster_url != null) + tmp = '<img src="' + data.poster_url + '" class="img-fluid">'; + str += m_col(3, tmp); + tmp = ""; + tmp += m_row_start(0); + tmp += m_col(3, "제목", "right"); + tmp += m_col(9, data.title); + tmp += m_row_end(); + tmp += m_row_start(0); + tmp += m_col(3, "시즌", "right"); + tmp += m_col(9, data.season); + tmp += m_row_end(); + for (i in data.detail) { + tmp += m_row_start(0); + key = Object.keys(data.detail[i])[0]; + value = data.detail[i][key]; + tmp += m_col(3, key, "right"); + tmp += m_col(9, value); + tmp += m_row_end(); + } + + str += m_col(9, tmp); + str += m_row_end(); + + // str += m_hr_black(); + str += "</div>" + for (i in data.episode) { + str += m_row_start(); + // tmp = '<img src="' + data.episode[i].image + '" class="img-fluid">' + // str += m_col(3, tmp) + tmp = "<strong>" + data.episode[i].title + "</strong><span>화. </span>"; + {#tmp += "<br>";#} + tmp += data.episode[i].filename + "<br><p></p>"; + + tmp += '<div class="form-inline">'; + tmp += + '<input id="checkbox_' + + data.episode[i].code + + '" name="checkbox_' + + data.episode[i].code + + '" type="checkbox" checked data-toggle="toggle" data-on="선 택" data-off="-" data-onstyle="success" data-offstyle="danger" data-size="small">    '; + // tmp += m_button('add_queue_btn', '다운로드 추가', [{'key': 'code', 'value': data.episode[i].code}]) + tmp += m_button("add_queue_btn", "다운로드 추가", [ + {key: "idx", value: i}, + ]); + // tmp += '<button id="play_video" name="play_video" class="btn btn-sm btn-outline-primary" data-idx="'+i+'">바로보기</button>'; + tmp += "</div>"; + str += m_col(12, tmp); + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle(); + } + + $(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_linkkf_search.html b/templates/anime_downloader_linkkf_search.html index abdeb66..d6be7df 100644 --- a/templates/anime_downloader_linkkf_search.html +++ b/templates/anime_downloader_linkkf_search.html @@ -1,352 +1,101 @@ {% 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 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> - <form id="program_auto_form"> - <div id="episode_list"></div> - </form> </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> -<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 = '' + <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> + <!--전체--> - const loader = document.getElementById("preloader"); + <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 dismissLoadingScreen = function () { - loader.style.display = "none"; - }; + const loader = document.getElementById("preloader"); - const wait3seconds = function () { - // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp - const result = setTimeout(dismissLoadingScreen, 2000); - }; + const dismissLoadingScreen = function () { + loader.style.display = "none"; + }; - window.addEventListener("load", wait3seconds); - // window.addEventListener("load", dismissLoadingScreen); + const wait3seconds = function () { + // REFERENCE: https://www.w3schools.com/jsref/met_win_settimeout.asp + const result = setTimeout(dismissLoadingScreen, 2000); + }; - - 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) + window.addEventListener("load", wait3seconds); + // window.addEventListener("load", dismissLoadingScreen); const observer = lozad('.lozad', { @@ -356,569 +105,951 @@ }); 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 + const get_anime_list = (type, page) => { + console.log(`type: ${type}, page: ${page}`) + let url = '' + let data = {"page": page, "type": type} - if ($("#input_search").val() === "") { - console.log("search keyword nothing"); - return false; - } + 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 'complete': + url = '/' + package_name + '/ajax/' + sub + '/anime_list' + current_cate = 'complete' + break; + case 'complete': + url = '/' + package_name + '/ajax/' + sub + '/complete_list' + current_cate = 'complete' + break + default: + break; + } - $.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); + $.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) + make_screen_movie_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 - } 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); + 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 { - make_screen_movie_list(response.data, response.page); + 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 = ""; + let new_anime = true; + let new_style = '' + console.log('page a: ', page) + console.log(data) + console.log(data.episode) + + let page_elem = ""; + if (page === undefined) { + } else { + page_elem = '<span class="badge bg-warning">' + page + "</span>"; + } + + // str += "<div id='page_caption' style='border-bottom: aqua 1px solid; margin-bottom: 5px'>"; + str += "<div id='page_caption' style='padding-bottom: 3px'>"; + str += + '<button type="button" class="btn btn-info">Page ' + + page_elem + + "</button>"; + str += "</div>"; + str += '<div id="inner_screen_movie" class="row infinite-scroll">'; + for (let i in data.episode) { + if (data.episode[i].code === data.latest_anime_code) { + new_anime = false + } + + if (new_anime && page === '1') { + new_style = 'new-anime'; + } else { + new_style = ''; + } + + tmp = '<div class="col-6 col-sm-4 col-md-3">'; + tmp += '<div class="card ' + new_style + '">'; + // tmp += '<div class="card-header">'; + + // tmp += + // '<img class="card-img-top lazyload" src="./static/img_loader_x200.svg" data-original="' + data.episode[i].image_link + '" />'; + tmp += + '<img class="card-img-top lazy" src="{{ url_for('.static', filename='img_loader_x200.svg') }}" data-lazy-src="' + + data.episode[i].image_link + + '" style="cursor: pointer" onclick="location.href=\'./request?code=' + + data.episode[i].code + + "'\"/>"; + if (current_cate === "ing") { + tmp += + '<span class="badge badge-danger badge-on-image">' + + data.episode[i].chapter + + "</span>"; + } + // tmp += '<div class="card-body '+ new_anime ? 'new-anime' : '' +'">'; + tmp += '<div class="card-body">'; + tmp += '<h5 class="card-title">' + data.episode[i].title + "</h5>"; + tmp += + '<button id="add_whitelist" name="add_whitelist" class="btn btn-sm btn-favorite mb-1" data-code="' + + data.episode[i].code + + '"><p class="card-text">' + + data.episode[i].code + + " <i class=\"bi bi-heart-fill\"></i></p></button>"; + tmp += + '<a href="./request?code=' + + data.episode[i].code + + '" class="btn btn-primary cut-text">' + + data.episode[i].title + + "</a>"; + tmp += "</div>"; + tmp += "</div>"; + // tmp += "</div>" + tmp += "</div>"; + str += tmp; + } + str += "</div>"; + str += m_hr_black(); + + if (page > 1) { + const temp = document.createElement("div"); + temp.innerHTML = str; + while (temp.firstChild) { + document.getElementById("screen_movie_list").appendChild(temp.firstChild); } page++; - next_page++; + } else { + document.getElementById("screen_movie_list").innerHTML = str; + } + $("img.lazyload").lazyload({ + threshold: 100, + 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"); + } }) - .catch((error) => console.error("Error:", error)); - }; + + get_anime_list("ing", 1) - 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 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 debounce = (func, delay) => { - let timeoutId = null; - return (...args) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(func.bind(null, ...args), delay); + }); + + $("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)); }; - }; - - 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; - } + 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); + } + }; - [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; + const debounce = (func, delay) => { + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; + }; - background-color: rgba(0, 0, 0, 0.8); + document.addEventListener("scroll", debounce(onScroll, 300)); + window.lazyLoadOptions = { + elements_selector: + "img[data-lazy-src],.rocket-lazyload,iframe[data-lazy-src]", + data_src: "lazy-src", + data_srcset: "lazy-srcset", + data_sizes: "lazy-sizes", + class_loading: "lazyloading", + class_loaded: "lazyloaded", + threshold: 50, + callback_loaded: function (element) { + if ( + element.tagName === "IFRAME" && + element.dataset.rocketLazyload == "fitvidscompatible" + ) { + if (element.classList.contains("lazyloaded")) { + if (typeof window.jQuery != "undefined") { + if (jQuery.fn.fitVids) { + jQuery(element).parent().fitVids(); + } + } + } + } + }, + }; + window.addEventListener( + "LazyLoad::Initialized", + function (e) { + var lazyLoadInstance = e.detail.instance; + if (window.MutationObserver) { + var observer = new MutationObserver(function (mutations) { + var image_count = 0; + var iframe_count = 0; + var rocketlazy_count = 0; + mutations.forEach(function (mutation) { + for (var i = 0; i < mutation.addedNodes.length; i++) { + if ( + typeof mutation.addedNodes[i].getElementsByTagName !== "function" + ) { + continue; + } + if ( + typeof mutation.addedNodes[i].getElementsByClassName !== + "function" + ) { + continue; + } + images = mutation.addedNodes[i].getElementsByTagName("img"); + is_image = mutation.addedNodes[i].tagName == "IMG"; + iframes = mutation.addedNodes[i].getElementsByTagName("iframe"); + is_iframe = mutation.addedNodes[i].tagName == "IFRAME"; + rocket_lazy = + mutation.addedNodes[i].getElementsByClassName("rocket-lazyload"); + image_count += images.length; + iframe_count += iframes.length; + rocketlazy_count += rocket_lazy.length; + if (is_image) { + image_count += 1; + } + if (is_iframe) { + iframe_count += 1; + } + } + }); + if (image_count > 0 || iframe_count > 0 || rocketlazy_count > 0) { + lazyLoadInstance.update(); + } + }); + var b = document.getElementsByTagName("body")[0]; + var config = { + childList: !0, + subtree: !0, + }; + observer.observe(b, config); + } + }, + !1 + ); - -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); + </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 + async + src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@17.7.0/dist/lazyload.min.js" + ></script> - -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%; + <style> + button.code-button { + min-width: 82px !important; } - .card-columns { - column-count: 2; - column-gap: 1.25rem; + .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 { - 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; + margin-bottom: 0.75rem; } - #yommi_wrapper { - max-width: 80%; + .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; } - } - .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); + .loader-line-wrap:nth-child(1) { + animation-delay: -50ms; } - 100% { - transform: rotate(360deg); + + .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"> + </style> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.1/font/bootstrap-icons.css"> {% endblock %}