diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
index 8d4a7dd9..5b1929b9 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
@@ -6,17 +6,17 @@ import ctbrec.io.HtmlParser;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import ctbrec.sites.AbstractSite;
-import okhttp3.*;
+import okhttp3.FormBody;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
@@ -27,15 +27,10 @@ import static java.nio.charset.StandardCharsets.UTF_8;
public class MVLive extends AbstractSite {
- private static final Logger LOG = LoggerFactory.getLogger(MVLive.class);
private static final String STARS = "stars";
public static final String WS_ORIGIN = "https://live.manyvids.com";
- public static final String BASE_URL = "https://www.manyvids.com";
- public static final String LIVE_URL = BASE_URL + "/mv-live/";
- private final Pattern configPattern = Pattern.compile("");
- private final Pattern graphQlUrlPattern = Pattern.compile("api:\\s*\"(https://.+?.appsync-api..+?.amazonaws.com/graphql)\"");
- private String graphqlBaseUri;
+ public static final String BASE_URL = "https://www.manyvids.com/";
private MVLiveHttpClient httpClient;
private String mvtoken;
@@ -47,7 +42,7 @@ public class MVLive extends AbstractSite {
@Override
public String getBaseUrl() {
- return LIVE_URL;
+ return BASE_URL;
}
@Override
@@ -120,157 +115,15 @@ public class MVLive extends AbstractSite {
// nothing to do
}
- private String getGraphQlUrl() {
- if (graphqlBaseUri == null) {
- try {
- String spaBundleUrl = getSpaBundleUrl();
- loadGraphqlApiConfig(spaBundleUrl);
- LOG.debug("Using graphql API at {}", graphqlBaseUri);
- } catch (IOException e) {
- LOG.error("Error while initializing {}", getName(), e);
- }
- }
- return graphqlBaseUri;
- }
-
- private void loadGraphqlApiConfig(String spaBundleUrl) throws IOException {
- Request request = new Request.Builder()
- .url(spaBundleUrl)
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .header(ORIGIN, MVLive.BASE_URL)
- .header(REFERER, MVLive.BASE_URL)
- .build();
- LOG.debug("Loading graphql config {}", spaBundleUrl);
- try (Response response = getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String content = response.body().string();
- Matcher m = graphQlUrlPattern.matcher(content);
- if (m.find()) {
- graphqlBaseUri = m.group(1);
- } else {
- throw new IllegalStateException("GraphQL URL not found");
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private String getSpaBundleUrl() throws IOException {
- Request request = new Request.Builder()
- .url(getBaseUrl())
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .header(REFERER, MVLive.BASE_URL)
- .build();
- try (Response response = getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String content = response.body().string();
- Matcher m = configPattern.matcher(content);
- if (m.find()) {
- JSONObject apps = new JSONObject(m.group(1)).getJSONObject("apps");
- JSONObject mvlive = apps.getJSONObject("@manyvids/live");
- String spaBundle = mvlive.getString("spaBundle");
- return spaBundle;
- } else {
- throw new IllegalStateException("config not found");
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- public List getModels() throws IOException {
- String body = new JSONObject()
- .put("query", """
- query LanderLiveSessions($nextToken: String, $limit: Int, $country: String, $subdivision: String, $audience: String) {
- liveSessions(
- nextToken: $nextToken
- limit: $limit
- country: $country
- subdivision: $subdivision
- audience: $audience
- ) {
- presenters {
- id
- age
- avatarSrc
- city
- country
- name
- previewImgSrc
- starOfTheDay
- status
- sessionType
- streamingStartDate
- towerImgSrc
- profileHandle
- }
- nextToken
- }
- }
- """)
- .put("variables", new JSONObject()
- .put("audience", "public")
- .put("country", Locale.getDefault().getDisplayCountry(Locale.ENGLISH))
- .put("limit", 100)
- .put("nextToken", JSONObject.NULL)
- .put("subdivision", "")
- ).toString(2);
- RequestBody requestBody = RequestBody.Companion.create(body, MediaType.parse("application/json"));
-
- Request request = newRequestBuilder()
- .post(requestBody)
- .build();
- try (Response response = getHttpClient().execute(request)) {
- String content = response.body().string();
- if (response.isSuccessful()) {
- JSONObject responseBody = new JSONObject(content);
- JSONArray presenters = responseBody.getJSONObject("data").getJSONObject("liveSessions").getJSONArray("presenters");
- List models = new LinkedList<>();
- for (int i = 0; i < presenters.length(); i++) {
- JSONObject presenter = presenters.getJSONObject(i);
- try {
- MVLiveModel model = createModel(presenter.getString("name"));
- model.setId(presenter.getString("id"));
- model.setDisplayName(presenter.optString("name", model.getName()));
- model.setPreview(presenter.optString("towerImgSrc"));
- model.setOnlineState(mapState(presenter.getString("sessionType")));
- models.add(model);
- } catch (Exception e) {
- LOG.error("Couldn't parse model: {}", presenter.toString(2), e);
- }
- }
- return models;
- } else {
- LOG.debug("Response: {}", content);
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private State mapState(String status) {
- return switch (status) {
- case "PUBLIC" -> State.ONLINE;
- case "PRIVATE" -> State.PRIVATE;
- default -> State.OFFLINE;
- };
- }
-
@Override
public boolean supportsSearch() {
return true;
}
- @Override
- public boolean searchRequiresLogin() {
- return false;
- }
-
String getMvToken() throws IOException {
if (mvtoken == null) {
Request request = new Request.Builder()
- .url("https://www.manyvids.com/")
+ .url(BASE_URL)
.header(USER_AGENT, getConfig().getSettings().httpUserAgent)
.build();
try (Response response = getHttpClient().execute(request)) {
@@ -356,17 +209,4 @@ public class MVLive extends AbstractSite {
return super.createModelFromUrl(url);
}
-
- private Request.Builder newRequestBuilder() {
- return new Request.Builder()
- .url(getGraphQlUrl())
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .header(ORIGIN, MVLive.BASE_URL)
- .header(REFERER, MVLive.BASE_URL)
- .header(AUTHORIZATION, "GUEST");
- }
-
-
}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
index 25ecb945..4e3091d9 100644
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
+++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
@@ -11,33 +11,28 @@ import ctbrec.io.HttpException;
import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.ModelOfflineException;
+import lombok.extern.slf4j.Slf4j;
import okhttp3.Request;
import okhttp3.Response;
import org.json.JSONException;
import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URLEncoder;
import java.time.Duration;
import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
+import java.util.*;
import java.util.concurrent.ExecutionException;
-import static ctbrec.Model.State.OFFLINE;
-import static ctbrec.Model.State.ONLINE;
+import static ctbrec.Model.State.*;
import static ctbrec.io.HttpConstants.*;
import static java.nio.charset.StandardCharsets.UTF_8;
+@Slf4j
public class MVLiveModel extends AbstractModel {
- private static final transient Logger LOG = LoggerFactory.getLogger(MVLiveModel.class);
-
private transient MVLiveHttpClient httpClient;
private transient MVLiveClient client;
private transient JSONObject roomLocation;
@@ -48,31 +43,31 @@ public class MVLiveModel extends AbstractModel {
@Override
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
if (ignoreCache) {
- boolean modelFound = false;
- MVLive site = (MVLive) getSite();
- for (Model model : site.getModels()) {
- if (model.getName().equalsIgnoreCase(getName()) || model.getDisplayName().equalsIgnoreCase(getName())) {
- this.onlineState = model.getOnlineState(true);
- setName(model.getName());
- setDisplayName(model.getDisplayName());
- setUrl(model.getUrl());
- modelFound = true;
- break;
+ String url = "https://creator-api.vidchat.manyvids.com/creator?urlHandle=" + URLEncoder.encode(getName(), UTF_8);
+ Request req = new Request.Builder()
+ .url(url)
+ .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
+ .build();
+ try (Response response = getHttpClient().execute(req)) {
+ if (response.isSuccessful()) {
+ String body = response.body().string();
+ JSONObject creator = new JSONObject(body);
+ updateStateFromJson(creator);
+ } else {
+ log.debug("{} URL: {}\n\tResponse: {}", response.code(), url, response.body().string());
+ throw new HttpException(response.code(), response.message());
}
}
- if (!modelFound) {
- this.onlineState = OFFLINE;
- }
}
return this.onlineState == ONLINE;
}
@Override
public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
- LOG.debug("Loading {}", getUrl());
+ log.debug("Loading {}", getUrl());
try {
StreamLocation streamLocation = getClient().getStreamLocation(this);
- LOG.debug("Got the stream location from WS {}", streamLocation.masterPlaylist);
+ log.debug("Got the stream location from WS {}", streamLocation.masterPlaylist);
roomNumber = streamLocation.roomNumber;
updateCloudFlareCookies();
MasterPlaylist masterPlaylist = getMasterPlaylist(streamLocation.masterPlaylist);
@@ -89,7 +84,7 @@ public class MVLiveModel extends AbstractModel {
if (src.mediaPlaylistUrl.contains("?")) {
src.mediaPlaylistUrl = src.mediaPlaylistUrl.substring(0, src.mediaPlaylistUrl.lastIndexOf('?'));
}
- LOG.debug("Media playlist {}", src.mediaPlaylistUrl);
+ log.debug("Media playlist {}", src.mediaPlaylistUrl);
sources.add(src);
}
}
@@ -101,7 +96,7 @@ public class MVLiveModel extends AbstractModel {
}
private MasterPlaylist getMasterPlaylist(String url) throws IOException, ParseException, PlaylistException {
- LOG.trace("Loading master playlist {}", url);
+ log.trace("Loading master playlist {}", url);
Request req = new Request.Builder()
.url(url)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
@@ -115,7 +110,7 @@ public class MVLiveModel extends AbstractModel {
MasterPlaylist master = playlist.getMasterPlaylist();
return master;
} else {
- LOG.debug("{} URL: {}\n\tResponse: {}", response.code(), url, response.body().string());
+ log.debug("{} URL: {}\n\tResponse: {}", response.code(), url, response.body().string());
throw new HttpException(response.code(), response.message());
}
}
@@ -123,14 +118,14 @@ public class MVLiveModel extends AbstractModel {
public void updateCloudFlareCookies() throws IOException {
String url = getApiUrl() + '/' + getRoomNumber() + "/player-settings/" + getDisplayName();
- LOG.trace("Getting CF cookies: {}", url);
+ log.trace("Getting CF cookies: {}", url);
Request req = new Request.Builder()
.url(url)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.build();
try (Response response = getHttpClient().execute(req)) {
if (!response.isSuccessful()) {
- LOG.debug("Loading CF cookies not successful: {}", response.body().string());
+ log.debug("Loading CF cookies not successful: {}", response.body().string());
throw new HttpException(response.code(), response.message());
}
}
@@ -146,7 +141,7 @@ public class MVLiveModel extends AbstractModel {
if (json.optBoolean("success")) {
roomNumber = json.getString("floorId");
} else {
- LOG.debug("Room number response: {}", json.toString(2));
+ log.debug("Room number response: {}", json.toString(2));
throw new ModelOfflineException(this);
}
}
@@ -170,7 +165,7 @@ public class MVLiveModel extends AbstractModel {
fetchGeneralCookies();
httpClient.fetchAuthenticationCookies();
String url = "https://roompool.live.manyvids.com/roompool/" + getDisplayName() + "?private=false";
- LOG.debug("Fetching room location from {}", url);
+ log.debug("Fetching room location from {}", url);
Request req = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
@@ -182,7 +177,7 @@ public class MVLiveModel extends AbstractModel {
if (response.isSuccessful()) {
String body = response.body().string();
roomLocation = new JSONObject(body);
- LOG.trace("Room location response: {}", roomLocation);
+ log.debug("Room location response: {}", roomLocation);
lastRoomLocationUpdate = Instant.now();
return roomLocation;
} else {
@@ -251,6 +246,7 @@ public class MVLiveModel extends AbstractModel {
@Override
public void readSiteSpecificData(JsonReader reader) throws IOException {
if (reader.hasNext()) {
+ //noinspection ResultOfMethodCallIgnored
reader.nextName();
id = reader.nextString();
}
@@ -263,4 +259,34 @@ public class MVLiveModel extends AbstractModel {
public String getId() {
return id;
}
+
+ public void updateStateFromJson(JSONObject creator) {
+ setId(creator.getString("id"));
+ setDisplayName(creator.optString("display_name", null));
+ setUrl(creator.getString("session_url"));
+ setOnlineState(mapState(creator.optString("live_status"), creator.optString("session_type")));
+ setPreview(creator.optString("avatar", null));
+ }
+
+ protected Model.State mapState(String liveStatus, String sessionType) {
+ if (Objects.equals("ONLINE", liveStatus)) {
+ switch (sessionType) {
+ case "PUBLIC" -> {
+ return ONLINE;
+ }
+ case "PRIVATE" -> {
+ return PRIVATE;
+ }
+ case "OFFLINE" -> {
+ return OFFLINE;
+ }
+ default -> {
+ log.debug("Unknown state {}", sessionType);
+ return OFFLINE;
+ }
+ }
+ } else {
+ return OFFLINE;
+ }
+ }
}