SC Spy tests

This commit is contained in:
jafea7 2025-04-21 15:45:45 +10:00
parent 5fde8f8197
commit 1d44af51bd
2 changed files with 197 additions and 47 deletions

View File

@ -6,6 +6,8 @@ import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.PlaylistData; import com.iheartradio.m3u8.data.PlaylistData;
import ctbrec.AbstractModel; import ctbrec.AbstractModel;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.StringUtil; import ctbrec.StringUtil;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import ctbrec.recorder.download.RecordingProcess; import ctbrec.recorder.download.RecordingProcess;
@ -29,6 +31,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static ctbrec.Model.State.*; import static ctbrec.Model.State.*;
@ -45,35 +48,59 @@ public class StripchatModel extends AbstractModel {
@Override @Override
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
if (ignoreCache) { JSONObject info;
JSONObject jsonResponse = getModelInfo(); if (ignoreCache && (info = getModelInfo()).has("user")) {
if (jsonResponse.has("user")) { JSONObject cam;
JSONObject user = jsonResponse.getJSONObject("user").getJSONObject("user"); JSONObject user = info.getJSONObject("user").getJSONObject("user");
String status = user.optString("status"); String status = user.optString("status");
mapOnlineState(status); mapOnlineState(status);
if (onlineState == OFFLINE) { if (onlineState == OFFLINE) {
setLastSeen(user.optString("statusChangedAt")); setLastSeen(user.optString("statusChangedAt"));
}
if (isBanned(user)) {
log.debug("Model inactive or deleted: {}", getName());
setMarkedForLaterRecording(true);
}
if ((onlineState == PRIVATE) && jsonResponse.has("cam")) {
JSONObject cam = jsonResponse.getJSONObject("cam");
if (StringUtil.isNotBlank(cam.optString(KEY_MODEL_TOKEN))) {
setOnlineState(ONLINE);
return true;
}
}
} }
if (jsonResponse.optString("error").equals("Not Found")) { if (isBanned(user)) {
log.debug("Model inactive or deleted: {}", getName());
// Config.getInstance().setModelNotes(this, "Model inactive or deleted");
setMarkedForLaterRecording(true); setMarkedForLaterRecording(true);
setOnlineState(OFFLINE); }
if (onlineState == PRIVATE && info.has("cam") && StringUtil.isNotBlank((cam = info.getJSONObject("cam")).optString(KEY_MODEL_TOKEN))) {
setOnlineState(ONLINE);
return true;
} }
} }
return onlineState == ONLINE; return onlineState == ONLINE;
} }
// @Override
// public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
// JSONObject jsonResponse = getModelInfo();
// if (ignoreCache) {
// if (jsonResponse.has("user")) {
// JSONObject user = jsonResponse.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());
// setMarkedForLaterRecording(true);
// }
// if ((onlineState == PRIVATE) && jsonResponse.has("cam")) {
// JSONObject cam = jsonResponse.getJSONObject("cam");
// if (StringUtil.isNotBlank(cam.optString(KEY_MODEL_TOKEN))) {
// setOnlineState(ONLINE);
// return true;
// }
// }
// }
// if (jsonResponse.optString("error").equals("Not Found")) {
// setMarkedForLaterRecording(true);
// setOnlineState(OFFLINE);
// }
// }
// return onlineState == ONLINE;
// }
private boolean isBanned(JSONObject user) { private boolean isBanned(JSONObject user) {
boolean isDeleted = user.optBoolean("isDeleted", false); boolean isDeleted = user.optBoolean("isDeleted", false);
boolean isApprovedModel = user.optBoolean("isApprovedModel", true); boolean isApprovedModel = user.optBoolean("isApprovedModel", true);
@ -92,17 +119,45 @@ public class StripchatModel extends AbstractModel {
private void mapOnlineState(String status) { private void mapOnlineState(String status) {
switch (status) { switch (status) {
case "public" -> setOnlineState(ONLINE); case "public": {
case "idle" -> setOnlineState(AWAY); setOnlineState(Model.State.ONLINE);
case "private", "p2p", "groupShow", "virtualPrivate" -> setOnlineState(PRIVATE); break;
case "off" -> setOnlineState(OFFLINE); }
default -> { 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()); log.debug("Unknown online state {} for model {}", status, getName());
setOnlineState(OFFLINE); setOnlineState(Model.State.OFFLINE);
} }
} }
} }
// private void mapOnlineState(String status) {
// switch (status) {
// case "public" -> setOnlineState(ONLINE);
// case "idle" -> setOnlineState(AWAY);
// case "private", "p2p", "groupShow", "virtualPrivate" -> setOnlineState(PRIVATE);
// case "off" -> setOnlineState(OFFLINE);
// default -> {
// log.debug("Unknown online state {} for model {}", status, getName());
// setOnlineState(OFFLINE);
// }
// }
// }
private JSONObject getModelInfo() throws IOException { private JSONObject getModelInfo() throws IOException {
if (Objects.nonNull(modelInfo) && Duration.between(lastInfoRequest, Instant.now()).getSeconds() < 5) { if (Objects.nonNull(modelInfo) && Duration.between(lastInfoRequest, Instant.now()).getSeconds() < 5) {
return modelInfo; return modelInfo;
@ -113,7 +168,7 @@ public class StripchatModel extends AbstractModel {
} }
private JSONObject loadModelInfo() throws IOException { private JSONObject loadModelInfo() throws IOException {
String url = getSite().getBaseUrl() + "/api/front/v2/models/username/" + getName() + "/cam?timezoneOffset=0&triggerRequest=loadCam&uniq=" + getUniq(); String url = getSite().getBaseUrl() + "/api/front/v2/models/username/" + getName() + "/cam?timezoneOffset=0&triggerRequest=loadCam&uniq=" + StripchatUtil.getUniq();
Request req = new Request.Builder() Request req = new Request.Builder()
.url(url) .url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
@ -123,19 +178,70 @@ public class StripchatModel extends AbstractModel {
.header(REFERER, getUrl()) .header(REFERER, getUrl())
.header(ORIGIN, getSite().getBaseUrl()) .header(ORIGIN, getSite().getBaseUrl())
.build(); .build();
try (Response response = site.getHttpClient().execute(req)) { try (Response response = site.getHttpClient().execute(req);){
if (response.isSuccessful()) { if (response.isSuccessful()) {
JSONObject jsonResponse = new JSONObject(response.body().string()); JSONObject jsonResponse = new JSONObject(response.body().string());
return jsonResponse; 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> modelGroup = site.getRecorder().getModelGroup(this);
if (modelGroup.isPresent()) {
modelGroup.get().add(newUrl);
}
// Config.getInstance().setModelNotes(this, "Old username: " + oldName);
setName(newName);
setUrl(newUrl);
log.warn("Model {} renamed to {}", (Object)oldName, (Object)newName);
}
}
catch (Exception exception) {
// empty catch block
} }
} }
@Override @Override
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
log.debug("getStreamSources: modelInfo = \n{}", modelInfo.toString(2)); // Added
String url = getMasterPlaylistUrl(); String url = getMasterPlaylistUrl();
log.debug("getStreamSources: url = {}", url); // Added
MasterPlaylist masterPlaylist = getMasterPlaylist(url); MasterPlaylist masterPlaylist = getMasterPlaylist(url);
List<StreamSource> streamSources = extractStreamSources(masterPlaylist); List<StreamSource> streamSources = extractStreamSources(masterPlaylist);
try { try {
@ -166,10 +272,10 @@ public class StripchatModel extends AbstractModel {
src.setHeight(playlist.getStreamInfo().getResolution().height); src.setHeight(playlist.getStreamInfo().getResolution().height);
src.setWidth(playlist.getStreamInfo().getResolution().width); src.setWidth(playlist.getStreamInfo().getResolution().width);
src.setMediaPlaylistUrl(playlist.getUri()); src.setMediaPlaylistUrl(playlist.getUri());
if (src.getMediaPlaylistUrl().contains("?")) { // if (src.getMediaPlaylistUrl().contains("?")) {
src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))); // src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?')));
} // }
log.trace("Media playlist {}", src.getMediaPlaylistUrl()); log.debug("Media playlist {}", src.getMediaPlaylistUrl());
sources.add(src); sources.add(src);
} }
} }
@ -177,7 +283,6 @@ public class StripchatModel extends AbstractModel {
} }
private MasterPlaylist getMasterPlaylist(String url) throws IOException, ParseException, PlaylistException { private MasterPlaylist getMasterPlaylist(String url) throws IOException, ParseException, PlaylistException {
log.trace("Loading master playlist {}", url);
Request req = new Request.Builder() Request req = new Request.Builder()
.url(url) .url(url)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
@ -185,20 +290,44 @@ public class StripchatModel extends AbstractModel {
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
String body = response.body().string(); String body = response.body().string();
log.trace(body); log.debug(body);
InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8)); InputStream inputStream = new ByteArrayInputStream(body.getBytes(UTF_8));
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
Playlist playlist = parser.parse(); Playlist playlist = parser.parse();
MasterPlaylist master = playlist.getMasterPlaylist(); MasterPlaylist master = playlist.getMasterPlaylist();
return master; return master;
} else { } // else {
throw new HttpException(response.code(), response.message()); 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 { private String getMasterPlaylistUrl() throws IOException {
JSONObject info = getModelInfo(); JSONObject info = getModelInfo();
log.debug("getMasterPlaylistUrl: modelInfo = \n{}", info.toString(2)); // Added
if (info.has("user")) { if (info.has("user")) {
JSONObject user = info.getJSONObject("user").getJSONObject("user"); JSONObject user = info.getJSONObject("user").getJSONObject("user");
long id = user.optLong("id"); long id = user.optLong("id");
@ -210,10 +339,16 @@ public class StripchatModel extends AbstractModel {
String token = ""; String token = "";
if (info.has("cam")) { if (info.has("cam")) {
JSONObject cam = info.getJSONObject("cam"); JSONObject cam = info.getJSONObject("cam");
if (StringUtil.isNotBlank(cam.optString(KEY_MODEL_TOKEN))) { token = cam.optString(KEY_MODEL_TOKEN);
token = "&aclAuth=" + cam.getString(KEY_MODEL_TOKEN); log.debug("getMasterPlaylistUrl: Cam token: {}", token); // Added
if (StringUtil.isNotBlank(token)) {
token = "&aclAuth=" + token;
log.debug("Spy start for {}", getName()); 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}"; 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); return MessageFormat.format(hlsUrlTemplate, String.valueOf(id), vrSuffix, token);
@ -257,13 +392,21 @@ public class StripchatModel extends AbstractModel {
JSONObject info = getModelInfo(); JSONObject info = getModelInfo();
JSONObject user = info.getJSONObject("user").getJSONObject("user"); JSONObject user = info.getJSONObject("user").getJSONObject("user");
long id = user.optLong("id"); long id = user.optLong("id");
long userId = client.getUserId();
String url = Stripchat.getBaseUri() + "/api/front/users/" + client.getUserId() + "/favorites/" + id; 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() JSONObject requestParams = new JSONObject()
.put("csrfToken", client.getCsrfToken()) .put("csrfToken", client.getCsrfToken())
.put("csrfTimestamp", client.getCsrfTimestamp()) .put("csrfTimestamp", client.getCsrfTimestamp())
.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp()) .put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp())
.put("uniq", getUniq()) .put("uniq", getUniq())
.put("an", new JSONArray()
.put(eventData))
.put("ampl", client.getAmpl()); .put("ampl", client.getAmpl());
RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON); RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON);
Request request = new Request.Builder() Request request = new Request.Builder()
@ -291,15 +434,22 @@ public class StripchatModel extends AbstractModel {
JSONObject info = getModelInfo(); JSONObject info = getModelInfo();
JSONObject user = info.getJSONObject("user").getJSONObject("user"); JSONObject user = info.getJSONObject("user").getJSONObject("user");
long id = user.optLong("id"); long id = user.optLong("id");
long userId = client.getUserId();
JSONArray favoriteIds = new JSONArray().put(id); JSONArray favoriteIds = new JSONArray().put(id);
favoriteIds.put(id); // favoriteIds.put(id);
String url = Stripchat.getBaseUri() + "/api/front/users/" + client.getUserId() + "/favorites"; 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() JSONObject requestParams = new JSONObject()
.put("favoriteIds", favoriteIds) .put("favoriteIds", favoriteIds)
.put("csrfToken", client.getCsrfToken()) .put("csrfToken", client.getCsrfToken())
.put("csrfTimestamp", client.getCsrfTimestamp()) .put("csrfTimestamp", client.getCsrfTimestamp())
.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp()) .put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp())
.put("an", new JSONArray().put(eventData))
.put("uniq", getUniq()); .put("uniq", getUniq());
RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON); RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON);
Request request = new Request.Builder() Request request = new Request.Builder()

View File

@ -1,6 +1,6 @@
package ctbrec.sites.stripchat; package ctbrec.sites.stripchat;
// import ctbrec.sites.stripchat.Stripchat; import ctbrec.sites.stripchat.Stripchat;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant; import java.time.Instant;
import java.util.Random; import java.util.Random;