From 73e1337a4328c8b66d5a1d82a33f5096ceb22a02 Mon Sep 17 00:00:00 2001 From: jafea7 Date: Mon, 21 Apr 2025 15:45:45 +1000 Subject: [PATCH] Fix SC spy capture --- .../sites/stripchat/StripchatModel.java | 187 ++++++++++++++++-- 1 file changed, 169 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java index 93dd95d6..cf835ef2 100644 --- a/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java +++ b/common/src/main/java/ctbrec/sites/stripchat/StripchatModel.java @@ -6,6 +6,8 @@ import com.iheartradio.m3u8.data.Playlist; import com.iheartradio.m3u8.data.PlaylistData; import ctbrec.AbstractModel; import ctbrec.Config; +// import ctbrec.Model; +import ctbrec.ModelGroup; import ctbrec.StringUtil; import ctbrec.io.HttpException; import ctbrec.recorder.download.RecordingProcess; @@ -29,6 +31,7 @@ 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.*; @@ -43,10 +46,34 @@ public class StripchatModel extends AbstractModel { private transient JSONObject modelInfo; private transient Instant lastInfoRequest = Instant.EPOCH; + // @Override + // public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + // JSONObject info; + // if (ignoreCache && (info = getModelInfo()).has("user")) { + // JSONObject cam; + // JSONObject user = info.getJSONObject("user").getJSONObject("user"); + // String status = user.optString("status"); + // mapOnlineState(status); + // if (onlineState == OFFLINE) { + // setLastSeen(user.optString("statusChangedAt")); + // } + // if (isBanned(user)) { + // log.debug("Model inactive or deleted: {}", getName()); + // // Config.getInstance().setModelNotes(this, "Model inactive or deleted"); // <- from v5.0.24 + // setMarkedForLaterRecording(true); + // } + // if (onlineState == PRIVATE && info.has("cam") && StringUtil.isNotBlank((cam = info.getJSONObject("cam")).optString(KEY_MODEL_TOKEN))) { + // setOnlineState(ONLINE); + // return true; + // } + // } + // return onlineState == ONLINE; + // } + @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + JSONObject jsonResponse = getModelInfo(); if (ignoreCache) { - JSONObject jsonResponse = getModelInfo(); if (jsonResponse.has("user")) { JSONObject user = jsonResponse.getJSONObject("user").getJSONObject("user"); String status = user.optString("status"); @@ -90,6 +117,34 @@ public class StripchatModel extends AbstractModel { } } + // private void mapOnlineState(String status) { + // switch (status) { + // case "public": { + // setOnlineState(Model.State.ONLINE); + // break; + // } + // case "idle": { + // setOnlineState(Model.State.AWAY); + // break; + // } + // case "private": + // case "p2p": + // case "groupShow": + // case "virtualPrivate": { + // setOnlineState(Model.State.PRIVATE); + // break; + // } + // case "off": { + // setOnlineState(Model.State.OFFLINE); + // break; + // } + // default: { + // log.debug("Unknown online state {} for model {}", status, getName()); + // setOnlineState(Model.State.OFFLINE); + // } + // } + // } + private void mapOnlineState(String status) { switch (status) { case "public" -> setOnlineState(ONLINE); @@ -123,19 +178,70 @@ public class StripchatModel extends AbstractModel { .header(REFERER, getUrl()) .header(ORIGIN, getSite().getBaseUrl()) .build(); - try (Response response = site.getHttpClient().execute(req)) { + try (Response response = site.getHttpClient().execute(req);){ if (response.isSuccessful()) { - JSONObject jsonResponse = new JSONObject(response.body().string()); + JSONObject jsonResponse = new JSONObject(response.body().string()); return jsonResponse; - } else { - throw new HttpException(response.code(), response.message()); } + if (response.code() == 404) { + checkIfRenamed(response.body().string()); + } + throw new HttpException(response.code(), response.message()); + } + } + + // private JSONObject loadModelInfo() throws IOException { + // String url = getSite().getBaseUrl() + "/api/front/v2/models/username/" + getName() + "/cam?timezoneOffset=0&triggerRequest=loadCam&uniq=" + getUniq(); + // Request req = new Request.Builder() + // .url(url) + // .header(ACCEPT, MIMETYPE_APPLICATION_JSON) + // .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) + // .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) + // .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + // .header(REFERER, getUrl()) + // .header(ORIGIN, getSite().getBaseUrl()) + // .build(); + // try (Response response = site.getHttpClient().execute(req)) { + // if (response.isSuccessful()) { + // JSONObject jsonResponse = new JSONObject(response.body().string()); + // return jsonResponse; + // } else { + // throw new HttpException(response.code(), response.message()); + // } + // } + // } + + private void checkIfRenamed(String responseText) { + try { + String newName; + JSONObject data; + JSONObject jsonError = new JSONObject(responseText); + if (jsonError.has("data") && (data = jsonError.getJSONObject("data")).has("newUsername") && StringUtil.isNotBlank(newName = data.optString("newUsername"))) { + String oldName = getName(); + String newUrl = site.getBaseUrl() + "/" + newName; + Optional modelGroup = site.getRecorder().getModelGroup(this); + if (modelGroup.isPresent()) { + modelGroup.get().add(newUrl); + } + // Config.getInstance().setModelNotes(this, "Old username: " + oldName); // <- from v5.0.24 + setName(newName); + setUrl(newUrl); + log.warn("Model {} renamed to {}", (Object)oldName, (Object)newName); + } + } + catch (Exception exception) { + // empty catch block } } @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { + log.debug("getStreamSources: modelInfo = \n{}", modelInfo.toString(2)); // Added + String url = getMasterPlaylistUrl(); + + log.debug("getStreamSources: url = {}", url); // Added + MasterPlaylist masterPlaylist = getMasterPlaylist(url); List streamSources = extractStreamSources(masterPlaylist); try { @@ -166,10 +272,11 @@ public class StripchatModel extends AbstractModel { src.setHeight(playlist.getStreamInfo().getResolution().height); src.setWidth(playlist.getStreamInfo().getResolution().width); src.setMediaPlaylistUrl(playlist.getUri()); - if (src.getMediaPlaylistUrl().contains("?")) { - src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))); - } - log.trace("Media playlist {}", src.getMediaPlaylistUrl()); + // v5.3.2 commented out, truncating URL before auth token + // if (src.getMediaPlaylistUrl().contains("?")) { + // src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))); + // } + log.debug("Media playlist {}", src.getMediaPlaylistUrl()); sources.add(src); } } @@ -177,7 +284,6 @@ public class StripchatModel extends AbstractModel { } private MasterPlaylist getMasterPlaylist(String url) throws IOException, ParseException, PlaylistException { - log.trace("Loading master playlist {}", url); Request req = new Request.Builder() .url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) @@ -185,20 +291,44 @@ public class StripchatModel extends AbstractModel { try (Response response = getSite().getHttpClient().execute(req)) { if (response.isSuccessful()) { String body = response.body().string(); - log.trace(body); + log.debug(body); InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); MasterPlaylist master = playlist.getMasterPlaylist(); return master; - } else { - throw new HttpException(response.code(), response.message()); - } + } // else { + throw new HttpException(response.code(), response.message()); + // } } } + // private String getMasterPlaylistUrl() throws IOException { + // JSONObject info = getModelInfo(); + // if (info.has("user")) { + // JSONObject cam; + // JSONObject user = info.getJSONObject("user").getJSONObject("user"); + // long modelId = user.optLong("id"); + + // boolean saveVR = Config.getInstance().getSettings().stripchatVR; + // boolean isVRStream = user.optBoolean("isVr", false); + // String vrSuffix = (saveVR && isVRStream) ? "_vr" : ""; + + // Object token = ""; + // if (info.has("cam") && StringUtil.isNotBlank((cam = info.getJSONObject("cam")).optString(KEY_MODEL_TOKEN))) { + // log.debug("Cam token: {}", token); // Added + // token = "&aclAuth=" + cam.getString(KEY_MODEL_TOKEN); + // log.debug("Spy start for {}", getName()); + // } + // String hlsUrlTemplate = "https://edge-hls.doppiocdn.com/hls/{0}/master/{0}_auto.m3u8?playlistType=Standart{1}"; + // return MessageFormat.format(hlsUrlTemplate, String.valueOf(modelId), token); + // } + // throw new IOException("Playlist URL not found"); + // } + private String getMasterPlaylistUrl() throws IOException { JSONObject info = getModelInfo(); + // log.debug("getMasterPlaylistUrl: modelInfo = \n{}", info.toString(2)); // Added if (info.has("user")) { JSONObject user = info.getJSONObject("user").getJSONObject("user"); long id = user.optLong("id"); @@ -210,10 +340,16 @@ public class StripchatModel extends AbstractModel { String token = ""; if (info.has("cam")) { JSONObject cam = info.getJSONObject("cam"); - if (StringUtil.isNotBlank(cam.optString(KEY_MODEL_TOKEN))) { - token = "&aclAuth=" + cam.getString(KEY_MODEL_TOKEN); + token = cam.optString(KEY_MODEL_TOKEN); + log.debug("getMasterPlaylistUrl: Cam token: {}", token); // Added + if (StringUtil.isNotBlank(token)) { + token = "&aclAuth=" + token; log.debug("Spy start for {}", getName()); - } + } + // if (StringUtil.isNotBlank(cam.optString(KEY_MODEL_TOKEN))) { + // token = "&aclAuth=" + cam.getString(KEY_MODEL_TOKEN); + // log.debug("Spy start for {}", getName()); + // } } String hlsUrlTemplate = "https://edge-hls.doppiocdn.com/hls/{0}{1}/master/{0}{1}_auto.m3u8?playlistType=Standart{2}"; return MessageFormat.format(hlsUrlTemplate, String.valueOf(id), vrSuffix, token); @@ -257,13 +393,21 @@ public class StripchatModel extends AbstractModel { JSONObject info = getModelInfo(); JSONObject user = info.getJSONObject("user").getJSONObject("user"); long id = user.optLong("id"); + long userId = client.getUserId(); String url = Stripchat.getBaseUri() + "/api/front/users/" + client.getUserId() + "/favorites/" + id; + JSONObject eventData = StripchatUtil.getEventData() + .put("eventName", "fav") + .put("f.action", "add") + .put("f.userId", userId) + .put("f.modelId", id); JSONObject requestParams = new JSONObject() .put("csrfToken", client.getCsrfToken()) .put("csrfTimestamp", client.getCsrfTimestamp()) .put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp()) .put("uniq", getUniq()) + .put("an", new JSONArray() + .put(eventData)) .put("ampl", client.getAmpl()); RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON); Request request = new Request.Builder() @@ -291,15 +435,22 @@ public class StripchatModel extends AbstractModel { JSONObject info = getModelInfo(); JSONObject user = info.getJSONObject("user").getJSONObject("user"); long id = user.optLong("id"); + long userId = client.getUserId(); JSONArray favoriteIds = new JSONArray().put(id); - favoriteIds.put(id); + // favoriteIds.put(id); // duplicated above? String url = Stripchat.getBaseUri() + "/api/front/users/" + client.getUserId() + "/favorites"; + JSONObject eventData = StripchatUtil.getEventData() + .put("eventName", "fav") + .put("f.action", "remove") + .put("f.userId", userId) + .put("f.modelId", id); JSONObject requestParams = new JSONObject() .put("favoriteIds", favoriteIds) .put("csrfToken", client.getCsrfToken()) .put("csrfTimestamp", client.getCsrfTimestamp()) .put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp()) + .put("an", new JSONArray().put(eventData)) .put("uniq", getUniq()); RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON); Request request = new Request.Builder()