First Commit
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
import logging
|
||||
from datetime import date, datetime, timedelta
|
||||
from functools import lru_cache
|
||||
from typing import List
|
||||
from xml.sax.saxutils import unescape
|
||||
|
||||
from epg2xml.providers import EPGProgram, EPGProvider
|
||||
|
||||
log = logging.getLogger(__name__.rsplit(".", maxsplit=1)[-1].upper())
|
||||
today = date.today()
|
||||
|
||||
|
||||
class WAVVE(EPGProvider):
|
||||
"""EPGProvider for WAVVE
|
||||
|
||||
데이터: jsonapi
|
||||
요청수: 1
|
||||
특이사항:
|
||||
- 해외나 VPS는 차단 가능성이 높음
|
||||
"""
|
||||
|
||||
referer = "https://www.wavve.com/"
|
||||
title_regex = r"^(.*?)(?:\s*[\(<]?([\d]+)회[\)>]?)?(?:\([월화수목금토일]?\))?(\([선별전주\(\)재방]*?재[\d방]?\))?\s*(?:\[(.+)\])?$"
|
||||
|
||||
base_url = "https://apis.wavve.com"
|
||||
base_params = {
|
||||
"apikey": "E5F3E0D30947AA5440556471321BB6D9",
|
||||
"client_version": "6.0.1",
|
||||
"device": "pc",
|
||||
"drm": "wm",
|
||||
"partner": "pooq",
|
||||
"pooqzone": "none",
|
||||
"region": "kor",
|
||||
"targetage": "all",
|
||||
}
|
||||
|
||||
def __init__(self, cfg):
|
||||
super().__init__(cfg)
|
||||
self.sess.headers.update({"wavve-credential": "none"})
|
||||
|
||||
def __url(self, url: str) -> str:
|
||||
"""completes partial urls from api response or for api request"""
|
||||
if url.startswith(("http://", "https://")):
|
||||
return url
|
||||
if url.startswith("/"):
|
||||
return self.base_url + url
|
||||
return "https://" + url
|
||||
|
||||
def __params(self, **params) -> dict:
|
||||
"""returns url parameters for api requests with base ones"""
|
||||
p = self.base_params.copy()
|
||||
p.update(params)
|
||||
return p
|
||||
|
||||
def __get(self, url: str, **kwargs):
|
||||
url = self.__url(url)
|
||||
params = self.__params(**kwargs.pop("params", {}))
|
||||
return self.request(url, params=params, **kwargs)
|
||||
|
||||
def get_svc_channels(self) -> List[dict]:
|
||||
today_str = today.strftime("%Y-%m-%d")
|
||||
hour_min = datetime.now().hour // 3
|
||||
# 현재 시간과 가까운 미래에 서비스 가능한 채널만 가져옴
|
||||
params = {
|
||||
"enddatetime": f"{today_str} {(hour_min+1)*3:02d}:00",
|
||||
"genre": "all",
|
||||
"limit": 500,
|
||||
"offset": 0,
|
||||
"startdatetime": f"{today_str} {hour_min*3:02d}:00",
|
||||
}
|
||||
return [
|
||||
{
|
||||
"Name": x["channelname"],
|
||||
"Icon_url": self.__url(x["channelimage"]),
|
||||
"ServiceId": x["channelid"],
|
||||
}
|
||||
for x in self.__get("/live/epgs", params=params)["list"]
|
||||
]
|
||||
|
||||
def __epg_of_program(self, channelid: str, data: dict) -> EPGProgram:
|
||||
_epg = EPGProgram(channelid)
|
||||
_epg.stime = datetime.strptime(data["starttime"], "%Y-%m-%d %H:%M")
|
||||
_epg.etime = datetime.strptime(data["endtime"], "%Y-%m-%d %H:%M")
|
||||
# 채널이름은 그대로 들어오고 프로그램 제목은 escape되어 들어옴
|
||||
_epg.title = unescape(data["title"])
|
||||
if m := self.title_regex.match(_epg.title):
|
||||
_epg.title = m.group(1)
|
||||
_epg.title_sub = m.group(4)
|
||||
episode = (m.group(2) or "").replace("회", "").strip()
|
||||
_epg.ep_num = None if episode == "0" else episode
|
||||
_epg.rebroadcast = bool(m.group(3))
|
||||
_epg.rating = 0 if data["targetage"] == "n" else int(data["targetage"])
|
||||
|
||||
# 추가 정보 가져오기
|
||||
if not self.cfg["GET_MORE_DETAILS"]:
|
||||
return _epg
|
||||
programid = data["programid"].strip()
|
||||
if not programid:
|
||||
# 개별 programid가 없는 경우도 있으니 체크해야함
|
||||
return _epg
|
||||
detail = self.get_program_details(programid)
|
||||
if not detail:
|
||||
return _epg
|
||||
# 여러가지 추가 정보가 제공되지만
|
||||
# 방송되지 않은 미래의 프로그램/에피소드 정보는 반영되지 않았기에
|
||||
# 일부 정보만 유효함을 유념
|
||||
synopsis = detail["seasonsynopsis"] or detail["programsynopsis"] or detail["episodesynopsis"]
|
||||
_epg.desc = "\n".join(
|
||||
[x.replace("<br>", "\n").strip() for x in synopsis.splitlines()]
|
||||
) # carriage return(\r) 제거, <br> 제거
|
||||
_epg.categories = [detail["genretext"].strip()]
|
||||
_epg.poster_url = self.__url(detail["seasonposterimage"].strip())
|
||||
_epg.keywords = [x["text"] for x in detail["tags"]["list"]]
|
||||
actors = detail.get("season_actors") or detail.get("actors") or {"list": []}
|
||||
directors = detail.get("season_directors") or detail.get("directors") or {"list": []}
|
||||
writers = detail.get("season_writers") or detail.get("writers") or {"list": []}
|
||||
_epg.cast = [{"name": x["text"], "title": "actor"} for x in actors["list"]]
|
||||
_epg.crew = [{"name": x["text"], "title": "director"} for x in directors["list"]]
|
||||
_epg.crew += [{"name": x["text"], "title": "writer"} for x in writers["list"]]
|
||||
return _epg
|
||||
|
||||
def get_programs(self) -> None:
|
||||
# parameters for requests
|
||||
params = {
|
||||
"enddatetime": (today + timedelta(days=int(self.cfg["FETCH_LIMIT"]) - 1)).strftime("%Y-%m-%d 24:00"),
|
||||
"genre": "all",
|
||||
"limit": 500,
|
||||
"offset": 0,
|
||||
"startdatetime": today.strftime("%Y-%m-%d 00:00"),
|
||||
}
|
||||
channeldict = {x["channelid"]: x for x in self.__get("/live/epgs", params=params)["list"]}
|
||||
|
||||
for idx, _ch in enumerate(self.req_channels):
|
||||
log.info("%03d/%03d %s", idx + 1, len(self.req_channels), _ch)
|
||||
for program in channeldict[_ch.svcid]["list"]:
|
||||
try:
|
||||
_epg = self.__epg_of_program(_ch.id, program)
|
||||
except Exception:
|
||||
log.exception("프로그램 파싱 중 예외: %s", _ch)
|
||||
else:
|
||||
_ch.programs.append(_epg)
|
||||
|
||||
@lru_cache
|
||||
def get_program_details(self, programid: str) -> dict:
|
||||
try:
|
||||
params = {"history": "season", "programid": programid}
|
||||
data = self.__get("/fz/vod/programs/landing", params=params)
|
||||
if data.get("resultcode") in ["550"]:
|
||||
# 애초에 유효하지 않은 programid가 있을 수 있음
|
||||
# { "resultcode": "550", "resultmessage": "해당 데이터가 없습니다." }
|
||||
return None
|
||||
return self.__get(f"/fz/vod/contents-detail/{data['content_id'].strip()}")
|
||||
except Exception:
|
||||
log.exception("프로그램 상세 정보 요청 중 예외: %s", programid)
|
||||
return None
|
||||
Reference in New Issue
Block a user