From 727b38a969378094e21c15469f60d55effd097d1 Mon Sep 17 00:00:00 2001 From: jafea7 Date: Sun, 27 Apr 2025 22:52:47 +1000 Subject: [PATCH] Fix WinkTv capture --- .../main/java/ctbrec/sites/winktv/WinkTv.java | 66 +++++++-- .../java/ctbrec/sites/winktv/WinkTvModel.java | 140 ++++++++++-------- 2 files changed, 130 insertions(+), 76 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/winktv/WinkTv.java b/common/src/main/java/ctbrec/sites/winktv/WinkTv.java index ee7efa37..8eda6a39 100644 --- a/common/src/main/java/ctbrec/sites/winktv/WinkTv.java +++ b/common/src/main/java/ctbrec/sites/winktv/WinkTv.java @@ -5,6 +5,7 @@ import ctbrec.StringUtil; import ctbrec.io.HttpClient; import ctbrec.io.HttpException; import ctbrec.sites.AbstractSite; +import lombok.extern.slf4j.Slf4j; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.Response; @@ -22,14 +23,15 @@ import java.util.regex.Pattern; import static ctbrec.io.HttpConstants.*; +@Slf4j public class WinkTv extends AbstractSite { - public static String domain = "www.winktv.co.kr"; public static String baseUri = "https://www.winktv.co.kr"; private HttpClient httpClient; @Override public void init() throws IOException { + // Initialization logic if needed } @Override @@ -63,7 +65,7 @@ public class WinkTv extends AbstractSite { @Override public Double getTokenBalance() throws IOException { - return 0d; + return 0.0; } @Override @@ -111,6 +113,7 @@ public class WinkTv extends AbstractSite { if (StringUtil.isBlank(q)) { return Collections.emptyList(); } + String url = "https://api.winktv.co.kr/v1/live"; FormBody body = new FormBody.Builder() .add("offset", "0") @@ -118,6 +121,7 @@ public class WinkTv extends AbstractSite { .add("orderBy", "user") .add("searchVal", URLEncoder.encode(q, "utf-8")) .build(); + Request req = new Request.Builder() .url(url) .header(USER_AGENT, getConfig().getSettings().httpUserAgent) @@ -128,29 +132,57 @@ public class WinkTv extends AbstractSite { .header(ORIGIN, getBaseUrl()) .post(body) .build(); - try (Response response = getHttpClient().execute(req)) { - if (response.isSuccessful()) { - JSONObject json = new JSONObject(response.body().string()); - if (json.optBoolean("result") && json.has("list")) { - List models = new ArrayList<>(); + + try (Response response = getHttpClient().execute(req)) { + if (!response.isSuccessful()) { + throw new HttpException(response.code(), response.message()); + } + + JSONObject json = new JSONObject(response.body().string()); + log.debug("WinkTv: Parsed JSON: {}", json.toString()); + if (!json.optBoolean("result") || !json.has("list")) { + return Collections.emptyList(); + } + JSONArray results = json.getJSONArray("list"); if (results.length() == 0) { return Collections.emptyList(); } + + List models = new ArrayList<>(); for (int i = 0; i < results.length(); i++) { JSONObject result = results.getJSONObject(i); WinkTvModel model = createModel(result.optString("userId")); model.setPreview(result.optString("thumbUrl")); models.add(model); } + return models; - } else { - return Collections.emptyList(); } - } else { - throw new HttpException(response.code(), response.message()); - } - } + + // try (Response response = getHttpClient().execute(req)) { + // if (response.isSuccessful()) { + // JSONObject json = new JSONObject(response.body().string()); + // if (json.optBoolean("result") && json.has("list")) { + // List models = new ArrayList<>(); + // JSONArray results = json.getJSONArray("list"); + // if (results.length() == 0) { + // return Collections.emptyList(); + // } + // for (int i = 0; i < results.length(); i++) { + // JSONObject result = results.getJSONObject(i); + // WinkTvModel model = createModel(result.optString("userId")); + // model.setPreview(result.optString("thumbUrl")); + // models.add(model); + // } + // return models; + // } else { + // return Collections.emptyList(); + // } + // } else { + // throw new HttpException(response.code(), response.message()); + // } + // } } @Override @@ -165,19 +197,21 @@ public class WinkTv extends AbstractSite { @Override public Model createModelFromUrl(String url) { - String[] patterns = { + String[] patterns = new String[]{ "https://.*?winktv.co.kr/live/play/([_a-zA-Z0-9]+)", "https://.*?winktv.co.kr/channel/([_a-zA-Z0-9]+)", "https://.*?pandalive.co.kr/live/play/([_a-zA-Z0-9]+)", "https://.*?pandalive.co.kr/channel/([_a-zA-Z0-9]+)" }; - for (String p : patterns) { - Matcher m = Pattern.compile(p).matcher(url); + + for (String pattern : patterns) { + Matcher m = Pattern.compile(pattern).matcher(url); if (m.matches()) { String modelName = m.group(1); return createModel(modelName); } } + return super.createModelFromUrl(url); } } diff --git a/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java b/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java index e61ba7d9..33e65c6b 100644 --- a/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java +++ b/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java @@ -9,15 +9,12 @@ import ctbrec.Config; import ctbrec.io.HttpException; import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.StreamSource; +import ctbrec.recorder.download.hls.HlsdlDownload; import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import okhttp3.FormBody; -import okhttp3.Request; -import okhttp3.Response; -import org.json.JSONObject; - +// import ctbrec.sites.winktv.WinkTvHttpClient; +// import lombok.Getter; +// import lombok.Setter; +import static ctbrec.io.HttpConstants.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -26,11 +23,20 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ExecutionException; +import java.nio.charset.StandardCharsets; +import lombok.extern.slf4j.Slf4j; + +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.Response; + +import org.json.JSONObject; + import static ctbrec.Model.State.*; -import static ctbrec.io.HttpConstants.*; -import static java.nio.charset.StandardCharsets.UTF_8; @Slf4j public class WinkTvModel extends AbstractModel { @@ -38,11 +44,8 @@ public class WinkTvModel extends AbstractModel { private static final String KEY_MEDIA = "media"; private int[] resolution = new int[]{0, 0}; - @Getter - @Setter private boolean adult = false; private transient JSONObject modelInfo; - private transient Instant lastInfoRequest = Instant.EPOCH; @Override @@ -50,6 +53,7 @@ public class WinkTvModel extends AbstractModel { if (ignoreCache) { try { JSONObject json = getModelInfo(); + log.debug("WinkTvModel-isOnline: {}", json.toString()); if (json.has(KEY_MEDIA)) { JSONObject media = json.getJSONObject(KEY_MEDIA); boolean isLive = media.optBoolean("isLive"); @@ -71,15 +75,16 @@ public class WinkTvModel extends AbstractModel { @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { - if (!failFast || onlineState == UNKNOWN) { - try { - onlineState = isOnline(true) ? ONLINE : OFFLINE; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - onlineState = OFFLINE; - } catch (IOException | ExecutionException e) { - onlineState = OFFLINE; - } + if (failFast && onlineState != UNKNOWN) { + return onlineState; + } + try { + onlineState = isOnline(true) ? ONLINE : OFFLINE; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + onlineState = OFFLINE; + } catch (IOException | ExecutionException e) { + onlineState = OFFLINE; } return onlineState; } @@ -98,8 +103,12 @@ public class WinkTvModel extends AbstractModel { if (playlist.hasStreamInfo()) { StreamSource src = new StreamSource(); src.setBandwidth(playlist.getStreamInfo().getBandwidth()); - src.setHeight(playlist.getStreamInfo().getResolution().height); - src.setWidth(playlist.getStreamInfo().getResolution().width); + src.setHeight(Optional.ofNullable(playlist.getStreamInfo().getResolution()) + .map(res -> res.height) + .orElse(0)); + src.setWidth(Optional.ofNullable(playlist.getStreamInfo().getResolution()) + .map(res -> res.width) + .orElse(0)); src.setMediaPlaylistUrl(playlist.getUri()); log.trace("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); @@ -113,11 +122,17 @@ public class WinkTvModel extends AbstractModel { Request req = new Request.Builder() .url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .header(ACCEPT, "*/*") + .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) + .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) + .header(REFERER, this.getUrl()) + .header(ORIGIN, this.getSite().getBaseUrl()) .build(); + try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); + InputStream inputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); MasterPlaylist master = playlist.getMasterPlaylist(); @@ -130,36 +145,13 @@ public class WinkTvModel extends AbstractModel { private String getMasterPlaylistUrl() throws IOException { JSONObject json = getModelInfo(); - JSONObject info = json.getJSONObject("bjInfo"); - long userIdx = info.optLong("idx"); - String url = "https://api.winktv.co.kr/v1/live/play"; - FormBody body = new FormBody.Builder() - .add("action", "watch") - .add("userIdx", String.valueOf(userIdx)) - .add("password", "") - .build(); - Request req = new Request.Builder() - .url(url) - .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) - .header(ACCEPT, MIMETYPE_APPLICATION_JSON) - .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) - .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) - .header(REFERER, getUrl()) - .header(ORIGIN, getSite().getBaseUrl()) - .post(body) - .build(); - try (Response response = getSite().getHttpClient().execute(req)) { - if (response.isSuccessful()) { - JSONObject jsonResponse = new JSONObject(response.body().string()); - JSONObject playlist = jsonResponse.getJSONObject("PlayList"); - JSONObject hls = playlist.getJSONArray("hls").getJSONObject(0); - String hlsUrl = hls.optString("url"); - return hlsUrl; - } else { - log.debug("Error while get master playlist url for {}: {}", getName(), response.body().string()); - throw new HttpException(response.code(), response.message()); - } + if (json.has("PlayList")) { + JSONObject playlist = json.getJSONObject("PlayList"); + JSONObject hls = playlist.getJSONArray("hls").getJSONObject(0); + String hlsUrl = hls.optString("url") + "&player_version=1.20.0"; + return hlsUrl; } + throw new IOException("Master playlist URL not found"); } @Override @@ -179,18 +171,21 @@ public class WinkTvModel extends AbstractModel { } private JSONObject getModelInfo() throws IOException { - if (modelInfo == null || Duration.between(lastInfoRequest, Instant.now()).getSeconds() < 5) { - lastInfoRequest = Instant.now(); - modelInfo = loadModelInfo(); + if (Objects.nonNull(modelInfo) && Duration.between(lastInfoRequest, Instant.now()).getSeconds() < 5L) { + return modelInfo; } + lastInfoRequest = Instant.now(); + modelInfo = loadModelInfo(); return modelInfo; } private JSONObject loadModelInfo() throws IOException { - String url = "https://api.winktv.co.kr/v1/member/bj"; + String url = "https://api.winktv.co.kr/v1/live/play"; FormBody body = new FormBody.Builder() .add("userId", getName()) - .add("info", KEY_MEDIA) + .add("action", "watch") + .add("password", "") + .add("shareLinkType", "") .build(); Request req = new Request.Builder() .url(url) @@ -205,6 +200,7 @@ public class WinkTvModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { JSONObject jsonResponse = new JSONObject(response.body().string()); + log.debug("WinkTvModel-loadModelInfo: {}", jsonResponse.toString()); return jsonResponse; } else { throw new HttpException(response.code(), response.message()); @@ -212,6 +208,19 @@ public class WinkTvModel extends AbstractModel { } } + public String getPreviewURL() throws IOException { + JSONObject json = this.getModelInfo(); + if (json.has("media")) { + JSONObject media = json.getJSONObject("media"); + return media.optString("ivsThumbnail"); + } + if (json.has("bjInfo")) { + JSONObject info = json.getJSONObject("bjInfo"); + return info.optString("thumbUrl"); + } + return ""; + } + @Override public boolean follow() throws IOException { return false; @@ -222,6 +231,14 @@ public class WinkTvModel extends AbstractModel { return false; } + public boolean isAdult() { + return adult; + } + + public void setAdult(boolean a) { + adult = a; + } + @Override public void receiveTip(Double tokens) throws IOException { // not implemented @@ -236,6 +253,9 @@ public class WinkTvModel extends AbstractModel { @Override public RecordingProcess createDownload() { - return new MergedFfmpegHlsDownload(getSite().getHttpClient()); + if (Config.getInstance().getSettings().useHlsdl) { + return new HlsdlDownload(); + } + return new MergedFfmpegHlsDownload(new WinkTvHttpClient(Config.getInstance())); } }