From 6e7b7709b3550bfcdd0d74465ff4e01b7942e2ce Mon Sep 17 00:00:00 2001 From: flaskfarm Date: Fri, 28 Oct 2022 06:09:16 +0900 Subject: [PATCH] update --- .gitignore | 1 + lib/framework/static/css/custom.css | 4 + lib/framework/static/js/sjva_ui14.js | 49 -------- lib/framework/version.py | 2 +- lib/support/site/__init__.py | 21 ++-- lib/support/site/kakaotv.py | 172 +++++++++++++++++++++++++++ lib/support/site/kakaotv.pyf | 1 + 7 files changed, 192 insertions(+), 58 deletions(-) create mode 100644 lib/support/site/kakaotv.py create mode 100644 lib/support/site/kakaotv.pyf diff --git a/.gitignore b/.gitignore index b89f706..1c765cc 100644 --- a/.gitignore +++ b/.gitignore @@ -147,6 +147,7 @@ tmp/ lib/support/site/tving.py lib/support/site/wavve.py lib/support/site/seezn.py +lib/support/site/kakao.py *.bat output/ *.mkv diff --git a/lib/framework/static/css/custom.css b/lib/framework/static/css/custom.css index b8458e0..958c82e 100644 --- a/lib/framework/static/css/custom.css +++ b/lib/framework/static/css/custom.css @@ -102,3 +102,7 @@ background-color: #ffff0080 !important; margin-left:0.7rem; margin-right:0.7rem; margin-bottom: .5rem; } } + +.dropdown-menu { + margin:-2px; +} diff --git a/lib/framework/static/js/sjva_ui14.js b/lib/framework/static/js/sjva_ui14.js index 5312110..95a1b33 100644 --- a/lib/framework/static/js/sjva_ui14.js +++ b/lib/framework/static/js/sjva_ui14.js @@ -97,52 +97,3 @@ function m_tab_content(name, content, active) { return str; } - - - - - -document.addEventListener("DOMContentLoaded", function(){ - /////// Prevent closing from click inside dropdown -document.querySelectorAll('.dropdown-menu').forEach(function(element){ - element.addEventListener('click', function (e) { - e.stopPropagation(); - }); -}) - - // make it as accordion for smaller screens -if (window.innerWidth < 992) { - - // close all inner dropdowns when parent is closed - document.querySelectorAll('.navbar .dropdown').forEach(function(everydropdown){ - everydropdown.addEventListener('hidden.bs.dropdown', function () { - // after dropdown is hidden, then find all submenus - this.querySelectorAll('.submenu').forEach(function(everysubmenu){ - // hide every submenu as well - everysubmenu.style.display = 'none'; - }); - }) - }); - - document.querySelectorAll('.dropdown-menu a').forEach(function(element){ - element.addEventListener('click', function (e) { - - let nextEl = this.nextElementSibling; - if(nextEl && nextEl.classList.contains('submenu')) { - // prevent opening link if link needs to open dropdown - e.preventDefault(); - console.log(nextEl); - if(nextEl.style.display == 'block'){ - nextEl.style.display = 'none'; - } else { - nextEl.style.display = 'block'; - } - - } - }); - }) -} -// end if innerWidth - -}); -// DOMContentLoaded end \ No newline at end of file diff --git a/lib/framework/version.py b/lib/framework/version.py index 31ba31e..d94d2e7 100644 --- a/lib/framework/version.py +++ b/lib/framework/version.py @@ -1 +1 @@ -VERSION="4.0.44" \ No newline at end of file +VERSION="4.0.45" \ No newline at end of file diff --git a/lib/support/site/__init__.py b/lib/support/site/__init__.py index 245392f..c53f772 100644 --- a/lib/support/site/__init__.py +++ b/lib/support/site/__init__.py @@ -2,11 +2,16 @@ import os from support import SupportSC -if os.path.exists(os.path.join(os.path.dirname(__file__), 'tving.py')): - from .seezn import SupportSeezn - from .tving import SupportTving - from .wavve import SupportWavve -else: - SupportTving = SupportSC.load_module_f(__file__, 'tving').SupportTving - SupportWavve = SupportSC.load_module_f(__file__, 'wavve').SupportWavve - SupportSeezn = SupportSC.load_module_f(__file__, 'seezn').SupportSeezn +try: + if os.path.exists(os.path.join(os.path.dirname(__file__), 'tving.py')): + from .kakaotv import SupportKakaotv + from .seezn import SupportSeezn + from .tving import SupportTving + from .wavve import SupportWavve + else: + SupportTving = SupportSC.load_module_f(__file__, 'tving').SupportTving + SupportWavve = SupportSC.load_module_f(__file__, 'wavve').SupportWavve + SupportSeezn = SupportSC.load_module_f(__file__, 'seezn').SupportSeezn + SupportKakaotv = SupportSC.load_module_f(__file__, 'kakaotv').SupportKakaotv +except: + pass diff --git a/lib/support/site/kakaotv.py b/lib/support/site/kakaotv.py new file mode 100644 index 0000000..99f1461 --- /dev/null +++ b/lib/support/site/kakaotv.py @@ -0,0 +1,172 @@ +import copy +import os +import re +import time +import traceback +import uuid +from base64 import b64encode +from datetime import datetime + +import requests +from support import SupportFile, d, default_headers, logger + +try: + from lxml import html +except: + os.system("pip install lxml") + from lxml import html + +try: + import wv_tool +except: + pass + +class SupportKakaotv: + uu = str(uuid.uuid4()).replace('-', '') + + @classmethod + def get_recent_channel(cls): + res = requests.get('https://tv.kakao.com/top') + root = html.fromstring(res.text) + #logger.debug(root) + + tags = root.xpath('//div[@class="area_category"]') + #logger.debug(tags) + now = datetime.now() + item_list = [] + for tag in tags: + entity = {'episodes':[]} + entity['title'] = tag.xpath('.//span[@class="txt_subject"]')[0].text + first_item = tag.xpath('.//div[@class="inner_favoritem"]')[0] + link = first_item.xpath('.//a')[0].attrib['href'] + entity['channel_id'] = link.split('/')[2] + entity['upload_time'] = first_item.xpath('.//a/span[3]/span[2]/span[2]')[0].attrib['data-raw-date'] + upload_time = datetime.strptime(entity['upload_time'], '%Y-%m-%d %H:%M:%S') + if (now - upload_time).days > 10: + break + item_list.append(entity) + return item_list + + + @classmethod + def get_video_list(cls, channel_id): + try: + ret = [] + root = html.fromstring(requests.get(f"https://tv.kakao.com/channel/{channel_id}/playlist", headers=default_headers).text) + tags = root.xpath('//*[@id="mArticle"]/div[2]/ul/li[1]') + for tag in tags: + name = tag.xpath('a/span[2]/strong')[0].text + + if name.find('본편') != -1: + channel_name = re.match(r'\[(.*?)\]', name).group(1).strip() + playlist_url = f"https://tv.kakao.com{tag.xpath('a')[0].attrib['href']}" + playlist_root = html.fromstring(requests.get(playlist_url).text) + playlist_item_tags = playlist_root.xpath('//*[@id="playerPlaylist"]/ul[1]/li') + for playlist_item in playlist_item_tags: + episode_entity = {'channel_id':channel_id, 'channel_name':channel_name} + episode_entity['link'] = 'https://tv.kakao.com' + playlist_item.xpath('a')[0].attrib['href'] + episode_entity['clip_id'] = playlist_item.xpath('a')[0].attrib['href'].split('?')[0].split('/')[-1] + episode_entity['no'] = int(playlist_item.xpath('a/span[1]')[0].text) + episode_entity['title'] = playlist_item.xpath('a/span[3]/strong')[0].text + episode_entity['img'] = 'https:' + playlist_item.xpath('a/span[2]/img')[0].attrib['src'] + match = re.match(r"(\d+)회", episode_entity['title']) + if match: + episode_entity['no'] = int(match.group(1)) + try: + episode_entity['pay'] = playlist_item.xpath('a/span[3]/span/span')[0].text + #continue + except: + episode_entity['pay'] = '무료' + episode_entity['filename'] = '{title}.S{season_number}E{episode_number}.1080p.WEB-DL.AAC.H.264.SW{site}.mkv'.format( + title = SupportFile.text_for_filename(channel_name), + season_number = str(1).zfill(2), + episode_number = str(episode_entity['no']).zfill(2), + site = 'KK', + ) + ret.append(episode_entity) + ret = list(reversed(ret)) + return ret + except Exception as e: + logger.error(f'Exception:{str(e)}') + logger.error(traceback.format_exc()) + + @classmethod + def make_wvtool_config(cls, info): + timestamp = str(time.time()*1000).split('.')[0] + url = f"https://tv.kakao.com/katz/v4/ft/cliplink/{info['clip_id']}/readyNplay?player=monet_html5&referer=&uuid={cls.uu}&profile=HIGH4&service=kakao_tv§ion=channel&fields=seekUrl,abrVideoLocationList&playerVersion=3.14.1&appVersion=106.0.0.0&startPosition=0&tid=&dteType=PC&continuousPlay=false&autoPlay=false&contentType=&drmType=widevine&ab=&literalList=&{timestamp}" + + data = requests.get(url, headers=default_headers).json() + vid = data['vmapReq']['content_data']['vid'] + tid = data['tid'] + + headers = copy.deepcopy(default_headers) + headers['x-kamp-auth'] = f"Bearer {data['kampLocation']['token']}" + url = f"https://kamp.kakao.com/vod/v1/src/{vid}?tid={tid}¶m_auth=true&{timestamp}" + data = requests.get(url, headers=headers).json() + + mpd_headers = copy.deepcopy(default_headers) + mpd_headers['Origin'] = 'https://tv.kakao.com' + mpd_headers['Referer'] = 'https://tv.kakao.com/' + mpd_url = data['streams'][0]['url'] + + if data['is_drm']: + return { + 'logger' : logger, + 'mpd_url' : mpd_url, + 'code' : info['clip_id'], + 'output_filename' : info['filename'], + 'mpd_headers': mpd_headers, + 'clean' : False, + 'license_url': 'https://drm-license.kakaopage.com/v1/license', + 'attach_url_param': '?' + mpd_url.split('?')[1], + 'vars' : { + 'data': data + } + } + else: + return None + + + + try: + import wv_tool + class WVDownloaderKakao(wv_tool.WVDownloader): + def do_make_key(self): + postdata = {} + postdata = copy.deepcopy(default_headers) + postdata['headers'] = {} + postdata['headers']['Host'] = 'drm-license.kakaopage.com' + postdata['headers']['Origin'] = 'https://tv.kakao.com' + postdata['headers']['Referer'] = 'https://tv.kakao.com/' + postdata['data'] = {} + postdata['data']['token'] = self.config['vars']['data']['drm']['token'] + postdata['data']['provider'] = self.config['vars']['data']['drm']['provider'] + + wv = wv_tool.WVDecryptManager(self.pssh) + payload = wv.get_challenge() + payload = b64encode(payload) + payload = payload.decode('ascii') + postdata['data']['payload'] = payload + widevine_license = requests.post(url=self.license_url, data=postdata['data'], headers=postdata['headers']) + data = widevine_license.json() + #logger.error(d(data)) + license_b64 = data['payload'] + correct, keys = wv.get_result(license_b64) + + if correct: + for key in keys: + tmp = key.split(':') + self.key.append({'kid':tmp[0], 'key':tmp[1]}) + + except Exception as e: + logger.error('Exception:%s', e) + logger.error(traceback.format_exc()) + + +""" +url = "https://tv.kakao.com/embed/player/cliplink/433009293?service=kakao_tv§ion=channel&autoplay=1&profile=HIGH4&wmode=transparent" + + +url = "https://play-tv.kakao.com/katz/v1/close/cliplink/433009293/info" + +""" diff --git a/lib/support/site/kakaotv.pyf b/lib/support/site/kakaotv.pyf new file mode 100644 index 0000000..2db2e3e --- /dev/null +++ b/lib/support/site/kakaotv.pyf @@ -0,0 +1 @@ +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 \ No newline at end of file