diff --git a/info.json b/info.json index 024c50f..967652a 100755 --- a/info.json +++ b/info.json @@ -1,10 +1,10 @@ { - "version": "0.3.1.0", - "name": "linkkf-yommi", + "version": "0.0.1.0", + "name": "inflearn", "category_name": "vod", "icon": "", - "developer": "projectdx && persuade", - "description": "linkkf 사이트에서 애니 다운로드", + "developer": "projectdx", + "description": "인프런 강좌 사이트 에서 강좌 다운로드", "home": "https://github.com/projectdx75/linkkf-yommi", "more": "", "category": "vod" diff --git a/logic.py b/logic.py index 097e23a..48308ba 100755 --- a/logic.py +++ b/logic.py @@ -30,7 +30,7 @@ logger = get_logger(package_name) class Logic(object): db_default = { - "inflearn_url": "https://www.inflearn.com/", + "inflearn_url": "https://www.inflearn.com", "download_path": os.path.join(path_data, "inflearn"), "inflearn_auto_make_folder": "True", "inflearn_auto_make_season_folder": "True", diff --git a/logic_inflearn.py b/logic_inflearn.py index 4d0887c..ac0280f 100755 --- a/logic_inflearn.py +++ b/logic_inflearn.py @@ -30,6 +30,7 @@ for package in packages: import requests from lxml import html, etree from bs4 import BeautifulSoup +from urllib import parse # import snoop # from snoop import spy @@ -43,7 +44,7 @@ from framework.logger import get_logger # 패키지 # from .plugin import package_name, logger # from anime_downloader.logic_ohli24 import ModelOhli24Item -from .model import ModelSetting, ModelLinkkf, ModelLinkkfProgram +from .model import ModelSetting, ModelInflearn, ModelInflearnProgram from .logic_queue import LogicQueue ######################################################### @@ -62,6 +63,7 @@ class LogicInflearn(object): session = None referer = None current_data = None + season = "1" @staticmethod def get_html(url): @@ -182,7 +184,7 @@ class LogicInflearn(object): ret = {} if LogicInflearn.current_data is not None: program = ( - db.session.query(ModelLinkkfProgram) + db.session.query(ModelInflearnProgram) .filter_by(programcode=LogicInflearn.current_data["code"]) .first() ) @@ -219,7 +221,7 @@ class LogicInflearn(object): season = int(new_season) if LogicInflearn.current_data is not None: program = ( - db.session.query(ModelLinkkfProgram) + db.session.query(ModelInflearnProgram) .filter_by(programcode=LogicInflearn.current_data["code"]) .first() ) @@ -292,9 +294,9 @@ class LogicInflearn(object): return ret @staticmethod - def get_airing_info(): + def get_lecture_list(): try: - url = f"{ModelSetting.get('inflearn_url')}/airing" + url = f"{ModelSetting.get('inflearn_url')}/curation/latest?page=1" html_content = LogicInflearn.get_html(url) download_path = ModelSetting.get("download_path") tree = html.fromstring(html_content) @@ -386,45 +388,66 @@ class LogicInflearn(object): logger.error(traceback.format_exc()) @staticmethod - def get_anime_list_info(cate, page): + def get_lecture_list_info(cate, page): try: - if cate == "ing": - url = f"{ModelSetting.get('inflearn_url')}/airing/page/{page}" - elif cate == "complete": - url = f"{ModelSetting.get('inflearn_url')}/anime-list/page/{page}" - logger.debug(f"get_anime_list_info():url >> {url}") + url = "" + if cate == "recent": + # url = f"{ModelSetting.get('inflearn_url')}/curation/latest?page={page}&order={cate}" + url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + elif cate == "rating": + url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + elif cate == "popular": + url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + elif cate == "seq": + url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + logger.debug(f"get_lecture_list_info():url >> {url}") html_content = LogicInflearn.get_html(url) - download_path = ModelSetting.get("download_path") + # logger.debug("html_content: %s", html_content) + tree = html.fromstring(html_content) - tmp_items = tree.xpath('//div[@class="item"]') - # logger.info('tmp_items:::', tmp_items) + tmp_items = tree.xpath( + '//div[contains(@class, "courses_card_list_body")]/div' + ) + # logger.info("tmp_items::: %s", tmp_items) - data = {"ret": "success", "page": page} - - data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] - data["episode_count"] = len(tmp_items) - data["episode"] = [] + data = { + "ret": "success", + "page": page, + "total_page": 100, + "episode_count": len(tmp_items), + "episode": [], + } for item in tmp_items: entity = {} entity["link"] = item.xpath(".//a/@href")[0] - entity["code"] = re.search(r"[0-9]+", entity["link"]).group() - entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ + entity["code"] = entity["link"].split("/")[-1] + entity["_code"] = item.xpath("/div/@data-productid") + # logger.debug(item) + # entity["code"] = 1 + entity["title"] = item.xpath('.//p[@class="course_title"]/text()')[ 0 ].strip() - entity["image_link"] = item.xpath( - './/img[@class="photo"]/@data-lazy-src' - )[0] - entity["chapter"] = item.xpath(".//a/button/span//text()")[0] + entity["teacher"] = item.xpath('.//div[@class="instructor"]/text()')[ + 0 + ].strip() + # entity["price"] = item.xpath( + # './/div[@class="price"]//span[@class="pay_price"]/text()' + # )[0].strip() + entity["price"] = item.xpath('.//div[@class="price"]/text()') + entity["image_link"] = item.xpath('.//img[@class="swiper-lazy"]/@src') + entity["chapter"] = entity["price"] + # entity["chapter"] = item.xpath(".//a/button/span//text()")[0] # logger.info('entity:::', entity['title']) data["episode"].append(entity) - json_file_path = os.path.join(download_path, "airing_list.json") - logger.debug("json_file_path:: %s", json_file_path) + # json_file_path = os.path.join(download_path, "airing_list.json") + # logger.debug("json_file_path:: %s", json_file_path) - with open(json_file_path, "w") as outfile: - json.dump(data, outfile) + # with open(json_file_path, "w") as outfile: + # json.dump(data, outfile) + # logger.debug("data:: %s", data) return data @@ -432,6 +455,73 @@ class LogicInflearn(object): logger.error("Exception:%s", e) logger.error(traceback.format_exc()) + # @staticmethod + # def get_lecture_list_info(cate, page): + # try: + # url = "" + # if cate == "recent": + # # url = f"{ModelSetting.get('inflearn_url')}/curation/latest?page={page}&order={cate}" + # url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + # elif cate == "rating": + # url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + # elif cate == "popular": + # url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + # elif cate == "seq": + # url = f"{ModelSetting.get('inflearn_url')}/courses?page={page}&order={cate}" + # logger.debug(f"get_lecture_list_info():url >> {url}") + # + # html_content = LogicInflearn.get_html(url) + # # logger.debug("html_content: %s", html_content) + # + # tree = html.fromstring(html_content) + # tmp_items = tree.xpath( + # '//ul[@class="tag-courses__list e-tag-courses__list"]/li' + # ) + # # logger.info("tmp_items::: %s", tmp_items) + # + # data = { + # "ret": "success", + # "page": page, + # "total_page": 100, + # "episode_count": len(tmp_items), + # "episode": [], + # } + # + # for item in tmp_items: + # entity = {} + # entity["link"] = item.xpath(".//a/@href")[0] + # entity["code"] = entity["link"].split("/")[-1] + # entity["_code"] = item.attrib["data-id"] + # # logger.debug(item) + # # entity["code"] = 1 + # entity["title"] = item.xpath('.//div[@class="info__basic"]//h3/text()')[ + # 0 + # ].strip() + # entity["teacher"] = item.xpath( + # './/div[@class="info__basic"]//h4/text()' + # )[0].strip() + # entity["price"] = item.xpath( + # './/div[@class="course-card__price"]/dd/text()' + # )[0].strip() + # entity["image_link"] = item.xpath('.//img[@class="swiper-lazy"]/@src') + # entity["chapter"] = entity["price"] + # # entity["chapter"] = item.xpath(".//a/button/span//text()")[0] + # # logger.info('entity:::', entity['title']) + # data["episode"].append(entity) + # + # # json_file_path = os.path.join(download_path, "airing_list.json") + # # logger.debug("json_file_path:: %s", json_file_path) + # + # # with open(json_file_path, "w") as outfile: + # # json.dump(data, outfile) + # # logger.debug("data:: %s", data) + # + # return data + # + # except Exception as e: + # logger.error("Exception:%s", e) + # logger.error(traceback.format_exc()) + @staticmethod def get_screen_movie_info(page): try: @@ -473,246 +563,168 @@ class LogicInflearn(object): logger.error("Exception:%s", e) logger.error(traceback.format_exc()) - @staticmethod - def get_complete_anilist_info(page): - try: - url = f"{ModelSetting.get('inflearn_url')}/anime-list/page/{page}" - - html_content = LogicInflearn.get_html(url) - download_path = ModelSetting.get("download_path") - tree = html.fromstring(html_content) - tmp_items = tree.xpath('//div[@class="item"]') - # logger.info('tmp_items:::', tmp_items) - - data = {"ret": "success", "page": page} - - data["episode_count"] = len(tmp_items) - data["episode"] = [] - - if tree.xpath('//*[@id="wp_page"]//text()'): - data["total_page"] = tree.xpath('//*[@id="wp_page"]//text()')[-1] - else: - data["total_page"] = 0 - - for item in tmp_items: - entity = {} - entity["link"] = item.xpath(".//a/@href")[0] - entity["code"] = re.search(r"[0-9]+", entity["link"]).group() - entity["title"] = item.xpath('.//span[@class="name-film"]//text()')[ - 0 - ].strip() - entity["image_link"] = item.xpath( - './/img[@class="photo"]/@data-lazy-src' - )[0] - # logger.info('entity:::', entity['title']) - data["episode"].append(entity) - - json_file_path = os.path.join(download_path, "airing_list.json") - logger.debug("json_file_path:: %s", json_file_path) - - with open(json_file_path, "w") as outfile: - json.dump(data, outfile) - - return data - - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - @staticmethod def get_title_info(code): try: - if ( - LogicInflearn.current_data is not None - and LogicInflearn.current_data["code"] == code - and LogicInflearn.current_data["ret"] - ): - return LogicInflearn.current_data - url = "%s/%s" % (ModelSetting.get("inflearn_url"), code) - # logger.info(url) + # if ( + # LogicInflearn.current_data is not None + # and LogicInflearn.current_data["code"] == code + # and LogicInflearn.current_data["ret"] + # ): + # return LogicInflearn.current_data + url = "%s/course/%s" % (ModelSetting.get("inflearn_url"), parse.quote(code)) + logger.info(url) html_content = LogicInflearn.get_html(url) sys.setrecursionlimit(10**7) # logger.info(html_content) - tree = html.fromstring(html_content) + # tree = html.fromstring(html_content) # tree = etree.fromstring( # html_content, parser=etree.XMLParser(huge_tree=True) # ) # tree1 = BeautifulSoup(html_content, "lxml") - - soup = BeautifulSoup(html_content, "html.parser") # tree = etree.HTML(str(soup)) # logger.info(tree) - data = {"code": code, "ret": False} - # //*[@id="body"]/div/div[1]/article/center/strong - # tmp = tree.xpath('/html/body/div[2]/div/div/article/center/strong' - # )[0].text_content().strip().encode('utf8') - # tmp = tree.xpath('//*[@id="body"]/div/div[1]/article/center/strong')[0].text_content().strip() - # logger.info('tmp::>', tree.xpath('//div[@class="hrecipe"]/article/center/strong')) - # tmp1 = tree.xpath("//div[contains(@id, 'related')]/ul/a") - # tmp = tree1.find_element(By.Xpath, "//ul/a") - tmp = soup.select("ul > a") + + soup = BeautifulSoup(html_content, "html.parser") + # logger.debug(soup.select_one("div.cd-header__thumbnail-cover")) + data["poster_url"] = soup.select_one( + "div.cd-header__thumbnail-cover div img" + )["src"] + data["title"] = soup.select_one("div.cd-header__title").text + main_title = soup.select_one("div.cd-header__title").text + # data["item_id"] = soup.select_one('meta[property="dable:item_id"]')[ + # "content" + # ] + # item_id = data["item_id"] + data["save_folder"] = data["title"] + data["season"] = "1" + + # tmp = soup.select("ul > a") # logger.debug(f"tmp1 size:=> {str(len(tmp))}") + curriculum_content = soup.find_all("a", {"class": "cd-accordion__unit"}) + preview_path = [] + for i, elem in enumerate(curriculum_content): + # print(elem) + preview_path.append(elem["href"]) + # print(f"{i}. {elem['href']}") - 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() + # 미리보기 가능 1번 동영상 뷰 페이지로 이동 + # self.getVideoInfo(preview_path[0]) + base_url = "https://www.inflearn.com" + url = base_url + parse.quote(preview_path[0]) + logger.debug(f"url::::: {url}") - # print(tmp) - # logger.info(tmp) - match = re.compile(r"(?P\d+)기").search(tmp) - if match: - data["season"] = match.group("season") - else: - data["season"] = "1" + resData = requests.get(url, timeout=20) - # replace_str = f'({data["season"]}기)' - # logger.info(replace_str) - data["_id"] = str(code) - data["title"] = tmp.replace(data["season"] + "기", "").strip() - data["title"] = data["title"].replace("()", "").strip() - data["title"] = ( - Util.change_text_for_use_filename(data["title"]) - .replace("OVA", "") - .strip() - ) - # logger.info(f"title:: {data['title']}") - try: - # data['poster_url'] = tree.xpath( - # '//*[@id="body"]/div/div/div[1]/center/img' - # )[0].attrib['data-src'] + if resData.url != url: + # redirect occurred; likely symbol doesn't exist or cannot be found. + raise requests.TooManyRedirects() - data["poster_url"] = tree.xpath( - '//*[@id="body"]/div/div[1]/div[1]/center/img' - )[0].attrib["data-lazy-src"] - data["detail"] = [ - { - "info": tree.xpath("/html/body/div[2]/div/div[1]/div[1]")[0] - .text_content() - .strip() - } - ] - except Exception as e: - data["detail"] = [{"정보없음": ""}] - data["poster_url"] = None + resData.raise_for_status() - data["rate"] = tree.xpath('span[@class="tag-score"]') - # tag_score = tree.xpath('//span[@class="taq-score"]').text_content().strip() - tag_score = tree.xpath('//span[@class="taq-score"]')[0].text_content() - # logger.debug(tag_score) - tag_count = ( - tree.xpath('//span[contains(@class, "taq-count")]')[0] - .text_content() - .strip() - ) - data_rate = tree.xpath('//div[@class="rating"]/div/@data-rate') - # logger.debug("data_rate::> %s", data_rate) - # tmp = tree.xpath('//*[@id="relatedpost"]/ul/li') - # tmp = tree.xpath('//article/a') - # 수정된 - # tmp = tree.xpath("//ul/a") - tmp = soup.select("ul > a") + # soup = BeautifulSoup(resData.text, "html.parser") + soup = BeautifulSoup(resData.text, "html.parser") - # logger.debug(f"tmp size:=> {str(len(tmp))}") - # logger.info(tmp) - if tmp is not None: - data["episode_count"] = str(len(tmp)) - else: - data["episode_count"] = "0" + items = soup.find_all("div", attrs={"class": "unit-el"}) + # print(len(items)) + lecture_list = [] - data["episode"] = [] - # tags = tree.xpath( - # '//*[@id="syno-nsc-ext-gen3"]/article/div[1]/article/a') - # tags = tree.xpath("//ul/a") - tags = soup.select("ul > a") + # create xlsx file + # wb = Workbook() + # ws = wb.active # create xlsx sheet + # ws.append( + # ["title", "data_id", "run_time", "api_url", "file_name", "hlsUrl"] + # ) - # 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']}") + temp = [] + # print(type(items)) program = ( - db.session.query(ModelLinkkfProgram).filter_by(programcode=code).first() + db.session.query(ModelInflearnProgram) + .filter_by(programcode=code) + .first() ) if program is None: - program = ModelLinkkfProgram(data) + program = ModelInflearnProgram(data) db.session.add(program) db.session.commit() else: data["save_folder"] = program.save_folder data["season"] = program.season - idx = 1 - for t in tags: - entity = { - "_id": data["code"], - "program_code": data["code"], - "program_title": data["title"], - "save_folder": Util.change_text_for_use_filename( - data["save_folder"] - ), - "title": t.text.strip(), - # "title": t.text_content().strip(), - } - # entity['code'] = re1.search(t.attrib['href']).group('code') + for idx, item in enumerate(items): + # + temp1 = {} + print("idx::", idx) + data_id = item["data-id"] - # logger.debug(f"title ::>{entity['title']}") + run_time = "" + title = item.find("div", attrs={"class": "title"}).get_text() + if item.find("span", {"class": "runtime"}) is not None: + run_time = item.find("span", {"class": "runtime"}).get_text() + api_url = f"{base_url}/api/course/{code}/lecture/{data_id}" - # 고유id임을 알수 없는 말도 안됨.. - # 에피소드 코드가 고유해야 상태값 갱신이 제대로 된 값에 넣어짐 - p = re.compile(r"([0-9]+)화?") - m_obj = p.match(entity["title"]) - # logger.info(m_obj.group()) - # entity['code'] = data['code'] + '_' +str(idx) + temp1["season"] = "1" + LogicInflearn.season = "1" + # logger.debug(api_url) + m3u8_info = LogicInflearn.getM3u8_info( + api_url, LogicInflearn.season, idx + ) + # print(api_url) + # print('type::::', type(m3u8_url)) + logger.debug(m3u8_info) + # ws.append( + # [ + # title, + # data_id, + # run_time, + # api_url, + # m3u8_info["name"], + # m3u8_info["hlsUrl"], + # ] + # ) - 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"] + # temp.append(title, data_id, run_time, api_url,m3u8_info['name'], m3u8_info['hlsUrl']) + # temp1['title'] = title + temp1["save_folder"] = Util.change_text_for_use_filename( + data["save_folder"] + ) - # logger.info('episode_code', episode_code) - # entity["url"] = t.attrib["href"] - entity["url"] = t["href"] - entity["season"] = data["season"] + # logger.debug(temp1["save_folder"]) - # 저장경로 저장 tmp_save_path = ModelSetting.get("download_path") if ModelSetting.get("auto_make_folder") == "True": - program_path = os.path.join(tmp_save_path, entity["save_folder"]) - entity["save_path"] = program_path + program_path = os.path.join(tmp_save_path, temp1["save_folder"]) + temp1["save_path"] = program_path if ModelSetting.get("inflearn_auto_make_season_folder"): - entity["save_path"] = os.path.join( - entity["save_path"], "Season %s" % int(entity["season"]) + temp1["save_path"] = os.path.join( + temp1["save_path"], "Season %s" % int(temp1["season"]) ) - data["episode"].append(entity) - entity["image"] = data["poster_url"] + temp1["title"] = title + temp1["data_id"] = data_id + temp1["item_id"] = m3u8_info["data_id"] + temp1["code"] = temp1["item_id"] + temp1["run_time"] = run_time + temp1["api_url"] = api_url + temp1["name"] = m3u8_info["name"] + temp1["filename"] = m3u8_info["filename"] + # logger.debug(temp1["name"]) + # logger.debug(temp1["filename"]) + temp1["url"] = m3u8_info["hlsUrl"] + # temp1["url"] = m3u8_info["hlsUrl"] + temp1["size"] = m3u8_info["size"] + temp.append(temp1) - # entity['title'] = t.text_content().strip().encode('utf8') - - # entity['season'] = data['season'] - # logger.debug(f"save_folder::2> {data['save_folder']}") - entity["filename"] = LogicInflearn.get_filename( - data["save_folder"], data["season"], entity["title"] - ) - idx = idx + 1 - data["ret"] = True + # print(temp) # logger.info('data', data) + # LogicInflearn.current_data = temp + data["episode"] = temp LogicInflearn.current_data = data - - # srt 파일 처리 + # logger.debug(data) return data except Exception as e: @@ -728,6 +740,79 @@ class LogicInflearn(object): data["ret"] = "error" return data + @staticmethod + def getM3u8_info(url, season, idx): + data_id = "" + m3u8_url = "" + name = "" + size = "" + duration = "" + filename = "" + title = "" + res_data = LogicInflearn.getHtml(url, "json").json() + + # logger.info(f"getM3u8_info()::url => {url}") + logger.info(f"getM3u8_info::url => {url}") + # logger.debug("resData::: %s", res_data) + try: + if res_data["course"]["id"] is not None: + data_id = res_data["course"]["id"] + if res_data["course"]["id"] is not None: + title = res_data["course"]["title"] + if res_data["newBOX"]["video"]["name"] is not None: + name = res_data["newBOX"]["video"]["name"] + filename = f"{title}.{name.split('.')[0]}.S{season.zfill(2)}.E{str(idx).zfill(3)}.{name.split('.')[-1]}" + if res_data["newBOX"]["video"]["vod_info"]["hlsUrl"] is not None: + # logger.debug(res_data["newBOX"]["video"]["vod_info"]["hlsUrl"]) + m3u8_url = res_data["newBOX"]["video"]["vod_info"]["hlsUrl"] + size = res_data["newBOX"]["video"]["vod_info"]["size"] + duration = res_data["newBOX"]["video"]["vod_info"]["duration"] + # return { + # "name": name, + # "hlsUrl": m3u8_url, + # "size": size, + # "duration": duration, + # } + except KeyError: + pass + # name = "" + # m3u8_url = "" + # size = None + return { + "data_id": data_id, + "title": title, + "name": name, + "hlsUrl": m3u8_url, + "size": size, + "duration": duration, + "filename": filename, + } + + @staticmethod + def getHtml(url, header): + o = parse.urlparse(url) + # print(o) + tmp_url = f"{o.scheme}://{o.netloc}{parse.quote(o.path)}" + # print('tmp_url', tmp_url) + + # if (header == 'json'): + # resData = requests.get(tmp_url).json() + # else: + # resData = requests.get(tmp_url) + + resData = requests.get(tmp_url) + + # print('resData:::', resData) + if ( + resData.url != tmp_url + ): # redirect occurred; likely symbol doesn't exist or cannot be found. + raise requests.TooManyRedirects() + # print(resHtml.text) + + resData.raise_for_status() + + return resData + @staticmethod def get_filename(maintitle, season, title): try: @@ -763,12 +848,13 @@ class LogicInflearn(object): @staticmethod def get_info_by_code(code): - logger.error("get_info_by_code: %s", code) + logger.info(f"get_info_by_code: {code}") + # logger.debug(LogicInflearn.current_data) try: if LogicInflearn.current_data is not None: for t in LogicInflearn.current_data["episode"]: - if t["code"] == code: + if t["data_id"] == code: return t except Exception as e: logger.error("Exception:%s", e) @@ -790,8 +876,8 @@ class LogicInflearn(object): for code in whitelist_programs: logger.info("auto download start : %s", code) downloaded = ( - db.session.query(ModelLinkkf) - .filter(ModelLinkkf.completed.is_(True)) + db.session.query(ModelInflearn) + .filter(ModelInflearn.completed.is_(True)) .filter_by(programcode=code) .with_for_update() .all() @@ -815,8 +901,39 @@ class LogicInflearn(object): logger.error("Exception:%s", e) logger.error(traceback.format_exc()) + @staticmethod + def download(form): + try: + ret = {} + logger.debug("download call") + # ret = None + # options = { + # "save_path": form["save_path"], + # "filename": form["filename"], + # "format": form["format"], + # } + logger.debug(form) + # Todo: + # tmp = LogicQueue.add_queue(form.to_dict()) + tmp = LogicQueue.add_youtube_queue(form.to_dict()) + + logger.debug("add_queue : tmp >> %s", tmp) + # ret["ret"] = "success" if tmp else "fail" + ret["ret"] = tmp + return ret + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + @staticmethod def reset_db() -> bool: - db.session.query(ModelLinkkf).delete() + db.session.query(ModelInflearn).delete() db.session.commit() return True + + @staticmethod + def get_excel_info(): + _path_dir = "/WD/Users/yommi/Work/fastapi/app/inflearn_xlsx" + file_list = os.listdir(_path_dir) + # logger.debug(file_list) + return file_list diff --git a/logic_queue.py b/logic_queue.py index 7acfc99..5843f1a 100755 --- a/logic_queue.py +++ b/logic_queue.py @@ -16,6 +16,7 @@ from datetime import datetime import requests # third-party +from .api_youtube_dl import APIYoutubeDL # sjva 공용 from framework import db, scheduler, path_data @@ -26,7 +27,7 @@ from framework.logger import get_logger # 패키지 # from .plugin import package_name, logger import system -from .model import ModelSetting, ModelLinkkf +from .model import ModelSetting, ModelInflearn # from plugin import LogicModuleBase, FfmpegQueueEntity, FfmpegQueue, default_route_socketio @@ -75,7 +76,7 @@ class QueueEntity: class LogicQueue(object): download_queue = None download_thread = None - current_ffmpeg_count = 0 + current_youtube_count = 0 def refresh_status(self): self.module_logic.socketio_callback("status", self.as_dict()) @@ -96,22 +97,8 @@ class LogicQueue(object): logger.error("Exception:%s", e) logger.error(traceback.format_exc()) - # @staticmethod - # def download_thread_function(): - # while True: - # try: - # entity = LogicQueue.download_queue.get() - # logger.debug( - # "Queue receive item:%s %s", entity.title_id, entity.episode_id - # ) - # # LogicAni.process(entity) - # LogicQueue.download_queue.task_done() - # except Exception as e: - # logger.error("Exception:%s", e) - # logger.error(traceback.format_exc()) - @staticmethod - def download_thread_function(): + def download_thread_function() -> None: headers = None from . import plugin @@ -119,42 +106,30 @@ class LogicQueue(object): while True: try: while True: - if LogicQueue.current_ffmpeg_count < int( + if LogicQueue.current_youtube_count < int( ModelSetting.get("max_ffmpeg_process_count") ): break # logger.debug(LogicQueue.current_ffmpeg_count) time.sleep(5) entity = LogicQueue.download_queue.get() - - # Todo: 고찰 - # if entity.cancel: - # continue - - # logger.debug( - # "download_thread_function()::entity.info['code'] >> %s", entity - # ) + logger.debug(f"entity:: {entity.info}") if entity is None: continue # db에 해당 에피소드가 존재하는 확인 - db_entity = ModelLinkkf.get_by_inflearn_id(entity.info["code"]) + db_entity = ModelInflearn.get_by_inflearn_id(entity.info["code"]) if db_entity is None: - episode = ModelLinkkf("auto", info=entity.info) + episode = ModelInflearn("auto", info=entity.info) db.session.add(episode) db.session.commit() else: - # episode = ModelLinkkf("auto", info=entity.info) - # query = db.session.query(ModelLinkkf).filter_by(episodecode=entity.info.episodecode).with_for_update().first() pass from .logic_inflearn import LogicInflearn - # entity.url = LogicLinkkfYommi.get_video_url( - # entity.info['code']) - # logger.debug(f"entity.info[url] = {entity.info['url']}") - entity.url = LogicInflearn.get_video_url(entity.info["url"]) + entity.url = entity.info["url"] # logger.info('entity.info: %s', entity.info['url']) logger.debug(f"entity.url: {entity.url}") @@ -163,8 +138,8 @@ class LogicQueue(object): # logger.info('entity: %s', entity.__dict__) # logger.info('entity.url:::> %s', entity.url) - if entity.url[0] is None: - self.ffmpeg_status_kor = "URL실패" + if entity.url is None: + QueueEntity.ffmpeg_status_kor = "URL실패" plugin.socketio_list_refresh() continue @@ -187,8 +162,8 @@ class LogicQueue(object): logger.debug("program path make fail!!") # 파일 존재여부 체크 - if entity.url[1] is not None: - referer = entity.url[1] + if entity.url is not None: + referer = "http://www.inflearn.com" 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': @@ -215,7 +190,7 @@ class LogicQueue(object): # logger.debug(f"referer: {referer}") f = ffmpeg.Ffmpeg( - entity.url[0], + entity.url, entity.info["filename"], plugin_id=entity.entity_id, listener=LogicQueue.ffmpeg_listener, @@ -224,36 +199,37 @@ class LogicQueue(object): call_plugin=package_name, save_path=save_path, headers=headers, + # proxy=f" -correct_ts_overflow 0", ) f.start() - LogicQueue.current_ffmpeg_count += 1 + LogicQueue.current_youtube_count += 1 LogicQueue.download_queue.task_done() # vtt file to srt file - from framework.common.util import write_file, convert_vtt_to_srt - from urllib import parse - - ourls = parse.urlparse(entity.url[1]) - # print(ourls) - # logger.info('ourls:::>', ourls) - base_url = f"{ourls.scheme}://{ourls.netloc}" - # logger.info('base_url:::>', base_url) - - # Todo: 임시 커밋 로직 해결하면 다시 처리 - # if "linkkf.app" in base_url: - # base_url = f"{ourls.scheme}://kfani.me" - - vtt_url = base_url + entity.url[2] - logger.debug(f"srt:url => {vtt_url}") - srt_filepath = os.path.join( - save_path, entity.info["filename"].replace(".mp4", ".ko.srt") - ) - # logger.info('srt_filepath::: %s', srt_filepath) - if entity.url[2] is not None and not os.path.exists(srt_filepath): - vtt_data = requests.get(vtt_url, headers=headers).text - srt_data = convert_vtt_to_srt(vtt_data) - write_file(srt_data, srt_filepath) + # from framework.common.util import write_file, convert_vtt_to_srt + # from urllib import parse + # + # ourls = parse.urlparse(entity.url[1]) + # # print(ourls) + # # logger.info('ourls:::>', ourls) + # base_url = f"{ourls.scheme}://{ourls.netloc}" + # # logger.info('base_url:::>', base_url) + # + # # Todo: 임시 커밋 로직 해결하면 다시 처리 + # # if "linkkf.app" in base_url: + # # base_url = f"{ourls.scheme}://kfani.me" + # + # vtt_url = base_url + entity.url[2] + # logger.debug(f"srt:url => {vtt_url}") + # srt_filepath = os.path.join( + # save_path, entity.info["filename"].replace(".mp4", ".ko.srt") + # ) + # # logger.info('srt_filepath::: %s', srt_filepath) + # if entity.url[2] is not None and not os.path.exists(srt_filepath): + # vtt_data = requests.get(vtt_url, headers=headers).text + # srt_data = convert_vtt_to_srt(vtt_data) + # write_file(srt_data, srt_filepath) except Exception as e: logger.error("Exception:%s", e) @@ -269,7 +245,7 @@ class LogicQueue(object): if arg["type"] == "status_change": if arg["status"] == ffmpeg.Status.DOWNLOADING: episode = ( - db.session.query(ModelLinkkf) + db.session.query(ModelInflearn) .filter_by(episodecode=arg["plugin_id"]) .with_for_update() .first() @@ -285,7 +261,7 @@ class LogicQueue(object): elif arg["type"] == "last": LogicQueue.current_ffmpeg_count += -1 episode = ( - db.session.query(ModelLinkkf) + db.session.query(ModelInflearn) .filter_by(episodecode=arg["plugin_id"]) .with_for_update() .first() @@ -351,6 +327,111 @@ class LogicQueue(object): # logger.error("Exception:%s", e) # logger.error(traceback.format_exc()) # return False + + @staticmethod + def add_youtube_queue(info): + # logger.debug(type(info)) + logger.debug(f"add_youtube_queue():info {info}") + while True: + try: + _temp = info["data"] + + if not isinstance(_temp, dict): + _info = json.loads(_temp.replace("'", '"')) + else: + _info = _temp + + logger.debug(_info) + _info["code"] = _info["data_id"] + # _info["url"] = _info["hlsUrl"] + # _info["filename"] = _info["name"] + # while True: + # # if LogicQueue.current_youtube_count < int( + # # ModelSetting.get("max_ffmpeg_process_count") + # # ): + # if LogicQueue.current_youtube_count < 3: + # logger.debug("break!!!") + # break + # # logger.debug(LogicQueue.current_ffmpeg_count) + # time.sleep(2) + + # entity = LogicQueue.download_queue.get() + # logger.debug(f"entity:: {entity}") + # + # if entity is None: + # continue + + logger.debug("add_queue()::info >> %s", _info) + logger.debug(type(_info)) + + # episode[] code (episode_code) + db_entity = ModelInflearn.get_by_inflearn_id(_info["code"]) + + if db_entity is None: + # entity = QueueEntity.create(_info) + + # download = APIYoutubeDL.download( + # package_name, + # db_entity.code, + # url, + # filename=entity.filename, + # save_path=entity.save_path, + # format_code=entity.format, + # preferredcodec="mp3" if entity.convert_mp3 else None, + # dateafter=date_after, + # playlist="reverse" if entity.playlistreverse else None, + # start=False, + # cookiefile=ModelSetting.get("cookiefile_path"), + # ) + + # LogicQueue.download_queue.put(entity) + download = APIYoutubeDL.download( + package_name, + _info["code"], + _info["url"], + filename=_info["filename"], + save_path=_info["save_path"], + format_code="mp4", + # preferredcodec="mp3" if entity.convert_mp3 else None, + preferredcodec=None, + dateafter=None, + playlist=None, + start=True, + cookiefile=None, + ) + LogicQueue.current_youtube_count += 1 + # download.start() + return "enqueue_db_append" + elif db_entity.status != "completed": + logger.debug(f"db_entity.status: {db_entity.status}") + # entity = QueueEntity.create(_info) + # return "Debugging" + # LogicQueue.download_queue.put(entity) + download = APIYoutubeDL.download( + package_name, + _info["code"], + _info["url"], + filename=_info["filename"], + save_path=_info["save_path"], + format_code="mp4", + # preferredcodec="mp3" if entity.convert_mp3 else None, + preferredcodec=None, + dateafter=None, + playlist=None, + start=True, + cookiefile=None, + ) + download.start() + + logger.debug("add_queue()::enqueue_db_exist") + return "enqueue_db_exist" + else: + return "db_completed" + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + @staticmethod def add_queue(info): try: @@ -358,21 +439,92 @@ class LogicQueue(object): # Todo: # if is_exist(info): # return 'queue_exist' - # logger.debug("add_queue()::info >> %s", info) + # logger.debug("info::", info["_id"]) + # logger.debug() + logger.debug(info) + logger.debug(type(info)) + _temp = info + + _info = json.loads(_temp.replace("'", '"')) + _info["code"] = _info["data_id"] + # _info["url"] = _info["hlsUrl"] + _info["filename"] = _info["name"] + + logger.debug("add_queue()::info >> %s", _info) + logger.debug(type(_info)) # episode[] code (episode_code) - db_entity = ModelLinkkf.get_by_inflearn_id(info["code"]) - # logger.debug("add_queue:: db_entity >> %s", db_entity) + db_entity = ModelInflearn.get_by_inflearn_id(_info["code"]) + logger.debug("add_queue:: db_entity >> %s", db_entity) if db_entity is None: - entity = QueueEntity.create(info) + entity = QueueEntity.create(_info) # logger.debug("add_queue()::entity >> %s", entity) LogicQueue.download_queue.put(entity) return "enqueue_db_append" elif db_entity.status != "completed": - entity = QueueEntity.create(info) + logger.debug(f"db_entity.status: {db_entity.status}") + entity = QueueEntity.create(_info) + # return "Debugging" + LogicQueue.download_queue.put(entity) + + logger.debug("add_queue()::enqueue_db_exist") + return "enqueue_db_exist" + else: + return "db_completed" + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + return False + + @staticmethod + def add_checked_youtube_queue(info): + + logger.debug(f"add_checked_youtube_queue():info {info}") + + try: + _temp = info["data"] + + if not isinstance(_temp, dict): + _info = json.loads(_temp.replace("'", '"')) + else: + _info = _temp + + logger.debug(_info) + _info["code"] = _info["data_id"] + + logger.debug("add_queue()::info >> %s", _info) + logger.debug(type(_info)) + + # episode[] code (episode_code) + db_entity = ModelInflearn.get_by_inflearn_id(_info["code"]) + logger.debug("add_queue:: db_entity >> %s", db_entity) + + if db_entity is None: + # entity = QueueEntity.create(_info) + entity = APIYoutubeDL.download( + package_name, + _info["code"], + _info["url"], + filename=_info["filename"], + save_path=_info["save_path"], + format_code="mp4", + # preferredcodec="mp3" if entity.convert_mp3 else None, + preferredcodec=None, + dateafter=None, + playlist=None, + start=True, + cookiefile=None, + ) + # logger.debug("add_queue()::entity >> %s", entity) + LogicQueue.download_queue.put(entity) + return "enqueue_db_append" + elif db_entity.status != "completed": + logger.debug(f"db_entity.status: {db_entity.status}") + entity = QueueEntity.create(_info) # return "Debugging" LogicQueue.download_queue.put(entity) diff --git a/model.py b/model.py index f58ea0a..276489c 100755 --- a/model.py +++ b/model.py @@ -61,7 +61,7 @@ class ModelSetting(db.Model): # logger.error(traceback.format_exc()) -class ModelLinkkfProgram(db.Model): +class ModelInflearnProgram(db.Model): __tablename__ = "plugin_%s_program" % package_name __table_args__ = {"mysql_collate": "utf8_general_ci"} __bind_key__ = package_name @@ -79,6 +79,7 @@ class ModelLinkkfProgram(db.Model): self.created_time = datetime.now() self.programcode = data["code"] self.save_folder = data["title"] + logger.debug(f"self.save_folder:: {self.save_folder}") self.season = data["season"] def __repr__(self): @@ -96,7 +97,7 @@ class ModelLinkkfProgram(db.Model): self.season = data["season"] -class ModelLinkkf(db.Model): +class ModelInflearn(db.Model): __tablename__ = "plugin_%s_auto_episode" % package_name __table_args__ = {"mysql_collate": "utf8_general_ci"} __bind_key__ = package_name @@ -160,7 +161,7 @@ class ModelLinkkf(db.Model): def set_info(self, data): self.contents_json = data - self.programcode = data["program_code"] + self.programcode = data["item_id"] self.episodecode = data["code"] @classmethod diff --git a/plugin.py b/plugin.py index b757656..0d03a23 100755 --- a/plugin.py +++ b/plugin.py @@ -34,7 +34,7 @@ from system.logic import SystemLogic from .logic import Logic from .logic_inflearn import LogicInflearn from .logic_queue import QueueEntity, LogicQueue -from .model import ModelSetting, ModelLinkkf +from .model import ModelSetting, ModelInflearn # blueprint = Blueprint(package_name, # package_name, @@ -73,6 +73,7 @@ menu = { ["request", "요청"], ["category", "검색"], ["queue", "큐"], + ["excel", "액셀"], ["list", "목록"], ["log", "로그"], ], @@ -118,7 +119,7 @@ def home(): return redirect("/%s/category" % package_name) -@blueprint.route("/") +@blueprint.route("/", methods=["GET", "POST"]) @login_required def detail(sub): # arg = { @@ -134,19 +135,49 @@ def detail(sub): arg["is_running"] = str(scheduler.is_running(package_name)) arg["template_name"] = "%s_%s" % (package_name, sub) return render_template("%s_%s.html" % (package_name, sub), arg=arg) - elif sub in ["request", "queue", "list"]: + elif sub == "request": setting_list = db.session.query(ModelSetting).all() arg = Util.db_list_to_dict(setting_list) arg["package_name"] = package_name + logger.debug(f"current_code: {LogicInflearn.current_data}") arg["current_code"] = ( LogicInflearn.current_data["code"] if LogicInflearn.current_data is not None else "" ) - arg["template_name"] = "%s_%s" % (package_name, sub) + + # arg["template_name"] = "%s_%s" % (package_name, sub) + arg["template_name"] = f"{package_name}_{sub}" + + logger.debug(f"arg: {arg}") + return render_template("%s_%s.html" % (package_name, sub), arg=arg) + + elif sub in ["queue", "list"]: + setting_list = db.session.query(ModelSetting).all() + arg = Util.db_list_to_dict(setting_list) + arg["package_name"] = package_name + logger.debug(f"current_code: {LogicInflearn.current_data}") + arg["current_code"] = ( + LogicInflearn.current_data["code"] + if LogicInflearn.current_data is not None + else "" + ) + + # arg["template_name"] = "%s_%s" % (package_name, sub) + arg["template_name"] = f"{package_name}_{sub}" + + logger.debug(f"arg: \n{arg}") return render_template("%s_%s.html" % (package_name, sub), arg=arg) elif sub == "category": + setting_list = db.session.query(ModelSetting).all() + arg = Util.db_list_to_dict(setting_list) + arg["package_name"] = package_name + arg["template_name"] = "%s_%s" % (package_name, sub) + # logger.debug(f"arg:: {arg}") + return render_template("%s_%s.html" % (package_name, sub), arg=arg) + elif sub == "excel": + setting_list = db.session.query(ModelSetting).all() arg = Util.db_list_to_dict(setting_list) arg["package_name"] = package_name @@ -186,9 +217,10 @@ def ajax(sub): code = request.form["code"] data = LogicInflearn.get_title_info(code) # logger.debug("data::> %s", data) - # current_data = data + LogicInflearn.current_data = data # return jsonify(data) + # logger.debug(data) if data["ret"] == "error": return jsonify(data) else: @@ -200,6 +232,17 @@ def ajax(sub): # logger.error("Exception:%s", e) # logger.error(traceback.format_exc()) return jsonify({"ret": "error", "log": e}) + elif sub == "excel_list": + try: + data = LogicInflearn.get_excel_info() + logger.debug(f"data[]: {data}") + return jsonify({"ret": "success", "data": data}) + + except Exception as e: + logger.error("Exception:%s", e) + logger.error(traceback.format_exc()) + return jsonify({"ret": "error", "log": e}) + elif sub == "search": try: query = request.form["query"] @@ -210,23 +253,26 @@ def ajax(sub): except Exception as e: logger.error("Exception:%s", e) logger.error(traceback.format_exc()) - elif sub == "anime_list": + # elif sub == "anime_list": + # try: + # # logger.debug(request.form) + # page = request.form["page"] + # cate = request.form["type"] + # # data = LogicLinkkfYommi.get_screen_movie_info(page) + # data = LogicInflearn.get_lecture_list_info(cate, page) + # dummy_data = {"ret": "success", "data": data} + # return jsonify(data) + # except Exception as e: + # logger.error("Exception:%s", e) + # logger.error(traceback.format_exc()) + elif sub == "get_lecture_list": try: # logger.debug(request.form) page = request.form["page"] cate = request.form["type"] - # data = LogicLinkkfYommi.get_screen_movie_info(page) - data = LogicInflearn.get_anime_list_info(cate, page) - dummy_data = {"ret": "success", "data": data} - return jsonify(data) - except Exception as e: - logger.error("Exception:%s", e) - logger.error(traceback.format_exc()) - elif sub == "airing_list": - try: - data = LogicInflearn.get_airing_info() + data = LogicInflearn.get_lecture_list_info(cate, page) # dummy_data = {"ret": "success", "data": data} - logger.debug(f"airing_list:: {data}") + logger.debug(f"lecture_list:: {data}") return jsonify(data) except Exception as e: logger.error("Exception:%s", e) @@ -285,6 +331,10 @@ def ajax(sub): except Exception as e: logger.error("Exception:%s", e) logger.error(traceback.format_exc()) + elif sub == "add_download": + # logger.debug("add_download:: %s", request.form) + ret = LogicInflearn.download(request.form) + return jsonify(ret) elif sub == "add_queue": try: ret = {} @@ -321,16 +371,20 @@ def ajax(sub): return jsonify(ret) elif sub == "add_queue_checked_list": ret = {} + infos = {"code": request.form["lecture_id"]} try: from .logic_queue import LogicQueue code = request.form["code"] + logger.debug("code:: %s", code) code_list = code.split(",") + logger.debug(f"code_list[]: {code_list}") count = 0 for c in code_list: info = LogicInflearn.get_info_by_code(c) if info is not None: - tmp = LogicQueue.add_queue(info) + infos["data"] = info + tmp = LogicQueue.add_checked_youtube_queue(infos) count += 1 ret["ret"] = "success" ret["log"] = str(count) @@ -358,13 +412,13 @@ def ajax(sub): # logger.info("db :::>", ModelLinkkf.web_list(request)) data = [{}] dummy_data = {"ret": "success", "method": "web_list", "data": data} - return jsonify(ModelLinkkf.web_list(request)) + return jsonify(ModelInflearn.web_list(request)) # return jsonify(dummy_data) except Exception as e: logger.error("Exception: %s", e) logger.error(traceback.format_exc()) elif sub == "db_remove": - return jsonify(ModelLinkkf.delete_by_id(request.form["id"])) + return jsonify(ModelInflearn.delete_by_id(request.form["id"])) # reset_db elif sub == "reset_db": ret = {} diff --git a/static/css/inflearn_excel.css b/static/css/inflearn_excel.css new file mode 100644 index 0000000..2599583 --- /dev/null +++ b/static/css/inflearn_excel.css @@ -0,0 +1,201 @@ +button.code-button { + min-width: 82px !important; +} +.tooltip { + position: relative; + display: block; +} + +[data-tooltip-text]:hover { + position: relative; +} + +[data-tooltip-text]:after { + -webkit-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + + background-color: rgba(0, 0, 0, 0.8); + + -webkit-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + -moz-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + color: #ffffff; + font-size: 12px; + margin-bottom: 10px; + padding: 7px 12px; + position: absolute; + width: auto; + min-width: 50px; + max-width: 300px; + word-wrap: break-word; + + z-index: 9999; + + opacity: 0; + left: -9999px; + top: 90%; + + content: attr(data-tooltip-text); +} + +[data-tooltip-text]:hover:after { + top: 230%; + left: 0; + opacity: 1; +} +[data-tooltip-text]:hover { + position: relative; +} + +[data-tooltip-text]:after { + -webkit-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + -moz-transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + transition: bottom 0.3s ease-in-out, opacity 0.3s ease-in-out; + + background-color: rgba(0, 0, 0, 0.8); + + -webkit-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + -moz-box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + box-shadow: 0px 0px 3px 1px rgba(50, 50, 50, 0.4); + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + color: #ffffff; + font-size: 12px; + margin-bottom: 10px; + padding: 7px 12px; + position: absolute; + width: auto; + min-width: 50px; + max-width: 300px; + word-wrap: break-word; + + z-index: 9999; + + opacity: 0; + left: -9999px; + top: -210% !important; + + content: attr(data-tooltip-text); +} + +[data-tooltip-text]:hover:after { + top: 130%; + left: 0; + opacity: 1; +} + +#airing_list { + display: none; +} + +.cut-text { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; +} + +@media (min-width: 576px) { + .container { + max-width: 100%; + } +} + +#screen_movie_list { + margin-top: 10px; +} +/* .spinner {*/ +/* width: 40px;*/ +/* height: 40px;*/ +/* background-color: #333;*/ + +/* margin: 100px auto;*/ +/* -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;*/ +/* animation: sk-rotateplane 1.2s infinite ease-in-out;*/ +/*}*/ + +/*@-webkit-keyframes sk-rotateplane {*/ +/* 0% { -webkit-transform: perspective(120px) }*/ +/* 50% { -webkit-transform: perspective(120px) rotateY(180deg) }*/ +/* 100% { -webkit-transform: perspective(120px) rotateY(180deg) rotateX(180deg) }*/ +/*}*/ + +/*@keyframes sk-rotateplane {*/ +/* 0% {*/ +/* transform: perspective(120px) rotateX(0deg) rotateY(0deg);*/ +/* -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg)*/ +/* } 50% {*/ +/* transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);*/ +/* -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg)*/ +/* } 100% {*/ +/* transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);*/ +/* -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);*/ +/* }*/ + +/*}*/ +.spinner { + width: 40px; + height: 40px; + + position: relative; + margin: 100px auto; +} + +.double-bounce1, +.double-bounce2 { + width: 100%; + height: 100%; + border-radius: 50%; + background-color: #333; + opacity: 0.6; + position: absolute; + top: 0; + left: 0; + + -webkit-animation: sk-bounce 2s infinite ease-in-out; + animation: sk-bounce 2s infinite ease-in-out; +} + +.double-bounce2 { + -webkit-animation-delay: -1s; + animation-delay: -1s; +} + +@-webkit-keyframes sk-bounce { + 0%, + 100% { + -webkit-transform: scale(0); + } + 50% { + -webkit-transform: scale(1); + } +} + +@keyframes sk-bounce { + 0%, + 100% { + transform: scale(0); + -webkit-transform: scale(0); + } + 50% { + transform: scale(1); + -webkit-transform: scale(1); + } +} + +.badge-on-image { + position: absolute; + top: 2px; + /*bottom: 2px; !* position where you want it *!*/ + right: 2px; + padding: 5px 12px; +} diff --git a/static/js/inflearn_category.js b/static/js/inflearn_category.js index 93fd496..b4035e3 100644 --- a/static/js/inflearn_category.js +++ b/static/js/inflearn_category.js @@ -9,324 +9,257 @@ let next_page = Number; let current_cate = ""; let total_page = ""; -$("#anime_category #ing").on("click", function () { - let spinner = document.getElementById("spinner"); - spinner.style.display = "block"; - current_cate = "ing"; - get_anime_list(1, "ing"); +$("#anime_category #recent").on("click", function () { + let spinner = document.getElementById("spinner"); + spinner.style.display = "block"; + current_cate = "recent"; + get_lecture_list(1, "recent"); }); -$("#anime_category #movie").on("click", function () { - current_cate = "movie"; - get_anime_screen_movie(1); +$("#anime_category #rating").on("click", function () { + current_cate = "rating"; + get_lecture_list(1, "rating"); }); -$("#anime_category #complete_anilist").on("click", function () { - current_cate = "complete"; - get_complete_anilist(1); +$("#anime_category #popular").on("click", function () { + current_cate = "popular"; + get_lecture_list(1, "popular"); +}); +$("#anime_category #seq").on("click", function () { + current_cate = "seq"; + get_lecture_list(1, "seq"); }); $("body").on("click", "#btn_search", function (e) { - e.preventDefault(); - let query = $("#input_search").val(); - // console.log(query); + e.preventDefault(); + let query = $("#input_search").val(); + // console.log(query); - if ($("#input_search").val() === "") { - console.log("search keyword nothing"); - return false; - } + if ($("#input_search").val() === "") { + console.log("search keyword nothing"); + return false; + } - $.ajax({ - url: "/" + package_name + "/ajax/search", - type: "POST", - cache: false, - data: { query: query }, - // dataType: "json", - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - success: function (ret) { - if (ret.ret) { - make_screen_movie_list(ret); - } else { - $.notify("분석 실패
" + ret.log, { - type: "warning", - }); - } - }, - }); + $.ajax({ + url: "/" + package_name + "/ajax/search", + type: "POST", + cache: false, + data: {query: query}, + // dataType: "json", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + success: function (ret) { + if (ret.ret) { + make_screen_movie_list(ret); + } else { + $.notify("분석 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); }); -const get_airing_list = () => { - let spinner = document.getElementById("spinner"); - spinner.style.display = "block"; - $.ajax({ - url: "/" + package_name + "/ajax/airing_list", - type: "GET", - cache: false, - dataType: "json", - success: (ret) => { - current_airing_data = ret; - // console.log("ret::>", ret); - if (current_airing_data !== "") { - make_airing_list(ret); - div_visible = true; - spinner.style.display = "none"; - // console.log(div_visible); - } - }, - }); - if (div_visible) { - // {#$('#airing_list').toggle()#} - } -}; +const get_lecture_list = (page, type) => { + let url = ""; + let data = {page: page, type: type}; -const get_anime_list = (page, type) => { - let url = ""; - let data = { page: page, type: type }; + switch (type) { + case "recent": + url = "/" + package_name + "/ajax/get_lecture_list"; + current_cate = "recent"; + break; + case "ranking": + url = "/" + package_name + "/ajax/get_lecture_list"; + current_cate = "ranking"; + break; + case "popular": + url = "/" + package_name + "/ajax/get_lecture_list"; + current_cate = "popular"; + break; + case "seq": + url = "/" + package_name + "/ajax/get_lecture_list"; + current_cate = "seq"; + break; + default: + break; + } - switch (type) { - case "ing": - url = "/" + package_name + "/ajax/anime_list"; - current_cate = "ing"; - break; - case "movie": - url = "/" + package_name + "/ajax/screen_movie_list"; - current_cate = "movie"; - break; - case "complete": - url = "/" + package_name + "/ajax/screen_movie_list"; - current_cate = "complete"; - break; - default: - break; - } + $.ajax({ + url: url, + type: "POST", + data: data, + cache: false, + dataType: "json", + success: (ret) => { + current_screen_movie_data = ret; + total_page = ret.total_page; + console.log("ret::>", ret); - $.ajax({ - url: url, - type: "POST", - data: data, - cache: false, - dataType: "json", - success: (ret) => { - current_screen_movie_data = ret; - total_page = ret.total_page; - // console.log("ret::>", ret); - - if (current_screen_movie_data !== "") { - make_screen_movie_list(ret, page); - div_visible = true; - // console.log(div_visible); - // $("img.lazyload").lazyload({ - // threshold : 400, - // effect : "fadeIn", - // }); - } - next_page = page + 1; - }, - }); -}; - -const get_anime_screen_movie = (page) => { - let data = { page: page }; - $.ajax({ - url: "/" + package_name + "/ajax/screen_movie_list", - type: "POST", - data: data, - cache: false, - dataType: "json", - success: (ret) => { - current_screen_movie_data = ret; - total_page = ret.total_page; - // console.log("ret::>", ret); - - if (current_screen_movie_data !== "") { - make_screen_movie_list(ret, page); - $("img.lazyload").lazyload({ - threshold: 100, - effect: "fadeIn", - }); - div_visible = true; - } - next_page = page + 1; - }, - }); -}; - -const get_complete_anilist = (page) => { - let data = { page: page }; - $.ajax({ - url: "/" + package_name + "/ajax/complete_anilist", - type: "POST", - data: data, - cache: false, - dataType: "json", - success: (ret) => { - current_screen_movie_data = ret; - console.log("get_complete_anilist():ret >", ret); - total_page = ret.total_page; - - if (current_screen_movie_data !== "") { - make_screen_movie_list(ret, page); - $("img.lazyload").lazyload({ - threshold: 100, - effect: "fadeIn", - }); - div_visible = true; - } - next_page = page + 1; - }, - }); + if (current_screen_movie_data !== "") { + make_screen_movie_list(ret, page); + div_visible = true; + // console.log(div_visible); + // $("img.lazyload").lazyload({ + // threshold : 400, + // effect : "fadeIn", + // }); + } + next_page = page + 1; + }, + }); }; // console.log(div_visible); $(document).on("click", "button.code-button", function (e) { - e.preventDefault(); - // console.log("click"); - // console.log("code to click:" + $(this).data("code")); - document.getElementById("code").value = $(this).data("code"); - $("#code").val($(this).data("code")); - $("#airing_list").toggle(); - code = document.getElementById("code").value; - document.getElementById("analysis_btn").click(); + e.preventDefault(); + // console.log("click"); + // console.log("code to click:" + $(this).data("code")); + document.getElementById("code").value = $(this).data("code"); + $("#code").val($(this).data("code")); + $("#airing_list").toggle(); + code = document.getElementById("code").value; + document.getElementById("analysis_btn").click(); }); $(".code-button").tooltip(); -$("body").on("click", "#analysis_btn", function (e) { - e.preventDefault(); - code = document.getElementById("code").value; - if (document.getElementById("code").value === "") { - console.log("code 값을 입력 해주세요."); - return; - } - - $.ajax({ - url: "/" + package_name + "/ajax/analysis", - type: "POST", - cache: false, - data: { code: code }, - dataType: "json", - success: function (ret) { - if (ret.ret) { - make_program(ret); - } else { - $.notify("분석 실패
" + ret.log, { - type: "warning", - }); - } - }, - }); -}); +// $("body").on("click", "#analysis_btn", function (e) { +// e.preventDefault(); +// code = document.getElementById("code").value; +// if (document.getElementById("code").value === "") { +// console.log("code 값을 입력 해주세요."); +// return; +// } +// +// $.ajax({ +// url: "/" + package_name + "/ajax/analysis", +// type: "POST", +// cache: false, +// data: { code: code }, +// dataType: "json", +// success: function (ret) { +// if (ret.ret) { +// console.log(ret) +// make_program(ret); +// } else { +// $.notify("분석 실패
" + ret.log, { +// type: "warning", +// }); +// } +// }, +// }); +// }); $("body").on("click", "#go_inflearn_btn", function (e) { - e.preventDefault(); - window.open(inflearn_url, "_blank"); + e.preventDefault(); + window.open(inflearn_url, "_blank"); }); function make_airing_list(data) { - let str = ""; - let tmp = ""; + let str = ""; + let tmp = ""; - tmp = - '"; - str += m_hr_black(); - str += - '
'; - for (i in data.episode) { tmp = - '
"; + '"; + str += m_hr_black(); + str += + '
'; + for (i in data.episode) { + tmp = + '
"; - str += tmp; - } - str += "
"; - str += m_hr_black(); + str += tmp; + } + str += "
"; + str += m_hr_black(); - document.getElementById("airing_list").innerHTML = str; - $("img.lazyload").lazyload({ - threshold: 100, - effect: "fadeIn", - }); + document.getElementById("airing_list").innerHTML = str; + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); } function make_screen_movie_list(data, page) { - let str = ""; - let tmp = ""; + let str = ""; + let tmp = ""; - let page_elem = ""; - if (page === undefined) { - } else { - page_elem = '' + page + ""; - } - - str += "
"; - str += - '"; - str += "
"; - str += '
'; - for (let i in data.episode) { - tmp = '
'; - tmp += '
'; - // tmp += '
'; - - // tmp += - // ''; - tmp += - '"; - if (current_cate == "ing") { - tmp += - '' + - data.episode[i].chapter + - ""; + let page_elem = ""; + if (page === undefined) { + } else { + page_elem = '' + page + ""; } - tmp += '
'; - tmp += '
' + data.episode[i].title + "
"; - tmp += - '"; - tmp += - '' + - data.episode[i].title + - ""; - tmp += "
"; - tmp += "
"; - // tmp += "
" - tmp += "
"; - str += tmp; - } - str += "
"; - 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); + str += "
"; + str += + '"; + str += "
"; + str += '
'; + for (let i in data.episode) { + tmp = '
'; + tmp += '
'; + // tmp += '
'; + + // tmp += + // ''; + tmp += + '"; + if (current_cate == "recent") { + tmp += + '' + + data.episode[i].chapter + + ""; + } + tmp += '
'; + tmp += '
' + data.episode[i].title + "
"; + tmp += + '"; + tmp += + '' + + data.episode[i].title + + ""; + tmp += "
"; + tmp += "
"; + // tmp += "
" + tmp += "
"; + str += tmp; } - page++; - } else { - document.getElementById("screen_movie_list").innerHTML = str; - } - $("img.lazyload").lazyload({ - threshold: 100, - effect: "fadeIn", - }); + str += "
"; + str += m_hr_black(); + + if (page > 1) { + const temp = document.createElement("div"); + temp.innerHTML = str; + while (temp.firstChild) { + document.getElementById("screen_movie_list").appendChild(temp.firstChild); + } + page++; + } else { + document.getElementById("screen_movie_list").innerHTML = str; + } + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); } const spinner = document.getElementById("spinner"); @@ -334,371 +267,353 @@ const imagesContainer = document.getElementById("inner_screen_movie"); const infiniteContainer = document.getElementById("screen_movie_list"); const loadNextAnimes = (cate, page) => { - spinner.style.display = "block"; - const data = { type: cate, page: String(page) }; - let url = ""; + spinner.style.display = "block"; + const data = {type: cate, page: String(page)}; + let url = ""; - switch (cate) { - case "ing": - url = "/" + package_name + "/ajax/anime_list"; - current_cate = "ing"; - break; - case "movie": - url = "/" + package_name + "/ajax/screen_movie_list"; - current_cate = "movie"; - break; - case "complete": - url = "/" + package_name + "/ajax/anime_list"; - current_cate = "complete"; - break; - default: - break; - } + url = "/" + package_name + "/ajax/get_lecture_list"; + switch (cate) { + case "recent": - // console.log('fetch_url::>', url) - console.log("cate::>", cate); - console.log("current_cate::>", current_cate); + current_cate = "recent"; + break; + case "ranking": + current_cate = "ranking"; + break; + case "popular": + current_cate = "popular"; + break; + case "seq": + current_cate = "seq"; + break; + default: + break; + } - if (page > total_page) { - console.log("전체 페이지 초과"); - document.getElementById("spinner").style.display = "none"; - return false; - } + // console.log('fetch_url::>', url) + console.log("cate::>", cate); + console.log("current_cate::>", current_cate); - fetch(url, { - method: "POST", - cache: "no-cache", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams(data), - }) - .then((res) => { - document.getElementById("spinner").style.display = "block"; - return res.json(); + if (page > total_page) { + console.log("전체 페이지 초과"); + document.getElementById("spinner").style.display = "none"; + return false; + } + + fetch(url, { + method: "POST", + cache: "no-cache", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(data), }) - .then((response) => { - // console.log("return page:::> ", response.page); - make_screen_movie_list(response, response.page); - $("img.lazyload").lazyload({ - threshold: 100, - effect: "fadeIn", - }); - page++; - next_page++; - }) - .catch((error) => console.error("Error:", error)); + .then((res) => { + document.getElementById("spinner").style.display = "block"; + return res.json(); + }) + .then((response) => { + // console.log("return page:::> ", response.page); + make_screen_movie_list(response, response.page); + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); + page++; + next_page++; + }) + .catch((error) => console.error("Error:", error)); }; const onScroll = (e) => { - threshold = 50; - console.dir(e.target.scrollingElement.scrollHeight); - const { scrollTop, scrollHeight, clientHeight } = e.target.scrollingElement; - // if (Math.round(clientHeight + scrollTop) >= scrollHeight + threshold) { - if (clientHeight + scrollTop + threshold >= scrollHeight) { - document.getElementById("spinner").style.display = "block"; - // setTimeout() - console.log("loading"); - // console.log(current_cate) - // console.log("now page::> ", page); - // console.log("next_page::> ", next_page); - setTimeout(loadNextAnimes(current_cate, next_page), 1500); - $("img.lazyload").lazyload({ - threshold: 100, - effect: "fadeIn", - }); - } + let threshold = 50; + console.dir(e.target.scrollingElement.scrollHeight); + const {scrollTop, scrollHeight, clientHeight} = e.target.scrollingElement; + // if (Math.round(clientHeight + scrollTop) >= scrollHeight + threshold) { + if (clientHeight + scrollTop + threshold >= scrollHeight) { + document.getElementById("spinner").style.display = "block"; + // setTimeout() + console.log("loading"); + // console.log(current_cate) + // console.log("now page::> ", page); + // console.log("next_page::> ", next_page); + setTimeout(loadNextAnimes(current_cate, next_page), 1500); + $("img.lazyload").lazyload({ + threshold: 100, + effect: "fadeIn", + }); + } }; const debounce = (func, delay) => { - let timeoutId = null; - return (...args) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(func.bind(null, ...args), delay); - }; + let timeoutId = null; + return (...args) => { + clearTimeout(timeoutId); + timeoutId = setTimeout(func.bind(null, ...args), delay); + }; }; document.addEventListener("scroll", debounce(onScroll, 300)); function make_program(data) { - let str, - tmp = ""; + let str, + tmp = ""; - tmp = '
'; - tmp += m_button("check_download_btn", "선택 다운로드 추가", []); - tmp += m_button("all_check_on_btn", "전체 선택", []); - tmp += m_button("all_check_off_btn", "전체 해제", []); - tmp += - '    '; - tmp += "
"; - tmp += '
'; - tmp += m_button("apply_new_title_btn", "저장폴더명 변경", []); - tmp += - '    '; - tmp += m_button("apply_new_season_btn", "시즌 변경 (숫자만 가능)", []); - tmp += m_button("search_tvdb_btn", "TVDB", []); - tmp += m_button("add_whitelist", "스케쥴링 추가", []); - tmp += "
"; - tmp = m_button_group(tmp); - str += tmp; - // program - str += m_hr_black(); - str += m_row_start(0); - tmp = ""; - if (data.poster_url != null) - tmp = ''; - str += m_col(3, tmp); - tmp = ""; - tmp += m_row_start(0); - tmp += m_col(3, "제목", "right"); - tmp += m_col(9, data.title); - tmp += m_row_end(); - tmp += m_row_start(0); - tmp += m_col(3, "시즌", "right"); - tmp += m_col(9, data.season); - tmp += m_row_end(); - for (i in data.detail) { - tmp += m_row_start(0); - key = Object.keys(data.detail[i])[0]; - value = data.detail[i][key]; - tmp += m_col(3, key, "right"); - tmp += m_col(9, value); - tmp += m_row_end(); - } - - str += m_col(9, tmp); - str += m_row_end(); - - str += m_hr_black(); - for (i in data.episode) { - str += m_row_start(); - // tmp = '' - // str += m_col(3, tmp) - tmp = "" + data.episode[i].title + ""; - tmp += "
"; - tmp += data.episode[i].filename + "

"; - - tmp += '
'; + tmp = '
'; + tmp += m_button("check_download_btn", "선택 다운로드 추가", []); + tmp += m_button("all_check_on_btn", "전체 선택", []); + tmp += m_button("all_check_off_btn", "전체 해제", []); tmp += - '    '; - tmp += m_button("add_queue_btn", "다운로드 추가", [ - { key: "code", value: data.episode[i].code }, - ]); + '    '; tmp += "
"; - str += m_col(12, tmp); + tmp += '
'; + tmp += m_button("apply_new_title_btn", "저장폴더명 변경", []); + tmp += + '    '; + tmp += m_button("apply_new_season_btn", "시즌 변경 (숫자만 가능)", []); + tmp += m_button("search_tvdb_btn", "TVDB", []); + tmp += m_button("add_whitelist", "스케쥴링 추가", []); + tmp += "
"; + tmp = m_button_group(tmp); + str += tmp; + // program + str += m_hr_black(); + str += m_row_start(0); + tmp = ""; + if (data.poster_url != null) + tmp = ''; + 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(); - if (i != data.length - 1) str += m_hr(0); - } - document.getElementById("episode_list").innerHTML = str; - $('input[id^="checkbox_"]').bootstrapToggle(); + + str += m_hr_black(); + for (i in data.episode) { + str += m_row_start(); + // tmp = '' + // str += m_col(3, tmp) + tmp = "" + data.episode[i].title + ""; + tmp += "
"; + tmp += data.episode[i].filename + "

"; + + tmp += '
'; + tmp += + '    '; + tmp += m_button("add_queue_btn", "다운로드 추가", [ + {key: "code", value: data.episode[i].code}, + ]); + tmp += "
"; + str += m_col(12, tmp); + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle(); } $("body").on("click", "#all_check_on_btn", function (e) { - e.preventDefault(); - $('input[id^="checkbox_"]').bootstrapToggle("on"); + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("on"); }); $("body").on("click", "#all_check_off_btn", function (e) { - e.preventDefault(); - $('input[id^="checkbox_"]').bootstrapToggle("off"); + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("off"); }); $("body").on("click", "#search_tvdb_btn", function (e) { - e.preventDefault(); - new_title = document.getElementById("new_title").value; - url = "https://www.thetvdb.com/search?query=" + new_title; - window.open(url, "_blank"); + e.preventDefault(); + new_title = document.getElementById("new_title").value; + url = "https://www.thetvdb.com/search?query=" + new_title; + window.open(url, "_blank"); }); -$("body").on("click", "#add_whitelist", function (e) { - e.preventDefault(); - let data_code = $(this).attr("data-code"); - console.log(data_code); - $.ajax({ - url: "/" + package_name + "/ajax/add_whitelist", - type: "POST", - cache: false, - data: JSON.stringify({ data_code: data_code }), - contentType: "application/json;charset=UTF-8", - dataType: "json", - success: function (ret) { - if (ret.ret) { - $.notify("추가하였습니다.
", { - type: "success", - }); - make_program(ret); - } else { - $.notify("추가 실패
" + ret.log, { - type: "warning", - }); - } - }, - }); -}); +// $("body").on("click", "#add_whitelist", function (e) { +// e.preventDefault(); +// let data_code = $(this).attr("data-code"); +// console.log(data_code); +// $.ajax({ +// url: "/" + package_name + "/ajax/add_whitelist", +// type: "POST", +// cache: false, +// data: JSON.stringify({ data_code: data_code }), +// contentType: "application/json;charset=UTF-8", +// dataType: "json", +// success: function (ret) { +// if (ret.ret) { +// $.notify("추가하였습니다.
", { +// type: "success", +// }); +// make_program(ret); +// } else { +// $.notify("추가 실패
" + ret.log, { +// type: "warning", +// }); +// } +// }, +// }); +// }); $("body").on("click", "#apply_new_title_btn", function (e) { - e.preventDefault(); - new_title = document.getElementById("new_title").value; - $.ajax({ - url: "/" + package_name + "/ajax/apply_new_title", - type: "POST", - cache: false, - data: { new_title: new_title }, - dataType: "json", - success: function (ret) { - if (ret.ret) { - $.notify("적용하였습니다.
", { - type: "success", - }); - // console.log(ret); - make_program(ret); - } else { - $.notify("적용 실패
" + ret.log, { - type: "warning", - }); - } - }, - }); + e.preventDefault(); + new_title = document.getElementById("new_title").value; + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_title", + type: "POST", + cache: false, + data: {new_title: new_title}, + dataType: "json", + success: function (ret) { + + if (ret.ret) { + $.notify("적용하였습니다.
", { + type: "success", + }); + // console.log(ret); + make_program(ret); + } else { + $.notify("적용 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); }); $("body").on("click", "#apply_new_season_btn", function (e) { - e.preventDefault(); - new_season = document.getElementById("new_season").value; - if ($.isNumeric(new_season) == false) { - $.notify("시즌은 숫자여야 합니다.
" + ret.log, { - type: "warning", - }); - } else { - $.ajax({ - url: "/" + package_name + "/ajax/apply_new_season", - type: "POST", - cache: false, - data: { new_season: new_season }, - dataType: "json", - success: function (ret) { - if (ret.ret) { - $.notify("적용하였습니다.
", { - type: "success", - }); - make_program(ret); - } else { - $.notify("적용 실패
" + ret.log, { + e.preventDefault(); + new_season = document.getElementById("new_season").value; + if ($.isNumeric(new_season) == false) { + $.notify("시즌은 숫자여야 합니다.
" + ret.log, { type: "warning", - }); - } - }, - }); - } + }); + } else { + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_season", + type: "POST", + cache: false, + data: {new_season: new_season}, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("적용하였습니다.
", { + type: "success", + }); + make_program(ret); + } else { + $.notify("적용 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); + } }); // 하나씩 다운로드 추가 $("body").on("click", "#add_queue_btn", function (e) { - e.preventDefault(); - code = $(this).data("code"); - $.ajax({ - url: "/" + package_name + "/ajax/add_queue", - type: "POST", - cache: false, - data: { code: code }, - dataType: "json", - success: function (data) { - if (data.ret === "success") { - $.notify("다운로드 작업을 추가 하였습니다.", { - type: "success", - }); - } else if (data.ret === "fail") { - $.notify("이미 큐에 있습니다. 삭제 후 추가하세요.", { - type: "warning", - }); - } else if (data.ret === "no_data") { - $.notify("잘못된 코드입니다.", { - type: "warning", - }); - } else { - $.notify("추가 실패
" + ret.log, { - type: "warning", - }); - } - }, - }); + e.preventDefault(); + code = $(this).data("code"); + $.ajax({ + url: "/" + package_name + "/ajax/add_queue", + type: "POST", + cache: false, + data: {code: code}, + dataType: "json", + success: function (data) { + if (data.ret === "success") { + $.notify("다운로드 작업을 추가 하였습니다.", { + type: "success", + }); + } else if (data.ret === "fail") { + $.notify("이미 큐에 있습니다. 삭제 후 추가하세요.", { + type: "warning", + }); + } else if (data.ret === "no_data") { + $.notify("잘못된 코드입니다.", { + type: "warning", + }); + } else { + $.notify("추가 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); }); $("body").on("click", "#check_download_btn", function (e) { - e.preventDefault(); - all = $('input[id^="checkbox_"]'); - str = ""; - for (i in all) { - if (all[i].checked) { - code = all[i].id.split("_")[1]; - str += code + ","; + e.preventDefault(); + all = $('input[id^="checkbox_"]'); + str = ""; + for (i in all) { + if (all[i].checked) { + code = all[i].id.split("_")[1]; + str += code + ","; + } } - } - if (str == "") { - $.notify("선택하세요.", { - type: "warning", + if (str == "") { + $.notify("선택하세요.", { + type: "warning", + }); + return; + } + $.ajax({ + url: "/" + package_name + "/ajax/add_queue_checked_list", + type: "POST", + cache: false, + data: {code: str}, + dataType: "json", + success: function (data) { + if (data.ret == "success") { + $.notify("" + data.log + "개를 추가하였습니다.", { + type: "success", + }); + } else { + $.notify("" + data.log + "", { + type: "warning", + }); + } + }, }); - return; - } - $.ajax({ - url: "/" + package_name + "/ajax/add_queue_checked_list", - type: "POST", - cache: false, - data: { code: str }, - dataType: "json", - success: function (data) { - if (data.ret == "success") { - $.notify("" + data.log + "개를 추가하였습니다.", { - type: "success", - }); - } else { - $.notify("" + data.log + "", { - type: "warning", - }); - } - }, - }); }); -// $("#go_modal_airing").on("shown.bs.modal", function () { -// // {#get_airing_list()#} -// $("#exModal").trigger("focus"); -// }); - -// $("#go_modal_airing").click(function (e) { -// e.preventDefault(); -// console.log("open modal"); -// // $("#exModal").bootstrapToggle(); -// if (current_airing_data === "") { -// get_airing_list(); -// } -// $("#inner_airing").toggle(); -// $("#airing_list").toggle(); -// }); - -// $("#go_modal_airing").attr("class", "btn btn-primary"); $(document).ready(function () { - $("#input_search").keydown(function (key) { - if (key.keyCode === 13) { - // alert("엔터키를 눌렀습니다."); - $("#btn_search").trigger("click"); - } - }); - let spinner = document.getElementById("spinner"); - spinner.style.display = "block"; - get_anime_list(1, "ing"); - // $("img.lazyload").lazyload({ - // threshold : 200, - // effect : "fadeIn", - // }); + $("#input_search").keydown(function (key) { + if (key.keyCode === 13) { + // alert("엔터키를 눌렀습니다."); + $("#btn_search").trigger("click"); + } + }); + let spinner = document.getElementById("spinner"); + spinner.style.display = "block"; + get_lecture_list(1, "recent"); }); // @@ -713,79 +628,79 @@ $(document).ready(function () { // // 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(); - } + 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(); + "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); } - }); - var b = document.getElementsByTagName("body")[0]; - var config = { - childList: !0, - subtree: !0, - }; - observer.observe(b, config); - } - }, - !1 + }, + !1 ); diff --git a/static/js/inflearn_excel.js b/static/js/inflearn_excel.js new file mode 100755 index 0000000..c911ee6 --- /dev/null +++ b/static/js/inflearn_excel.js @@ -0,0 +1,399 @@ +let current_data = ""; +let current_airing_data = ""; +let code = ""; +let div_visible = false; +let total_page = ""; + +const params = new Proxy(new URLSearchParams(window.location.search), { + get: (searchParams, prop) => searchParams.get(prop), +}); + +// console.log('current_airing_data', current_airing_data); + +const get_excel_list = () => { + $.ajax({ + url: "/" + package_name + "/ajax/excel_list", + type: "GET", + cache: false, + dataType: "json", + success: (ret) => { + if (ret.ret == "success" && ret.data != null) { + current_airing_data = ret; + total_page = ret.total_page; + console.log(ret) + if (current_airing_data !== "") { + make_airing_list(ret); + div_visible = true; + // console.log(div_visible) + } + } else { + $.notify("분석 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); + +}; + + +$(document).on("click", "button.code-button", function (e) { + e.preventDefault(); + // console.log('click') + // console.log('code to click:' + $(this).data("code")) + document.getElementById("code").value = $(this).data("code"); + $("#code").val($(this).data("code")); + $("#airing_list").toggle(); + code = document.getElementById("code").value; + document.getElementById("analysis_btn").click(); +}); +$(".code-button").tooltip(); + + +$("body").on("click", "#go_inflearn_btn", function (e) { + e.preventDefault(); + window.open(inflearn_url, "_blank"); +}); + +function make_airing_list(data) { + let str = ""; + let tmp = ""; + + tmp = + '"; + str += m_hr_black(); + // {#str += m_row_start(0);#} + // {##} + // {#str += m_row_end();#} + // {#str += m_hr_black();#} + str += + '
'; + for (i in data.episode) { + // {#str += m_row_start();#} + // {#tmp = '
' + data.episode[i].title+ '';#} + // + // {#tmp += '
'#} + // {#tmp += '' + data.episode[i].code + '
';#} + // {#str += m_col(12, tmp)#} + tmp = + '
"; + // {#if (i === 10) {#} + // {# tmp += '
'#} + str += tmp; + } + str += "
"; + str += m_hr_black(); + + document.getElementById("airing_list").innerHTML = str; +} + +function make_program(data) { + current_data = data; + // console.log('current_data:: ', data) + str = ""; + tmp = '
'; + tmp += m_button("check_download_btn", "선택 다운로드 추가", []); + tmp += m_button("all_check_on_btn", "전체 선택", []); + tmp += m_button("all_check_off_btn", "전체 해제", []); + tmp += + '   '; + tmp += "
"; + tmp += '
'; + tmp += m_button("apply_new_title_btn", "저장폴더명 변경", []); + // tmp += + // '   '; + // tmp += m_button("apply_new_season_btn", "시즌 변경 (숫자만 가능)", []); + // tmp += m_button("search_tvdb_btn", "TVDB", []); + tmp += m_button("add_whitelist", "스케쥴링 추가", []); + tmp += "
"; + tmp = m_button_group(tmp); + str += tmp; + // program + str += m_hr_black(); + str += m_row_start(0); + tmp = ""; + if (data.poster_url != null) + tmp = ''; + str += m_col(3, tmp); + tmp = ""; + tmp += m_row_start(0); + tmp += m_col(3, "제목", "right"); + tmp += m_col(9, data.title); + tmp += m_row_end(); + tmp += m_row_start(0); + tmp += m_col(3, "시즌", "right"); + tmp += m_col(9, data.season); + tmp += m_row_end(); + for (i in data.detail) { + tmp += m_row_start(0); + key = Object.keys(data.detail[i])[0]; + value = data.detail[i][key]; + tmp += m_col(3, key, "right"); + tmp += m_col(9, value); + tmp += m_row_end(); + } + + str += m_col(9, tmp); + str += m_row_end(); + + str += m_hr_black(); + for (i in data.episode) { + str += m_row_start(); + // tmp = '' + // str += m_col(3, tmp) + tmp = "" + data.episode[i].title + ""; + tmp += "
"; + tmp += data.episode[i].filename + "

"; + + tmp += '
'; + tmp += + '    '; + // tmp += m_button('add_queue_btn', '다운로드 추가', [{'key': 'code', 'value': data.episode[i].code}]) + tmp += m_button("add_download", "다운로드 추가", [ + {key: "idx", value: i}, + ]); + tmp += "
"; + str += m_col(12, tmp); + str += m_row_end(); + if (i != data.length - 1) str += m_hr(0); + } + document.getElementById("episode_list").innerHTML = str; + $('input[id^="checkbox_"]').bootstrapToggle(); +} + +$("body").on("click", "#all_check_on_btn", function (e) { + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("on"); +}); + +$("body").on("click", "#all_check_off_btn", function (e) { + e.preventDefault(); + $('input[id^="checkbox_"]').bootstrapToggle("off"); +}); + +$("body").on("click", "#search_tvdb_btn", function (e) { + e.preventDefault(); + new_title = document.getElementById("new_title").value; + url = "https://www.thetvdb.com/search?query=" + new_title; + window.open(url, "_blank"); +}); + +$("body").on("click", "#add_whitelist", function (e) { + e.preventDefault(); + $.ajax({ + url: "/" + package_name + "/ajax/add_whitelist", + type: "POST", + cache: false, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("추가하였습니다.
", { + type: "success", + }); + make_program(ret); + } else { + $.notify("추가 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#apply_new_title_btn", function (e) { + e.preventDefault(); + new_title = document.getElementById("new_title").value; + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_title", + type: "POST", + cache: false, + data: {new_title: new_title}, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("적용하였습니다.
", { + type: "success", + }); + // console.log(ret) + make_program(ret); + } else { + $.notify("적용 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#apply_new_season_btn", function (e) { + e.preventDefault(); + new_season = document.getElementById("new_season").value; + if ($.isNumeric(new_season) == false) { + $.notify("시즌은 숫자여야 합니다.
" + ret.log, { + type: "warning", + }); + } else { + $.ajax({ + url: "/" + package_name + "/ajax/apply_new_season", + type: "POST", + cache: false, + data: {new_season: new_season}, + dataType: "json", + success: function (ret) { + if (ret.ret) { + $.notify("적용하였습니다.
", { + type: "success", + }); + make_program(ret); + } else { + $.notify("적용 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); + } +}); + +// 하나씩 다운로드 추가 +$("body").on("click", "#add_download", function (e) { + e.preventDefault(); + // code = $(this).data('code'); + code = current_data.episode[$(this).data("idx")].code; + // console.log('code:: ', code) + let data = current_data.episode[$(this).data("idx")]; + // console.log('data:: ', data) + $.ajax({ + url: "/" + package_name + "/ajax/add_download", + type: "POST", + cache: false, + data: {code: code, data: JSON.stringify(data)}, + dataType: "json", + success: function (data) { + // console.log('#add_queue_btn::data >>', data) + if (data.ret === "enqueue_db_append") { + $.notify("다운로드 작업을 추가 하였습니다.", { + type: "success", + }); + } else if (data.ret === "enqueue_db_exist") { + $.notify("DB에 존재하는 에피소드입니다.", { + type: "warning", + }); + } else if (data.ret === "db_completed") { + $.notify("DB에 완료 기록이 있습니다.", { + type: "warning", + }); + } else if (data.ret === "fail") { + $.notify("이미 큐에 있습니다. 삭제 후 추가하세요.", { + type: "warning", + }); + } else if (data.ret === "no_data") { + $.notify("잘못된 코드입니다.", { + type: "warning", + }); + } else if (data.ret === "Debugging") { + $.notify("Debugging", { + type: "warning", + }); + } else { + $.notify("추가 실패
" + ret.log, { + type: "warning", + }); + } + }, + }); +}); + +$("body").on("click", "#check_download_btn", function (e) { + e.preventDefault(); + all = $('input[id^="checkbox_"]'); + str = ""; + for (i in all) { + if (all[i].checked) { + code = all[i].id.split("_")[1]; + str += code + ","; + } + } + if (str == "") { + $.notify("선택하세요.", { + type: "warning", + }); + return; + } + $.ajax({ + url: "/" + package_name + "/ajax/add_queue_checked_list", + type: "POST", + cache: false, + data: {code: str}, + dataType: "json", + success: function (data) { + if (data.ret == "success") { + $.notify("" + data.log + "개를 추가하였습니다.", { + type: "success", + }); + } else { + $.notify("" + data.log + "", { + type: "warning", + }); + } + }, + }); +}); + +$("#go_modal_airing").on("shown.bs.modal", function () { + // {#get_airing_list()#} + $("#exModal").trigger("focus"); +}); + +$("#go_modal_airing").click(function (e) { + e.preventDefault(); + // console.log('open modal') + $("#exModal").bootstrapToggle(); + if (current_airing_data === "") { + get_airing_list(); + } + $("#inner_airing").toggle(); + $("#airing_list").toggle(); +}); + +$("#go_modal_airing").attr("class", "btn btn-primary"); + +// {#$(function () {#} +// {# $('[data-tooltip="true"]').tooltip()#} +// {#});#} + +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// +// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// -// - - - - +
-
+