diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cd1d8ff..fce35208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ======================== * Fixed bug, which caused some recordings to get stuck * Fixed follow/unfollow for CamSoda +* Fixed MVLive downloads +* Fixed bug in cookie handling, which also prevent MVLive downloads from working * Ignore list is now saved as URLs only. The old format is not compatible anymore, so make sure, that you export them again, if you created a backup before. diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java index 76aa2ac5..0048466a 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java @@ -34,8 +34,6 @@ public class MVLive extends AbstractSite { private static final Logger LOG = LoggerFactory.getLogger(MVLive.class); - public static final String APP_HOST = "app-v2.live.manyvids.com"; - public static final String WS_URL = "wss://" + APP_HOST; public static final String WS_ORIGIN = "https://live.manyvids.com"; public static final String BASE_URL = "https://www.manyvids.com/MVLive/"; diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java index 06cb5dc5..d0452dbb 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java @@ -66,10 +66,11 @@ public class MVLiveClient { roomId = response.optString("roomId"); int randomNumber = 100 + rng.nextInt(800); String randomString = UUID.randomUUID().toString().replace("-", "").substring(0, 8); - String wsUrl = String.format("%s/api/%s/eventbus/%s/%s/websocket", WS_URL, roomNumber, randomNumber, randomString); + String wsUrl = model.getApiUrl().replace("https", "wss"); + String url = String.format("%s/%s/eventbus/%s/%s/websocket", wsUrl, roomNumber, randomNumber, randomString); - LOG.info("Websocket is null. Starting a new connection to {}", wsUrl); - ws = createWebSocket(wsUrl, roomId, model.getDisplayName()); + LOG.info("Websocket is null. Starting a new connection to {}", url); + ws = createWebSocket(url, roomId, model.getDisplayName()); } } @@ -99,7 +100,7 @@ public class MVLiveClient { super.onOpen(webSocket, response); try { connecting = false; - LOG.debug("WS open: [{}]", response.body().string()); + LOG.trace("WS open: [{}]", response.body().string()); scheduler = new ScheduledThreadPoolExecutor(1); scheduler.scheduleAtFixedRate(() -> sendMessages(new Ping()), 5, 5, TimeUnit.SECONDS); } catch (IOException e) { @@ -111,7 +112,7 @@ public class MVLiveClient { public void onClosed(WebSocket webSocket, int code, String reason) { super.onClosed(webSocket, code, reason); connecting = false; - LOG.info("MVLive websocket closed: {} {}", code, reason); + LOG.trace("MVLive websocket closed: {} {}", code, reason); MVLiveClient.this.ws = null; running = false; synchronized (streamUrlMonitor) { @@ -146,14 +147,14 @@ public class MVLiveClient { if (Objects.equal("o", text)) { sendMessages(new Ping()); sendMessages(new GetBroadcastHealth(roomId, modelName, (m, r) -> { - LOG.debug("--> {}", m); - LOG.debug("<-- {}", r); + LOG.trace("--> {}", m); + LOG.trace("<-- {}", r); String addr = r.getJSONObject("body").optString("subscribeAddress"); sendMessages(new RegisterMessage(addr, (mr, rr) -> { - LOG.debug("--> {}", mr); - LOG.debug("<-- {}", rr); + LOG.trace("--> {}", mr); + LOG.trace("<-- {}", rr); masterPlaylist = rr.getJSONObject("body").optString("videoUrl"); - LOG.debug("Got the URL: {}", masterPlaylist); + LOG.trace("Got the URL: {}", masterPlaylist); stop(); synchronized (streamUrlMonitor) { streamUrlMonitor.notifyAll(); @@ -182,7 +183,7 @@ public class MVLiveClient { @Override public void onMessage(WebSocket webSocket, ByteString bytes) { super.onMessage(webSocket, bytes); - LOG.debug("Binary Message: {}", bytes.hex()); + LOG.trace("Binary Message: {}", bytes.hex()); } }); } diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java index 139074dc..431c2d9e 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java @@ -44,9 +44,6 @@ public class MVLiveHlsDownload extends HlsDownload { ((MVLiveModel)getModel()).updateCloudFlareCookies(); } catch (IOException e) { LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e); } } } diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java index 491fdba3..8bdc0d86 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java @@ -45,9 +45,6 @@ public class MVLiveMergedHlsDownload extends MergedFfmpegHlsDownload { ((MVLiveModel)getModel()).updateCloudFlareCookies(); } catch (IOException e) { LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e); } } } diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java index cca2d5d1..e40be69b 100644 --- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java +++ b/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java @@ -2,18 +2,20 @@ package ctbrec.sites.manyvids; import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; -import static ctbrec.sites.manyvids.MVLive.*; import static java.nio.charset.StandardCharsets.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +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.concurrent.ExecutionException; +import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,8 +48,11 @@ public class MVLiveModel extends AbstractModel { private transient MVLiveHttpClient httpClient; private transient MVLiveClient client; + private transient JSONObject roomLocation; + private transient Instant lastRoomLocationUpdate = Instant.EPOCH; private String roomNumber; + @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { if (ignoreCache) { @@ -89,7 +94,7 @@ public class MVLiveModel extends AbstractModel { String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String segmentUri = baseUrl + playlist.getUri(); src.mediaPlaylistUrl = segmentUri; - if(src.mediaPlaylistUrl.contains("?")) { + if (src.mediaPlaylistUrl.contains("?")) { src.mediaPlaylistUrl = src.mediaPlaylistUrl.substring(0, src.mediaPlaylistUrl.lastIndexOf('?')); } LOG.debug("Media playlist {}", src.mediaPlaylistUrl); @@ -118,13 +123,14 @@ public class MVLiveModel extends AbstractModel { MasterPlaylist master = playlist.getMasterPlaylist(); return master; } else { + LOG.debug("{} URL: {}\n\tResponse: {}", response.code(), url, response.body().string()); throw new HttpException(response.code(), response.message()); } } } - public void updateCloudFlareCookies() throws IOException, InterruptedException { - String url = "https://" + APP_HOST + "/api/" + getRoomNumber() + "/player-settings/" + getDisplayName(); + public void updateCloudFlareCookies() throws IOException { + String url = getApiUrl() + '/' + getRoomNumber() + "/player-settings/" + getDisplayName(); LOG.trace("Getting CF cookies: {}", url); Request req = new Request.Builder() .url(url) @@ -132,13 +138,17 @@ public class MVLiveModel extends AbstractModel { .build(); try (Response response = getHttpClient().execute(req)) { if (!response.isSuccessful()) { - LOG.debug(response.body().string()); + LOG.debug("Loading CF cookies not successful: {}", response.body().string()); throw new HttpException(response.code(), response.message()); } } } - public String getRoomNumber() throws IOException, InterruptedException { + String getApiUrl() throws JSONException, IOException { + return getRoomLocation().getString("publicAPIURL"); + } + + public String getRoomNumber() throws IOException { if (StringUtil.isBlank(roomNumber)) { JSONObject json = getRoomLocation(); if (json.optBoolean("success")) { @@ -164,26 +174,31 @@ public class MVLiveModel extends AbstractModel { } public JSONObject getRoomLocation() throws IOException { - fetchGeneralCookies(); - httpClient.fetchAuthenticationCookies(); - String url = "https://roompool.live.manyvids.com/roompool/" + getDisplayName() + "?private=false"; - LOG.debug("Fetching room location from {}", url); - Request req = new Request.Builder() - .url(url) - .header(ACCEPT, MIMETYPE_APPLICATION_JSON) - .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) - .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) - .header(REFERER, MVLive.WS_ORIGIN + "/stream/" + getName()) - .build(); - try (Response response = getHttpClient().execute(req)) { - if (response.isSuccessful()) { - String body = response.body().string(); - JSONObject json = new JSONObject(body); - LOG.trace("Room location response: {}", json); - return json; - } else { - throw new HttpException(response.code(), response.message()); + if (Duration.between(lastRoomLocationUpdate, Instant.now()).getSeconds() > 60) { + fetchGeneralCookies(); + httpClient.fetchAuthenticationCookies(); + String url = "https://roompool.live.manyvids.com/roompool/" + getDisplayName() + "?private=false"; + LOG.debug("Fetching room location from {}", url); + Request req = new Request.Builder() + .url(url) + .header(ACCEPT, MIMETYPE_APPLICATION_JSON) + .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .header(REFERER, MVLive.WS_ORIGIN + "/stream/" + getName()) + .build(); + try (Response response = getHttpClient().execute(req)) { + if (response.isSuccessful()) { + String body = response.body().string(); + roomLocation = new JSONObject(body); + LOG.trace("Room location response: {}", roomLocation); + lastRoomLocationUpdate = Instant.now(); + return roomLocation; + } else { + throw new HttpException(response.code(), response.message()); + } } + } else { + return roomLocation; } }