From 9ff933daf14ba001a2cb38568d42c207d1553f3a Mon Sep 17 00:00:00 2001 From: Jafea7 Date: Sun, 4 May 2025 21:17:44 +1000 Subject: [PATCH] Fix WinkTv capture --- common/src/main/java/ctbrec/Settings.java | 2 +- .../main/java/ctbrec/sites/winktv/WinkTv.java | 58 +++++---- .../java/ctbrec/sites/winktv/WinkTvModel.java | 123 ++++++++++-------- 3 files changed, 105 insertions(+), 78 deletions(-) diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index d82df466..4584cc40 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -77,7 +77,7 @@ public class Settings { public int defaultPriority = 50; public boolean deleteOrphanedRecordingMetadata = true; public boolean determineResolution = false; - public List disabledSites = new ArrayList<>(Arrays.asList("Streamray", "WinkTv")); + public List disabledSites = new ArrayList<>(Arrays.asList("Streamray")); public String downloadFilename = "$sanitize(${modelName})_$format(${localDateTime})"; public List dreamcamTabs = new ArrayList<>(Arrays.asList("girls")); public List eventHandlers = new ArrayList<>(); diff --git a/common/src/main/java/ctbrec/sites/winktv/WinkTv.java b/common/src/main/java/ctbrec/sites/winktv/WinkTv.java index ee7efa37..5ac5a81b 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,28 +132,32 @@ 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<>(); - 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 { + 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; } } @@ -165,19 +173,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 46233dc1..b6f436c9 100644 --- a/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java +++ b/common/src/main/java/ctbrec/sites/winktv/WinkTvModel.java @@ -9,9 +9,10 @@ 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.Getter; +// import lombok.Setter; import lombok.extern.slf4j.Slf4j; import okhttp3.FormBody; import okhttp3.Request; @@ -26,11 +27,13 @@ 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 static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; -import static java.nio.charset.StandardCharsets.UTF_8; +import java.nio.charset.StandardCharsets; @Slf4j public class WinkTvModel extends AbstractModel { @@ -38,11 +41,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 +50,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 +72,16 @@ public class WinkTvModel extends AbstractModel { @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { - if (!failFast || onlineState == UNKNOWN || onlineState == UNCHECKED) { - 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 +100,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 +119,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 +142,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 +168,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 +197,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 +205,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 +228,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 +250,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())); } }