Compare commits

..

10 Commits

Author SHA1 Message Date
reusedname cb44a32079 Set version to 5.3.3 2025-04-11 18:55:31 +05:00
reusedname df77c6faee cosmetic online state fix 2025-04-11 18:53:20 +05:00
reusedname 0aa5dadda7 SC model-dependent CDN host for master playlist 2025-04-11 18:52:48 +05:00
reusedname e583aad604 fixes
- fix empty host name in flaresolverr's host list
- update SC thumbnail url
2025-04-06 16:59:38 +05:00
reusedname 7a0dc3a371 new CB stream info url 2025-04-06 16:51:56 +05:00
reusedname c9fd95c247 fix flaresolverr's user agent not applying 2025-04-01 10:42:37 +05:00
reusedname 1f26371283 fix misplaced condition 2025-03-14 11:50:47 +05:00
reusedname f549580526 Update CB thumbnails host 2025-03-13 12:29:37 +05:00
reusedname f8613c8817 fix CB playlist URLs 2025-03-12 21:39:40 +05:00
reusedname cac8ee37d9 revert to also respecting order of models in a group 2025-03-12 18:56:34 +05:00
17 changed files with 115 additions and 58 deletions

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<version>5.3.2</version> <version>5.3.3</version>
<relativePath>../master</relativePath> <relativePath>../master</relativePath>
</parent> </parent>

View File

@ -15,6 +15,7 @@ import okhttp3.Request;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -35,7 +36,7 @@ public class ChaturbateApiUpdateService extends PaginatedScheduledService {
protected List<Model> call() throws Exception { protected List<Model> call() throws Exception {
var request = new Request.Builder() var request = new Request.Builder()
.url(url) .url(url)
.header(USER_AGENT, chaturbate.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, chaturbate.getHttpClient().getEffectiveUserAgent(URI.create(url).getHost()))
.header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.build(); .build();
try (var response = chaturbate.getHttpClient().execute(request)) { try (var response = chaturbate.getHttpClient().execute(request)) {

View File

@ -13,6 +13,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.Collections; import java.util.Collections;
import java.util.Objects; import java.util.Objects;
@ -35,7 +36,7 @@ public class ChaturbateElectronLoginDialog {
config.put("url", site.getBaseUrl() + "/auth/login/"); config.put("url", site.getBaseUrl() + "/auth/login/");
config.put("w", 640); config.put("w", 640);
config.put("h", 480); config.put("h", 480);
config.put("userAgent", site.getHttpClient().getEffectiveUserAgent()); config.put("userAgent", site.getHttpClient().getEffectiveUserAgent(URI.create(site.getBaseUrl()).getHost()));
var msg = new JSONObject(); var msg = new JSONObject();
msg.put("config", config); msg.put("config", config);
browser.run(msg, msgHandler); browser.run(msg, msgHandler);

View File

@ -14,6 +14,7 @@ import org.json.JSONObject;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -54,7 +55,7 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
.header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(USER_AGENT, chaturbate.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, chaturbate.getHttpClient().getEffectiveUserAgent(URI.create(pageUrl).getHost()))
.build(); .build();
try (var response = chaturbate.getHttpClient().execute(request)) { try (var response = chaturbate.getHttpClient().execute(request)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {

View File

@ -19,7 +19,7 @@ public abstract class AbstractStripchatUpdateService extends PaginatedScheduledS
if (timestamp == 0) { if (timestamp == 0) {
return model.optString("previewUrlThumbBig"); return model.optString("previewUrlThumbBig");
} }
return MessageFormat.format("https://img.strpst.com/thumbs/{0}/{1}", String.valueOf(timestamp), String.valueOf(id)); return MessageFormat.format("https://img.doppiocdn.net/thumbs/{0}/{1}", String.valueOf(timestamp), String.valueOf(id));
} }
protected List<String> createTags(JSONObject model) { protected List<String> createTags(JSONObject model) {

View File

@ -3,6 +3,7 @@ package ctbrec.ui.tabs.recorded;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.Recording; import ctbrec.Recording;
import ctbrec.Model.State;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.ui.JavaFxModel; import ctbrec.ui.JavaFxModel;
@ -327,7 +328,7 @@ public class RecordedModelsTab extends AbstractRecordedModelsTab implements TabS
if (Objects.equals(onlineModel, fxm)) { if (Objects.equals(onlineModel, fxm)) {
fxm.setOnlineProperty(true); fxm.setOnlineProperty(true);
try { try {
fxm.setOnlineStateProperty(onlineModel.getOnlineState(true)); fxm.setOnlineStateProperty(Model.State.ONLINE);
} catch (Exception e) {} } catch (Exception e) {}
break; break;
} }

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<version>5.3.2</version> <version>5.3.3</version>
<relativePath>../master</relativePath> <relativePath>../master</relativePath>
</parent> </parent>
@ -94,6 +94,11 @@
<artifactId>commons-collections</artifactId> <artifactId>commons-collections</artifactId>
<version>3.2.2</version> <version>3.2.2</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.7</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -26,7 +26,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -34,7 +33,6 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.time.*; import java.time.*;
import java.util.Optional;
import static ctbrec.io.HttpConstants.ACCEPT_ENCODING_GZIP; import static ctbrec.io.HttpConstants.ACCEPT_ENCODING_GZIP;
import static ctbrec.io.HttpConstants.CONTENT_ENCODING; import static ctbrec.io.HttpConstants.CONTENT_ENCODING;
@ -51,6 +49,7 @@ public abstract class HttpClient {
protected OkHttpClient client; protected OkHttpClient client;
protected Cache cache; protected Cache cache;
protected Config config; protected Config config;
@Getter
protected boolean loggedIn = false; protected boolean loggedIn = false;
protected long cacheSize; protected long cacheSize;
protected int cacheLifeTime = 600; protected int cacheLifeTime = 600;
@ -454,7 +453,11 @@ public abstract class HttpClient {
} }
// overridable default user agent (used for Flaresolverr) // overridable default user agent (used for Flaresolverr)
public String getEffectiveUserAgent() { public String getEffectiveUserAgent(String host) {
if (config.getSettings().flaresolverr.useForDomains.contains(host)) {
return config.getSettings().flaresolverr.userAgent;
} else {
return config.getSettings().httpUserAgent; return config.getSettings().httpUserAgent;
} }
}
} }

View File

@ -20,6 +20,8 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.List;
public class RecordingPreconditions { public class RecordingPreconditions {
@ -199,13 +201,16 @@ public class RecordingPreconditions {
// go through each model in group in descendind priority order, checking this model last amongst same-prio models // go through each model in group in descendind priority order, checking this model last amongst same-prio models
// this is to make sure that we only stop lower priority recordings in favor of this one. I.e. if same-prio model is recordnig, let it run // this is to make sure that we only stop lower priority recordings in favor of this one. I.e. if same-prio model is recordnig, let it run
for (var groupModel : modelGroup.get().getModelUrls().stream() var models = modelGroup.get().getModelUrls().stream()
.map(modelUrl -> getModelForUrl(modelUrl)) .map(modelUrl -> getModelForUrl(modelUrl))
.filter(x -> x.isPresent()) .filter(x -> x.isPresent())
.map(x -> x.get()) .map(x -> x.get())
.toList();
for (var groupModel : IntStream.range(0, models.size()).boxed()
.sorted(Comparator .sorted(Comparator
.comparing((Model m) -> m.getPriority()).reversed() // high to low .comparing((Integer i) -> models.get(i).getPriority()).reversed() // high to low
.thenComparing((Model m) -> m.getUrl().equals(model.getUrl()))) // this model last (false -> true) .thenComparing((Integer i) -> i)) // in group's order
.map((Integer i) -> models.get(i))
.toList()) { .toList()) {
if (model.getUrl().equals(groupModel.getUrl())) { if (model.getUrl().equals(groupModel.getUrl())) {
// no other model with higher prio is online, start recording // no other model with higher prio is online, start recording

View File

@ -11,6 +11,7 @@ import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
@ -55,7 +56,7 @@ public class Chaturbate extends AbstractSite {
ChaturbateModel m = new ChaturbateModel(this); ChaturbateModel m = new ChaturbateModel(this);
m.setName(normalizedName); m.setName(normalizedName);
m.setUrl(getBaseUrl() + '/' + normalizedName + '/'); m.setUrl(getBaseUrl() + '/' + normalizedName + '/');
m.setPreview("https://roomimg.stream.highwebmedia.com/ri/" + normalizedName + ".jpg?" + Instant.now().getEpochSecond()); m.setPreview("https://thumb.live.mmcdn.com/riw/" + normalizedName + ".jpg?" + Instant.now().getEpochSecond());
return m; return m;
} }
@ -69,7 +70,7 @@ public class Chaturbate extends AbstractSite {
String url = "https://chaturbate.com/p/" + username + "/"; String url = "https://chaturbate.com/p/" + username + "/";
Request req = new Request.Builder() Request req = new Request.Builder()
.url(url) .url(url)
.header(USER_AGENT, getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, getHttpClient().getEffectiveUserAgent(URI.create(url).getHost()))
.build(); .build();
try (Response resp = getHttpClient().execute(req)) { try (Response resp = getHttpClient().execute(req)) {
if (resp.isSuccessful()) { if (resp.isSuccessful()) {
@ -131,7 +132,7 @@ public class Chaturbate extends AbstractSite {
// search online models // search online models
Request req = new Request.Builder() Request req = new Request.Builder()
.url(url) .url(url)
.header(USER_AGENT, getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, getHttpClient().getEffectiveUserAgent(URI.create(url).getHost()))
.header(ACCEPT, "*/*") .header(ACCEPT, "*/*")
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(REFERER, getBaseUrl()) .header(REFERER, getBaseUrl())

View File

@ -8,6 +8,7 @@ import okhttp3.*;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.URI;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
@ -28,15 +29,6 @@ public class ChaturbateHttpClient extends HttpClient {
super("chaturbate", config); super("chaturbate", config);
} }
@Override
public String getEffectiveUserAgent() {
if (flaresolverr != null) {
return config.getSettings().flaresolverr.userAgent;
} else {
return config.getSettings().httpUserAgent;
}
}
private void extractCsrfToken(Request request) { private void extractCsrfToken(Request request) {
try { try {
Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken"); Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken");
@ -65,7 +57,7 @@ public class ChaturbateHttpClient extends HttpClient {
} }
Request login = new Request.Builder() Request login = new Request.Builder()
.url(Chaturbate.baseUrl + PATH) .url(Chaturbate.baseUrl + PATH)
.header(USER_AGENT, getEffectiveUserAgent()) .header(USER_AGENT, getEffectiveUserAgent(URI.create(Chaturbate.baseUrl).getHost()))
.build(); .build();
try (var initResponse = client.newCall(login).execute()) { try (var initResponse = client.newCall(login).execute()) {
String content = initResponse.body().string(); String content = initResponse.body().string();
@ -81,7 +73,7 @@ public class ChaturbateHttpClient extends HttpClient {
login = new Request.Builder() login = new Request.Builder()
.url(Chaturbate.baseUrl + PATH) .url(Chaturbate.baseUrl + PATH)
.header(REFERER, Chaturbate.baseUrl + PATH) .header(REFERER, Chaturbate.baseUrl + PATH)
.header(USER_AGENT, getEffectiveUserAgent()) .header(USER_AGENT, getEffectiveUserAgent(URI.create(Chaturbate.baseUrl).getHost()))
.post(body) .post(body)
.build(); .build();
@ -106,7 +98,7 @@ public class ChaturbateHttpClient extends HttpClient {
String url = "https://chaturbate.com/api/ts/chatmessages/pm_users/?offset=0"; String url = "https://chaturbate.com/api/ts/chatmessages/pm_users/?offset=0";
Request req = new Request.Builder() Request req = new Request.Builder()
.url(url) .url(url)
.header(USER_AGENT, getEffectiveUserAgent()) .header(USER_AGENT, getEffectiveUserAgent(URI.create(url).getHost()))
.build(); .build();
try (Response response = execute(req)) { try (Response response = execute(req)) {
boolean result = false; boolean result = false;

View File

@ -22,6 +22,7 @@ import java.io.ByteArrayInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URI;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
@ -30,11 +31,14 @@ import java.util.concurrent.ExecutionException;
import static ctbrec.Model.State.*; import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*; import static ctbrec.io.HttpConstants.*;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.regex.Pattern;
@Slf4j @Slf4j
public class ChaturbateModel extends AbstractModel { public class ChaturbateModel extends AbstractModel {
private static final String PUBLIC = "public"; private static final String PUBLIC = "public";
private static final Pattern serverPattern = Pattern.compile("live-.+amlst");
private int[] resolution = new int[2]; private int[] resolution = new int[2];
private transient StreamInfo streamInfo; private transient StreamInfo streamInfo;
private transient Instant lastStreamInfoRequest = Instant.EPOCH; private transient Instant lastStreamInfoRequest = Instant.EPOCH;
@ -75,7 +79,7 @@ public class ChaturbateModel extends AbstractModel {
private boolean isOffline() { private boolean isOffline() {
String normalizedName = getName().toLowerCase().trim(); String normalizedName = getName().toLowerCase().trim();
String previewUrl = "https://roomimg.stream.highwebmedia.com/ri/" + normalizedName + ".jpg?" + Instant.now().getEpochSecond(); String previewUrl = "https://thumb.live.mmcdn.com/riw/" + normalizedName + ".jpg?" + Instant.now().getEpochSecond();
if (offlineImageSize == 0) { if (offlineImageSize == 0) {
offlineImageSize = getOfflineImageSize(); // NOSONAR offlineImageSize = getOfflineImageSize(); // NOSONAR
} }
@ -85,7 +89,7 @@ public class ChaturbateModel extends AbstractModel {
private int getOfflineImageSize() { private int getOfflineImageSize() {
String[] names = {"Sophia", "Helena", "Olivia", "Natasha", "Emmy", "Jenny", "Diana", "Teresa", "Julia", "Polly", "Amanda"}; String[] names = {"Sophia", "Helena", "Olivia", "Natasha", "Emmy", "Jenny", "Diana", "Teresa", "Julia", "Polly", "Amanda"};
String randomName = names[RNG.nextInt(names.length)] + RNG.nextInt(99); String randomName = names[RNG.nextInt(names.length)] + RNG.nextInt(99);
String previewUrl = "https://roomimg.stream.highwebmedia.com/ri/" + randomName + ".jpg?" + Instant.now().getEpochSecond(); String previewUrl = "https://thumb.live.mmcdn.com/riw/" + randomName + ".jpg?" + Instant.now().getEpochSecond();
int imageSize = getImageSize(previewUrl); int imageSize = getImageSize(previewUrl);
if (imageSize == 0) { if (imageSize == 0) {
imageSize = 21971; imageSize = 21971;
@ -97,7 +101,7 @@ public class ChaturbateModel extends AbstractModel {
int imageSize = 0; int imageSize = 0;
Request req = new Request.Builder() Request req = new Request.Builder()
.url(url) .url(url)
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent(URI.create(url).getHost()))
.head() .head()
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
@ -190,7 +194,7 @@ public class ChaturbateModel extends AbstractModel {
.post(body) .post(body)
.header(REFERER, "https://chaturbate.com/" + getName() + "/") .header(REFERER, "https://chaturbate.com/" + getName() + "/")
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent("chaturbate.com"))
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
@ -211,7 +215,7 @@ public class ChaturbateModel extends AbstractModel {
src.setBandwidth(playlist.getStreamInfo().getBandwidth()); src.setBandwidth(playlist.getStreamInfo().getBandwidth());
src.setHeight(playlist.getStreamInfo().getResolution().height); src.setHeight(playlist.getStreamInfo().getResolution().height);
src.setWidth(playlist.getStreamInfo().getResolution().width); src.setWidth(playlist.getStreamInfo().getResolution().width);
String masterUrl = streamInfo.url; String masterUrl = streamInfo.hls_source;
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
String segmentUri = baseUrl + playlist.getUri(); String segmentUri = baseUrl + playlist.getUri();
src.setMediaPlaylistUrl(segmentUri); src.setMediaPlaylistUrl(segmentUri);
@ -239,7 +243,7 @@ public class ChaturbateModel extends AbstractModel {
// do an initial request to get cookies // do an initial request to get cookies
Request req = new Request.Builder() Request req = new Request.Builder()
.url(getUrl()) .url(getUrl())
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent(URI.create(getUrl()).getHost()))
.build(); .build();
Response resp = site.getHttpClient().execute(req); Response resp = site.getHttpClient().execute(req);
resp.close(); resp.close();
@ -258,7 +262,7 @@ public class ChaturbateModel extends AbstractModel {
.header(ACCEPT, "*/*") .header(ACCEPT, "*/*")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5") .header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(REFERER, getUrl()) .header(REFERER, getUrl())
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent(URI.create(url).getHost()))
.header("X-CSRFToken", ((ChaturbateHttpClient) site.getHttpClient()).getToken()) .header("X-CSRFToken", ((ChaturbateHttpClient) site.getHttpClient()).getToken())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.build(); .build();
@ -295,14 +299,9 @@ public class ChaturbateModel extends AbstractModel {
if (streamInfo != null && Duration.between(lastStreamInfoRequest, Instant.now()).getSeconds() < 5) { if (streamInfo != null && Duration.between(lastStreamInfoRequest, Instant.now()).getSeconds() < 5) {
return streamInfo; return streamInfo;
} }
RequestBody body = new FormBody.Builder()
.add("room_slug", getName())
.add("bandwidth", "high")
.build();
Request req = new Request.Builder() Request req = new Request.Builder()
.url(getSite().getBaseUrl() + "/get_edge_hls_url_ajax/") .url(getSite().getBaseUrl() + "/api/chatvideocontext/" + getName() + '/')
.post(body) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent(URI.create(getSite().getBaseUrl()).getHost()))
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
@ -311,6 +310,23 @@ public class ChaturbateModel extends AbstractModel {
String content = response.body().string(); String content = response.body().string();
log.trace("Raw stream info for model {}: {}", getName(), content); log.trace("Raw stream info for model {}: {}", getName(), content);
streamInfo = mapper.readValue(content, StreamInfo.class); streamInfo = mapper.readValue(content, StreamInfo.class);
if (streamInfo.cmaf_edge) {
if (streamInfo.hls_source.contains("playlist.m3u8")) {
streamInfo.hls_source = streamInfo.hls_source.replace("playlist.m3u8", "playlist_sfm4s.m3u8");
} else if (streamInfo.hls_source.contains("playlist_sfm4s.m3u8")) {
streamInfo.hls_source = streamInfo.hls_source.replace("playlist_sfm4s.m3u8", "playlist.m3u8");
}
var match = serverPattern.matcher(streamInfo.hls_source);
if (getSite().getHttpClient().isLoggedIn()) {
streamInfo.hls_source = match.replaceFirst("live-fhls/amlst");
} else {
streamInfo.hls_source = match.replaceFirst("live-c-fhls/amlst");
}
}
return streamInfo; return streamInfo;
} else { } else {
int code = response.code(); int code = response.code();
@ -322,7 +338,7 @@ public class ChaturbateModel extends AbstractModel {
private int[] getResolution() throws IOException, ParseException, PlaylistException { private int[] getResolution() throws IOException, ParseException, PlaylistException {
int[] res = new int[2]; int[] res = new int[2];
if (!getStreamInfo().url.startsWith("http")) { if (!getStreamInfo().hls_source.startsWith("http")) {
return res; return res;
} }
@ -361,10 +377,10 @@ public class ChaturbateModel extends AbstractModel {
} }
private MasterPlaylist getMasterPlaylist(StreamInfo streamInfo) throws IOException, ParseException, PlaylistException { private MasterPlaylist getMasterPlaylist(StreamInfo streamInfo) throws IOException, ParseException, PlaylistException {
log.trace("Loading master playlist {}", streamInfo.url); log.trace("Loading master playlist {}", streamInfo.hls_source);
Request req = new Request.Builder() Request req = new Request.Builder()
.url(streamInfo.url) .url(streamInfo.hls_source)
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent(URI.create(streamInfo.hls_source).getHost()))
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
@ -385,7 +401,7 @@ public class ChaturbateModel extends AbstractModel {
public boolean exists() throws IOException { public boolean exists() throws IOException {
Request req = new Request.Builder() // @formatter:off Request req = new Request.Builder() // @formatter:off
.url(getUrl()) .url(getUrl())
.header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent()) .header(USER_AGENT, site.getHttpClient().getEffectiveUserAgent(URI.create(getUrl()).getHost()))
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.build(); // @formatter:on .build(); // @formatter:on
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {

View File

@ -1,8 +1,8 @@
package ctbrec.sites.chaturbate; package ctbrec.sites.chaturbate;
public class StreamInfo { public class StreamInfo {
public String url; public String hls_source;
public String room_status; public String room_status;
public String hidden_message; public String hidden_message;
public boolean success; public boolean cmaf_edge;
} }

View File

@ -16,6 +16,8 @@ import lombok.extern.slf4j.Slf4j;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import org.apache.commons.text.StringSubstitutor;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -30,6 +32,8 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.HashMap;
import java.util.Map;
import static ctbrec.Model.State.*; import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*; import static ctbrec.io.HttpConstants.*;
@ -197,6 +201,26 @@ public class StripchatModel extends AbstractModel {
} }
} }
private String getHlsUrlTemplate() throws IOException {
log.trace("Loading URL template for {}", getName());
Request req = new Request.Builder()
.url(getSite().getBaseUrl() + "/api/front/v3/config/initial?requestPath=%2F" + getName() + "&timezoneOffset=0")
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.build();
try (Response response = getSite().getHttpClient().execute(req)) {
if (response.isSuccessful()) {
JSONObject jsonResponse = new JSONObject(response.body().string());
var common = jsonResponse.getJSONObject("initial").getJSONObject("common");
var urlTemplate = common.getString("hlsStreamUrlTemplate");
var host = common.getString("hlsStreamHost");
return urlTemplate.replace("{cdnHost}", host);
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private String getMasterPlaylistUrl() throws IOException { private String getMasterPlaylistUrl() throws IOException {
JSONObject info = getModelInfo(); JSONObject info = getModelInfo();
if (info.has("user")) { if (info.has("user")) {
@ -215,8 +239,15 @@ public class StripchatModel extends AbstractModel {
log.debug("Spy start for {}", getName()); 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); // the master playlist host can vary per model
String hlsUrlTemplate = getHlsUrlTemplate();
Map<String, Object> params = new HashMap<>();
params.put("streamName", String.valueOf(id) + vrSuffix);
params.put("suffix", "_auto");
var result = StringSubstitutor.replace(hlsUrlTemplate, params, "{", "}") + "?playlistType=Standart" + token;
return result;
} else { } else {
throw new IOException("Playlist URL not found"); throw new IOException("Playlist URL not found");
} }

View File

@ -6,7 +6,7 @@
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>5.3.2</version> <version>5.3.3</version>
<modules> <modules>
<module>../common</module> <module>../common</module>

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>ctbrec</groupId> <groupId>ctbrec</groupId>
<artifactId>master</artifactId> <artifactId>master</artifactId>
<version>5.3.2</version> <version>5.3.3</version>
<relativePath>../master</relativePath> <relativePath>../master</relativePath>
</parent> </parent>

View File

@ -34,7 +34,7 @@ function loadConfig() {
return this.value.join('\n'); return this.value.join('\n');
}, },
write: function (value) { write: function (value) {
this.value = value.split('\n') this.value = value === '' ? [] : value.split('\n')
}, },
owner: param owner: param
}); });