diff --git a/.gitignore b/.gitignore
index a7d4d710..73f0c6d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
/client.iml
/.idea/
/target/
+.project
\ No newline at end of file
diff --git a/client/.classpath b/client/.classpath
index 34bb78b4..a48e43d2 100644
--- a/client/.classpath
+++ b/client/.classpath
@@ -9,9 +9,10 @@
+
-
+
@@ -32,6 +33,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/.project b/client/.project
index 63f2cd35..8b5a0b7c 100644
--- a/client/.project
+++ b/client/.project
@@ -22,7 +22,7 @@
- 1743313759722
+ 1742623224298
30
diff --git a/client/.settings/org.eclipse.jdt.apt.core.prefs b/client/.settings/org.eclipse.jdt.apt.core.prefs
new file mode 100644
index 00000000..dfa4f3ad
--- /dev/null
+++ b/client/.settings/org.eclipse.jdt.apt.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.apt.aptEnabled=true
+org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
+org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations
diff --git a/client/.settings/org.eclipse.jdt.core.prefs b/client/.settings/org.eclipse.jdt.core.prefs
index 3b784bcd..e1a03f96 100644
--- a/client/.settings/org.eclipse.jdt.core.prefs
+++ b/client/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=15
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=15
+org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -12,5 +12,6 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.source=15
+org.eclipse.jdt.core.compiler.source=21
diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java
index cb401da8..f941b920 100644
--- a/client/src/main/java/ctbrec/ui/CamrecApplication.java
+++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java
@@ -22,19 +22,14 @@ import ctbrec.recorder.Recorder;
import ctbrec.recorder.RemoteRecorder;
import ctbrec.recorder.SimplifiedLocalRecorder;
import ctbrec.sites.Site;
-import ctbrec.sites.amateurtv.AmateurTv;
import ctbrec.sites.bonga.BongaCams;
import ctbrec.sites.cam4.Cam4;
import ctbrec.sites.camsoda.Camsoda;
import ctbrec.sites.chaturbate.Chaturbate;
-import ctbrec.sites.cherrytv.CherryTv;
import ctbrec.sites.dreamcam.Dreamcam;
import ctbrec.sites.fc2live.Fc2Live;
import ctbrec.sites.flirt4free.Flirt4Free;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.sites.manyvids.MVLive;
import ctbrec.sites.mfc.MyFreeCams;
-import ctbrec.sites.secretfriends.SecretFriends;
import ctbrec.sites.showup.Showup;
import ctbrec.sites.streamate.Streamate;
import ctbrec.sites.streamray.Streamray;
@@ -176,19 +171,14 @@ public class CamrecApplication extends Application {
}
private void createSites() {
- sites.add(new AmateurTv());
sites.add(new BongaCams());
sites.add(new Cam4());
sites.add(new Camsoda());
sites.add(new Chaturbate());
- sites.add(new CherryTv());
sites.add(new Dreamcam());
sites.add(new Fc2Live());
sites.add(new Flirt4Free());
- sites.add(new LiveJasmin());
- sites.add(new MVLive());
sites.add(new MyFreeCams());
- sites.add(new SecretFriends());
sites.add(new Showup());
sites.add(new Streamate());
sites.add(new Stripchat());
diff --git a/client/src/main/java/ctbrec/ui/SiteUiFactory.java b/client/src/main/java/ctbrec/ui/SiteUiFactory.java
index 187ccb0d..ca8cfdf4 100644
--- a/client/src/main/java/ctbrec/ui/SiteUiFactory.java
+++ b/client/src/main/java/ctbrec/ui/SiteUiFactory.java
@@ -1,38 +1,28 @@
package ctbrec.ui;
import ctbrec.sites.Site;
-import ctbrec.sites.amateurtv.AmateurTv;
import ctbrec.sites.bonga.BongaCams;
import ctbrec.sites.cam4.Cam4;
import ctbrec.sites.camsoda.Camsoda;
import ctbrec.sites.chaturbate.Chaturbate;
-import ctbrec.sites.cherrytv.CherryTv;
import ctbrec.sites.dreamcam.Dreamcam;
import ctbrec.sites.fc2live.Fc2Live;
import ctbrec.sites.flirt4free.Flirt4Free;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.sites.manyvids.MVLive;
import ctbrec.sites.mfc.MyFreeCams;
-import ctbrec.sites.secretfriends.SecretFriends;
import ctbrec.sites.showup.Showup;
import ctbrec.sites.streamate.Streamate;
import ctbrec.sites.streamray.Streamray;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.sites.winktv.WinkTv;
import ctbrec.sites.xlovecam.XloveCam;
-import ctbrec.ui.sites.amateurtv.AmateurTvSiteUi;
import ctbrec.ui.sites.bonga.BongaCamsSiteUi;
import ctbrec.ui.sites.cam4.Cam4SiteUi;
import ctbrec.ui.sites.camsoda.CamsodaSiteUi;
import ctbrec.ui.sites.chaturbate.ChaturbateSiteUi;
-import ctbrec.ui.sites.cherrytv.CherryTvSiteUi;
import ctbrec.ui.sites.dreamcam.DreamcamSiteUi;
import ctbrec.ui.sites.fc2live.Fc2LiveSiteUi;
import ctbrec.ui.sites.flirt4free.Flirt4FreeSiteUi;
-import ctbrec.ui.sites.jasmin.LiveJasminSiteUi;
-import ctbrec.ui.sites.manyvids.MVLiveSiteUi;
import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi;
-import ctbrec.ui.sites.secretfriends.SecretFriendsSiteUi;
import ctbrec.ui.sites.showup.ShowupSiteUi;
import ctbrec.ui.sites.streamate.StreamateSiteUi;
import ctbrec.ui.sites.streamray.StreamraySiteUi;
@@ -42,18 +32,13 @@ import ctbrec.ui.sites.xlovecam.XloveCamSiteUi;
public class SiteUiFactory {
- private static AmateurTvSiteUi amateurTvUi;
private static BongaCamsSiteUi bongaSiteUi;
private static Cam4SiteUi cam4SiteUi;
private static CamsodaSiteUi camsodaSiteUi;
private static ChaturbateSiteUi ctbSiteUi;
- private static CherryTvSiteUi cherryTvSiteUi;
private static Fc2LiveSiteUi fc2SiteUi;
private static Flirt4FreeSiteUi flirt4FreeSiteUi;
- private static LiveJasminSiteUi jasminSiteUi;
- private static MVLiveSiteUi mvLiveSiteUi;
private static MyFreeCamsSiteUi mfcSiteUi;
- private static SecretFriendsSiteUi secretFriendsSiteUi;
private static ShowupSiteUi showupSiteUi;
private static StreamateSiteUi streamateSiteUi;
private static StripchatSiteUi stripchatSiteUi;
@@ -66,12 +51,7 @@ public class SiteUiFactory {
}
public static synchronized SiteUI getUi(Site site) { // NOSONAR
- if (site instanceof AmateurTv) {
- if (amateurTvUi == null) {
- amateurTvUi = new AmateurTvSiteUi((AmateurTv) site);
- }
- return amateurTvUi;
- } else if (site instanceof BongaCams) {
+ if (site instanceof BongaCams) {
if (bongaSiteUi == null) {
bongaSiteUi = new BongaCamsSiteUi((BongaCams) site);
}
@@ -91,11 +71,6 @@ public class SiteUiFactory {
ctbSiteUi = new ChaturbateSiteUi((Chaturbate) site);
}
return ctbSiteUi;
- } else if (site instanceof CherryTv) {
- if (cherryTvSiteUi == null) {
- cherryTvSiteUi = new CherryTvSiteUi((CherryTv) site);
- }
- return cherryTvSiteUi;
} else if (site instanceof Dreamcam) {
if (dreamcamSiteUi == null) {
dreamcamSiteUi = new DreamcamSiteUi((Dreamcam) site);
@@ -111,21 +86,11 @@ public class SiteUiFactory {
flirt4FreeSiteUi = new Flirt4FreeSiteUi((Flirt4Free) site);
}
return flirt4FreeSiteUi;
- } else if (site instanceof MVLive) {
- if (mvLiveSiteUi == null) {
- mvLiveSiteUi = new MVLiveSiteUi((MVLive) site);
- }
- return mvLiveSiteUi;
} else if (site instanceof MyFreeCams) {
if (mfcSiteUi == null) {
mfcSiteUi = new MyFreeCamsSiteUi((MyFreeCams) site);
}
return mfcSiteUi;
- } else if (site instanceof SecretFriends) {
- if (secretFriendsSiteUi == null) {
- secretFriendsSiteUi = new SecretFriendsSiteUi((SecretFriends) site);
- }
- return secretFriendsSiteUi;
} else if (site instanceof Showup) {
if (showupSiteUi == null) {
showupSiteUi = new ShowupSiteUi((Showup) site);
@@ -136,11 +101,6 @@ public class SiteUiFactory {
streamateSiteUi = new StreamateSiteUi((Streamate) site);
}
return streamateSiteUi;
- } else if (site instanceof LiveJasmin) {
- if (jasminSiteUi == null) {
- jasminSiteUi = new LiveJasminSiteUi((LiveJasmin) site);
- }
- return jasminSiteUi;
} else if (site instanceof Stripchat) {
if (stripchatSiteUi == null) {
stripchatSiteUi = new StripchatSiteUi((Stripchat) site);
diff --git a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvConfigUI.java b/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvConfigUI.java
deleted file mode 100644
index b4496f88..00000000
--- a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvConfigUI.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package ctbrec.ui.sites.amateurtv;
-
-import ctbrec.Config;
-import ctbrec.sites.amateurtv.AmateurTv;
-import ctbrec.ui.DesktopIntegration;
-import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.sites.AbstractConfigUI;
-import javafx.geometry.Insets;
-import javafx.scene.Parent;
-import javafx.scene.control.Button;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.Label;
-import javafx.scene.control.PasswordField;
-import javafx.scene.control.TextField;
-import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Priority;
-
-public class AmateurTvConfigUI extends AbstractConfigUI {
- private AmateurTv site;
-
- public AmateurTvConfigUI(AmateurTv site) {
- this.site = site;
- }
-
- @Override
- public Parent createConfigPanel() {
- GridPane layout = SettingsTab.createGridLayout();
- var settings = Config.getInstance().getSettings();
-
- var row = 0;
- var l = new Label("Active");
- layout.add(l, 0, row);
- var enabled = new CheckBox();
- enabled.setSelected(!settings.disabledSites.contains(site.getName()));
- enabled.setOnAction(e -> {
- if(enabled.isSelected()) {
- settings.disabledSites.remove(site.getName());
- } else {
- settings.disabledSites.add(site.getName());
- }
- save();
- });
- GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- layout.add(enabled, 1, row++);
-
- layout.add(new Label("Amateur.TV User"), 0, row);
- var username = new TextField(settings.amateurTvUsername);
- username.setPrefWidth(300);
- username.textProperty().addListener((ob, o, n) -> {
- if(!n.equals(Config.getInstance().getSettings().amateurTvUsername)) {
- Config.getInstance().getSettings().amateurTvUsername = username.getText();
- site.getHttpClient().logout();
- save();
- }
- });
- GridPane.setFillWidth(username, true);
- GridPane.setHgrow(username, Priority.ALWAYS);
- GridPane.setColumnSpan(username, 2);
- layout.add(username, 1, row++);
-
- layout.add(new Label("Amateur.TV Password"), 0, row);
- var password = new PasswordField();
- password.setText(settings.amateurTvPassword);
- password.textProperty().addListener((ob, o, n) -> {
- if(!n.equals(Config.getInstance().getSettings().amateurTvPassword)) {
- Config.getInstance().getSettings().amateurTvPassword = password.getText();
- site.getHttpClient().logout();
- save();
- }
- });
- GridPane.setFillWidth(password, true);
- GridPane.setHgrow(password, Priority.ALWAYS);
- GridPane.setColumnSpan(password, 2);
- layout.add(password, 1, row++);
-
- var createAccount = new Button("Create new Account");
- createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
- layout.add(createAccount, 1, row++);
- GridPane.setColumnSpan(createAccount, 2);
-
- var deleteCookies = new Button("Delete Cookies");
- deleteCookies.setOnAction(e -> site.getHttpClient().clearCookies());
- layout.add(deleteCookies, 1, row);
- GridPane.setColumnSpan(deleteCookies, 2);
-
- GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(deleteCookies, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- return layout;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvElectronLoginDialog.java b/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvElectronLoginDialog.java
deleted file mode 100644
index 4a609392..00000000
--- a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvElectronLoginDialog.java
+++ /dev/null
@@ -1,126 +0,0 @@
-package ctbrec.ui.sites.amateurtv;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.sites.amateurtv.AmateurTv;
-import ctbrec.ui.ExternalBrowser;
-import okhttp3.Cookie;
-import okhttp3.Cookie.Builder;
-import okhttp3.CookieJar;
-import okhttp3.HttpUrl;
-
-public class AmateurTvElectronLoginDialog {
-
- private static final Logger LOG = LoggerFactory.getLogger(AmateurTvElectronLoginDialog.class);
- public static final String DOMAIN = "amateur.tv";
- public static final String URL = AmateurTv.BASE_URL;
- private CookieJar cookieJar;
- private ExternalBrowser browser;
-
- public AmateurTvElectronLoginDialog(CookieJar cookieJar) throws IOException {
- this.cookieJar = cookieJar;
- browser = ExternalBrowser.getInstance();
- try {
- var config = new JSONObject();
- config.put("url", URL);
- config.put("w", 640);
- config.put("h", 480);
- var msg = new JSONObject();
- msg.put("config", config);
- browser
- .onReady(this::onReady)
- .run(msg, msgHandler);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IOException("Couldn't wait for login dialog", e);
- } finally {
- browser.close();
- }
- }
-
- private Consumer msgHandler = line -> {
- if (!line.startsWith("{")) {
- LOG.error("Didn't received a JSON object {}", line);
- } else {
- var json = new JSONObject(line);
-
- var loginSuccessful = false;
- if (json.has("cookies")) {
- var cookiesFromBrowser = json.getJSONArray("cookies");
- for (var i = 0; i < cookiesFromBrowser.length(); i++) {
- var cookie = cookiesFromBrowser.getJSONObject(i);
- if (cookie.getString("domain").contains(DOMAIN)) {
- Builder b = new Cookie.Builder()
- .path(cookie.getString("path"))
- .domain(DOMAIN)
- .name(cookie.getString("name"))
- .value(cookie.getString("value"))
- .expiresAt((long) cookie.optDouble("expirationDate") * 1000l);
- if (cookie.optBoolean("hostOnly")) {
- b.hostOnlyDomain(DOMAIN);
- }
- if (cookie.optBoolean("httpOnly")) {
- b.httpOnly();
- }
- if (cookie.optBoolean("secure")) {
- b.secure();
- }
- Cookie c = b.build();
- cookieJar.saveFromResponse(HttpUrl.parse(AmateurTv.BASE_URL), Collections.singletonList(c));
- LOG.debug("{}={}", c.name(), c.value());
- if (Objects.equals(c.name(), "userType") && Objects.equals(c.value(), "registered")) {
- loginSuccessful = true;
- }
- }
- }
- }
-
- if (loginSuccessful) {
- try {
- browser.close();
- return;
- } catch (IOException e) {
- LOG.error("Couldn't send shutdown request to external browser", e);
- }
- }
-
- try {
- browser.executeJavaScript("document.querySelector('div[class~=\"cy_ubCoins\"]') != null")
- .thenAccept(b -> {
- LOG.debug("Result: {}", b);
- if (Boolean.TRUE.equals(b)) {
- try {
- browser.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- })
- .exceptionally(ex -> {LOG.error("Error", ex); return null;});
-
- browser.executeJavaScript("if (!loginDialogVisible) { document.querySelector('button').innerHTML.indexOf('I agree') >= 0 && document.querySelector('button').click(); }");
- browser.executeJavaScript("if (!loginDialogVisible) { document.querySelector('button[aria-label=\"open drawer\"]').click(); }"); // open the burger menu to get to the login button
- browser.executeJavaScript("if (!loginDialogVisible) { document.querySelectorAll('button').forEach(function(b) { if (b.textContent === 'Log in') b.click(); }); }"); // click the login button to open the login dialog
- browser.executeJavaScript("loginDialogVisible = document.querySelectorAll('div[class~=\"MuiDialog-container\"]').length > 1");
- browser.executeJavaScript("if (loginDialogVisible) throw new Error(\"Stop execution right here\")");
- } catch(Exception e) {
- LOG.warn("Couldn't auto fill username and password for Amateur.TV", e);
- }
- }
- };
-
- private void onReady() {
- try {
- browser.executeJavaScript("let loginDialogVisible = document.querySelectorAll('div[class~=\"MuiDialog-container\"]').length > 1");
- } catch(Exception e) {
- LOG.warn("Couldn't auto fill username and password for Amateur.TV", e);
- }
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvFollowedTab.java b/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvFollowedTab.java
deleted file mode 100644
index c6f187a3..00000000
--- a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvFollowedTab.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package ctbrec.ui.sites.amateurtv;
-
-import ctbrec.sites.Site;
-import ctbrec.ui.tabs.FollowedTab;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-
-public class AmateurTvFollowedTab extends ThumbOverviewTab implements FollowedTab {
-
- public AmateurTvFollowedTab(String title, PaginatedScheduledService updateService, Site site) {
- super(title, updateService, site);
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvSiteUi.java b/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvSiteUi.java
deleted file mode 100644
index 2196e50d..00000000
--- a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvSiteUi.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package ctbrec.ui.sites.amateurtv;
-
-import java.io.IOException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.sites.amateurtv.AmateurTv;
-import ctbrec.sites.amateurtv.AmateurTvHttpClient;
-import ctbrec.ui.controls.Dialogs;
-import ctbrec.ui.sites.AbstractSiteUi;
-import ctbrec.ui.sites.ConfigUI;
-import ctbrec.ui.tabs.TabProvider;
-
-public class AmateurTvSiteUi extends AbstractSiteUi {
-
- private static final Logger LOG = LoggerFactory.getLogger(AmateurTvSiteUi.class);
-
- private final AmateurTv site;
- private AmateurTvTabProvider tabProvider;
- private AmateurTvConfigUI configUi;
-
- public AmateurTvSiteUi(AmateurTv amateurTv) {
- this.site = amateurTv;
- }
-
- @Override
- public TabProvider getTabProvider() {
- if (tabProvider == null) {
- tabProvider = new AmateurTvTabProvider(site);
- }
- return tabProvider;
- }
-
- @Override
- public ConfigUI getConfigUI() {
- if (configUi == null) {
- configUi = new AmateurTvConfigUI(site);
- }
- return configUi;
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- if (!site.credentialsAvailable()) {
- return false;
- }
-
- boolean automaticLogin = site.login();
- if (automaticLogin) {
- return true;
- } else {
- // login with external browser window
- try {
- new AmateurTvElectronLoginDialog(site.getHttpClient().getCookieJar());
- } catch (Exception e1) {
- LOG.error("Error logging in with external browser", e1);
- Dialogs.showError("Login error", "Couldn't login to " + site.getName(), e1);
- }
-
- AmateurTvHttpClient httpClient = (AmateurTvHttpClient) site.getHttpClient();
- return httpClient.checkLoginSuccess();
- }
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvTabProvider.java b/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvTabProvider.java
deleted file mode 100644
index 5f0f6e99..00000000
--- a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvTabProvider.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package ctbrec.ui.sites.amateurtv;
-
-import ctbrec.sites.amateurtv.AmateurTv;
-import ctbrec.ui.sites.AbstractTabProvider;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.scene.Scene;
-import javafx.scene.control.Tab;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class AmateurTvTabProvider extends AbstractTabProvider {
-
-
- private AmateurTvFollowedTab followedTab;
-
- public AmateurTvTabProvider(AmateurTv amateurTv) {
- super(amateurTv);
- }
-
- @Override
- protected List getSiteTabs(Scene scene) {
- List tabs = new ArrayList<>();
-
- // all
- var url = AmateurTv.BASE_URL + "/v3/readmodel/cache/onlinecamlist";
- var updateService = new AmateurTvUpdateService((AmateurTv) site, url);
- tabs.add(createTab("All", updateService));
-
- // female
- url = AmateurTv.BASE_URL + "/v3/readmodel/cache/sectioncamlist?genre=[%22w%22]";
- updateService = new AmateurTvUpdateService((AmateurTv) site, url);
- tabs.add(createTab("Female", updateService));
-
- // male
- url = AmateurTv.BASE_URL + "/v3/readmodel/cache/sectioncamlist?genre=[%22m%22]";
- updateService = new AmateurTvUpdateService((AmateurTv) site, url);
- tabs.add(createTab("Male", updateService));
-
- // couples
- url = AmateurTv.BASE_URL + "/v3/readmodel/cache/sectioncamlist?genre=[%22c%22]";
- updateService = new AmateurTvUpdateService((AmateurTv) site, url);
- tabs.add(createTab("Couples", updateService));
-
- // trans
- url = AmateurTv.BASE_URL + "/v3/readmodel/cache/sectioncamlist?genre=[%22t%22]";
- updateService = new AmateurTvUpdateService((AmateurTv) site, url);
- tabs.add(createTab("Trans", updateService));
-
- // followed
- url = AmateurTv.BASE_URL + "/v3/readmodel/cache/favorites";
- updateService = new AmateurTvUpdateService((AmateurTv) site, url);
- updateService.requiresLogin(true);
- followedTab = new AmateurTvFollowedTab("Followed", updateService, site);
- followedTab.setRecorder(recorder);
- tabs.add(followedTab);
-
- return tabs;
- }
-
- private Tab createTab(String title, PaginatedScheduledService updateService) {
- var tab = new ThumbOverviewTab(title, updateService, site);
- tab.setRecorder(recorder);
- return tab;
- }
-
- @Override
- public Tab getFollowedTab() {
- return followedTab;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvUpdateService.java b/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvUpdateService.java
deleted file mode 100644
index d419278a..00000000
--- a/client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvUpdateService.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package ctbrec.ui.sites.amateurtv;
-
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.sites.amateurtv.AmateurTv;
-import ctbrec.sites.amateurtv.AmateurTvModel;
-import ctbrec.ui.SiteUiFactory;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import javafx.concurrent.Task;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-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.stream.Collectors;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class AmateurTvUpdateService extends PaginatedScheduledService {
-
- private static final Logger LOG = LoggerFactory.getLogger(AmateurTvUpdateService.class);
- private static final int ITEMS_PER_PAGE = 48;
-
- private AmateurTv site;
- private String url;
- private boolean requiresLogin = false;
- private List modelsList;
- private Instant lastListInfoRequest = Instant.EPOCH;
-
- public AmateurTvUpdateService(AmateurTv site, String url) {
- this.site = site;
- this.url = url;
- }
-
- @Override
- protected Task> createTask() {
- return new Task>() {
- @Override
- public List call() throws IOException {
- if (requiresLogin) {
- if (!SiteUiFactory.getUi(site).login()) {
- throw new IOException("- Login is required");
- }
- ;
- }
- return getModelList().stream()
- .skip((page - 1) * (long) ITEMS_PER_PAGE)
- .limit(ITEMS_PER_PAGE)
- .collect(Collectors.toList()); // NOSONAR
- }
- };
- }
-
- private List getModelList() throws IOException {
- if (Duration.between(lastListInfoRequest, Instant.now()).getSeconds() < 30) {
- return modelsList;
- }
- lastListInfoRequest = Instant.now();
- modelsList = loadModelList();
- if (modelsList == null) {
- modelsList = Collections.emptyList();
- }
- return modelsList;
- }
-
- private List loadModelList() throws IOException {
- LOG.debug("Fetching page {}", url);
- Request request = new Request.Builder()
- .url(url)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT, Locale.ENGLISH.getLanguage())
- .header(REFERER, site.getBaseUrl() + "/following")
- .build();
- try (Response response = site.getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String content = response.body().string();
- List models = new ArrayList<>();
- JSONObject json = new JSONObject(content);
- if (json.has("body")) {
- JSONObject body = json.getJSONObject("body");
- if (body.has("cams")) {
- JSONArray cams = body.getJSONArray("cams");
- parseModels(cams, models);
- }
- if (body.has("list") && body.has("total")) {
- if (body.optInt("total") > 0) {
- JSONArray list = body.getJSONArray("list");
- parseModels(list, models);
- }
- }
- }
- if (json.has("cams")) {
- JSONArray cams = json.getJSONArray("cams");
- parseModels(cams, models);
- }
- return models;
- } else {
- int code = response.code();
- throw new IOException("HTTP status " + code);
- }
- }
- }
-
- private void parseModels(JSONArray jsonModels, List models) {
- for (var i = 0; i < jsonModels.length(); i++) {
- JSONObject m = jsonModels.getJSONObject(i);
- String name = m.optString("username");
- AmateurTvModel model = (AmateurTvModel) site.createModel(name);
- if (m.optBoolean("capturesEnabled", true) && m.has("capture")) {
- model.setPreview(m.optString("capture"));
- } else {
- model.setPreview(site.getBaseUrl() + m.optString("avatar"));
- }
- model.setDescription(m.optString("topic"));
- models.add(model);
- }
- }
-
- public void requiresLogin(boolean requiresLogin) {
- this.requiresLogin = requiresLogin;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java
deleted file mode 100644
index 82f0c8e4..00000000
--- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package ctbrec.ui.sites.cherrytv;
-
-import ctbrec.Config;
-import ctbrec.sites.cherrytv.CherryTv;
-import ctbrec.ui.DesktopIntegration;
-import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.sites.AbstractConfigUI;
-import javafx.geometry.Insets;
-import javafx.scene.Parent;
-import javafx.scene.control.*;
-import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Priority;
-
-public class CherryTvConfigUI extends AbstractConfigUI {
- private final CherryTv site;
-
- public CherryTvConfigUI(CherryTv cherryTv) {
- this.site = cherryTv;
- }
-
- @Override
- public Parent createConfigPanel() {
- var layout = SettingsTab.createGridLayout();
- var settings = Config.getInstance().getSettings();
-
- var row = 0;
- var l = new Label("Active");
- layout.add(l, 0, row);
- var enabled = new CheckBox();
- enabled.setSelected(!settings.disabledSites.contains(site.getName()));
- enabled.setOnAction(e -> {
- if (enabled.isSelected()) {
- settings.disabledSites.remove(site.getName());
- } else {
- settings.disabledSites.add(site.getName());
- }
- save();
- });
- GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- layout.add(enabled, 1, row++);
-
- layout.add(new Label(site.getName() + " User"), 0, row);
- var username = new TextField(Config.getInstance().getSettings().cherryTvUsername);
- username.textProperty().addListener((ob, o, n) -> {
- if (!n.equals(Config.getInstance().getSettings().cherryTvUsername)) {
- Config.getInstance().getSettings().cherryTvUsername = username.getText();
- site.getHttpClient().logout();
- save();
- }
- });
- GridPane.setFillWidth(username, true);
- GridPane.setHgrow(username, Priority.ALWAYS);
- GridPane.setColumnSpan(username, 2);
- layout.add(username, 1, row++);
-
- layout.add(new Label(site.getName() + " Password"), 0, row);
- var password = new PasswordField();
- password.setText(Config.getInstance().getSettings().cherryTvPassword);
- password.textProperty().addListener((ob, o, n) -> {
- if (!n.equals(Config.getInstance().getSettings().cherryTvPassword)) {
- Config.getInstance().getSettings().cherryTvPassword = password.getText();
- site.getHttpClient().logout();
- save();
- }
- });
- GridPane.setFillWidth(password, true);
- GridPane.setHgrow(password, Priority.ALWAYS);
- GridPane.setColumnSpan(password, 2);
- layout.add(password, 1, row++);
-
- var createAccount = new Button("Create new Account");
- createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
- layout.add(createAccount, 1, row++);
- GridPane.setColumnSpan(createAccount, 2);
-
- var deleteCookies = new Button("Delete Cookies");
- deleteCookies.setOnAction(e -> site.getHttpClient().clearCookies());
- layout.add(deleteCookies, 1, row);
- GridPane.setColumnSpan(deleteCookies, 2);
-
- GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(deleteCookies, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- return layout;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java
deleted file mode 100644
index 07bb6684..00000000
--- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java
+++ /dev/null
@@ -1,90 +0,0 @@
-
-package ctbrec.ui.sites.cherrytv;
-
-import ctbrec.sites.cherrytv.CherryTv;
-import ctbrec.ui.tabs.FollowedTab;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.concurrent.WorkerStateEvent;
-import javafx.geometry.Insets;
-import javafx.scene.Scene;
-import javafx.scene.control.Label;
-import javafx.scene.control.RadioButton;
-import javafx.scene.control.ToggleGroup;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.scene.layout.HBox;
-
-public class CherryTvFollowedTab extends ThumbOverviewTab implements FollowedTab {
- private final Label status;
- private ToggleGroup group;
-
- public CherryTvFollowedTab(String title, CherryTv site) {
- super(title, new CherryTvFollowedUpdateService(site), site);
- status = new Label("Logging in...");
- grid.getChildren().add(status);
- }
-
- @Override
- protected void createGui() {
- super.createGui();
- group = new ToggleGroup();
- addOnlineOfflineSelector();
- setFilter(true);
- }
-
- private void addOnlineOfflineSelector() {
- var online = new RadioButton("online");
- online.setToggleGroup(group);
- var offline = new RadioButton("offline");
- offline.setToggleGroup(group);
- pagination.getChildren().add(online);
- pagination.getChildren().add(offline);
- HBox.setMargin(online, new Insets(5, 5, 5, 40));
- HBox.setMargin(offline, new Insets(5, 5, 5, 5));
- online.setSelected(true);
- group.selectedToggleProperty().addListener(e -> {
- setFilter(online.isSelected());
- queue.clear();
- updateService.restart();
- });
- }
-
- private void setFilter(boolean online) {
- ((CherryTvUpdateService) updateService).setFilter(m -> {
- try {
- return m.isOnline(false) == online;
- } catch (InterruptedException ex) {
- Thread.currentThread().interrupt();
- return false;
- } catch (Exception ex) {
- return false;
- }
- });
- }
-
- @Override
- protected void onSuccess() {
- grid.getChildren().remove(status);
- super.onSuccess();
- }
-
- @Override
- protected void onFail(WorkerStateEvent event) {
- status.setText("Login failed");
- super.onFail(event);
- }
-
- @Override
- public void selected() {
- status.setText("Logging in...");
- super.selected();
- }
-
- public void setScene(Scene scene) {
- scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
- if (this.isSelected() && event.getCode() == KeyCode.DELETE) {
- follow(selectedThumbCells, false);
- }
- });
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java
deleted file mode 100644
index 060a3222..00000000
--- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package ctbrec.ui.sites.cherrytv;
-
-import ctbrec.Model;
-import ctbrec.sites.cherrytv.CherryTv;
-import ctbrec.sites.cherrytv.CherryTvModel;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import static ctbrec.Model.State.OFFLINE;
-import static ctbrec.Model.State.ONLINE;
-
-public class CherryTvFollowedUpdateService extends CherryTvUpdateService {
-
- private static final Logger LOG = LoggerFactory.getLogger(CherryTvFollowedUpdateService.class);
-
- public CherryTvFollowedUpdateService(CherryTv site) {
- super("following", site, true);
- url = "https://api.cherry.tv/graphql?operationName=findFollowingBroadcastsByPage&variables={\"limit\":1000}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"241ae6ae3c2bd62e78432b4d51a92a1baa59d9e94d173867a2a45586704465d1\"}}";
- }
-
- @Override
- protected List parseModels(String body) throws IOException {
- var json = new JSONObject(body);
- //LOG.debug(json.toString(2));
- if (json.has("errors")) {
- JSONArray errors = json.getJSONArray("errors");
- JSONObject first = errors.getJSONObject(0);
- throw new IOException(first.getString("message"));
- }
- List models = new ArrayList<>();
- try {
- JSONArray followings = json.getJSONObject("data").getJSONObject("broadcasts").getJSONArray("broadcasts");
- for (int i = 0; i < followings.length(); i++) {
- JSONObject following = followings.getJSONObject(i);
- CherryTvModel model = site.createModel(following.optString("username"));
- model.setId(following.getString("id"));
- model.setPreview(following.optString("imageUrl"));
- var online = following.optString("broadcastStatus").equalsIgnoreCase("Live");
- model.setOnline(online);
- model.setOnlineState(online ? ONLINE : OFFLINE);
- models.add(model);
- }
- } catch (JSONException e) {
- LOG.error("Couldn't parse JSON, the structure might have changed", e);
- }
- return models;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java
deleted file mode 100644
index 33c62dcf..00000000
--- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package ctbrec.ui.sites.cherrytv;
-
-import ctbrec.sites.cherrytv.CherryTv;
-import ctbrec.ui.sites.AbstractSiteUi;
-import ctbrec.ui.sites.ConfigUI;
-import ctbrec.ui.tabs.TabProvider;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-public class CherryTvSiteUi extends AbstractSiteUi {
- private static final Logger LOG = LoggerFactory.getLogger(CherryTvSiteUi.class);
-
- private final CherryTv cherryTv;
- private CherryTvTabProvider tabProvider;
- private CherryTvConfigUI configUi;
-
- public CherryTvSiteUi(CherryTv cherryTv) {
- this.cherryTv = cherryTv;
- }
-
- @Override
- public TabProvider getTabProvider() {
- if (tabProvider == null) {
- tabProvider = new CherryTvTabProvider(cherryTv);
- }
- return tabProvider;
- }
-
- @Override
- public ConfigUI getConfigUI() {
- if (configUi == null) {
- configUi = new CherryTvConfigUI(cherryTv);
- }
- return configUi;
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- return cherryTv.login();
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java
deleted file mode 100644
index a790d5e6..00000000
--- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package ctbrec.ui.sites.cherrytv;
-
-import ctbrec.sites.cherrytv.CherryTv;
-import ctbrec.ui.sites.AbstractTabProvider;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.scene.Scene;
-import javafx.scene.control.Tab;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class CherryTvTabProvider extends AbstractTabProvider {
-
- private final CherryTvFollowedTab followedTab;
-
- public CherryTvTabProvider(CherryTv cherryTv) {
- super(cherryTv);
-
- followedTab = new CherryTvFollowedTab("Following", (CherryTv) site);
- followedTab.setImageAspectRatio(1);
- followedTab.preserveAspectRatioProperty().set(false);
- followedTab.setRecorder(recorder);
- }
-
- @Override
- protected List getSiteTabs(Scene scene) {
- List tabs = new ArrayList<>();
-
- tabs.add(createTab("Female", "girls"));
- tabs.add(createTab("Trans", "trans"));
- tabs.add(createTab("Group Show", "groupshow"));
- tabs.add(followedTab);
- return tabs;
- }
-
- @Override
- public Tab getFollowedTab() {
- return followedTab;
- }
-
- private Tab createTab(String name, String url) {
- var updateService = new CherryTvUpdateService(url, (CherryTv) site, false);
- var tab = new ThumbOverviewTab(name, updateService, site);
- tab.setImageAspectRatio(9.0 / 16.0);
- tab.preserveAspectRatioProperty().set(false);
- tab.setRecorder(recorder);
- return tab;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java
deleted file mode 100644
index 9297fc25..00000000
--- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package ctbrec.ui.sites.cherrytv;
-
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.io.HttpException;
-import ctbrec.sites.cherrytv.CherryTv;
-import ctbrec.sites.cherrytv.CherryTvModel;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import javafx.concurrent.Task;
-import okhttp3.Request;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import static ctbrec.Model.State.OFFLINE;
-import static ctbrec.Model.State.ONLINE;
-import static ctbrec.io.HttpConstants.ACCEPT_LANGUAGE;
-import static ctbrec.io.HttpConstants.USER_AGENT;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class CherryTvUpdateService extends PaginatedScheduledService {
-
- private static final Logger LOG = LoggerFactory.getLogger(CherryTvUpdateService.class);
- protected static final long MODELS_PER_PAGE = 50;
-
- protected String url;
- private final boolean loginRequired;
- protected final CherryTv site;
- private Predicate filter;
-
- public CherryTvUpdateService(String slug, CherryTv site, boolean loginRequired) {
- this.site = site;
- this.url = "https://api.cherry.tv/graphql?query=" + URLEncoder.encode(BROADCASTS_QUERY
- .replace(" ", "")
- .replace("${slug}", slug), UTF_8);
- this.loginRequired = loginRequired;
-
- ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
- var t = new Thread(r);
- t.setDaemon(true);
- t.setName("CherryTvUpdateService");
- return t;
- });
- setExecutor(executor);
- }
-
- @Override
- protected Task> createTask() {
- return new Task<>() {
- @Override
- public List call() throws IOException {
- if (loginRequired && !site.getHttpClient().login()) {
- throw new IOException("Login failed");
- }
-
- LOG.debug("Fetching page {}", url);
-
- var request = new Request.Builder()
- .url(url)
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .build();
- try (var response = site.getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body()).string();
- Stream stream = parseModels(body).stream();
- if (filter != null) {
- stream = stream.filter(filter);
- }
- return stream.skip((page - 1) * MODELS_PER_PAGE)
- .limit(MODELS_PER_PAGE)
- .collect(Collectors.toList());
- } else {
- LOG.debug(Objects.requireNonNull(response.body()).string());
- throw new HttpException(response.code(), response.message());
- }
- }
- }
- };
- }
-
- protected List parseModels(String body) throws IOException {
- var json = new JSONObject(body);
- if (json.has("errors")) {
- JSONArray errors = json.getJSONArray("errors");
- JSONObject first = errors.getJSONObject(0);
- throw new IOException(first.getString("message"));
- }
- List models = new ArrayList<>();
- try {
- JSONArray broadcasts = json.getJSONObject("data").getJSONObject("broadcastsPaged").getJSONArray("broadcasts");
- for (int i = 0; i < broadcasts.length(); i++) {
- JSONObject broadcast = broadcasts.getJSONObject(i);
- CherryTvModel model = site.createModel(broadcast.optString("username"));
- model.setDisplayName(broadcast.optString("title"));
- model.setDescription(broadcast.optString("description"));
- model.setPreview(broadcast.optString("thumbnailUrl"));
- var online = broadcast.optString("showStatus").equalsIgnoreCase("Public")
- && broadcast.optString("broadcastStatus").equalsIgnoreCase("Live");
- model.setOnline(online);
- model.setOnlineState(online ? ONLINE : OFFLINE);
- JSONArray tags = broadcast.optJSONArray("tags");
- if (tags != null) {
- for (int j = 0; j < tags.length(); j++) {
- model.getTags().add(tags.getString(j));
- }
- }
- models.add(model);
- }
- } catch (JSONException e) {
- LOG.error("Couldn't parse JSON, the structure might have changed", e);
- }
- return models;
- }
-
- public void setFilter(Predicate filter) {
- this.filter = filter;
- }
-
- private static final String BROADCASTS_QUERY = """
- {
- broadcastsPaged(query: {limit:1000,slug:"${slug}"}) {
- broadcasts {
- id
- title
- username
- description
- thumbnailUrl
- tags
- broadcastStatus
- showStatus
- }
- totalCount
- }
- }
- """;
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminConfigUi.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminConfigUi.java
deleted file mode 100644
index a9806412..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminConfigUi.java
+++ /dev/null
@@ -1,107 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import ctbrec.Config;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.ui.DesktopIntegration;
-import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.sites.AbstractConfigUI;
-import javafx.geometry.Insets;
-import javafx.scene.Parent;
-import javafx.scene.control.Button;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.Label;
-import javafx.scene.control.PasswordField;
-import javafx.scene.control.TextField;
-import javafx.scene.layout.GridPane;
-import javafx.scene.layout.Priority;
-
-public class LiveJasminConfigUi extends AbstractConfigUI {
- private LiveJasmin liveJasmin;
-
- public LiveJasminConfigUi(LiveJasmin liveJasmin) {
- this.liveJasmin = liveJasmin;
- }
-
- @Override
- public Parent createConfigPanel() {
- var settings = Config.getInstance().getSettings();
- var layout = SettingsTab.createGridLayout();
-
- var row = 0;
- var l = new Label("Active");
- layout.add(l, 0, row);
- var enabled = new CheckBox();
- enabled.setSelected(!settings.disabledSites.contains(liveJasmin.getName()));
- enabled.setOnAction(e -> {
- if(enabled.isSelected()) {
- settings.disabledSites.remove(liveJasmin.getName());
- } else {
- settings.disabledSites.add(liveJasmin.getName());
- }
- save();
- });
- GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- layout.add(enabled, 1, row++);
-
- layout.add(new Label("LiveJasmin User"), 0, row);
- var username = new TextField(Config.getInstance().getSettings().livejasminUsername);
- username.textProperty().addListener((ob, o, n) -> {
- if(!n.equals(Config.getInstance().getSettings().livejasminUsername)) {
- Config.getInstance().getSettings().livejasminUsername = n;
- liveJasmin.getHttpClient().logout();
- save();
- }
- });
- GridPane.setFillWidth(username, true);
- GridPane.setHgrow(username, Priority.ALWAYS);
- GridPane.setColumnSpan(username, 2);
- layout.add(username, 1, row++);
-
- layout.add(new Label("LiveJasmin Password"), 0, row);
- var password = new PasswordField();
- password.setText(Config.getInstance().getSettings().livejasminPassword);
- password.textProperty().addListener((ob, o, n) -> {
- if(!n.equals(Config.getInstance().getSettings().livejasminPassword)) {
- Config.getInstance().getSettings().livejasminPassword = n;
- liveJasmin.getHttpClient().logout();
- save();
- }
- });
- GridPane.setFillWidth(password, true);
- GridPane.setHgrow(password, Priority.ALWAYS);
- GridPane.setColumnSpan(password, 2);
- layout.add(password, 1, row++);
-
- layout.add(new Label("LiveJasmin Base URL"), 0, row);
- var baseUrl = new TextField();
- baseUrl.setText(Config.getInstance().getSettings().livejasminBaseUrl);
- baseUrl.textProperty().addListener((ob, o, n) -> {
- Config.getInstance().getSettings().livejasminBaseUrl = baseUrl.getText();
- save();
- });
- GridPane.setFillWidth(baseUrl, true);
- GridPane.setHgrow(baseUrl, Priority.ALWAYS);
- GridPane.setColumnSpan(baseUrl, 2);
- layout.add(baseUrl, 1, row++);
-
- var createAccount = new Button("Create new Account");
- createAccount.setOnAction(e -> DesktopIntegration.open(liveJasmin.getAffiliateLink()));
- layout.add(createAccount, 1, row++);
- GridPane.setColumnSpan(createAccount, 2);
-
- var deleteCookies = new Button("Delete Cookies");
- deleteCookies.setOnAction(e -> liveJasmin.getHttpClient().clearCookies());
- layout.add(deleteCookies, 1, row);
- GridPane.setColumnSpan(deleteCookies, 2);
-
- GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(baseUrl, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(deleteCookies, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
-
- username.setPrefWidth(300);
-
- return layout;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminElectronLoginDialog.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminElectronLoginDialog.java
deleted file mode 100644
index d7e0cf89..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminElectronLoginDialog.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.function.Consumer;
-
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.Config;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.ui.ExternalBrowser;
-import okhttp3.Cookie;
-import okhttp3.Cookie.Builder;
-import okhttp3.CookieJar;
-import okhttp3.HttpUrl;
-
-public class LiveJasminElectronLoginDialog {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminElectronLoginDialog.class);
- public static final String URL = LiveJasmin.baseUrl + "/en/auth/login";
- private CookieJar cookieJar;
- private ExternalBrowser browser;
-
- public LiveJasminElectronLoginDialog(CookieJar cookieJar) throws IOException {
- this.cookieJar = cookieJar;
- browser = ExternalBrowser.getInstance();
- try {
- var config = new JSONObject();
- config.put("url", URL);
- config.put("w", 640);
- config.put("h", 720);
- var msg = new JSONObject();
- msg.put("config", config);
- browser.run(msg, msgHandler);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IOException("Couldn't wait for login dialog", e);
- } catch (IOException e) {
- LOG.debug("Error while starting the browser or communication to it", e);
- } finally {
- browser.close();
- }
- }
-
- private Consumer msgHandler = line -> {
- if(!line.startsWith("{")) {
- System.err.println(line); // NOSONAR
- } else {
- var json = new JSONObject(line);
- if(json.has("url")) {
- var url = json.getString("url");
- if(url.endsWith("/auth/login")) {
- try {
- String username = Config.getInstance().getSettings().livejasminUsername;
- if (username != null && !username.trim().isEmpty()) {
- browser.executeJavaScript("document.querySelector('#login_form input[name=\"username\"]').value = '" + username + "';");
- }
- String password = Config.getInstance().getSettings().livejasminPassword;
- if (password != null && !password.trim().isEmpty()) {
- password = password.replace("'", "\\'");
- browser.executeJavaScript("document.querySelector('#login_form input[name=\"password\"]').value = '" + password + "';");
- }
- browser.executeJavaScript("document.getElementById('header_container').setAttribute('style', 'display:none');");
- browser.executeJavaScript("document.getElementById('footer').setAttribute('style', 'display:none');");
- browser.executeJavaScript("document.getElementById('react-container').setAttribute('style', 'display:none');");
- browser.executeJavaScript("document.getElementById('inner_container').setAttribute('style', 'padding: 0; margin: 1em');");
- browser.executeJavaScript("document.querySelector('div[class~=\"content_box\"]').setAttribute('style', 'margin: 1em');");
- } catch(Exception e) {
- LOG.warn("Couldn't auto fill username and password", e);
- }
- }
- if(json.has("cookies")) {
- var cookiesFromBrowser = json.getJSONArray("cookies");
- for (var i = 0; i < cookiesFromBrowser.length(); i++) {
- var cookie = cookiesFromBrowser.getJSONObject(i);
- Builder b = new Cookie.Builder()
- .path("/")
- .domain(LiveJasmin.baseDomain)
- .name(cookie.getString("name"))
- .value(cookie.getString("value"))
- .expiresAt(0);
- Cookie c = b.build();
- cookieJar.saveFromResponse(HttpUrl.parse(LiveJasmin.baseUrl), Collections.singletonList(c));
- }
- }
- if(url.contains("/member/")) {
- try {
- browser.close();
- } catch(IOException e) {
- LOG.error("Couldn't send close request to browser", e);
- }
- }
- }
- }
- };
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedTab.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedTab.java
deleted file mode 100644
index 330b8cc9..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedTab.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.ui.tabs.FollowedTab;
-import javafx.geometry.Insets;
-import javafx.scene.Scene;
-import javafx.scene.control.RadioButton;
-import javafx.scene.control.ToggleGroup;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-import javafx.scene.layout.HBox;
-
-public class LiveJasminFollowedTab extends LiveJasminTab implements FollowedTab {
-
- public LiveJasminFollowedTab(LiveJasmin liveJasmin) {
- super("Followed", new LiveJasminFollowedUpdateService(liveJasmin), liveJasmin);
- }
-
- @Override
- public void setScene(Scene scene) {
- scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
- if (this.isSelected() && event.getCode() == KeyCode.DELETE) {
- follow(selectedThumbCells, false);
- }
- });
- }
-
- @Override
- protected void createGui() {
- super.createGui();
- addOnlineOfflineSelector();
- }
-
- private void addOnlineOfflineSelector() {
- var group = new ToggleGroup();
- var online = new RadioButton("online");
- online.setToggleGroup(group);
- var offline = new RadioButton("offline");
- offline.setToggleGroup(group);
- pagination.getChildren().add(online);
- pagination.getChildren().add(offline);
- HBox.setMargin(online, new Insets(5,5,5,40));
- HBox.setMargin(offline, new Insets(5,5,5,5));
- online.setSelected(true);
- group.selectedToggleProperty().addListener(e -> {
- ((LiveJasminFollowedUpdateService)updateService).setShowOnline(online.isSelected());
- queue.clear();
- updateService.restart();
- });
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedUpdateService.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedUpdateService.java
deleted file mode 100644
index 9d32d853..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedUpdateService.java
+++ /dev/null
@@ -1,140 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import ctbrec.Config;
-import ctbrec.GlobalThreadPool;
-import ctbrec.Model;
-import ctbrec.io.HttpException;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.sites.jasmin.LiveJasminModel;
-import ctbrec.ui.SiteUiFactory;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import javafx.concurrent.Task;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.concurrent.Future;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class LiveJasminFollowedUpdateService extends PaginatedScheduledService {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminFollowedUpdateService.class);
- private final LiveJasmin liveJasmin;
- private final String url;
- private boolean showOnline = true;
-
- public LiveJasminFollowedUpdateService(LiveJasmin liveJasmin) {
- this.liveJasmin = liveJasmin;
- this.url = liveJasmin.getBaseUrl() + "/en/free/favourite/get-favourite-list";
- }
-
- @Override
- protected Task> createTask() {
- return new Task<>() {
- @Override
- public List call() throws IOException {
- if (!liveJasmin.credentialsAvailable()) {
- throw new RuntimeException("Credentials missing");
- }
-
- boolean loggedIn = SiteUiFactory.getUi(liveJasmin).login();
- if (!loggedIn) {
- throw new RuntimeException("Couldn't login to livejasmin");
- }
- Request request = new Request.Builder()
- .url(url)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, liveJasmin.getBaseUrl() + "/en/free/favorite")
- .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = liveJasmin.getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String body = response.body().string();
- List models = new ArrayList<>();
- JSONObject json = new JSONObject(body);
- if (json.has("success")) {
- JSONObject data = json.getJSONObject("data");
- JSONArray performers = data.getJSONArray("performers");
- List> loadDetailsFutures = new LinkedList<>();
- for (int i = 0; i < performers.length(); i++) {
- JSONObject m = performers.getJSONObject(i);
- String name = m.optString("pid");
- if (name.isEmpty()) {
- continue;
- }
-
- LiveJasminModel model = (LiveJasminModel) liveJasmin.createModel(name);
- model.setId(String.valueOf(m.get("id")));
- model.setDisplayName(m.getString("display_name"));
- Model.State onlineState = LiveJasminModel.mapStatus(m.getInt("status"));
- boolean online = onlineState == Model.State.ONLINE;
- model.setOnlineState(onlineState);
- if (online == showOnline) {
- models.add(model);
- }
- loadDetailsFutures.add(GlobalThreadPool.submit(() -> loadModelDetails(model)));
- }
- for (Future> future : loadDetailsFutures) {
- try {
- future.get();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (Exception e) {
- // details couldn't be loaded, but that doesn't matter
- }
- }
- LOG.debug("done");
- } else {
- LOG.error("Request failed:\n{}", body);
- throw new IOException("Response was not successful");
- }
- return models;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private void loadModelDetails(LiveJasminModel model) {
- try {
- String sessionId = liveJasmin.getHttpClient().getCookieJar().getCookie(HttpUrl.parse(liveJasmin.getBaseUrl()), "session").value();
- String detailsUrl = liveJasmin.getBaseUrl() + "/en/member/flash/get-performer-details/" + model.getName() + "?appletType=html5&noFlash=0&session=" + sessionId;
- Request request = new Request.Builder()
- .url(detailsUrl)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, liveJasmin.getBaseUrl() + "/en/member/chat-html5/" + model.getName())
- .build();
- try (Response response = liveJasmin.getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- JSONObject json = new JSONObject(response.body().string());
- if (json.optBoolean("success")) {
- JSONObject data = json.getJSONObject("data");
- model.setPreview(data.getString("profile_picture_url"));
- }
- }
- }
- } catch (IOException e) {
- // details couldn't be loaded, but that doesn't matter
- }
- }
- };
- }
-
- public void setShowOnline(boolean showOnline) {
- this.showOnline = showOnline;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminSiteUi.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminSiteUi.java
deleted file mode 100644
index 51b3d5bd..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminSiteUi.java
+++ /dev/null
@@ -1,78 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.sites.jasmin.LiveJasminHttpClient;
-import ctbrec.ui.controls.Dialogs;
-import ctbrec.ui.sites.AbstractSiteUi;
-import ctbrec.ui.sites.ConfigUI;
-import ctbrec.ui.tabs.TabProvider;
-
-public class LiveJasminSiteUi extends AbstractSiteUi {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminSiteUi.class);
- private final LiveJasmin liveJasmin;
- private LiveJasminTabProvider tabProvider;
- private LiveJasminConfigUi configUi;
- private long lastLoginTime = 0;
-
- public LiveJasminSiteUi(LiveJasmin liveJasmin) {
- this.liveJasmin = liveJasmin;
- }
-
- @Override
- public TabProvider getTabProvider() {
- if (tabProvider == null) {
- tabProvider = new LiveJasminTabProvider(liveJasmin);
- }
- return tabProvider;
- }
-
- @Override
- public ConfigUI getConfigUI() {
- if (configUi == null) {
- configUi = new LiveJasminConfigUi(liveJasmin);
- }
- return configUi;
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- // renew login every 30 min
- long now = System.currentTimeMillis();
- var renew = false;
- if ((now - lastLoginTime) > TimeUnit.MINUTES.toMillis(30)) {
- renew = true;
- }
-
- boolean automaticLogin = liveJasmin.login(renew);
- if (automaticLogin) {
- lastLoginTime = System.currentTimeMillis();
- return true;
- } else {
- lastLoginTime = System.currentTimeMillis();
-
- // login with external browser window
- try {
- new LiveJasminElectronLoginDialog(liveJasmin.getHttpClient().getCookieJar());
- } catch (Exception e1) {
- LOG.error("Error logging in with external browser", e1);
- Dialogs.showError("Login error", "Couldn't login to " + liveJasmin.getName(), e1);
- }
-
- LiveJasminHttpClient httpClient = (LiveJasminHttpClient) liveJasmin.getHttpClient();
- boolean loggedIn = httpClient.checkLoginSuccess();
- if (loggedIn) {
- LOG.info("Logged in");
- } else {
- LOG.info("Login failed");
- }
- return loggedIn;
- }
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTab.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTab.java
deleted file mode 100644
index d289a47d..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTab.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import java.io.IOException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.Config;
-import ctbrec.sites.Site;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.concurrent.WorkerStateEvent;
-import javafx.scene.Scene;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-import javafx.scene.input.KeyCode;
-import javafx.scene.input.KeyEvent;
-
-public class LiveJasminTab extends ThumbOverviewTab {
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminTab.class);
- protected Label status;
- protected Button acknowledge = new Button("That's alright");
- private boolean betaAcknowledged = Config.getInstance().getSettings().livejasminBetaAcknowledged;
-
- public LiveJasminTab(String title, PaginatedScheduledService updateService, Site site) {
- super(title, updateService, site);
- if(!betaAcknowledged) {
- status = new Label("LiveJasmin is not fully functional. Live previews do not work.\n"
- + "If you get errors while loading the tabs, try to create an account and open the Followed tab first.");
- grid.getChildren().add(status);
- grid.getChildren().add(acknowledge);
- } else {
- status = new Label("Loading...");
- grid.getChildren().add(status);
- }
-
- acknowledge.setOnAction(e -> {
- betaAcknowledged = true;
- Config.getInstance().getSettings().livejasminBetaAcknowledged = true;
- try {
- Config.getInstance().save();
- } catch (IOException e1) {
- LOG.error("Couldn't save config", e1);
- }
- status.setText("Loading...");
- grid.getChildren().remove(acknowledge);
- if(updateService != null) {
- updateService.cancel();
- updateService.reset();
- updateService.restart();
- }
- });
- }
-
- @Override
- protected void onSuccess() {
- if(Config.getInstance().getSettings().livejasminBetaAcknowledged) {
- grid.getChildren().remove(status);
- grid.getChildren().remove(acknowledge);
- super.onSuccess();
- }
- }
-
- @Override
- protected void onFail(WorkerStateEvent event) {
- if(Config.getInstance().getSettings().livejasminBetaAcknowledged) {
- status.setText("Error");
- grid.getChildren().remove(acknowledge);
- super.onFail(event);
- }
- }
-
- public void setScene(Scene scene) {
- scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
- if (this.isSelected() && event.getCode() == KeyCode.DELETE) {
- follow(selectedThumbCells, false);
- }
- });
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTabProvider.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTabProvider.java
deleted file mode 100644
index cd1a5e9b..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTabProvider.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.ui.sites.AbstractTabProvider;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.scene.Scene;
-import javafx.scene.control.Tab;
-import javafx.util.Duration;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class LiveJasminTabProvider extends AbstractTabProvider {
-
-
- private final LiveJasminFollowedTab followedTab;
-
- public LiveJasminTabProvider(LiveJasmin site) {
- super(site);
- followedTab = new LiveJasminFollowedTab(site);
- followedTab.setRecorder(recorder);
- followedTab.setImageAspectRatio(9.0 / 16.0);
- }
-
- @Override
- protected List getSiteTabs(Scene scene) {
- List tabs = new ArrayList<>();
- tabs.add(createTab("Girls", site.getBaseUrl() + "/en/girls/?listPageOrderType=most_popular"));
- tabs.add(createTab("New Girls", site.getBaseUrl() + "/en/girls/new-models/?listPageOrderType=most_popular"));
- tabs.add(createTab("Boys", site.getBaseUrl() + "/en/boys/?listPageOrderType=most_popular"));
- tabs.add(createTab("New Boys", site.getBaseUrl() + "/en/boys/new-models/?listPageOrderType=most_popular"));
- tabs.add(createTab("Couples", site.getBaseUrl() + "/en/girls/couple/?listPageOrderType=most_popular"));
- tabs.add(createTab("Trans", site.getBaseUrl() + "/en/boys/transboy/?listPageOrderType=most_popular"));
- tabs.add(followedTab);
- return tabs;
- }
-
- @Override
- public Tab getFollowedTab() {
- return followedTab;
- }
-
- private ThumbOverviewTab createTab(String title, String url) {
- var s = new LiveJasminUpdateService((LiveJasmin) site, url);
- s.setPeriod(Duration.seconds(60));
- ThumbOverviewTab tab = new ThumbOverviewTab(title, s, site);
- tab.setRecorder(recorder);
- tab.setImageAspectRatio(9.0 / 16.0);
- return tab;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java b/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java
deleted file mode 100644
index b3c7ca42..00000000
--- a/client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package ctbrec.ui.sites.jasmin;
-
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.io.HttpException;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.sites.jasmin.LiveJasminModel;
-import ctbrec.ui.SiteUiFactory;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import javafx.concurrent.Task;
-import okhttp3.Cookie;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.text.MessageFormat;
-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.stream.Collectors;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class LiveJasminUpdateService extends PaginatedScheduledService {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminUpdateService.class);
-
- private final String url;
- private final LiveJasmin liveJasmin;
- private final int modelsPerPage = 60;
-
- private String listPageId = "";
- private List modelsList;
- private int lastPageLoaded;
-
- private transient Instant lastListInfoRequest = Instant.EPOCH;
-
- public LiveJasminUpdateService(LiveJasmin liveJasmin, String url) {
- this.liveJasmin = liveJasmin;
- this.url = url;
- this.lastPageLoaded = 0;
- }
-
- @Override
- protected Task> createTask() {
- return new Task>() {
- @Override
- public List call() throws IOException {
- return getModelList().stream()
- .skip((page - 1) * (long) modelsPerPage)
- .limit(modelsPerPage)
- .collect(Collectors.toList()); // NOSONAR
- }
- };
- }
-
- private List getModelList() throws IOException {
- page = Math.min(page, 99);
- if ((lastPageLoaded > 0) && Duration.between(lastListInfoRequest, Instant.now()).getSeconds() < 60) {
- while (page > lastPageLoaded) {
- lastPageLoaded++;
- modelsList.addAll(loadMore());
- }
- return modelsList;
- }
- lastPageLoaded = 1;
- modelsList = loadModelList();
- while (page > lastPageLoaded) {
- lastPageLoaded++;
- modelsList.addAll(loadMore());
- }
- if (modelsList == null) {
- return Collections.emptyList();
- }
- return modelsList;
- }
-
- private List loadModelList() throws IOException {
- lastListInfoRequest = Instant.now();
- var cookieJar = liveJasmin.getHttpClient().getCookieJar();
-
- var sortCookie = new Cookie.Builder().domain(LiveJasmin.baseDomain).name("listPageOrderType").value("most_popular").build();
- cookieJar.saveFromResponse(HttpUrl.parse(liveJasmin.getBaseUrl()), Collections.singletonList(sortCookie));
-
- String category = (url.contains("boys")) ? "boys" : "girls";
- var categoryCookie = new Cookie.Builder().domain(LiveJasmin.baseDomain).name("category").value(category).build();
- cookieJar.saveFromResponse(HttpUrl.parse(liveJasmin.getBaseUrl()), Collections.singletonList(categoryCookie));
-
- LOG.debug("Fetching page {}", url);
- Request req = new Request.Builder()
- .url(url)
- .addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .addHeader(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .addHeader(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .addHeader(REFERER, liveJasmin.getBaseUrl())
- .addHeader(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = liveJasmin.getHttpClient().execute(req)) {
- LOG.debug("Response {} {}", response.code(), response.message());
- if (response.isSuccessful()) {
- String body = response.body().string();
- List models = new ArrayList<>();
- JSONObject json = new JSONObject(body);
- if (json.optBoolean("success")) {
- parseModels(models, json);
- } else if (json.optString("error").equals("Please login.")) {
- var siteUI = SiteUiFactory.getUi(liveJasmin);
- if (siteUI.login()) {
- return loadModelList();
- } else {
- LOG.error("Request failed:\n{}", body);
- throw new IOException("Response was not successful");
- }
- } else {
- LOG.error("Request failed:\n{}", body);
- throw new IOException("Response was not successful");
- }
- return models;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private List loadMore() throws IOException {
- lastListInfoRequest = Instant.now();
- String moreURL = liveJasmin.getBaseUrl() + MessageFormat.format("/en/list-page-ajax/show-more-json/{0}?wide=true&layout=layout-big&_dc={1}", listPageId, String.valueOf(System.currentTimeMillis()));
- LOG.debug("Fetching page {}", moreURL);
- Request req = new Request.Builder()
- .url(moreURL)
- .addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .addHeader(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .addHeader(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .addHeader(REFERER, liveJasmin.getBaseUrl())
- .addHeader(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = liveJasmin.getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = response.body().string();
- List models = new ArrayList<>();
- JSONObject json = new JSONObject(body);
- if (json.optBoolean("success")) {
- parseModels(models, json);
- }
- return models;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private void parseModels(List models, JSONObject json) {
- if (json.has("data")) {
- JSONObject data = json.getJSONObject("data");
- if (data.optInt("isLast") > 0) {
- lastPageLoaded = 999;
- }
- if (data.has("content")) {
- JSONObject content = data.getJSONObject("content");
- if (content.optInt("isLastPage") > 0) {
- lastPageLoaded = 999;
- }
- listPageId = content.optString("listPageId");
- JSONArray performers = content.getJSONArray("performers");
- for (var i = 0; i < performers.length(); i++) {
- var m = performers.getJSONObject(i);
- var name = m.optString("pid");
- if (name.isEmpty()) {
- continue;
- }
- LiveJasminModel model = (LiveJasminModel) liveJasmin.createModel(name);
- model.setId(m.getString("id"));
- model.setPreview(m.optString("profilePictureUrl"));
- model.setOnlineState(LiveJasminModel.mapStatus(m.optInt("status")));
- model.setDisplayName(m.optString("display_name", null));
- models.add(model);
- }
- } // if content
- } // if data
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveConfigUi.java b/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveConfigUi.java
deleted file mode 100644
index fbbda27a..00000000
--- a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveConfigUi.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package ctbrec.ui.sites.manyvids;
-
-import ctbrec.Config;
-import ctbrec.sites.manyvids.MVLive;
-import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.sites.AbstractConfigUI;
-import javafx.geometry.Insets;
-import javafx.scene.Parent;
-import javafx.scene.control.Button;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.Label;
-import javafx.scene.layout.GridPane;
-
-public class MVLiveConfigUi extends AbstractConfigUI {
- private MVLive site;
-
- public MVLiveConfigUi(MVLive site) {
- this.site = site;
- }
-
- @Override
- public Parent createConfigPanel() {
- var settings = Config.getInstance().getSettings();
- GridPane layout = SettingsTab.createGridLayout();
-
- var row = 0;
- var l = new Label("Active");
- layout.add(l, 0, row);
- var enabled = new CheckBox();
- enabled.setSelected(!settings.disabledSites.contains(site.getName()));
- enabled.setOnAction(e -> {
- if(enabled.isSelected()) {
- settings.disabledSites.remove(site.getName());
- } else {
- settings.disabledSites.add(site.getName());
- }
- save();
- });
- layout.add(enabled, 1, row++);
-
- var deleteCookies = new Button("Delete Cookies");
- deleteCookies.setOnAction(e -> site.getHttpClient().clearCookies());
- layout.add(deleteCookies, 1, row);
- GridPane.setColumnSpan(deleteCookies, 2);
-
- GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(deleteCookies, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- return layout;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveSiteUi.java b/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveSiteUi.java
deleted file mode 100644
index eae2d72d..00000000
--- a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveSiteUi.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package ctbrec.ui.sites.manyvids;
-
-import java.io.IOException;
-
-import ctbrec.sites.manyvids.MVLive;
-import ctbrec.ui.sites.AbstractSiteUi;
-import ctbrec.ui.sites.ConfigUI;
-import ctbrec.ui.tabs.TabProvider;
-
-public class MVLiveSiteUi extends AbstractSiteUi {
-
- private final MVLive mvlive;
- private MVLiveTabProvider tabProvider;
- private MVLiveConfigUi configUi;
-
- public MVLiveSiteUi(MVLive mvlive) {
- this.mvlive = mvlive;
- }
-
- @Override
- public TabProvider getTabProvider() {
- if (tabProvider == null) {
- tabProvider = new MVLiveTabProvider(mvlive);
- }
- return tabProvider;
- }
-
- @Override
- public ConfigUI getConfigUI() {
- if (configUi == null) {
- configUi = new MVLiveConfigUi(mvlive);
- }
- return configUi;
- }
-
- @Override
- public boolean login() throws IOException {
- return false;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveTabProvider.java b/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveTabProvider.java
deleted file mode 100644
index 60dbdfa2..00000000
--- a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveTabProvider.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package ctbrec.ui.sites.manyvids;
-
-import ctbrec.sites.manyvids.MVLive;
-import ctbrec.ui.sites.AbstractTabProvider;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.scene.Scene;
-import javafx.scene.control.Tab;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class MVLiveTabProvider extends AbstractTabProvider {
-
- public MVLiveTabProvider(MVLive mvlive) {
- super(mvlive);
- }
-
- @Override
- protected List getSiteTabs(Scene scene) {
- List tabs = new ArrayList<>();
- tabs.add(createTab("Online"));
- return tabs;
- }
-
- private Tab createTab(String title) {
- var updateService = new MVLiveUpdateService((MVLive) site, "https://api.vidchat.manyvids.com/creator/live");
- var tab = new ThumbOverviewTab(title, updateService, site);
- tab.setRecorder(site.getRecorder());
- tab.setImageAspectRatio(1);
- return tab;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveUpdateService.java b/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveUpdateService.java
deleted file mode 100644
index 2881c0c6..00000000
--- a/client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveUpdateService.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package ctbrec.ui.sites.manyvids;
-
-import ctbrec.Model;
-import ctbrec.io.HttpException;
-import ctbrec.sites.manyvids.MVLive;
-import ctbrec.sites.manyvids.MVLiveModel;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import javafx.concurrent.Task;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-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.stream.Collectors;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class MVLiveUpdateService extends PaginatedScheduledService {
-
- private final MVLive mvlive;
- private final String url;
- private final int modelsPerPage = 48;
- private static List modelsList;
- private static Instant lastListInfoRequest = Instant.EPOCH;
-
- private static final Logger LOG = LoggerFactory.getLogger(MVLiveUpdateService.class);
-
- public MVLiveUpdateService(MVLive site, String url) {
- this.mvlive = site;
- this.url = url;
- }
-
- @Override
- protected Task> createTask() {
- return new Task>() {
- @Override
- public List call() throws IOException {
- return getModelList().stream()
- .skip((page - 1) * (long) modelsPerPage)
- .limit(modelsPerPage)
- .collect(Collectors.toList()); // NOSONAR
- }
- };
- }
-
- private List getModelList() throws IOException {
- if (Duration.between(lastListInfoRequest, Instant.now()).getSeconds() < 30) {
- return modelsList;
- }
- lastListInfoRequest = Instant.now();
- modelsList = loadModels(url);
- if (modelsList == null) {
- return Collections.emptyList();
- }
- return modelsList;
- }
-
- protected List loadModels(String url) throws IOException {
- List models = new ArrayList<>();
- LOG.debug("Loading live models from {}", url);
- Request req = new Request.Builder()
- .url(url)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(ORIGIN, MVLive.BASE_URL)
- .header(REFERER, MVLive.BASE_URL)
- .header(AUTHORIZATION, "GUEST")
- .build();
- try (Response response = mvlive.getHttpClient().execute(req)) {
- String body = response.body().string();
- LOG.trace("response body: {}", body);
- if (response.isSuccessful()) {
- JSONObject json = new JSONObject(body);
- if (!json.has("live_creators")) {
- LOG.debug("Unexpected response:\n{}", json.toString(2));
- return Collections.emptyList();
- }
- JSONArray creators = json.getJSONArray("live_creators");
- for (int i = 0; i < creators.length(); i++) {
- JSONObject creator = creators.getJSONObject(i);
- MVLiveModel model = mvlive.createModel(creator.getString("url_handle"));
- model.updateStateFromJson(creator);
- models.add(model);
- }
- if (!json.optString("next_token").isBlank()) {
- models.addAll(loadModels(url + "?next_token=" + json.optString("next_token")));
- }
- } else {
- throw new HttpException(response.code(), body);
- }
- }
- return models;
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsConfigUI.java b/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsConfigUI.java
deleted file mode 100644
index f52bb11e..00000000
--- a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsConfigUI.java
+++ /dev/null
@@ -1,89 +0,0 @@
-
-package ctbrec.ui.sites.secretfriends;
-
-import ctbrec.Config;
-import ctbrec.sites.secretfriends.SecretFriends;
-import ctbrec.ui.settings.SettingsTab;
-import ctbrec.ui.sites.AbstractConfigUI;
-import javafx.geometry.Insets;
-import javafx.scene.Parent;
-import javafx.scene.control.Button;
-import javafx.scene.control.CheckBox;
-import javafx.scene.control.Label;
-import javafx.scene.layout.GridPane;
-
-public class SecretFriendsConfigUI extends AbstractConfigUI {
- private final SecretFriends site;
-
- public SecretFriendsConfigUI(SecretFriends secretFriends) {
- this.site = secretFriends;
- }
-
- @Override
- public Parent createConfigPanel() {
- GridPane layout = SettingsTab.createGridLayout();
- var settings = Config.getInstance().getSettings();
-
- var row = 0;
- var l = new Label("Active");
- layout.add(l, 0, row);
- var enabled = new CheckBox();
- enabled.setSelected(!settings.disabledSites.contains(site.getName()));
- enabled.setOnAction(e -> {
- if(enabled.isSelected()) {
- settings.disabledSites.remove(site.getName());
- } else {
- settings.disabledSites.add(site.getName());
- }
- save();
- });
- GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- layout.add(enabled, 1, row++);
-
-// layout.add(new Label(site.getName() + " User"), 0, row);
-// var username = new TextField(Config.getInstance().getSettings().stripchatUsername);
-// username.textProperty().addListener((ob, o, n) -> {
-// if(!n.equals(Config.getInstance().getSettings().stripchatUsername)) {
-// Config.getInstance().getSettings().stripchatUsername = username.getText();
-// site.getHttpClient().logout();
-// save();
-// }
-// });
-// GridPane.setFillWidth(username, true);
-// GridPane.setHgrow(username, Priority.ALWAYS);
-// GridPane.setColumnSpan(username, 2);
-// layout.add(username, 1, row++);
-//
-// layout.add(new Label(site.getName() + " Password"), 0, row);
-// var password = new PasswordField();
-// password.setText(Config.getInstance().getSettings().stripchatPassword);
-// password.textProperty().addListener((ob, o, n) -> {
-// if(!n.equals(Config.getInstance().getSettings().stripchatPassword)) {
-// Config.getInstance().getSettings().stripchatPassword = password.getText();
-// site.getHttpClient().logout();
-// save();
-// }
-// });
-// GridPane.setFillWidth(password, true);
-// GridPane.setHgrow(password, Priority.ALWAYS);
-// GridPane.setColumnSpan(password, 2);
-// layout.add(password, 1, row++);
-
-// var createAccount = new Button("Create new Account");
-// createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
-// layout.add(createAccount, 1, row++);
-// GridPane.setColumnSpan(createAccount, 2);
-
- var deleteCookies = new Button("Delete Cookies");
- deleteCookies.setOnAction(e -> site.getHttpClient().clearCookies());
- layout.add(deleteCookies, 1, row);
- GridPane.setColumnSpan(deleteCookies, 2);
-
-// GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
-// GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
-// GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- GridPane.setMargin(deleteCookies, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
- return layout;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsSiteUi.java b/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsSiteUi.java
deleted file mode 100644
index 6164ac0e..00000000
--- a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsSiteUi.java
+++ /dev/null
@@ -1,40 +0,0 @@
-package ctbrec.ui.sites.secretfriends;
-
-import ctbrec.sites.secretfriends.SecretFriends;
-import ctbrec.ui.sites.AbstractSiteUi;
-import ctbrec.ui.sites.ConfigUI;
-import ctbrec.ui.tabs.TabProvider;
-
-import java.io.IOException;
-
-public class SecretFriendsSiteUi extends AbstractSiteUi {
-
- private SecretFriendsTabProvider tabProvider;
- private SecretFriendsConfigUI configUi;
- private final SecretFriends site;
-
- public SecretFriendsSiteUi(SecretFriends site) {
- this.site = site;
- }
-
- @Override
- public TabProvider getTabProvider() {
- if (tabProvider == null) {
- tabProvider = new SecretFriendsTabProvider(site);
- }
- return tabProvider;
- }
-
- @Override
- public ConfigUI getConfigUI() {
- if (configUi == null) {
- configUi = new SecretFriendsConfigUI(site);
- }
- return configUi;
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- return site.login();
- }
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsTabProvider.java b/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsTabProvider.java
deleted file mode 100644
index 8940e10d..00000000
--- a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsTabProvider.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package ctbrec.ui.sites.secretfriends;
-
-import ctbrec.sites.secretfriends.SecretFriends;
-import ctbrec.ui.sites.AbstractTabProvider;
-import ctbrec.ui.tabs.ThumbOverviewTab;
-import javafx.scene.Scene;
-import javafx.scene.control.Tab;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class SecretFriendsTabProvider extends AbstractTabProvider {
-
- public SecretFriendsTabProvider(SecretFriends site) {
- super(site);
- }
-
- @Override
- protected List getSiteTabs(Scene scene) {
- List tabs = new ArrayList<>();
- tabs.add(createTab("Girls", SecretFriends.BASE_URI + "/users"));
- tabs.add(createTab("New", SecretFriends.BASE_URI + "/newgirls"));
- tabs.add(createTab("Couples", SecretFriends.BASE_URI + "/site/couple"));
- return tabs;
- }
-
- private Tab createTab(String title, String url) {
- var updateService = new SecretFriendsUpdateService(url, false, (SecretFriends) site);
- var tab = new ThumbOverviewTab(title, updateService, site);
- tab.setRecorder(recorder);
- return tab;
- }
-
-}
diff --git a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsUpdateService.java b/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsUpdateService.java
deleted file mode 100644
index 8a1b7e26..00000000
--- a/client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsUpdateService.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package ctbrec.ui.sites.secretfriends;
-
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.io.HtmlParser;
-import ctbrec.io.HttpException;
-import ctbrec.sites.secretfriends.SecretFriends;
-import ctbrec.sites.secretfriends.SecretFriendsModelParser;
-import ctbrec.ui.SiteUiFactory;
-import ctbrec.ui.tabs.PaginatedScheduledService;
-import javafx.concurrent.Task;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.*;
-
-import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
-import static ctbrec.io.HttpConstants.*;
-
-public class SecretFriendsUpdateService extends PaginatedScheduledService {
-
- private static final Logger LOG = LoggerFactory.getLogger(SecretFriendsUpdateService.class);
-
- private final String url;
- private final boolean loginRequired;
- private final SecretFriends site;
-
- public SecretFriendsUpdateService(String url, boolean loginRequired, SecretFriends site) {
- this.url = url;
- this.loginRequired = loginRequired;
- this.site = site;
- }
-
- @Override
- protected Task> createTask() {
- return new Task<>() {
- @Override
- public List call() throws IOException {
- if (loginRequired && !site.credentialsAvailable()) {
- return Collections.emptyList();
- } else {
- String paginatedUrl = url;
- if (page > 1) {
- String pager = (url.indexOf("/users") > 0) ? "Friend_page" : "AModel_page";
- paginatedUrl = HttpUrl.parse(url).newBuilder().addQueryParameter(pager, String.valueOf(page)).build().toString();
- }
- LOG.debug("Fetching page {}", paginatedUrl);
- if (loginRequired) {
- SiteUiFactory.getUi(site).login();
- }
- Request request = new Request.Builder()
- .url(paginatedUrl)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(REFERER, SecretFriends.BASE_URI)
- .build();
- try (Response response = site.getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- return parseModels(Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string());
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- }
- };
- }
-
- private List parseModels(String body) {
- List models = new ArrayList<>();
- Elements modelDivs = HtmlParser.getTags(body, "div[class~=model-wrapper]");
- LOG.debug("Found {} models", modelDivs.size());
- for (Element div : modelDivs) {
- try {
- models.add(SecretFriendsModelParser.parse(site, div));
- } catch (Exception e) {
- LOG.warn("Couldn't parse one of the models: {}", div.html(), e);
- }
- }
- return models;
- }
-
-
-}
diff --git a/common/.classpath b/common/.classpath
index 9cdc4d5e..635cdc28 100644
--- a/common/.classpath
+++ b/common/.classpath
@@ -6,7 +6,7 @@
-
+
@@ -26,6 +26,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/.project b/common/.project
index 900a869f..7414671b 100644
--- a/common/.project
+++ b/common/.project
@@ -22,7 +22,7 @@
- 1743313759734
+ 1742623224303
30
diff --git a/common/.settings/org.eclipse.core.resources.prefs b/common/.settings/org.eclipse.core.resources.prefs
index 839d647e..8f99abd8 100644
--- a/common/.settings/org.eclipse.core.resources.prefs
+++ b/common/.settings/org.eclipse.core.resources.prefs
@@ -2,4 +2,5 @@ eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding//src/test/java=UTF-8
+encoding//target/generated-sources/antlr4=UTF-8
encoding/=UTF-8
diff --git a/common/.settings/org.eclipse.jdt.apt.core.prefs b/common/.settings/org.eclipse.jdt.apt.core.prefs
new file mode 100644
index 00000000..dfa4f3ad
--- /dev/null
+++ b/common/.settings/org.eclipse.jdt.apt.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.apt.aptEnabled=true
+org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
+org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations
diff --git a/common/.settings/org.eclipse.jdt.core.prefs b/common/.settings/org.eclipse.jdt.core.prefs
index 3a0745fd..e1a03f96 100644
--- a/common/.settings/org.eclipse.jdt.core.prefs
+++ b/common/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -12,5 +12,6 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.source=21
diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java
index 837cff11..f81c542e 100644
--- a/common/src/main/java/ctbrec/Settings.java
+++ b/common/src/main/java/ctbrec/Settings.java
@@ -43,8 +43,6 @@ public class Settings {
}
- public String amateurTvUsername = "";
- public String amateurTvPassword = "";
public String bongacamsBaseUrl = "https://bongacams.com";
public String bongaPassword = "";
public String bongaUsername = "";
@@ -56,8 +54,6 @@ public class Settings {
public String chaturbateUsername = "";
public String chaturbateBaseUrl = "https://chaturbate.com";
public int chaturbateMsBetweenRequests = 1000;
- public String cherryTvPassword = "";
- public String cherryTvUsername = "";
public boolean chooseStreamQuality = false;
public String colorAccent = "#FFFFFF";
public String colorBase = "#FFFFFF";
@@ -91,10 +87,6 @@ public class Settings {
public byte[] key = null;
public List ignoredModels = new ArrayList<>();
public String lastDownloadDir = "";
- public String livejasminBaseUrl = "https://www.livejasmin.com";
- public boolean livejasminBetaAcknowledged = false;
- public String livejasminPassword = "";
- public String livejasminUsername = "";
public boolean livePreviews = false;
public boolean localRecording = true;
public boolean logFFmpegOutput = false;
diff --git a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTv.java b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTv.java
deleted file mode 100644
index f6b37752..00000000
--- a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTv.java
+++ /dev/null
@@ -1,161 +0,0 @@
-package ctbrec.sites.amateurtv;
-
-import ctbrec.Model;
-import ctbrec.StringUtil;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import ctbrec.sites.AbstractSite;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class AmateurTv extends AbstractSite {
-
- public static final String BASE_URL = "https://en.amateur.tv";
-
- private AmateurTvHttpClient httpClient;
-
-
- @Override
- public void init() throws IOException {
- // nothing to do
- }
-
- @Override
- public String getName() {
- return "Amateur.tv";
- }
-
- @Override
- public String getBaseUrl() {
- return BASE_URL;
- }
-
- @Override
- public AmateurTvModel createModel(String name) {
- AmateurTvModel model = new AmateurTvModel();
- model.setName(name);
- model.setUrl(BASE_URL + '/' + name);
- model.setDescription("");
- model.setSite(this);
- return model;
- }
-
- @Override
- public Double getTokenBalance() throws IOException {
- return 0d;
- }
-
- @Override
- public String getBuyTokensLink() {
- return getAffiliateLink();
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- return credentialsAvailable() && getHttpClient().login();
- }
-
- @Override
- public HttpClient getHttpClient() {
- if (httpClient == null) {
- httpClient = new AmateurTvHttpClient(getConfig());
- }
- return httpClient;
- }
-
- @Override
- public void shutdown() {
- if (httpClient != null) {
- httpClient.shutdown();
- }
- }
-
- @Override
- public boolean supportsTips() {
- return false;
- }
-
- @Override
- public boolean supportsFollow() {
- return true;
- }
-
- @Override
- public boolean supportsSearch() {
- return true;
- }
-
- @Override
- public List search(String q) throws IOException, InterruptedException {
- if (StringUtil.isBlank(q)) {
- return Collections.emptyList();
- }
- String url = getBaseUrl() + "/v3/readmodel/cache/filterbyusername/" + URLEncoder.encode(q, UTF_8);
- var req = new Request.Builder()
- .url(url)
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT, Locale.ENGLISH.getLanguage())
- .header(REFERER, getBaseUrl())
- .build();
- try (Response response = getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- JSONObject json = new JSONObject(response.body().string());
- List models = new ArrayList<>();
- JSONArray results = json.getJSONObject("cams").getJSONArray("nodes");
- int maxResults = Math.min(30, results.length());
- for (int i = 0; i < maxResults; i++) {
- JSONObject result = results.getJSONObject(i);
- var user = result.getJSONObject("user");
- AmateurTvModel model = createModel(user.optString("username"));
- model.setPreview(result.optString("imageURL"));
- models.add(model);
- }
- return models;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- @Override
- public boolean isSiteForModel(Model m) {
- return m instanceof AmateurTvModel;
- }
-
- @Override
- public boolean credentialsAvailable() {
- String username = getConfig().getSettings().amateurTvUsername;
- return username != null && !username.trim().isEmpty();
- }
-
- @Override
- public Model createModelFromUrl(String url) {
- Matcher m = Pattern.compile("https?://.*?amateur.tv/(.*)").matcher(url);
- if (m.matches()) {
- String modelName = m.group(1);
- return createModel(modelName);
- } else {
- return super.createModelFromUrl(url);
- }
- }
-
- @Override
- public String getAffiliateLink() {
- return BASE_URL;
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java
deleted file mode 100644
index 0e8819a2..00000000
--- a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package ctbrec.sites.amateurtv;
-
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.Recording;
-import ctbrec.io.BandwidthMeter;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.AbstractDownload;
-import ctbrec.recorder.download.RecordingProcess;
-import ctbrec.recorder.download.StreamSource;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Objects;
-import java.util.concurrent.ExecutorService;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class AmateurTvDownload extends AbstractDownload {
-
- private static final Logger LOG = LoggerFactory.getLogger(AmateurTvDownload.class);
- private static final int MAX_SECONDS_WITHOUT_TRANSFER = 20;
-
- private final HttpClient httpClient;
- private FileOutputStream fout;
- private Instant timeOfLastTransfer = Instant.MAX;
-
- private volatile boolean running;
- private volatile boolean started;
-
- private File targetFile;
-
- public AmateurTvDownload(HttpClient httpClient) {
- this.httpClient = httpClient;
- }
-
- @Override
- public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
- this.config = config;
- this.model = model;
- this.startTime = startTime;
- this.downloadExecutor = executorService;
- splittingStrategy = initSplittingStrategy(config.getSettings());
- targetFile = config.getFileForRecording(model, "mp4", startTime);
- timeOfLastTransfer = Instant.now();
- }
-
- @Override
- public void stop() {
- running = false;
- }
-
- @Override
- public void finalizeDownload() {
- if (fout != null) {
- try {
- LOG.debug("Closing recording file {}", targetFile);
- fout.close();
- } catch (IOException e) {
- LOG.error("Error while closing recording file {}", targetFile, e);
- }
- }
- }
-
- @Override
- public boolean isRunning() {
- return running;
- }
-
- @Override
- public void postProcess(Recording recording) {
- // nothing to do
- }
-
- @Override
- public File getTarget() {
- return targetFile;
- }
-
- @Override
- public String getPath(Model model) {
- String absolutePath = targetFile.getAbsolutePath();
- String recordingsDir = Config.getInstance().getSettings().recordingsDir;
- String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
- return relativePath;
- }
-
- @Override
- public boolean isSingleFile() {
- return true;
- }
-
- @Override
- public long getSizeInByte() {
- return getTarget().length();
- }
-
- @Override
- public RecordingProcess call() throws Exception {
- if (!started) {
- started = true;
- startDownload();
- }
-
- if (splittingStrategy.splitNecessary(this)) {
- stop();
- rescheduleTime = Instant.now();
- } else {
- rescheduleTime = Instant.now().plusSeconds(5);
- }
- if (!model.isOnline(true)) {
- stop();
- }
- if (Duration.between(timeOfLastTransfer, Instant.now()).getSeconds() > MAX_SECONDS_WITHOUT_TRANSFER) {
- LOG.info("No video data received for {} seconds. Stopping recording for model {}", MAX_SECONDS_WITHOUT_TRANSFER, model);
- stop();
- }
- return this;
- }
-
- private void startDownload() {
- downloadExecutor.submit(() -> {
- running = true;
- try {
- StreamSource src = model.getStreamSources().get(0);
- LOG.debug("Loading video from {}", src.getMediaPlaylistUrl());
- Request request = new Request.Builder()
- .url(src.getMediaPlaylistUrl())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, "en")
- .header(ORIGIN, AmateurTv.BASE_URL)
- .build();
- try (Response resp = httpClient.execute(request)) {
- if (resp.isSuccessful()) {
- LOG.debug("Recording video stream to {}", targetFile);
- Files.createDirectories(targetFile.getParentFile().toPath());
- fout = new FileOutputStream(targetFile);
-
- InputStream in = Objects.requireNonNull(resp.body()).byteStream();
- byte[] b = new byte[1024];
- int len;
- while (running && !Thread.currentThread().isInterrupted() && (len = in.read(b)) >= 0) {
- fout.write(b, 0, len);
- timeOfLastTransfer = Instant.now();
- getDownloadedBytes().addAndGet(len);
- BandwidthMeter.add(len);
- }
- } else {
- throw new HttpException(resp.code(), resp.message());
- }
- }
- } catch (Exception e) {
- LOG.error("Error while downloading MP4", e);
- }
- running = false;
- });
- }
-
-}
diff --git a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvHttpClient.java b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvHttpClient.java
deleted file mode 100644
index 37764a73..00000000
--- a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvHttpClient.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package ctbrec.sites.amateurtv;
-
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-import java.util.Locale;
-
-import org.json.JSONObject;
-
-import ctbrec.Config;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import okhttp3.Request;
-import okhttp3.Response;
-
-public class AmateurTvHttpClient extends HttpClient {
-
- public AmateurTvHttpClient(Config config) {
- super("amateurtv", config);
- }
-
- @Override
- public boolean login() throws IOException {
- return checkLoginSuccess();
- }
-
- /**
- * Check, if the login worked by requesting the user profile
- *
- * @throws IOException
- */
- public boolean checkLoginSuccess() throws IOException {
- String url = AmateurTv.BASE_URL + "/v3/readmodel/user/me";
- Request request = new Request.Builder()
- .url(url)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, AmateurTv.BASE_URL)
- .build();
- try (Response response = execute(request)) {
- if (response.isSuccessful()) {
- JSONObject json = new JSONObject(response.body().string());
- return json.has("username") && !json.getString("username").equalsIgnoreCase("guest");
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java b/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java
deleted file mode 100644
index 613e9fdd..00000000
--- a/common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package ctbrec.sites.amateurtv;
-
-import com.iheartradio.m3u8.ParseException;
-import com.iheartradio.m3u8.PlaylistException;
-import ctbrec.AbstractModel;
-import ctbrec.Config;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.RecordingProcess;
-import ctbrec.recorder.download.StreamSource;
-import ctbrec.recorder.download.hls.FfmpegHlsDownload;
-import okhttp3.FormBody;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.xml.bind.JAXBException;
-import java.io.IOException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-
-import static ctbrec.Model.State.*;
-import static ctbrec.io.HttpConstants.*;
-
-public class AmateurTvModel extends AbstractModel {
-
- private static final Logger LOG = LoggerFactory.getLogger(AmateurTvModel.class);
- private transient JSONArray qualities = new JSONArray();
- private int[] resolution = new int[2];
-
- @Override
- public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
- if (ignoreCache) {
- JSONObject json = getModelInfo();
- setOnlineState(OFFLINE);
-
- boolean online = json.optString("status").equalsIgnoreCase("online");
- if (online) setOnlineState(ONLINE);
-
- boolean brb = json.optBoolean("brb");
- if (brb) setOnlineState(AWAY);
-
- boolean privateChat = json.optString("privateChatStatus").equalsIgnoreCase("exclusive_private");
- if (privateChat) setOnlineState(PRIVATE);
- }
- return onlineState == ONLINE;
- }
-
- @Override
- public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
- if (!failFast || onlineState == UNKNOWN) {
- try {
- onlineState = isOnline(true) ? ONLINE : OFFLINE;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- onlineState = OFFLINE;
- } catch (IOException | ExecutionException e) {
- onlineState = OFFLINE;
- }
- }
- return onlineState;
- }
-
- @Override
- public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
- List streamSources = new ArrayList<>();
- String mediaPlaylistUrl = getMasterPlaylistUrl();
- qualities.forEach(item -> {
- String value = (String) item;
- String[] res = value.split("x");
- StreamSource src = new StreamSource();
- src.setMediaPlaylistUrl(MessageFormat.format("{0}&variant={1}", mediaPlaylistUrl, res[1]));
- src.setWidth(Integer.parseInt(res[0]));
- src.setHeight(Integer.parseInt(res[1]));
- src.setBandwidth(0);
- streamSources.add(src);
- });
- return streamSources;
- }
-
- private String getMasterPlaylistUrl() throws IOException {
- JSONObject json = getModelInfo();
- JSONObject videoTech = json.getJSONObject("videoTechnologies");
- qualities = json.getJSONArray("qualities");
- return videoTech.getString("fmp4-hls");
- }
-
- @Override
- public void invalidateCacheEntries() {
- resolution = new int[2];
- }
-
- @Override
- public void receiveTip(Double tokens) throws IOException {
- // not supported
- }
-
- @Override
- public int[] getStreamResolution(boolean failFast) throws ExecutionException {
- if (!failFast) {
- try {
- List sources = getStreamSources();
- if (!sources.isEmpty()) {
- StreamSource best = sources.get(0);
- resolution = new int[]{best.getWidth(), best.getHeight()};
- }
- } catch (IOException | ParseException | PlaylistException | JAXBException e) {
- throw new ExecutionException(e);
- }
- }
- return resolution;
- }
-
- @Override
- public boolean follow() throws IOException {
- String url = getSite().getBaseUrl() + "/v3/user/follow";
- return followUnfollow(url);
- }
-
- @Override
- public boolean unfollow() throws IOException {
- String url = getSite().getBaseUrl() + "/v3/user/unfollow";
- return followUnfollow(url);
- }
-
- private boolean followUnfollow(String url) throws IOException {
- if (!getSite().login()) {
- throw new IOException("Not logged in");
- }
-
- LOG.debug("Calling {}", url);
- RequestBody body = new FormBody.Builder()
- .add("username", getName())
- .build();
- Request req = new Request.Builder()
- .url(url)
- .method("POST", body)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, getUrl())
- .header(ORIGIN, getSite().getBaseUrl())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .build();
- try (Response resp = site.getHttpClient().execute(req)) {
- if (resp.isSuccessful()) {
- String msg = Objects.requireNonNull(resp.body()).string();
- JSONObject json = new JSONObject(msg);
- if (Objects.equals(json.getString("result"), "OK")) {
- LOG.debug("Follow/Unfollow -> {}", msg);
- return true;
- } else {
- LOG.debug(msg);
- throw new IOException("Response was " + msg);
- }
- } else {
- throw new HttpException(resp.code(), resp.message());
- }
- }
- }
-
- private JSONObject getModelInfo() throws IOException {
- String url = AmateurTv.BASE_URL + "/v3/readmodel/show/" + getName() + "/es";
- Request req = new Request.Builder().url(url)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT_LANGUAGE, "en")
- .header(REFERER, getSite().getBaseUrl() + '/' + getName())
- .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());
- }
- }
- }
-
- @Override
- public RecordingProcess createDownload() {
- return new FfmpegHlsDownload(getSite().getHttpClient());
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/cherrytv/CherryTv.java b/common/src/main/java/ctbrec/sites/cherrytv/CherryTv.java
deleted file mode 100644
index 7217e449..00000000
--- a/common/src/main/java/ctbrec/sites/cherrytv/CherryTv.java
+++ /dev/null
@@ -1,178 +0,0 @@
-package ctbrec.sites.cherrytv;
-
-import ctbrec.Model;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import ctbrec.sites.AbstractSite;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.Model.State.OFFLINE;
-import static ctbrec.Model.State.ONLINE;
-import static ctbrec.io.HttpConstants.*;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class CherryTv extends AbstractSite {
-
- private static final Logger LOG = LoggerFactory.getLogger(CherryTv.class);
-
- public static final String BASE_URL = "https://cherry.tv";
-
- private CherryTvHttpClient httpClient;
-
- @Override
- public String getName() {
- return "CherryTV";
- }
-
- @Override
- public String getBaseUrl() {
- return BASE_URL;
- }
-
- @Override
- public String getAffiliateLink() {
- return getBaseUrl();
- }
-
- @Override
- public CherryTvModel createModel(String name) {
- CherryTvModel model = new CherryTvModel();
- model.setName(name);
- model.setUrl(getBaseUrl() + '/' + name);
- model.setDescription("");
- model.setSite(this);
- return model;
- }
-
- @Override
- public Double getTokenBalance() throws IOException {
- return 0d;
- }
-
- @Override
- public String getBuyTokensLink() {
- return getAffiliateLink();
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- return getHttpClient().login();
- }
-
- @Override
- public HttpClient getHttpClient() {
- if (httpClient == null) {
- httpClient = new CherryTvHttpClient(getConfig());
- }
- return httpClient;
- }
-
- @Override
- public void init() throws IOException {
- // nothing to do
- }
-
- @Override
- public void shutdown() {
- if (httpClient != null) {
- httpClient.shutdown();
- }
- }
-
- @Override
- public boolean supportsTips() {
- return false;
- }
-
- @Override
- public boolean supportsFollow() {
- return true;
- }
-
- @Override
- public boolean supportsSearch() {
- return true;
- }
-
- @Override
- public List search(String q) throws IOException, InterruptedException {
- String url = "https://api.cherry.tv/graphql?operationName=Search&variables="
- + "{\"limit\":6,\"slug\":\"" + URLEncoder.encode(q, UTF_8) + "\"}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"b6b001b46111c5634b5d3f48caa5ad38e747d74a5c841f447f0094e7f2bc2fb1\"}}";
- Request req = new Request.Builder()
- .url(url)
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, getBaseUrl())
- .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- LOG.debug("Search URL: {}", req.url());
- List result = new LinkedList<>();
- try (Response response = getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- JSONObject json = new JSONObject(Objects.requireNonNull(response.body()).string());
- LOG.trace(json.toString(2));
- JSONObject data = json.getJSONObject("data");
- JSONArray streamers = data.getJSONArray("search");
- for (int i = 0; i < streamers.length(); i++) {
- JSONObject hit = streamers.getJSONObject(i);
- CherryTvModel model = createModel(hit.getString("id"));
- model.setId(hit.getString("id"));
- boolean online = hit.optString("broadcastStatus").equalsIgnoreCase("Live");
- model.setOnline(online);
- model.setOnlineState(online ? ONLINE : OFFLINE);
- model.setDescription(hit.getString("description"));
- model.setPreview(hit.getString("image"));
- result.add(model);
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- return result;
- }
-
- @Override
- public boolean isSiteForModel(Model m) {
- return m instanceof CherryTvModel;
- }
-
- @Override
- public boolean credentialsAvailable() {
- String username = getConfig().getSettings().cherryTvUsername;
- return username != null && !username.trim().isEmpty();
- }
-
- @Override
- public Model createModelFromUrl(String url) {
- Matcher m = Pattern.compile("https?://.*?cherry\\.tv/([^/]*?)/?").matcher(url);
- if (m.matches()) {
- String modelName = m.group(1);
- CherryTvModel model = createModel(modelName);
- try {
- model.isOnline(true);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (Exception e) {
- LOG.warn("Couldn't determine model id. This could cause problems in the future", e);
- }
- return model;
- } else {
- return super.createModelFromUrl(url);
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java
deleted file mode 100644
index 2e3247d6..00000000
--- a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package ctbrec.sites.cherrytv;
-
-import ctbrec.Config;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import okhttp3.*;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Collections;
-import java.util.Objects;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class CherryTvHttpClient extends HttpClient {
-
- private static final Logger LOG = LoggerFactory.getLogger(CherryTvHttpClient.class);
-
- public CherryTvHttpClient(Config config) {
- super("cherrytv", config);
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- if (loggedIn) {
- return true;
- }
-
- boolean cookiesWorked = checkLoginSuccess();
- if (cookiesWorked) {
- loggedIn = true;
- LOG.debug("Logged in with cookies");
- return true;
- }
-
- JSONObject body = new JSONObject()
- .put("operationName", "authenticateUser")
- .put("variables", new JSONObject()
- .put("accountName", config.getSettings().cherryTvUsername)
- .put("password", config.getSettings().cherryTvPassword)
- .put("sourceDomain", "cherry.tv")
- )
- .put("extensions", new JSONObject()
- .put("persistedQuery", new JSONObject()
- .put("version", 1)
- .put("sha256Hash", "dc7c4f9040e6bbbd9168e3e10867115117c52689beac68c2066c6e695759911a")
- )
- );
-
-
- RequestBody requestBody = RequestBody.create(body.toString(), MediaType.parse("application/json"));
- Request request = new Request.Builder()
- .url(CherryTv.BASE_URL + "/graphql")
- .header(REFERER, CherryTv.BASE_URL)
- .header(ORIGIN, CherryTv.BASE_URL)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .post(requestBody)
- .build();
-
- LOG.debug("Logging in: {}", request.url());
- try (Response response = execute(request)) {
- if (response.isSuccessful()) {
- JSONObject resp = new JSONObject(Objects.requireNonNull(response.body()).string());
- if (resp.has("data")) {
- JSONObject data = resp.getJSONObject("data");
- JSONObject login = data.getJSONObject("login");
- loggedIn = login.optBoolean("success");
- String jwt = login.optString("token");
- saveAsSessionCookie(jwt);
- LOG.debug("Login successful");
- return loggedIn;
- } else {
- LOG.error(resp.toString(2));
- return false;
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private void saveAsSessionCookie(String jwt) {
- HttpUrl url = HttpUrl.parse(CherryTv.BASE_URL);
- Objects.requireNonNull(url);
- long expiresAt = Instant.now().plus(1, ChronoUnit.DAYS).getEpochSecond();
- Cookie sessionCookie = new Cookie.Builder()
- .name("session")
- .value(jwt)
- .expiresAt(expiresAt)
- .domain(Objects.requireNonNull(url.topPrivateDomain()))
- .path("/")
- .secure().httpOnly()
- .build();
- getCookieJar().saveFromResponse(url, Collections.singletonList(sessionCookie));
- }
-
- private boolean checkLoginSuccess() {
- String url = CherryTv.BASE_URL + "/graphql?operationName=FindFollowings&variables={\"cursor\":\"0\",\"limit\":20}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"b4e8b260e7ab5958011b7a242ecbc13158f05b90380c0dbcafd6a8e3b4c96414\"}}";
- Request request = new Request.Builder()
- .url(url)
- .header(REFERER, CherryTv.BASE_URL)
- .header(ORIGIN, CherryTv.BASE_URL)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .build();
-
- try (Response response = execute(request)) {
- String body = Objects.requireNonNull(response.body()).string();
- LOG.debug("Login body: {}", body);
- if (response.isSuccessful()) {
- JSONObject json = new JSONObject(body);
- if (json.has("errors")) {
- LOG.error(json.toString(2));
- return false;
- } else {
- return json.optString("__typename").equals("FollowingList");
- }
- }
- return false;
- } catch (Exception e) {
- LOG.error("Login check failed", e);
- return false;
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java
deleted file mode 100644
index 8d4bc0fe..00000000
--- a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package ctbrec.sites.cherrytv;
-
-import com.iheartradio.m3u8.*;
-import com.iheartradio.m3u8.data.MasterPlaylist;
-import com.iheartradio.m3u8.data.Playlist;
-import com.iheartradio.m3u8.data.PlaylistData;
-import com.iheartradio.m3u8.data.StreamInfo;
-import ctbrec.*;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.StreamSource;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.MediaType;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
-import static ctbrec.Model.State.OFFLINE;
-import static ctbrec.Model.State.ONLINE;
-import static ctbrec.io.HttpConstants.*;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-@Slf4j
-public class CherryTvModel extends AbstractModel {
-
- private static final Pattern NEXT_DATA = Pattern.compile("");
-
- @Setter
- private boolean online = false;
- private int[] resolution;
- private String masterPlaylistUrl;
- @Getter
- @Setter
- private String id;
-
- @Override
- public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
- if (ignoreCache) {
- String url = getUrl();
- Request req = new Request.Builder().url(url)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*")
- .header(ACCEPT_LANGUAGE, "en")
- .header(REFERER, getSite().getBaseUrl())
- .build();
- try (Response resp = site.getHttpClient().execute(req)) {
- String body = Objects.requireNonNull(resp.body()).string();
- Matcher m = NEXT_DATA.matcher(body);
- if (m.find()) {
- JSONObject json = new JSONObject(m.group(1));
- updateModelProperties(json);
- } else {
- log.error("NEXT_DATA not found in model page {}", getUrl());
- return false;
- }
- } catch (JSONException e) {
- log.error("Unable to determine online state for {}. Probably the JSON structure in NEXT_DATA changed", getName());
- }
- }
- return online;
- }
-
- private void updateModelProperties(JSONObject json) {
- log.trace(json.toString(2));
- JSONObject apolloState = json.getJSONObject("props").getJSONObject("pageProps").getJSONObject("apolloState");
- online = false;
- onlineState = OFFLINE;
- for (Iterator iter = apolloState.keys(); iter.hasNext(); ) {
- String key = iter.next();
- if (key.startsWith("Broadcast:")) {
- log.trace("Model properties:\n{}", apolloState.toString(2));
- JSONObject broadcast = apolloState.getJSONObject(key);
- setDisplayName(broadcast.optString("title"));
- online = broadcast.optString("showStatus").equalsIgnoreCase("Public")
- && broadcast.optString("broadcastStatus").equalsIgnoreCase("Live");
- onlineState = online ? ONLINE : OFFLINE;
- masterPlaylistUrl = broadcast.optString("pullUrl", null);
- } else if (key.startsWith("Streamer:")) {
- JSONObject streamer = apolloState.getJSONObject(key);
- id = streamer.getString("id");
- }
- }
- }
-
- @Override
- public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
- if (!failFast) {
- try {
- isOnline(true);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- onlineState = OFFLINE;
- } catch (IOException | ExecutionException e) {
- onlineState = OFFLINE;
- }
- }
- return onlineState;
- }
-
- @Override
- public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
- try {
- isOnline(true);
- MasterPlaylist masterPlaylist = getMasterPlaylist();
- List sources = new ArrayList<>();
- for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
- if (playlist.hasStreamInfo()) {
- StreamSource src = new StreamSource();
- src.setBandwidth(playlist.getStreamInfo().getBandwidth());
- src.setHeight(Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0));
- src.setWidth(Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.width).orElse(0));
- String masterUrl = masterPlaylistUrl;
- String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
- String segmentUri = baseUrl + playlist.getUri();
- src.setMediaPlaylistUrl(segmentUri);
- if (src.getMediaPlaylistUrl().contains("?")) {
- src.setMediaPlaylistUrl(src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?')));
- }
- log.trace("Media playlist {}", src.getMediaPlaylistUrl());
- sources.add(src);
- }
- }
- return sources;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new ExecutionException(e);
- }
- }
-
- private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
- if (masterPlaylistUrl == null) {
- log.info("Master playlist not found for {}:{}. This probably is webrtc stream", getSite().getName(), getName());
- throw new StreamNotFoundException("Webrtc streams are not supported for " + getSite().getName());
- }
- log.trace("Loading master playlist {}", masterPlaylistUrl);
- Request req = new Request.Builder()
- .url(masterPlaylistUrl)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .build();
- try (Response response = getSite().getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body()).string();
- log.trace(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());
- }
- }
- }
-
- @Override
- public void invalidateCacheEntries() {
- resolution = null;
- }
-
- @Override
- public void receiveTip(Double tokens) throws IOException {
- throw new NotImplementedException();
- }
-
- @Override
- public int[] getStreamResolution(boolean failFast) throws ExecutionException {
- if (resolution == null) {
- if (failFast) {
- return new int[2];
- }
- try {
- if (!isOnline()) {
- return new int[2];
- }
- List sources = getStreamSources();
- Collections.sort(sources);
- StreamSource best = sources.get(sources.size() - 1);
- resolution = new int[]{best.getWidth(), best.getHeight()};
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage());
- resolution = new int[2];
- } catch (ExecutionException | IOException | ParseException | PlaylistException e) {
- log.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage());
- resolution = new int[2];
- }
- }
- return resolution;
- }
-
- @Override
- public boolean follow() throws IOException {
- return followUnfollow("follow", "a7a8241014074f5c02ac83863a47f7579e5f03167a7ec424ff65ad045c7fcf6f");
- }
-
- @Override
- public boolean unfollow() throws IOException {
- return followUnfollow("unfollow", "e91f8f5a60d33efb2dfb3348b977b78358862d3a5cd5ef0011a6aa6bb65d0bd4");
- }
-
- private boolean followUnfollow(String action, String persistedQueryHash) throws IOException {
- Request request = createFollowUnfollowRequest(action, persistedQueryHash);
- log.debug("Sending follow request for model {} with ID {}", getName(), getId());
- try (Response response = getSite().getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String responseBody = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
- log.debug(responseBody);
- JSONObject resp = new JSONObject(responseBody);
- if (resp.has("data") && !resp.isNull("data")) {
- JSONObject data = resp.getJSONObject("data");
- if (data.has(action + "User")) {
- return data.getJSONObject(action + "User").optBoolean("success");
- }
- } else if (resp.has("errors")) {
- JSONObject first = resp.getJSONArray("errors").getJSONObject(0);
- if (first.optString("message").matches("You have .*? the user")) {
- return true;
- }
- }
- log.debug(resp.toString(2));
- return false;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private Request createFollowUnfollowRequest(String action, String persistedQueryHash) throws IOException {
- if (StringUtil.isBlank(id)) {
- try {
- // if the id is not set yet, we call isOnline(true), where it gets set
- isOnline(true);
- } catch (ExecutionException e) {
- throw new IOException(e);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IOException(e);
- }
- }
-
- JSONObject body = new JSONObject()
- .put("operationName", action)
- .put("variables", new JSONObject()
- .put("userId", Objects.requireNonNull(id, "Model ID is null"))
- )
- .put("query", "mutation " + action + "($userId: ID!) {\n " + action + "User(userId: $userId) {\n success\n __typename\n }\n}\n")
- .put("extensions", new JSONObject()
- .put("persistedQuery", new JSONObject()
- .put("version", 1)
- .put("sha256Hash", persistedQueryHash)
- )
- );
-
- RequestBody requestBody = RequestBody.create(body.toString(), MediaType.parse("application/json"));
- return new Request.Builder()
- .url(CherryTv.BASE_URL + "/graphql")
- .header(REFERER, CherryTv.BASE_URL)
- .header(ORIGIN, CherryTv.BASE_URL)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .post(requestBody)
- .build();
- }
-
- @Override
- public void readSiteSpecificData(Map data) {
- id = data.get("id");
- }
-
- @Override
- public void writeSiteSpecificData(Map data) {
- data.put("id", id);
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasmin.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasmin.java
deleted file mode 100644
index 7d77cafe..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasmin.java
+++ /dev/null
@@ -1,217 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import ctbrec.Model;
-import ctbrec.NotLoggedInException;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import ctbrec.sites.AbstractSite;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class LiveJasmin extends AbstractSite {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasmin.class);
- public static String baseUrl = "https://www.livejasmin.com";
- public static String baseDomain = "www.livejasmin.com";
- private HttpClient httpClient;
-
- @Override
- public String getName() {
- return "LiveJasmin";
- }
-
- @Override
- public String getBaseUrl() {
- return baseUrl;
- }
-
- @Override
- public String getAffiliateLink() {
- return "https://awejmp.com/?siteId=jasmin&categoryName=girl&pageName=listpage&performerName=&prm[psid]=0xb00bface&prm[pstool]=205_1&prm[psprogram]=revs&prm[campaign_id]=&subAffId={SUBAFFID}&filters=";
- }
-
- @Override
- public Model createModel(String name) {
- LiveJasminModel model = new LiveJasminModel();
- model.setName(name);
- model.setDescription("");
- model.setSite(this);
- model.setUrl(getBaseUrl() + "/en/chat/" + name);
- return model;
- }
-
- @Override
- public Double getTokenBalance() throws IOException {
- if (getLiveJasminHttpClient().login()) {
- String sessionId = getLiveJasminHttpClient().getSessionId();
- String url = getBaseUrl() + "/en/offline-surprise/get-member-balance?session=" + sessionId;
- Request request = new Request.Builder().url(url)
- .addHeader(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .addHeader(ACCEPT, "*/*")
- .addHeader(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .addHeader(REFERER, getBaseUrl())
- .addHeader(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body()).string();
- JSONObject json = new JSONObject(body);
- if (json.optBoolean("success")) {
- return json.optDouble("result");
- } else {
- throw new IOException("Response was not successful: " + url + "\n" + body);
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- } else {
- throw new IOException(new NotLoggedInException());
- }
- }
-
- @Override
- public String getBuyTokensLink() {
- return getAffiliateLink();
- }
-
- @Override
- public boolean login() throws IOException {
- return getHttpClient().login();
- }
-
- @Override
- public HttpClient getHttpClient() {
- if (httpClient == null) {
- httpClient = new LiveJasminHttpClient(getConfig());
- }
- return httpClient;
- }
-
- @Override
- public void init() throws IOException {
- baseUrl = getConfig().getSettings().livejasminBaseUrl;
- HttpUrl url = HttpUrl.parse(baseUrl);
- baseDomain = url.topPrivateDomain();
- }
-
- @Override
- public void shutdown() {
- if (httpClient != null) {
- httpClient.shutdown();
- }
- }
-
- @Override
- public boolean supportsTips() {
- return true;
- }
-
- @Override
- public boolean supportsFollow() {
- return true;
- }
-
- @Override
- public boolean supportsSearch() {
- return true;
- }
-
- @Override
- public List search(String q) throws IOException, InterruptedException {
- String sessionId = getLiveJasminHttpClient().getSessionId();
- String pathSegment = sessionId.charAt(0) == 'm' ? "member" : "guest";
- String base = "https://api-gateway.dditsadn.com";
- String url = base + "/v2/" + pathSegment + "/auto-suggest/suggestions?"
- + "product=livejasmin"
- + "&session=" + sessionId
- + "&limit=5"
- + "&embed[]=data.performers.*"
- + "&version=v2"
- + "&criteria[]=searchText,%3D," + URLEncoder.encode(q, "utf-8")
- + "&criteria[]=language,%3D,en"
- + "&criteria[]=gender,%3D,female";
- Request request = new Request.Builder().url(url)
- .addHeader(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .addHeader(ACCEPT, "*/*")
- .addHeader(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .addHeader(REFERER, getBaseUrl())
- .addHeader(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- LOG.debug("Search URL: {}", url);
- try (Response response = getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body()).string();
- JSONObject json = new JSONObject(body);
- List models = new ArrayList<>();
- JSONObject data = json.getJSONObject("data");
- JSONArray performers = data.getJSONArray("performers");
- for (int i = 0; i < performers.length(); i++) {
- JSONObject performer = performers.getJSONObject(i);
- LiveJasminModel model = (LiveJasminModel) createModel(performer.getString("performerNick"));
- model.setDisplayName(performer.optString("displayName", model.getName()));
- model.setId(String.valueOf(performer.getLong("performerId")));
- JSONArray profilePics = performer.optJSONArray("profilePictures");
- if (profilePics != null && profilePics.length() > 0) {
- JSONObject profilePic = profilePics.getJSONObject(profilePics.length() - 1);
- model.setPreview("https:" + profilePic.getString("url"));
- }
- models.add(model);
- }
- return models;
- } else {
- throw new HttpException(response.code(), Objects.requireNonNull(response.body()).string());
- }
- }
- }
-
- @Override
- public boolean isSiteForModel(Model m) {
- return m instanceof LiveJasminModel;
- }
-
- @Override
- public boolean credentialsAvailable() {
- return !getConfig().getSettings().livejasminUsername.isEmpty();
- }
-
- private LiveJasminHttpClient getLiveJasminHttpClient() {
- return (LiveJasminHttpClient) getHttpClient();
- }
-
- @Override
- public Model createModelFromUrl(String url) {
- Matcher m = Pattern.compile("http.*?livejasmin\\.com.*?#!chat/(.*)").matcher(url);
- if (m.find()) {
- String name = m.group(1);
- return createModel(name);
- }
- m = Pattern.compile("http.*?livejasmin\\.com.*?/chat(?:-html5)?/(.*)").matcher(url);
- if (m.find()) {
- String name = m.group(1);
- return createModel(name);
- }
-
- return super.createModelFromUrl(url);
- }
-
- public boolean login(boolean renew) throws IOException {
- return getLiveJasminHttpClient().login(renew);
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHttpClient.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHttpClient.java
deleted file mode 100644
index c662f02c..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminHttpClient.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.NoSuchElementException;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.Config;
-import ctbrec.io.HttpClient;
-import okhttp3.Cookie;
-import okhttp3.HttpUrl;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-
-public class LiveJasminHttpClient extends HttpClient {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminHttpClient.class);
-
- protected LiveJasminHttpClient(Config config) {
- super("livejasmin", config);
-
- // delete all cookies, if we are guests, because old guest sessions cause
- // endless redirects
- if(Config.getInstance().getSettings().livejasminUsername.isEmpty()) {
- getCookieJar().clear();
- }
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- if (loggedIn) {
- return true;
- }
-
- boolean cookiesWorked = checkLoginSuccess();
- if (cookiesWorked) {
- loggedIn = true;
- LOG.debug("Logged in with cookies");
- return true;
- }
-
- return false;
- }
-
- public synchronized boolean login(boolean renew) throws IOException {
- if (loggedIn && !renew) {
- return true;
- }
-
- boolean cookiesWorked = checkLoginSuccess();
- if (cookiesWorked) {
- loggedIn = true;
- LOG.debug("Logged in with cookies");
- return true;
- }
-
- return false;
- }
-
- public boolean checkLoginSuccess() throws IOException {
- OkHttpClient temp = client.newBuilder()
- .followRedirects(false)
- .followSslRedirects(false)
- .build();
-
- String url = "https://m." + LiveJasmin.baseDomain + "/en/member/favourite/get-favourite-list?ajax=1";
- Request request = new Request.Builder()
- .url(url)
- .addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgentMobile)
- .addHeader(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .addHeader(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .addHeader(REFERER, LiveJasmin.baseUrl)
- .addHeader(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try(Response response = temp.newCall(request).execute()) {
- LOG.debug("Login Check {}: {} - {}", url, response.code(), response.message());
- if(response.isSuccessful()) {
- return true;
- } else {
- return false;
- }
- }
- }
-
- public String getSessionId() {
- Cookie sessionCookie = getCookieJar().getCookie(HttpUrl.parse(LiveJasmin.baseUrl), "session");
- if(sessionCookie != null) {
- return sessionCookie.value();
- } else {
- throw new NoSuchElementException("session cookie not found");
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminModel.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminModel.java
deleted file mode 100644
index 310d5115..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminModel.java
+++ /dev/null
@@ -1,255 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import com.iheartradio.m3u8.ParseException;
-import com.iheartradio.m3u8.PlaylistException;
-import ctbrec.AbstractModel;
-import ctbrec.Config;
-import ctbrec.StringUtil;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.RecordingProcess;
-import ctbrec.recorder.download.StreamSource;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class LiveJasminModel extends AbstractModel {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminModel.class);
- private String id;
- private boolean online = false;
- private int[] resolution;
-
- private final Random rng = new Random();
-
- private transient LiveJasminModelInfo modelInfo;
-
- @Override
- public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
- if (ignoreCache) {
- loadModelInfo();
- }
- return online;
- }
-
- protected void loadModelInfo() throws IOException {
- Request req = new Request.Builder().url(LiveJasmin.baseUrl)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_ENCODING, "deflate")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, getSite().getBaseUrl() + "/")
- .header(ORIGIN, getSite().getBaseUrl())
- .build();
- try (Response response = getSite().getHttpClient().execute(req)) {
- // do nothing we just want the cookies
- LOG.debug("Initial request succeeded: {} - {}", response.isSuccessful(), response.code());
- }
-
- String url = LiveJasmin.baseUrl + "/en/flash/get-performer-details/" + getName();
- req = new Request.Builder().url(url)
- .header(USER_AGENT, "Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15")
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT_ENCODING, "deflate")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, getSite().getBaseUrl() + "/")
- .header(ORIGIN, getSite().getBaseUrl())
- .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = getSite().getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = response.body().string();
- JSONObject json = new JSONObject(body);
- LOG.trace(json.toString(2));
- if (json.optBoolean("success")) {
- JSONObject data = json.getJSONObject("data");
- modelInfo = new LiveJasminModelInfo.LiveJasminModelInfoBuilder()
- .sbIp(data.optString("sb_ip", null))
- .sbHash(data.optString("sb_hash", null))
- .sessionId("m12345678901234567890123456789012")
- .jsm2session(getSite().getHttpClient().getCookiesByName("session").get(0).value())
- .performerId(data.optString("performer_id", getName()))
- .displayName(data.optString("display_name", getName()))
- .clientInstanceId(randomClientInstanceId())
- .status(data.optInt("status", -1))
- .build();
- if (data.has("channelsiteurl")) {
- setUrl(LiveJasmin.baseUrl + data.getString("channelsiteurl"));
- }
- onlineState = mapStatus(modelInfo.getStatus());
- online = onlineState == State.ONLINE
- && StringUtil.isNotBlank(modelInfo.getSbIp())
- && StringUtil.isNotBlank(modelInfo.getSbHash());
- LOG.debug("{} - status:{} {} {} {} {}", getName(), online, onlineState, Arrays.toString(resolution), getUrl(), id);
- } else {
- throw new IOException("Response was not successful: " + body);
- }
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private String randomClientInstanceId() {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < 32; i++) {
- sb.append(rng.nextInt(9) + 1);
- }
- return sb.toString();
- }
-
- public static State mapStatus(int status) {
- switch (status) {
- case 0 -> {
- return State.OFFLINE;
- }
- case 1 -> {
- return State.ONLINE;
- }
- case 2, 3 -> {
- return State.PRIVATE;
- }
- default -> {
- LOG.debug("Unkown state {}", status);
- return State.UNKNOWN;
- }
- }
- }
-
- @Override
- public void setOnlineState(State status) {
- super.setOnlineState(status);
- online = status == State.ONLINE;
- }
-
- @Override
- public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
- loadModelInfo();
-
- String websocketUrlTemplate = "wss://dss-relay-{ipWithDashes}.dditscdn.com/memberChat/jasmin{modelName}{sb_hash}?random={clientInstanceId}";
- String websocketUrl = websocketUrlTemplate
- .replace("{ipWithDashes}", modelInfo.getSbIp().replace('.', '-'))
- .replace("{modelName}", getName())
- .replace("{sb_hash}", modelInfo.getSbHash())
- .replace("{clientInstanceId}", modelInfo.getClientInstanceId());
- modelInfo.setWebsocketUrl(websocketUrl);
-
- LiveJasminStreamRegistration liveJasminStreamRegistration = new LiveJasminStreamRegistration(site, modelInfo);
- List streamSources = liveJasminStreamRegistration.getStreamSources();
- Collections.sort(streamSources);
- return streamSources;
- }
-
- @Override
- public void invalidateCacheEntries() {
- // noop
- }
-
- @Override
- public void receiveTip(Double tokens) throws IOException {
- LiveJasminTippingWebSocket tippingSocket = new LiveJasminTippingWebSocket(site.getHttpClient());
- try {
- tippingSocket.sendTip(this, Config.getInstance(), tokens);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- throw new IOException(e);
- }
- }
-
- @Override
- public int[] getStreamResolution(boolean failFast) throws ExecutionException {
- if (resolution == null) {
- if (failFast) {
- return new int[2];
- }
- try {
- loadModelInfo();
- } catch (IOException e) {
- throw new ExecutionException(e);
- }
- }
- return resolution;
- }
-
- @Override
- public boolean follow() throws IOException {
- return follow(true);
- }
-
- @Override
- public boolean unfollow() throws IOException {
- return follow(false);
- }
-
- private boolean follow(boolean follow) throws IOException {
- if (id == null) {
- loadModelInfo();
- }
-
- String sessionId = ((LiveJasminHttpClient) site.getHttpClient()).getSessionId();
- String url;
- if (follow) {
- url = site.getBaseUrl() + "/en/free/favourite/add-favourite?session=" + sessionId + "&performerId=" + id;
- } else {
- url = site.getBaseUrl() + "/en/free/favourite/delete-favourite?session=" + sessionId + "&performerId=" + id;
- }
- Request request = new Request.Builder()
- .url(url)
- .addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .addHeader(ACCEPT, "*/*")
- .addHeader(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .addHeader(REFERER, getUrl())
- .addHeader(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = site.getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String body = response.body().string();
- JSONObject json = new JSONObject(body);
- return json.optString("status").equalsIgnoreCase("ok");
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- public String getId() {
- return id;
- }
-
- public void setId(String id) {
- this.id = id;
- }
-
- @Override
- public void readSiteSpecificData(Map data) {
- id = data.get("id");
- }
-
- @Override
- public void writeSiteSpecificData(Map data) {
- if (id == null) {
- try {
- loadModelInfo();
- } catch (IOException e) {
- LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName());
- }
- }
- data.put("id", id);
- }
-
- public void setOnline(boolean online) {
- this.online = online;
- }
-
- @Override
- public RecordingProcess createDownload() {
- return new LiveJasminWebrtcDownload(getSite().getHttpClient());
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminModelInfo.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminModelInfo.java
deleted file mode 100644
index e2e29c9d..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminModelInfo.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import lombok.Builder;
-import lombok.Data;
-
-@Data
-@Builder
-public class LiveJasminModelInfo {
- private String websocketUrl;
- private String sbIp;
- private String sbHash;
- private String sessionId;
- private String jsm2session;
- private String performerId;
- private String displayName;
- private String clientInstanceId;
- private int status;
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java
deleted file mode 100644
index dcaac2da..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java
+++ /dev/null
@@ -1,258 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import ctbrec.Config;
-import ctbrec.recorder.download.StreamSource;
-import ctbrec.sites.Site;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.WebSocket;
-import okhttp3.WebSocketListener;
-import okio.ByteString;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.net.URLEncoder;
-import java.util.*;
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
-
-import static ctbrec.io.HttpConstants.USER_AGENT;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-@Slf4j
-public class LiveJasminStreamRegistration {
-
- private static final String KEY_EVENT = "event";
- private static final String KEY_FUNC_NAME = "funcName";
-
- private final Site site;
- private final LiveJasminModelInfo modelInfo;
- private final CyclicBarrier barrier = new CyclicBarrier(2);
-
- private int streamCount = 0;
- private WebSocket webSocket;
-
- public LiveJasminStreamRegistration(Site site, LiveJasminModelInfo modelInfo) {
- this.site = site;
- this.modelInfo = modelInfo;
- }
-
- List getStreamSources() {
- var streamSources = new LinkedList();
- try {
- Request webSocketRequest = new Request.Builder()
- .url(modelInfo.getWebsocketUrl())
- .addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgentMobile)
- .build();
- log.debug("Websocket: {}", modelInfo.getWebsocketUrl());
- webSocket = site.getHttpClient().newWebSocket(webSocketRequest, new WebSocketListener() {
- @Override
- public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
- Thread.currentThread().setName("Stream registration for " + modelInfo.getPerformerId());
- log.debug("onOpen");
- JSONObject register = new JSONObject()
- .put(KEY_EVENT, "register")
- .put("applicationId", "memberChat/jasmin" + modelInfo.getPerformerId() + modelInfo.getSbHash())
- .put("connectionData", new JSONObject()
- .put("sessionID", modelInfo.getSessionId())
- .put("jasmin2App", true)
- .put("isMobileClient", false)
- .put("platform", "desktop")
- .put("chatID", "freechat")
- .put("jsm2SessionId", modelInfo.getJsm2session())
- .put("userType", "user")
- .put("performerId", modelInfo.getPerformerId())
- .put("clientRevision", "")
- .put("livejasminTvmember", false)
- .put("newApplet", true)
- .put("livefeedtype", JSONObject.NULL)
- .put("gravityCookieId", "")
- .put("passparam", "")
- .put("brandID", "jasmin")
- .put("cobrandId", "livejasmin")
- .put("subbrand", "livejasmin")
- .put("siteName", "LiveJasmin")
- .put("siteUrl", "https://www.livejasmin.com")
- .put("clientInstanceId", modelInfo.getClientInstanceId())
- .put("armaVersion", "38.32.1-LIVEJASMIN-44016-1")
- .put("isPassive", false)
- .put("peekPatternJsm2", true)
- .put("chatHistoryRequired", true)
- );
- log.trace("Stream registration\n{}", register.toString(2));
- send(register.toString());
- send(new JSONObject().put(KEY_EVENT, "ping").toString());
- send(new JSONObject()
- .put(KEY_EVENT, "call")
- .put(KEY_FUNC_NAME, "makeActive")
- .put("data", new JSONArray())
- .toString());
- send(new JSONObject()
- .put(KEY_EVENT, "call")
- .put(KEY_FUNC_NAME, "setVideo")
- .put("data", new JSONArray()
- .put(JSONObject.NULL)
- .put(false)
- .put(true)
- .put(modelInfo.getJsm2session())
- )
- .toString());
- send(new JSONObject()
- .put(KEY_EVENT, "connectSharedObject")
- .put("name", "data/chat_so")
- .toString());
- }
-
- @Override
- public void onFailure(@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) {
- log.error("onFailure", t);
- awaitBarrier();
- webSocket.close(1000, "");
- }
-
- @Override
- public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
- log.trace("< {}", text);
- JSONObject message = new JSONObject(text);
- if (message.opt(KEY_EVENT).equals("pong")) {
- new Thread(() -> {
- try {
- Thread.sleep(message.optInt("nextPing"));
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- send(new JSONObject().put(KEY_EVENT, "ping").toString());
- }).start();
- } else if (message.optString(KEY_EVENT).equals("updateSharedObject") && message.optString("name").equals("data/chat_so")) {
- log.trace(message.toString(2));
- JSONArray list = message.getJSONArray("list");
- for (int i = 0; i < list.length(); i++) {
- JSONObject attribute = list.getJSONObject(i);
- if (attribute.optString("name").equals("streamList")) {
- JSONObject value = attribute.getJSONObject("newValue");
- JSONObject patterns = value.getJSONObject("patterns");
- String freePattern = patterns.getString("free");
- JSONArray streams = value.getJSONArray("streams");
- for (int j = 0; j < streams.length(); j++) {
- JSONObject stream = streams.getJSONObject(j);
- addStreamSource(streamSources, freePattern, stream);
- }
- Collections.sort(streamSources);
- Collections.reverse(streamSources);
- for (LiveJasminStreamSource src : streamSources) {
- JSONObject getVideoData = new JSONObject()
- .put(KEY_EVENT, "call")
- .put(KEY_FUNC_NAME, "getVideoData")
- .put("data", new JSONArray()
- .put(new JSONObject()
- .put("protocols", new JSONArray()
- .put("h5live")
- )
- .put("streamId", src.getStreamId())
- .put("correlationId", UUID.randomUUID().toString().replace("-", "").substring(0, 16))
- )
- );
- streamCount++;
- send(getVideoData.toString());
- }
- }
- }
- } else if (message.optString(KEY_FUNC_NAME).equals("setVideoData")) {
- JSONObject data = message.getJSONArray("data").getJSONArray(0).getJSONObject(0);
- String streamId = data.getString("streamId");
- String wssUrl = data.getJSONObject("protocol").getJSONObject("h5live").getString("wssUrl");
- streamSources.stream().filter(src -> Objects.equals(src.getStreamId(), streamId)).findAny().ifPresent(src -> src.setMediaPlaylistUrl(wssUrl));
- if (--streamCount == 0) {
- awaitBarrier();
- }
- } else if (!message.optString(KEY_FUNC_NAME).equals("chatHistory")) {
- log.trace("onMessageT {}", new JSONObject(text).toString(2));
- }
- }
-
- @Override
- public void onMessage(@NotNull WebSocket webSocket, @NotNull ByteString bytes) {
- log.trace("onMessageB");
- super.onMessage(webSocket, bytes);
- }
-
- @Override
- public void onClosed(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
- log.trace("onClosed {} {}", code, reason);
- super.onClosed(webSocket, code, reason);
- }
-
- @Override
- public void onClosing(@NotNull WebSocket webSocket, int code, @NotNull String reason) {
- log.trace("onClosing {} {}", code, reason);
- awaitBarrier();
- }
-
- private void send(String msg) {
- log.debug(" > {}", msg);
- webSocket.send(msg);
- }
- });
-
- log.debug("Waiting for websocket to return");
- awaitBarrier();
- log.debug("Websocket is done. Stream sources {}", streamSources);
- } catch (Exception e) {
- log.error("Couldn't determine stream sources", e);
- }
- return streamSources.stream().map(StreamSource.class::cast).collect(Collectors.toList()); // NOSONAR
- }
-
- private void addStreamSource(LinkedList streamSources, String pattern, JSONObject stream) {
- int w = stream.getInt("width");
- int h = stream.getInt("height");
- int bitrate = stream.getInt("bitrate") * 1024;
- String name = stream.getString("name");
- String streamName = pattern.replace("{$streamname}", name);
- String streamId = stream.getString("streamId");
-
- String rtmpUrl = "rtmp://{ip}/memberChat/jasmin{modelName}{sb_hash}?sessionId-{sessionId}|clientInstanceId-{clientInstanceId}"
- .replace("{ip}", modelInfo.getSbIp())
- .replace("{modelName}", modelInfo.getPerformerId())
- .replace("{sb_hash}", modelInfo.getSbHash())
- .replace("{sessionId}", modelInfo.getSessionId())
- .replace("{clientInstanceId}", modelInfo.getClientInstanceId());
-
- String hlsUrl = "https://dss-hls-{ipWithDashes}.dditscdn.com/h5live/http/playlist.m3u8?url={rtmpUrl}&stream={streamName}"
- .replace("{ipWithDashes}", modelInfo.getSbIp().replace('.', '-'))
- .replace("{rtmpUrl}", URLEncoder.encode(rtmpUrl, UTF_8))
- .replace("{streamName}", URLEncoder.encode(streamName, UTF_8));
-
- LiveJasminStreamSource streamSource = new LiveJasminStreamSource();
- streamSource.setMediaPlaylistUrl(hlsUrl);
- streamSource.setWidth(w);
- streamSource.setHeight(h);
- streamSource.setBandwidth(bitrate);
- streamSource.setRtmpUrl(rtmpUrl);
- streamSource.setStreamName(streamName);
- streamSource.setStreamId(streamId);
- streamSource.setStreamRegistration(this);
- streamSources.add(streamSource);
- }
-
- private void awaitBarrier() {
- try {
- barrier.await(10, TimeUnit.SECONDS);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- log.error(e.getLocalizedMessage(), e);
- } catch (TimeoutException | BrokenBarrierException e) {
- log.error(e.getLocalizedMessage(), e);
- }
- }
-
- void close() {
- webSocket.close(1000, "");
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamSource.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamSource.java
deleted file mode 100644
index 9b7a8703..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamSource.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import ctbrec.recorder.download.StreamSource;
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class LiveJasminStreamSource extends StreamSource {
- private String rtmpUrl;
- private String streamName;
- private String streamId;
- private LiveJasminStreamRegistration streamRegistration;
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminTippingWebSocket.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminTippingWebSocket.java
deleted file mode 100644
index 1e82c9f6..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminTippingWebSocket.java
+++ /dev/null
@@ -1,176 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-import java.util.Locale;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.Config;
-import ctbrec.GlobalThreadPool;
-import ctbrec.Model;
-import ctbrec.io.HttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.WebSocket;
-import okhttp3.WebSocketListener;
-import okio.ByteString;
-
-public class LiveJasminTippingWebSocket {
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminTippingWebSocket.class);
-
- private String applicationId;
- private String sessionId;
- private String jsm2SessionId;
- private String sb_ip;
- private String sb_hash;
- private String relayHost;
- private String streamHost;
- private String clientInstanceId = "01234567890123456789012345678901"; // TODO where to get or generate a random id?
- private WebSocket relay;
- private Throwable exception;
-
- private HttpClient client;
- private Model model;
-
- public LiveJasminTippingWebSocket(HttpClient client) {
- this.client = client;
- }
-
- public void sendTip(Model model, Config config, double amount) throws IOException, InterruptedException {
- this.model = model;
- getPerformerDetails(model.getName());
- LOG.debug("appid: {}", applicationId);
- LOG.debug("sessionid: {}",sessionId);
- LOG.debug("jsm2sessionid: {}",jsm2SessionId);
- LOG.debug("sb_ip: {}",sb_ip);
- LOG.debug("sb_hash: {}",sb_hash);
- LOG.debug("relay host: {}",relayHost);
- LOG.debug("stream host: {}",streamHost);
- LOG.debug("clientinstanceid {}",clientInstanceId);
-
- Request request = new Request.Builder()
- .url("https://" + relayHost + "/")
- .header(ORIGIN, LiveJasmin.baseUrl)
- .header(USER_AGENT, config.getSettings().httpUserAgent)
- .header(ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .build();
- Object monitor = new Object();
- relay = client.newWebSocket(request, new WebSocketListener() {
- @Override
- public void onOpen(WebSocket webSocket, Response response) {
- LOG.trace("relay open {}", model.getName());
- sendToRelay("{\"event\":\"register\",\"applicationId\":\"" + applicationId
- + "\",\"connectionData\":{\"jasmin2App\":true,\"isMobileClient\":false,\"platform\":\"desktop\",\"chatID\":\"freechat\","
- + "\"sessionID\":\"" + sessionId + "\"," + "\"jsm2SessionId\":\"" + jsm2SessionId + "\",\"userType\":\"user\"," + "\"performerId\":\""
- + model
- + "\",\"clientRevision\":\"\",\"proxyIP\":\"\",\"playerVer\":\"nanoPlayerVersion: 3.10.3 appCodeName: Mozilla appName: Netscape appVersion: 5.0 (X11) platform: Linux x86_64\",\"livejasminTvmember\":false,\"newApplet\":true,\"livefeedtype\":null,\"gravityCookieId\":\"\",\"passparam\":\"\",\"brandID\":\"jasmin\",\"cobrandId\":\"\",\"subbrand\":\"livejasmin\",\"siteName\":\"LiveJasmin\",\"siteUrl\":\""+LiveJasmin.baseUrl+"\","
- + "\"clientInstanceId\":\"" + clientInstanceId + "\",\"armaVersion\":\"34.10.0\",\"isPassive\":false}}");
- response.close();
- }
-
- @Override
- public void onMessage(WebSocket webSocket, String text) {
- LOG.trace("relay <-- {} T{}", model.getName(), text);
- JSONObject event = new JSONObject(text);
- if (event.optString("event").equals("accept")) {
- GlobalThreadPool.submit(() -> {
- sendToRelay("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
- });
- } else if(event.optString("event").equals("call")) {
- String func = event.optString("funcName");
- if (func.equals("setName")) {
- LOG.debug("Entered chat -> Sending tip of {}", amount);
- sendToRelay("{\"event\":\"call\",\"funcName\":\"sendSurprise\",\"data\":["+amount+",\"SurpriseGirlFlower\"]}");
- } else if (func.equals("startSurprise")) {
- // {"event":"call","funcName":"startSurprise","userId":"xyz_hash_gibberish","data":[{"memberid":"userxyz","amount":1,"tipName":"SurpriseGirlFlower","err_desc":"OK","err_text":"OK"}]}
- JSONArray dataArray = event.getJSONArray("data");
- JSONObject data = dataArray.getJSONObject(0);
- String errText = data.optString("err_text");
- String errDesc = data.optString("err_desc");
- LOG.debug("Tip response {} - {}", errText, errDesc);
- if(!errText.equalsIgnoreCase("OK")) {
- exception = new IOException(errText + " - " + errDesc);
- }
- synchronized (monitor) {
- monitor.notify();
- }
- }
- }
- }
-
- @Override
- public void onMessage(WebSocket webSocket, ByteString bytes) {
- LOG.trace("relay <-- {} B{}", model.getName(), bytes.toString());
- }
-
- @Override
- public void onClosed(WebSocket webSocket, int code, String reason) {
- LOG.trace("relay closed {} {} {}", code, reason, model.getName());
- exception = new IOException("Socket closed by server - " + code + " " + reason);
- synchronized (monitor) {
- monitor.notify();
- }
- }
-
- @Override
- public void onFailure(WebSocket webSocket, Throwable t, Response response) {
- exception = t;
- synchronized (monitor) {
- monitor.notify();
- }
- }
- });
- synchronized (monitor) {
- monitor.wait();
- }
- if(exception != null) {
- throw new IOException(exception);
- }
- }
-
- private void sendToRelay(String msg) {
- LOG.trace("relay --> {} {}", model.getName(), msg);
- relay.send(msg);
- }
-
- protected void getPerformerDetails(String name) throws IOException {
- String url = "https://m." + LiveJasmin.baseDomain + "/en/chat-html5/" + name;
- Request req = new Request.Builder()
- .url(url)
- .header(USER_AGENT, "Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15")
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(REFERER, LiveJasmin.baseUrl)
- .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .build();
- try (Response response = client.execute(req)) {
- if (response.isSuccessful()) {
- String body = response.body().string();
- JSONObject json = new JSONObject(body);
- if (json.optBoolean("success")) {
- JSONObject data = json.getJSONObject("data");
- JSONObject config = data.getJSONObject("config");
- JSONObject armageddonConfig = config.getJSONObject("armageddonConfig");
- JSONObject chatRoom = config.getJSONObject("chatRoom");
- sessionId = armageddonConfig.getString("sessionid");
- jsm2SessionId = armageddonConfig.getString("jsm2session");
- sb_hash = chatRoom.getString("sb_hash");
- sb_ip = chatRoom.getString("sb_ip");
- applicationId = "memberChat/jasmin" + name + sb_hash;
- relayHost = "dss-relay-" + sb_ip.replace('.', '-') + ".dditscdn.com";
- streamHost = "dss-live-" + sb_ip.replace('.', '-') + ".dditscdn.com";
- } else {
- throw new IOException("Response was not successful: " + body);
- }
- } else {
- throw new IOException(response.code() + " - " + response.message());
- }
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminWebrtcDownload.java b/common/src/main/java/ctbrec/sites/jasmin/LiveJasminWebrtcDownload.java
deleted file mode 100644
index 4ed01148..00000000
--- a/common/src/main/java/ctbrec/sites/jasmin/LiveJasminWebrtcDownload.java
+++ /dev/null
@@ -1,247 +0,0 @@
-package ctbrec.sites.jasmin;
-
-import com.iheartradio.m3u8.ParseException;
-import com.iheartradio.m3u8.PlaylistException;
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.Recording;
-import ctbrec.io.BandwidthMeter;
-import ctbrec.io.HttpClient;
-import ctbrec.recorder.download.AbstractDownload;
-import ctbrec.recorder.download.RecordingProcess;
-import ctbrec.recorder.download.StreamSource;
-import ctbrec.sites.showup.Showup;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.WebSocket;
-import okhttp3.WebSocketListener;
-import okio.ByteString;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class LiveJasminWebrtcDownload extends AbstractDownload {
-
- private static final Logger LOG = LoggerFactory.getLogger(LiveJasminWebrtcDownload.class);
- private static final int MAX_SECONDS_WITHOUT_TRANSFER = 20;
-
- private final HttpClient httpClient;
- private WebSocket ws;
- private FileOutputStream fout;
- private Instant timeOfLastTransfer = Instant.MAX;
-
- private volatile boolean running;
- private volatile boolean started;
-
-
- private File targetFile;
-
- public LiveJasminWebrtcDownload(HttpClient httpClient) {
- this.httpClient = httpClient;
- }
-
- @Override
- public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
- this.config = config;
- this.model = model;
- this.startTime = startTime;
- this.downloadExecutor = executorService;
- splittingStrategy = initSplittingStrategy(config.getSettings());
- targetFile = config.getFileForRecording(model, "mp4", startTime);
- timeOfLastTransfer = Instant.now();
- }
-
- @Override
- public void stop() {
- running = false;
- if (ws != null) {
- ws.close(1000, "");
- ws = null;
- }
- }
-
- @Override
- public void finalizeDownload() {
- if (fout != null) {
- try {
- LOG.debug("Closing recording file {}", targetFile);
- fout.close();
- } catch (IOException e) {
- LOG.error("Error while closing recording file {}", targetFile, e);
- }
- }
- }
-
- @Override
- public boolean isRunning() {
- return running;
- }
-
- @Override
- public void postProcess(Recording recording) {
- // nothing to do
- }
-
- @Override
- public File getTarget() {
- return targetFile;
- }
-
- @Override
- public String getPath(Model model) {
- String absolutePath = targetFile.getAbsolutePath();
- String recordingsDir = Config.getInstance().getSettings().recordingsDir;
- String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
- return relativePath;
- }
-
- @Override
- public boolean isSingleFile() {
- return true;
- }
-
- @Override
- public long getSizeInByte() {
- return getTarget().length();
- }
-
- @Override
- public RecordingProcess call() throws Exception {
- if (!started) {
- started = true;
- startDownload();
- }
-
- if (splittingStrategy.splitNecessary(this)) {
- stop();
- rescheduleTime = Instant.now();
- } else {
- rescheduleTime = Instant.now().plusSeconds(5);
- }
- if (!model.isOnline(true)) {
- stop();
- }
- if (Duration.between(timeOfLastTransfer, Instant.now()).getSeconds() > MAX_SECONDS_WITHOUT_TRANSFER) {
- LOG.info("No video data received for {} seconds. Stopping recording for model {}", MAX_SECONDS_WITHOUT_TRANSFER, model);
- stop();
- }
- return this;
- }
-
- private void startDownload() throws IOException, PlaylistException, ParseException, ExecutionException {
- LiveJasminModel liveJasminModel = (LiveJasminModel) model;
- List streamSources = liveJasminModel.getStreamSources();
- LiveJasminStreamSource streamSource = (LiveJasminStreamSource) selectStreamSource(streamSources);
- LiveJasminStreamRegistration streamRegistration = streamSource.getStreamRegistration();
- Request request = new Request.Builder()
- .url(streamSource.getMediaPlaylistUrl())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, "pl")
- .header(REFERER, model.getSite().getBaseUrl() + "/")
- .header(ORIGIN, Showup.BASE_URL)
- .build();
-
- running = true;
- LOG.debug("Opening webrtc connection {}", request.url());
- ws = httpClient.newWebSocket(request, new WebSocketListener() {
- @Override
- public void onOpen(WebSocket webSocket, Response response) {
- super.onOpen(webSocket, response);
- LOG.trace("onOpen {} {}", webSocket, response);
- response.close();
- try {
- LOG.trace("Recording video stream to {}", targetFile);
- Files.createDirectories(targetFile.getParentFile().toPath());
- fout = new FileOutputStream(targetFile);
- } catch (Exception e) {
- LOG.error("Couldn't open file {} to save the video stream", targetFile, e);
- stop();
- }
- }
-
- @Override
- public void onMessage(WebSocket webSocket, ByteString bytes) {
- super.onMessage(webSocket, bytes);
- LOG.trace("received video data with length {}", bytes.size());
- timeOfLastTransfer = Instant.now();
- try {
- byte[] videoData = bytes.toByteArray();
- fout.write(videoData);
- BandwidthMeter.add(videoData.length);
- } catch (IOException e) {
- if (running) {
- LOG.error("Couldn't write video stream to file", e);
- stop();
- }
- }
- }
-
- @Override
- public void onMessage(WebSocket webSocket, String text) {
- super.onMessage(webSocket, text);
- LOG.trace("onMessageT {} {}", webSocket, text);
- }
-
- @Override
- public void onFailure(WebSocket webSocket, Throwable t, Response response) {
- super.onFailure(webSocket, t, response);
- stop();
- if (t instanceof EOFException) {
- LOG.info("End of stream detected for model {}", model);
- } else {
- LOG.error("Websocket failure for model {} {}", model, response, t);
- }
- if (response != null) {
- response.close();
- }
- streamRegistration.close();
- }
-
- @Override
- public void onClosing(WebSocket webSocket, int code, String reason) {
- super.onClosing(webSocket, code, reason);
- LOG.trace("Websocket closing for model {} {} {}", model, code, reason);
- }
-
- @Override
- public void onClosed(WebSocket webSocket, int code, String reason) {
- super.onClosed(webSocket, code, reason);
- LOG.debug("Websocket closed for model {} {} {}", model, code, reason);
- stop();
- streamRegistration.close();
- }
- });
- }
-
- @Override
- public void awaitEnd() {
- long secondsToWait = 30;
- for (int i = 0; i < secondsToWait; i++) {
- if (ws == null) {
- return;
- } else {
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- LOG.warn("Interrupted while waiting for the download to terminate");
- }
- }
- }
- LOG.warn("Download didn't finish in {} seconds", secondsToWait);
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java b/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
deleted file mode 100644
index 5b1929b9..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLive.java
+++ /dev/null
@@ -1,212 +0,0 @@
-package ctbrec.sites.manyvids;
-
-import ctbrec.Model;
-import ctbrec.Model.State;
-import ctbrec.io.HtmlParser;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import ctbrec.sites.AbstractSite;
-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 java.io.IOException;
-import java.net.URLDecoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-public class MVLive extends AbstractSite {
-
- 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/";
-
- private MVLiveHttpClient httpClient;
- private String mvtoken;
-
- @Override
- public String getName() {
- return "MV Live";
- }
-
- @Override
- public String getBaseUrl() {
- return BASE_URL;
- }
-
- @Override
- public String getAffiliateLink() {
- return getBaseUrl() + "/Join-MV/1002294529";
- }
-
- @Override
- public MVLiveModel createModel(String name) {
- MVLiveModel model = new MVLiveModel();
- model.setName(name);
- model.setDescription("");
- model.setSite(this);
- model.setUrl(WS_ORIGIN + '/' + name);
- return model;
- }
-
- @Override
- public Double getTokenBalance() throws IOException {
- return 0d;
- }
-
- @Override
- public String getBuyTokensLink() {
- return getAffiliateLink();
- }
-
- @Override
- public boolean login() throws IOException {
- return false;
- }
-
- @Override
- public HttpClient getHttpClient() {
- if (httpClient == null) {
- httpClient = new MVLiveHttpClient(getConfig());
- }
- return httpClient;
- }
-
- @Override
- public void shutdown() {
- if (httpClient != null) {
- httpClient.shutdown();
- }
- }
-
- @Override
- public boolean supportsTips() {
- return false;
- }
-
- @Override
- public boolean supportsFollow() {
- return false;
- }
-
- @Override
- public boolean isSiteForModel(Model m) {
- return m instanceof MVLiveModel;
- }
-
- @Override
- public boolean credentialsAvailable() {
- return false;
- }
-
- @Override
- public void init() {
- // nothing to do
- }
-
- @Override
- public boolean supportsSearch() {
- return true;
- }
-
- String getMvToken() throws IOException {
- if (mvtoken == null) {
- Request request = new Request.Builder()
- .url(BASE_URL)
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .build();
- try (Response response = getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- Element tag = HtmlParser.getTag(response.body().string(), "html");
- mvtoken = tag.attr("data-mvtoken");
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
- return mvtoken;
- }
-
- @Override
- public List search(String q) throws IOException, InterruptedException {
- List result = new ArrayList<>();
- RequestBody body = new FormBody.Builder()
- .add("mvtoken", getMvToken())
- .add("type", "search")
- .add("category", STARS)
- .add("search", q)
- .build();
- Request request = new Request.Builder()
- .url("https://www.manyvids.com/includes/filterSearch.php")
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .header(ORIGIN, MVLive.BASE_URL)
- .header(REFERER, MVLive.BASE_URL)
- .header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
- .post(body)
- .build();
- try (Response response = getHttpClient().execute(request)) {
- if (response.isSuccessful()) {
- String responseBody = response.body().string();
- parseSearchResult(result, responseBody);
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- return result;
- }
-
- private void parseSearchResult(List result, String responseBody) {
- JSONObject json = new JSONObject(responseBody);
- if (json.has(STARS)) {
- JSONArray stars = json.getJSONArray(STARS);
- for (int i = 0; i < stars.length(); i++) {
- JSONObject star = stars.getJSONObject(i);
- String name = star.getString("label");
- MVLiveModel model = createModel(name);
- long id = star.getLong("id");
- String url = BASE_URL + "/Profile/" + id + '/' + model.getDisplayName().replace(" ", "-") + '/';
- model.setUrl(url);
- model.setPreview(star.getString("img"));
- if (star.optString("is_live").equals("1")) {
- if (star.optString("is_private").equals("1")) {
- model.setOnlineState(State.PRIVATE);
- } else {
- model.setOnlineState(State.ONLINE);
- }
- } else {
- model.setOnlineState(State.OFFLINE);
- }
- result.add(model);
- }
- }
- }
-
- @Override
- public Model createModelFromUrl(String url) {
- Matcher m = Pattern.compile("https://live.manyvids.com/(?:stream/)?(.*?)(?:/.*?)?").matcher(url.trim());
- if (m.matches()) {
- String modelName = URLDecoder.decode(m.group(1), UTF_8);
- return createModel(modelName);
- }
- m = Pattern.compile("https://www.manyvids.com/MVLive/(.*?)/\\d+/?").matcher(url.trim());
- if (m.matches()) {
- String modelName = URLDecoder.decode(m.group(1), UTF_8);
- return createModel(modelName);
- }
-
- return super.createModelFromUrl(url);
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
deleted file mode 100644
index d0452dbb..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java
+++ /dev/null
@@ -1,219 +0,0 @@
-package ctbrec.sites.manyvids;
-
-import static ctbrec.StringUtil.*;
-import static ctbrec.io.HttpConstants.*;
-import static ctbrec.sites.manyvids.MVLive.*;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Random;
-import java.util.UUID;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.common.base.Objects;
-
-import ctbrec.Config;
-import ctbrec.sites.manyvids.wsmsg.GetBroadcastHealth;
-import ctbrec.sites.manyvids.wsmsg.Message;
-import ctbrec.sites.manyvids.wsmsg.Ping;
-import ctbrec.sites.manyvids.wsmsg.RegisterMessage;
-import ctbrec.sites.manyvids.wsmsg.SendMessage;
-import okhttp3.Cookie;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.WebSocket;
-import okhttp3.WebSocketListener;
-import okio.ByteString;
-
-public class MVLiveClient {
-
- private static final Logger LOG = LoggerFactory.getLogger(MVLiveClient.class);
-
- private final Map futureResponses = new HashMap<>();
- private final MVLiveHttpClient httpClient;
- private final Object streamUrlMonitor = new Object();
- private final Random rng = new Random();
-
- private WebSocket ws;
- private volatile boolean running = false;
- private volatile boolean connecting = false;
- private String masterPlaylist = null;
- private String roomNumber;
- private String roomId;
- private ScheduledExecutorService scheduler;
-
- public MVLiveClient(MVLiveHttpClient httpClient) {
- this.httpClient = httpClient;
- }
-
- public void start(MVLiveModel model) throws IOException {
- running = true;
-
- if (ws == null && !connecting) {
- httpClient.fetchAuthenticationCookies();
- JSONObject response = model.getRoomLocation();
- roomNumber = response.optString("floorId");
- roomId = response.optString("roomId");
- int randomNumber = 100 + rng.nextInt(800);
- String randomString = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
- 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 {}", url);
- ws = createWebSocket(url, roomId, model.getDisplayName());
- }
- }
-
- private String getPhpSessionIdCookie() {
- List cookies = httpClient.getCookiesByName("PHPSESSID");
- return cookies.stream().map(c -> c.name() + "=" + c.value()).findFirst().orElse("");
- }
-
- public void stop() {
- running = false;
- Optional.ofNullable(scheduler).ifPresent(ScheduledExecutorService::shutdown);
- ws.close(1000, "Good Bye"); // terminate normally (1000)
- ws = null;
- }
-
- private WebSocket createWebSocket(String wsUrl, String roomId, String modelName) {
- connecting = true;
- Request req = new Request.Builder()
- .url(wsUrl)
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ORIGIN, WS_ORIGIN)
- .header(COOKIE, getPhpSessionIdCookie())
- .build();
- return httpClient.newWebSocket(req, new WebSocketListener() {
- @Override
- public void onOpen(WebSocket webSocket, Response response) {
- super.onOpen(webSocket, response);
- try {
- connecting = false;
- LOG.trace("WS open: [{}]", response.body().string());
- scheduler = new ScheduledThreadPoolExecutor(1);
- scheduler.scheduleAtFixedRate(() -> sendMessages(new Ping()), 5, 5, TimeUnit.SECONDS);
- } catch (IOException e) {
- LOG.error("Error while processing onOpen event", e);
- }
- }
-
- @Override
- public void onClosed(WebSocket webSocket, int code, String reason) {
- super.onClosed(webSocket, code, reason);
- connecting = false;
- LOG.trace("MVLive websocket closed: {} {}", code, reason);
- MVLiveClient.this.ws = null;
- running = false;
- synchronized (streamUrlMonitor) {
- streamUrlMonitor.notifyAll();
- }
- }
-
- @Override
- public void onFailure(WebSocket webSocket, Throwable t, Response response) {
- super.onFailure(webSocket, t, response);
- running = false;
- synchronized (streamUrlMonitor) {
- streamUrlMonitor.notifyAll();
- }
- connecting = false;
- if (response != null) {
- int code = response.code();
- String message = response.message();
- LOG.error("MVLive websocket failure: {} {}", code, message, t);
- response.close();
- } else {
- LOG.error("MVLive websocket failure", t);
- }
- MVLiveClient.this.ws = null;
- }
-
- @Override
- public void onMessage(WebSocket webSocket, String text) {
- super.onMessage(webSocket, text);
- LOG.trace("Message: {}", text);
- text = Optional.ofNullable(text).orElse("");
- if (Objects.equal("o", text)) {
- sendMessages(new Ping());
- sendMessages(new GetBroadcastHealth(roomId, modelName, (m, r) -> {
- LOG.trace("--> {}", m);
- LOG.trace("<-- {}", r);
- String addr = r.getJSONObject("body").optString("subscribeAddress");
- sendMessages(new RegisterMessage(addr, (mr, rr) -> {
- LOG.trace("--> {}", mr);
- LOG.trace("<-- {}", rr);
- masterPlaylist = rr.getJSONObject("body").optString("videoUrl");
- LOG.trace("Got the URL: {}", masterPlaylist);
- stop();
- synchronized (streamUrlMonitor) {
- streamUrlMonitor.notifyAll();
- }
- }));
- }));
- } else if (text.startsWith("a")) {
- JSONArray jsonArray = new JSONArray(text.substring(1));
- for (int i = 0; i < jsonArray.length(); i++) {
- String respJson = jsonArray.getString(i);
- JSONObject response = new JSONObject(respJson);
- String address = response.optString("address");
- if (isNotBlank(address)) {
- Message message = futureResponses.get(address);
- if (message != null) {
- message.handleResponse(response);
- if (!(message instanceof RegisterMessage)) {
- futureResponses.remove(address);
- }
- }
- }
- }
- }
- }
-
- @Override
- public void onMessage(WebSocket webSocket, ByteString bytes) {
- super.onMessage(webSocket, bytes);
- LOG.trace("Binary Message: {}", bytes.hex());
- }
- });
- }
-
- void sendMessages(Message... messages) {
- JSONArray msgs = new JSONArray();
- for (Message msg : messages) {
- msgs.put(msg.toString());
- if (msg instanceof SendMessage) {
- SendMessage sendMessage = (SendMessage) msg;
- futureResponses.put(sendMessage.getReplyAddress(), sendMessage);
- } else if (msg instanceof RegisterMessage) {
- RegisterMessage registerMessage = (RegisterMessage) msg;
- futureResponses.put(registerMessage.getAddress(), registerMessage);
- }
- }
- ws.send(msgs.toString());
- }
-
- public StreamLocation getStreamLocation(MVLiveModel model) throws IOException, InterruptedException {
- start(model);
- while (running) {
- synchronized (streamUrlMonitor) {
- streamUrlMonitor.wait(TimeUnit.SECONDS.toMillis(20000));
- break;
- }
- }
- if (ws != null) {
- stop();
- }
- return new StreamLocation(roomId, roomNumber, masterPlaylist);
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java
deleted file mode 100644
index 431c2d9e..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package ctbrec.sites.manyvids;
-
-import java.io.IOException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.io.HttpClient;
-import ctbrec.recorder.download.hls.HlsDownload;
-
-public class MVLiveHlsDownload extends HlsDownload {
- private static final Logger LOG = LoggerFactory.getLogger(MVLiveHlsDownload.class);
-
- private transient ScheduledExecutorService scheduler;
-
- public MVLiveHlsDownload(HttpClient client) {
- super(client);
- }
-
- @Override
- public MVLiveHlsDownload call() throws Exception {
- try {
- scheduler = new ScheduledThreadPoolExecutor(1, r -> {
- Thread t = new Thread(r);
- t.setDaemon(true);
- t.setName("MVLive CF cookie updater");
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- });
- scheduler.scheduleAtFixedRate(this::updateCloudFlareCookies, 120, 120, TimeUnit.SECONDS);
- updateCloudFlareCookies();
- super.call();
- } finally {
- scheduler.shutdown();
- }
- return this;
- }
-
- private void updateCloudFlareCookies() {
- try {
- ((MVLiveModel)getModel()).updateCloudFlareCookies();
- } catch (IOException e) {
- LOG.error("Couldn't update cloudflare cookies for model {}", getModel(), e);
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java
deleted file mode 100644
index 5343dd89..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package ctbrec.sites.manyvids;
-
-import static ctbrec.io.HttpConstants.*;
-
-import java.io.IOException;
-
-import ctbrec.Config;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import okhttp3.Request;
-import okhttp3.Response;
-
-public class MVLiveHttpClient extends HttpClient {
-
- public MVLiveHttpClient(Config config) {
- super("mvlive", config);
- }
-
- @Override
- public boolean login() throws IOException {
- return false;
- }
-
- public MVLiveHttpClient newSession() {
- MVLiveHttpClient newClient = new MVLiveHttpClient(config);
- newClient.client = newClient.client.newBuilder()
- .cookieJar(createCookieJar())
- .build();
- newClient.reconfigure();
- newClient.cookieJar.clear();
- return newClient;
- }
-
- public void fetchAuthenticationCookies() throws IOException {
- Request req = new Request.Builder()
- .url("https://www.manyvids.com/tak-live-redirect.php")
- .header(USER_AGENT, config.getSettings().httpUserAgent)
- .build();
- try (Response response = execute(req)) {
- if (!response.isSuccessful()) {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java b/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java
deleted file mode 100644
index 8bdc0d86..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package ctbrec.sites.manyvids;
-
-import java.io.IOException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import ctbrec.io.HttpClient;
-import ctbrec.recorder.download.hls.MergedFfmpegHlsDownload;
-
-public class MVLiveMergedHlsDownload extends MergedFfmpegHlsDownload {
-
- private static final Logger LOG = LoggerFactory.getLogger(MVLiveMergedHlsDownload.class);
-
- private transient ScheduledExecutorService scheduler;
-
- public MVLiveMergedHlsDownload(HttpClient client) {
- super(client);
- }
-
- @Override
- public MVLiveMergedHlsDownload call() throws Exception {
- try {
- scheduler = new ScheduledThreadPoolExecutor(1, r -> {
- Thread t = new Thread(r);
- t.setDaemon(true);
- t.setName("MVLive CF cookie updater");
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- });
- scheduler.scheduleAtFixedRate(this::updateCloudFlareCookies, 2, 2, TimeUnit.MINUTES);
- updateCloudFlareCookies();
- super.call();
- } finally {
- scheduler.shutdown();
- }
- return this;
- }
-
- private void updateCloudFlareCookies() {
- try {
- ((MVLiveModel)getModel()).updateCloudFlareCookies();
- } catch (IOException e) {
- 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
deleted file mode 100644
index 6806b168..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package ctbrec.sites.manyvids;
-
-import com.iheartradio.m3u8.*;
-import com.iheartradio.m3u8.data.MasterPlaylist;
-import com.iheartradio.m3u8.data.Playlist;
-import com.iheartradio.m3u8.data.PlaylistData;
-import com.iheartradio.m3u8.data.StreamInfo;
-import ctbrec.*;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.RecordingProcess;
-import ctbrec.recorder.download.StreamSource;
-import ctbrec.sites.ModelOfflineException;
-import lombok.Getter;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-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.*;
-import java.util.concurrent.ExecutionException;
-
-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 transient MVLiveHttpClient httpClient;
- private transient MVLiveClient client;
- private transient JSONObject roomLocation;
- private transient Instant lastRoomLocationUpdate = Instant.EPOCH;
- private String roomNumber;
- @Getter
- @Setter
- private String id;
-
- @Override
- public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
- if (ignoreCache) {
- String urlHandle = getDisplayName().toLowerCase().replace(" ", "-");
- String url = "https://api.vidchat.manyvids.com/creator?urlHandle=" + URLEncoder.encode(urlHandle, 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());
- }
- }
- }
- return this.onlineState == ONLINE;
- }
-
- @Override
- public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
- log.debug("Loading {}", getUrl());
- try {
- StreamLocation streamLocation = getClient().getStreamLocation(this);
- log.debug("Got the stream location from WS {}", streamLocation.masterPlaylist);
- roomNumber = streamLocation.roomNumber;
- updateCloudFlareCookies();
- MasterPlaylist masterPlaylist = getMasterPlaylist(streamLocation.masterPlaylist);
- List sources = new ArrayList<>();
- for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
- if (playlist.hasStreamInfo()) {
- StreamSource src = new StreamSource();
- src.setBandwidth(Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getBandwidth).orElse(0));
- src.setHeight(Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0));
- src.setWidth(Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.width).orElse(0));
- String masterUrl = streamLocation.masterPlaylist;
- String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
- String segmentUri = baseUrl + playlist.getUri();
- src.setMediaPlaylistUrl(segmentUri);
- if (src.getMediaPlaylistUrl().contains("?")) {
- src.setMediaPlaylistUrl((src.getMediaPlaylistUrl().substring(0, src.getMediaPlaylistUrl().lastIndexOf('?'))));
- }
- log.debug("Media playlist {}", src.getMediaPlaylistUrl());
- sources.add(src);
- }
- }
- return sources;
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- return Collections.emptyList();
- }
-
- 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)
- .build();
- try (Response response = getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = response.body().string();
- 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 {
- log.debug("{} URL: {}\n\tResponse: {}", response.code(), url, response.body().string());
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- 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)
- .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());
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- 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")) {
- roomNumber = json.getString("floorId");
- } else {
- log.debug("Room number response: {}", json.toString(2));
- throw new ModelOfflineException(this);
- }
- }
- return roomNumber;
- }
-
- private void fetchGeneralCookies() throws IOException {
- Request req = new Request.Builder()
- .url(getSite().getBaseUrl())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .build();
- try (Response response = getHttpClient().execute(req)) {
- if (!response.isSuccessful()) {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- public JSONObject getRoomLocation() throws IOException {
- 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.debug("Room location response: {}", roomLocation);
- lastRoomLocationUpdate = Instant.now();
- return roomLocation;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- } else {
- return roomLocation;
- }
- }
-
- private synchronized MVLiveClient getClient() {
- if (client == null) {
- client = new MVLiveClient(getHttpClient());
- }
- return client;
- }
-
- @Override
- public void invalidateCacheEntries() {
- roomNumber = null;
- }
-
- @Override
- public void receiveTip(Double tokens) throws IOException {
- throw new NotImplementedException("Sending tips is not implemeted for MVLive");
- }
-
- @Override
- public int[] getStreamResolution(boolean failFast) throws ExecutionException {
- return new int[]{1280, 720};
- }
-
- @Override
- public boolean follow() throws IOException {
- return false;
- }
-
- @Override
- public boolean unfollow() throws IOException {
- return false;
- }
-
- @Override
- public RecordingProcess createDownload() {
- if (Config.isServerMode() && !Config.getInstance().getSettings().recordSingleFile) {
- return new MVLiveHlsDownload(getHttpClient());
- } else {
- return new MVLiveMergedHlsDownload(getHttpClient());
- }
- }
-
- private synchronized MVLiveHttpClient getHttpClient() {
- if (httpClient == null) {
- MVLiveHttpClient siteHttpClient = (MVLiveHttpClient) getSite().getHttpClient();
- httpClient = siteHttpClient.newSession();
- }
- return httpClient;
- }
-
- @Override
- public void writeSiteSpecificData(Map data) {
- data.put("id", id);
- }
-
- @Override
- public void readSiteSpecificData(Map data) {
- id = data.get("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;
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/StreamLocation.java b/common/src/main/java/ctbrec/sites/manyvids/StreamLocation.java
deleted file mode 100644
index 3d098421..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/StreamLocation.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package ctbrec.sites.manyvids;
-
-public class StreamLocation {
-
- public String roomId;
- public String roomNumber;
- public String masterPlaylist;
-
- public StreamLocation(String roomId, String roomNumber, String masterPlaylist) {
- this.roomId = roomId;
- this.roomNumber = roomNumber;
- this.masterPlaylist = masterPlaylist;
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/GetBroadcastHealth.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/GetBroadcastHealth.java
deleted file mode 100644
index 871ab1a3..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/GetBroadcastHealth.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-import java.util.UUID;
-import java.util.function.BiConsumer;
-
-import org.json.JSONObject;
-
-public class GetBroadcastHealth extends SendMessage {
-
- private String roomId;
- private String showId;
-
- public GetBroadcastHealth(String roomId, String showId, BiConsumer responseConsumer) {
- super(responseConsumer);
- this.roomId = roomId;
- this.showId = showId;
- address = "api/StreamService";
- action = "getBroadcastHealth";
- }
-
- @Override
- public String toString() {
- JSONObject msg = build();
- JSONObject body = new JSONObject();
- body.put("connectionId", "");
- body.put("telemetryId", UUID.randomUUID().toString());
- body.put("roomId", roomId);
- body.put("showId", showId);
- msg.put("body", body);
- return msg.toString();
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/JoinChat.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/JoinChat.java
deleted file mode 100644
index 9ed56c50..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/JoinChat.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-import java.util.UUID;
-import java.util.function.BiConsumer;
-
-import org.json.JSONObject;
-
-public class JoinChat extends SendMessage {
-
- private String roomId;
- private String showId;
-
- public JoinChat(String roomId, String showId, BiConsumer responseConsumer) {
- super(responseConsumer);
- this.roomId = roomId;
- this.showId = showId;
- address = "api/ChatService";
- action = "join";
- }
-
- @Override
- public String toString() {
- JSONObject msg = build();
- JSONObject body = new JSONObject();
- body.put("connectionId", "");
- body.put("telemetryId", UUID.randomUUID().toString());
- body.put("roomId", roomId);
- body.put("showId", showId);
- body.put("joinPrivateShow", false);
- body.put("deviceSourceType", "DESKTOP");
- msg.put("body", body);
- return msg.toString();
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Message.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Message.java
deleted file mode 100644
index 81246f76..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Message.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-import java.util.Optional;
-import java.util.function.BiConsumer;
-
-import org.json.JSONObject;
-
-public abstract class Message extends JSONObject {
-
- private BiConsumer responseConsumer;
-
- public Message(BiConsumer responseConsumer) {
- this.responseConsumer = responseConsumer;
- }
-
- public void handleResponse(JSONObject response) {
- Optional.ofNullable(responseConsumer).ifPresent(consumer -> consumer.accept(this, response));
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Ping.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Ping.java
deleted file mode 100644
index 88cd4c05..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Ping.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-public class Ping extends Message {
-
- public Ping() {
- super(null);
- put("type", "ping");
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/RegisterMessage.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/RegisterMessage.java
deleted file mode 100644
index ecc8cc57..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/RegisterMessage.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-import java.util.function.BiConsumer;
-
-import org.json.JSONObject;
-
-public class RegisterMessage extends Message {
-
- protected String address;
-
- public RegisterMessage(String address, BiConsumer responseConsumer) {
- super(responseConsumer);
- this.address = address;
- }
-
- public String getAddress() {
- return address;
- }
-
- @Override
- public String toString() {
- JSONObject json = new JSONObject();
- json.put("type", "register");
- json.put("address", address);
- json.put("headers", new JSONObject());
- return json.toString();
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Response.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Response.java
deleted file mode 100644
index 4e48e4cb..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/Response.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-import org.json.JSONObject;
-
-public class Response extends JSONObject {
-
-}
diff --git a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/SendMessage.java b/common/src/main/java/ctbrec/sites/manyvids/wsmsg/SendMessage.java
deleted file mode 100644
index 9db7995f..00000000
--- a/common/src/main/java/ctbrec/sites/manyvids/wsmsg/SendMessage.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package ctbrec.sites.manyvids.wsmsg;
-
-import java.util.UUID;
-import java.util.function.BiConsumer;
-
-import org.json.JSONObject;
-
-public class SendMessage extends Message {
-
- protected String address;
- protected String action;
- protected String replyAddress;
-
- public SendMessage(BiConsumer responseConsumer) {
- super(responseConsumer);
- replyAddress = UUID.randomUUID().toString();
- }
-
- public String getReplyAddress() {
- return replyAddress;
- }
-
- protected JSONObject build() {
- JSONObject msg = new JSONObject();
- msg.put("type", "send");
- msg.put("address", address);
- msg.put("replyAddress", replyAddress);
- JSONObject headers = new JSONObject();
- headers.put("action", action);
- msg.put("headers", headers);
- return msg;
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriends.java b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriends.java
deleted file mode 100644
index ec5a327c..00000000
--- a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriends.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package ctbrec.sites.secretfriends;
-
-import ctbrec.Model;
-import ctbrec.io.HtmlParser;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import ctbrec.sites.AbstractSite;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.jsoup.nodes.Element;
-import org.jsoup.select.Elements;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
-import static ctbrec.io.HttpConstants.USER_AGENT;
-
-public class SecretFriends extends AbstractSite {
-
- private static final Logger LOG = LoggerFactory.getLogger(SecretFriends.class);
- public static final String BASE_URI = "https://www.secretfriends.com";
- private HttpClient httpClient;
-
- @Override
- public void init() throws IOException {
- // nothing to do
- }
-
- @Override
- public String getName() {
- return "SecretFriends";
- }
-
- @Override
- public String getBaseUrl() {
- return BASE_URI;
- }
-
- @Override
- public String getAffiliateLink() {
- return BASE_URI;
- }
-
- @Override
- public String getBuyTokensLink() {
- return getAffiliateLink();
- }
-
- @Override
- public SecretFriendsModel createModel(String name) {
- SecretFriendsModel model = new SecretFriendsModel();
- model.setName(name);
- model.setUrl(getBaseUrl() + "/friends/" + name);
- model.setSite(this);
- return model;
- }
-
- @Override
- public Double getTokenBalance() throws IOException {
- return 0d;
- }
-
- @Override
- public synchronized boolean login() throws IOException {
- return false;
- }
-
- @Override
- public HttpClient getHttpClient() {
- if (httpClient == null) {
- httpClient = new SecretFriendsHttpClient(getConfig());
- }
- return httpClient;
- }
-
- @Override
- public void shutdown() {
- if (httpClient != null) {
- httpClient.shutdown();
- }
- }
-
- @Override
- public boolean supportsTips() {
- return false;
- }
-
- @Override
- public boolean supportsFollow() {
- return false;
- }
-
- @Override
- public boolean supportsSearch() {
- return true;
- }
-
- @Override
- public List search(String q) throws IOException, InterruptedException {
- String url = BASE_URI + "/user?SearchForm[keyword]=" + URLEncoder.encode(q, "utf-8");
- Request req = new Request.Builder()
- .url(url)
- .header(USER_AGENT, getConfig().getSettings().httpUserAgent)
- .build();
- try (Response response = getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
- List models = new ArrayList<>();
- Elements modelDivs = HtmlParser.getTags(body, "div[class~=model-wrapper]");
- LOG.debug("Found {} models", modelDivs.size());
- for (Element div : modelDivs) {
- try {
- models.add(SecretFriendsModelParser.parse(this, div));
- } catch (Exception e) {
- LOG.warn("Couldn't parse one of the models: {}", div.html(), e);
- }
- }
- return models;
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- @Override
- public boolean isSiteForModel(Model m) {
- return m instanceof SecretFriendsModel;
- }
-
- @Override
- public boolean credentialsAvailable() {
- return false;
- }
-
- @Override
- public Model createModelFromUrl(String url) {
- Matcher m = Pattern.compile("https?://(?:www\\.)?secretfriends.com/friends/([^/]*?)/?").matcher(url);
- if (m.matches()) {
- String modelName = m.group(1);
- return createModel(modelName);
- } else {
- return super.createModelFromUrl(url);
- }
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsHttpClient.java b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsHttpClient.java
deleted file mode 100644
index db5fa62f..00000000
--- a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsHttpClient.java
+++ /dev/null
@@ -1,174 +0,0 @@
-package ctbrec.sites.secretfriends;
-
-import ctbrec.Config;
-import ctbrec.io.HttpClient;
-import ctbrec.io.HttpException;
-import okhttp3.MediaType;
-import okhttp3.Request;
-import okhttp3.RequestBody;
-import okhttp3.Response;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class SecretFriendsHttpClient extends HttpClient {
-
- private static final Logger LOG = LoggerFactory.getLogger(SecretFriendsHttpClient.class);
- public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
-
- private long userId;
- private String csrfToken;
- private String csrfTimestamp;
- private String csrfNotifyTimestamp;
-
- public SecretFriendsHttpClient(Config config) {
- super("secretfirends", config);
- }
-
- @Override
- public boolean login() throws IOException {
- if (loggedIn) {
- if (csrfToken == null) {
- loadCsrfToken();
- }
- return true;
- }
-
- // persisted cookies might let us log in
- if (checkLoginSuccess()) {
- loggedIn = true;
- LOG.debug("Logged in with cookies");
- if (csrfToken == null) {
- loadCsrfToken();
- }
- return true;
- }
-
- if (csrfToken == null) {
- loadCsrfToken();
- }
-
- String url = SecretFriends.BASE_URI + "/api/front/auth/login";
- JSONObject requestParams = new JSONObject();
- requestParams.put("loginOrEmail", config.getSettings().stripchatUsername);
- requestParams.put("password", config.getSettings().stripchatPassword);
- requestParams.put("remember", true);
- requestParams.put("csrfToken", csrfToken);
- requestParams.put("csrfTimestamp", csrfTimestamp);
- requestParams.put("csrfNotifyTimestamp", csrfNotifyTimestamp);
- RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON);
- Request request = new Request.Builder()
- .url(url)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(USER_AGENT, config.getSettings().httpUserAgent)
- .header(ORIGIN, SecretFriends.BASE_URI)
- .header(REFERER, SecretFriends.BASE_URI)
- .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
- .post(body)
- .build();
- try (Response response = execute(request)) {
- if (response.isSuccessful()) {
- JSONObject resp = new JSONObject(response.body().string());
- if(resp.has("user")) {
- JSONObject user = resp.getJSONObject("user");
- userId = user.optLong("id");
- return true;
- } else {
- return false;
- }
- } else {
- LOG.info("Auto-Login failed: {} {} {}", response.code(), response.message(), url);
- return false;
- }
- }
- }
-
- private void loadCsrfToken() throws IOException {
- String url = SecretFriends.BASE_URI + "/api/front/v2/config/data?requestPath=%2F&timezoneOffset=0";
- Request request = new Request.Builder()
- .url(url)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(USER_AGENT, config.getSettings().httpUserAgent)
- .header(ORIGIN, SecretFriends.BASE_URI)
- .header(REFERER, SecretFriends.BASE_URI)
- .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
- .build();
- try (Response response = execute(request)) {
- if (response.isSuccessful()) {
- JSONObject resp = new JSONObject(response.body().string());
- JSONObject data = resp.getJSONObject("data");
- csrfToken = data.optString("csrfToken");
- csrfTimestamp = data.optString("csrfTimestamp");
- csrfNotifyTimestamp = data.optString("csrfNotifyTimestamp");
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- /**
- * check, if the login worked
- * @throws IOException
- */
- public boolean checkLoginSuccess() throws IOException {
- userId = getUserId();
- String url = SecretFriends.BASE_URI + "/api/front/users/" + userId + "/favorites";
- Request request = new Request.Builder()
- .url(url)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(USER_AGENT, config.getSettings().httpUserAgent)
- .header(ORIGIN, SecretFriends.BASE_URI)
- .header(REFERER, SecretFriends.BASE_URI + "/favorites")
- .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
- .build();
- try (Response response = execute(request)) {
- if (response.isSuccessful()) {
- return true;
- }
- } catch (Exception e) {
- LOG.info("Login check returned unsuccessful: {}", e.getLocalizedMessage());
- }
- return false;
- }
-
- public long getUserId() throws JSONException, IOException {
- if (userId == 0) {
- String url = SecretFriends.BASE_URI + "/api/front/users/username/" + config.getSettings().stripchatUsername;
- Request request = new Request.Builder()
- .url(url)
- .header(ACCEPT, MIMETYPE_APPLICATION_JSON)
- .header(USER_AGENT, config.getSettings().httpUserAgent)
- .header(ORIGIN, SecretFriends.BASE_URI)
- .header(REFERER, SecretFriends.BASE_URI)
- .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
- .build();
- try (Response response = execute(request)) {
- if (response.isSuccessful()) {
- JSONObject resp = new JSONObject(response.body().string());
- JSONObject user = resp.getJSONObject("user");
- userId = user.optLong("id");
- } else {
- throw new HttpException(url, response.code(), response.message());
- }
- }
- }
- return userId;
- }
-
- public String getCsrfNotifyTimestamp() {
- return csrfNotifyTimestamp;
- }
-
- public String getCsrfTimestamp() {
- return csrfTimestamp;
- }
-
- public String getCsrfToken() {
- return csrfToken;
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java
deleted file mode 100644
index 7778776c..00000000
--- a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java
+++ /dev/null
@@ -1,198 +0,0 @@
-package ctbrec.sites.secretfriends;
-
-import com.iheartradio.m3u8.ParseException;
-import com.iheartradio.m3u8.PlaylistException;
-import ctbrec.AbstractModel;
-import ctbrec.Config;
-import ctbrec.io.HtmlParser;
-import ctbrec.io.HttpException;
-import ctbrec.recorder.download.RecordingProcess;
-import ctbrec.recorder.download.StreamSource;
-import okhttp3.HttpUrl;
-import okhttp3.Request;
-import okhttp3.Response;
-import org.json.JSONObject;
-import org.jsoup.nodes.Element;
-
-import javax.xml.bind.JAXBException;
-import java.io.IOException;
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL;
-import static ctbrec.Model.State.ONLINE;
-import static ctbrec.io.HttpConstants.*;
-
-public class SecretFriendsModel extends AbstractModel {
- private int[] resolution = new int[]{0, 0};
- private static final Random RNG = new Random();
-
- private static final String H5LIVE = "h5live";
- private static final String SECURITY = "security";
-
- @Override
- public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
- if (ignoreCache) {
- String url = SecretFriends.BASE_URI + "/friend/bio/" + getName();
- 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())
- .build();
- try (Response response = site.getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
- Element wrapper = HtmlParser.getTag(body, "div[class~=model-wrapper]");
- SecretFriendsModel parsedModel = SecretFriendsModelParser.parse((SecretFriends) getSite(), wrapper);
- setName(parsedModel.getName());
- setUrl(parsedModel.getUrl());
- setPreview(parsedModel.getPreview());
- setOnlineState(parsedModel.getOnlineState(true));
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
- return onlineState == ONLINE;
- }
-
- @Override
- public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
- String bioPage = loadBioPage();
- String streamName = getStreamName(bioPage);
- String streamId = getStreamId(bioPage);
- JSONObject token = getToken(streamName);
-
- String stream = streamName + "?host=www.secretfriends.com"
- + "&startAt=" + Instant.now().getEpochSecond()
- + "&userId=null&ip=0.0.0.0&cSessionId=guestKey"
- + "&streamId=" + streamId
- + "&groupId=null"
- + "&userAgent=" + Config.getInstance().getSettings().httpUserAgent;
-
- HttpUrl wsUrl = new HttpUrl.Builder()
- .scheme("https")
- .host("bintu-splay.nanocosmos.de")
- .addPathSegments("h5live/authstream")
- .addQueryParameter("url", "rtmp://bintu-splay.nanocosmos.de/splay")
- .addQueryParameter("stream", stream)
- .addQueryParameter("cid", String.valueOf(RNG.nextInt(899000) + 100000))
- .addQueryParameter("pid", String.valueOf(RNG.nextLong() + 10_000_000_000L))
- .addQueryParameter("token", token.getJSONObject(H5LIVE).getJSONObject(SECURITY).getString("token"))
- .addQueryParameter("expires", token.getJSONObject(H5LIVE).getJSONObject(SECURITY).getString("expires"))
- .addQueryParameter("options", token.getJSONObject(H5LIVE).getJSONObject(SECURITY).getString("options"))
- .build();
-
- StreamSource src = new StreamSource();
- src.setWidth(1280);
- src.setHeight(720);
- src.setMediaPlaylistUrl(wsUrl.toString());
- return Collections.singletonList(src);
- }
-
- private String getStreamId(String bioPage) throws IOException {
- Pattern p = Pattern.compile("app.configure\\((.*?)\\);");
- Matcher m = p.matcher(bioPage);
- if (m.find()) {
- JSONObject appConfig = new JSONObject(m.group(1));
- return appConfig.getJSONObject("page").getJSONObject("user").getString("id");
- } else {
- throw new IOException("app configuration not found in HTML");
- }
- }
-
- private JSONObject getToken(String streamName) throws IOException {
- String url = SecretFriends.BASE_URI + "/nano/generateToken?streamName=" + streamName;
- 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())
- .build();
- try (Response response = site.getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- String body = Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
- return new JSONObject(body);
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private String loadBioPage() throws IOException {
- String url = SecretFriends.BASE_URI + "/friends/" + getName();
- 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, getUrl())
- .build();
- try (Response response = site.getHttpClient().execute(req)) {
- if (response.isSuccessful()) {
- return Objects.requireNonNull(response.body(), HTTP_RESPONSE_BODY_IS_NULL).string();
- } else {
- throw new HttpException(response.code(), response.message());
- }
- }
- }
-
- private String getStreamName(String bioPage) throws IOException {
- Pattern p = Pattern.compile("'streamName'\\s*:\\s*\"(.*?)\",");
- Matcher m = p.matcher(bioPage);
- if (m.find()) {
- return m.group(1);
- } else {
- throw new IOException("Stream name not found in HTML");
- }
- }
-
- @Override
- public void invalidateCacheEntries() {
- resolution = new int[]{0, 0};
- }
-
- @Override
- public void receiveTip(Double tokens) throws IOException {
- // not implemented
- }
-
- @Override
- public int[] getStreamResolution(boolean failFast) throws ExecutionException {
- if (!failFast) {
- try {
- List sources = getStreamSources();
- if (!sources.isEmpty()) {
- StreamSource best = sources.get(sources.size() - 1);
- resolution = new int[]{best.getWidth(), best.getHeight()};
- }
- } catch (IOException | ParseException | PlaylistException | JAXBException e) {
- throw new ExecutionException(e);
- }
- }
- return resolution;
- }
-
- @Override
- public boolean follow() throws IOException {
- return false;
- }
-
- @Override
- public boolean unfollow() throws IOException {
- return false;
- }
-
- @Override
- public RecordingProcess createDownload() {
- return new SecretFriendsWebrtcDownload(getSite().getHttpClient());
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModelParser.java b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModelParser.java
deleted file mode 100644
index fa4b017b..00000000
--- a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModelParser.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package ctbrec.sites.secretfriends;
-
-import ctbrec.Model;
-import org.jsoup.nodes.Element;
-
-import java.util.Objects;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class SecretFriendsModelParser {
- private SecretFriendsModelParser() {
- }
-
- public static SecretFriendsModel parse(SecretFriends site, Element modelWrapper) {
- String name = parseName(modelWrapper);
- SecretFriendsModel model = site.createModel(name);
- model.setPreview(parsePreview(modelWrapper));
- model.setOnlineState(extractOnlineState(modelWrapper));
- return model;
- }
-
- private static String parsePreview(Element div) {
- Element wrapper = div.selectFirst("div.placeholder-wrapper");
- if (wrapper == null) {
- return null;
- }
- String style = wrapper.attr("style");
- Pattern p = Pattern.compile("background-image: url\\('(.*?)'\\)");
- Matcher m = p.matcher(style);
- if (m.find()) {
- return m.group(1);
- } else {
- return null;
- }
- }
-
- private static String parseName(Element div) {
- Element bioLink = Objects.requireNonNull(div.selectFirst("a[href*=/friend]"), "a[href*=/friend] not found");
- bioLink.setBaseUri(SecretFriends.BASE_URI);
- String href = bioLink.attr("href");
- if (href.contains("signup")) {
- return href.substring(href.indexOf('=') + 1);
- } else {
- String name = href.substring(href.lastIndexOf('/') + 1);
- if (name.indexOf('?') >= 0) {
- name = name.substring(0, name.indexOf('?'));
- }
- if (name.indexOf('#') >= 0) {
- name = name.substring(0, name.indexOf('#'));
- }
- return name;
- }
- }
-
- private static Model.State extractOnlineState(Element div) {
- Element modelTag = Objects.requireNonNull(div.selectFirst("div[class~=model-tag]"), "div.model-tag not found");
- Set cssClasses = modelTag.classNames();
- for (String cssClass : cssClasses) {
- switch (cssClass) {
- case "model-online":
- return Model.State.ONLINE;
- case "model-private":
- case "model-show":
- case "model-vip":
- return Model.State.PRIVATE;
- case "model-offline":
- return Model.State.OFFLINE;
- default:
- // keep going
- }
- }
- return Model.State.OFFLINE;
- }
-}
diff --git a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsWebrtcDownload.java b/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsWebrtcDownload.java
deleted file mode 100644
index ab82192b..00000000
--- a/common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsWebrtcDownload.java
+++ /dev/null
@@ -1,227 +0,0 @@
-package ctbrec.sites.secretfriends;
-
-import ctbrec.Config;
-import ctbrec.Model;
-import ctbrec.Recording;
-import ctbrec.io.BandwidthMeter;
-import ctbrec.io.HttpClient;
-import ctbrec.recorder.download.AbstractDownload;
-import ctbrec.recorder.download.RecordingProcess;
-import okhttp3.Request;
-import okhttp3.Response;
-import okhttp3.WebSocket;
-import okhttp3.WebSocketListener;
-import okio.ByteString;
-import org.json.JSONObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.concurrent.ExecutorService;
-import java.util.regex.Pattern;
-
-import static ctbrec.io.HttpConstants.*;
-
-public class SecretFriendsWebrtcDownload extends AbstractDownload {
-
- private static final Logger LOG = LoggerFactory.getLogger(SecretFriendsWebrtcDownload.class);
- private static final int MAX_SECONDS_WITHOUT_TRANSFER = 20;
-
- private final HttpClient httpClient;
- private WebSocket ws;
- private FileOutputStream fout;
- private Instant timeOfLastTransfer = Instant.MAX;
-
- private volatile boolean running;
- private volatile boolean started;
-
-
- private File targetFile;
-
- public SecretFriendsWebrtcDownload(HttpClient httpClient) {
- this.httpClient = httpClient;
- }
-
- @Override
- public void init(Config config, Model model, Instant startTime, ExecutorService executorService) throws IOException {
- this.config = config;
- this.model = model;
- this.startTime = startTime;
- this.downloadExecutor = executorService;
- splittingStrategy = initSplittingStrategy(config.getSettings());
- targetFile = config.getFileForRecording(model, "mp4", startTime);
- timeOfLastTransfer = Instant.now();
- }
-
- @Override
- public void stop() {
- running = false;
- if (ws != null) {
- ws.close(1000, "");
- ws = null;
- }
- }
-
- @Override
- public void finalizeDownload() {
- if (fout != null) {
- try {
- LOG.debug("Closing recording file {}", targetFile);
- fout.close();
- } catch (IOException e) {
- LOG.error("Error while closing recording file {}", targetFile, e);
- }
- }
- }
-
- @Override
- public boolean isRunning() {
- return running;
- }
-
- @Override
- public void postProcess(Recording recording) {
- // nothing to do
- }
-
- @Override
- public File getTarget() {
- return targetFile;
- }
-
- @Override
- public String getPath(Model model) {
- String absolutePath = targetFile.getAbsolutePath();
- String recordingsDir = Config.getInstance().getSettings().recordingsDir;
- String relativePath = absolutePath.replaceFirst(Pattern.quote(recordingsDir), "");
- return relativePath;
- }
-
- @Override
- public boolean isSingleFile() {
- return true;
- }
-
- @Override
- public long getSizeInByte() {
- return getTarget().length();
- }
-
- @Override
- public RecordingProcess call() throws Exception {
- if (!started) {
- started = true;
- startDownload();
- }
-
- if (splittingStrategy.splitNecessary(this)) {
- stop();
- rescheduleTime = Instant.now();
- } else {
- rescheduleTime = Instant.now().plusSeconds(5);
- }
- if (!model.isOnline(true)) {
- stop();
- }
- if (Duration.between(timeOfLastTransfer, Instant.now()).getSeconds() > MAX_SECONDS_WITHOUT_TRANSFER) {
- LOG.info("No video data received for {} seconds. Stopping recording for model {}", MAX_SECONDS_WITHOUT_TRANSFER, model);
- stop();
- }
- return this;
- }
-
- private void startDownload() throws IOException {
- Request request;
- try {
- request = new Request.Builder()
- .url(model.getStreamSources().get(0).getMediaPlaylistUrl())
- .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
- .header(ACCEPT, "*/*")
- .header(ACCEPT_LANGUAGE, "pl")
- .header(ORIGIN, model.getSite().getBaseUrl())
- .build();
- } catch (Exception e) {
- throw new IOException(e);
- }
-
- running = true;
- LOG.debug("Opening webrtc connection {}", request.url());
- ws = httpClient.newWebSocket(request, new WebSocketListener() {
- @Override
- public void onOpen(WebSocket webSocket, Response response) {
- super.onOpen(webSocket, response);
- LOG.trace("onOpen {} {}", webSocket, response);
- response.close();
- try {
- LOG.debug("Recording video stream to {}", targetFile);
- Files.createDirectories(targetFile.getParentFile().toPath());
- fout = new FileOutputStream(targetFile);
- } catch (Exception e) {
- LOG.error("Couldn't open file {} to save the video stream", targetFile, e);
- stop();
- }
- }
-
- @Override
- public void onMessage(WebSocket webSocket, ByteString bytes) {
- super.onMessage(webSocket, bytes);
- timeOfLastTransfer = Instant.now();
- try {
- byte[] videoData = bytes.toByteArray();
- fout.write(videoData);
- BandwidthMeter.add(videoData.length);
- } catch (IOException e) {
- if (running) {
- LOG.error("Couldn't write video stream to file", e);
- stop();
- }
- }
- }
-
- @Override
- public void onMessage(WebSocket webSocket, String text) {
- super.onMessage(webSocket, text);
- LOG.trace("onMessageT {} {}", webSocket, text);
- JSONObject msg = new JSONObject(text);
- if (msg.optString("eventType").equals("onStreamInfo")) {
- JSONObject streamInfo = msg.getJSONObject("onStreamInfo");
- JSONObject videoInfo = streamInfo.getJSONObject("videoInfo");
- LOG.info("Stream resolution for {} is {}", model, videoInfo.getInt("height"));
- }
- }
-
- @Override
- public void onFailure(WebSocket webSocket, Throwable t, Response response) {
- super.onFailure(webSocket, t, response);
- stop();
- if (t instanceof EOFException) {
- LOG.info("End of stream detected for model {}", model);
- } else {
- LOG.error("Websocket failure for model {} {} {}", model, response, t);
- }
- if (response != null) {
- response.close();
- }
- }
-
- @Override
- public void onClosing(WebSocket webSocket, int code, String reason) {
- super.onClosing(webSocket, code, reason);
- LOG.trace("Websocket closing for model {} {} {}", model, code, reason);
- }
-
- @Override
- public void onClosed(WebSocket webSocket, int code, String reason) {
- super.onClosed(webSocket, code, reason);
- LOG.debug("Websocket closed for model {} {} {}", model, code, reason);
- stop();
- }
- });
- }
-}
diff --git a/common/src/main/resources/docs/ConfigurationFile.md b/common/src/main/resources/docs/ConfigurationFile.md
index ba2d96c9..c78d76a7 100644
--- a/common/src/main/resources/docs/ConfigurationFile.md
+++ b/common/src/main/resources/docs/ConfigurationFile.md
@@ -98,7 +98,7 @@ ignored. This is a collection of the most interesting values:
- **timeoutRecordingEndingAt** - [00:00 - 23:59] - End of the recording timeout timeframe - No new recordings will be started in this period
- **useHlsdl** - [`true`,`false`] Use hlsdl to record the live streams. You also have to set `hlsdlExecutable`, if hlsdl is not globally available on your
- system. hlsdl won't be used for MV Live, LiveJasmin and Showup.
+ system. hlsdl won't be used for Showup.
- **webinterface** (server only) - [`true`,`false`] Enables the webinterface for the server. You can access it with http://host:port/static/index.html Don't
activate this on
diff --git a/docs/index.html b/docs/index.html
index afc7d24a..778ccba2 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -79,7 +79,7 @@
-->
CTB Recorder
- A free recording software for different camsites.
Currently supported: BongaCams, Cam4, CamSoda, Chaturbate, FC2Live, LiveJasmin, MyFreeCams, Streamate
+ A free recording software for different camsites.
Currently supported: BongaCams, Cam4, CamSoda, Chaturbate, FC2Live, MyFreeCams, Streamate
diff --git a/master/.project b/master/.project
index 569790bc..415e7acc 100644
--- a/master/.project
+++ b/master/.project
@@ -16,7 +16,7 @@
- 1743313759740
+ 1742623224306
30
diff --git a/server/.classpath b/server/.classpath
index 18e367a0..c19eff97 100644
--- a/server/.classpath
+++ b/server/.classpath
@@ -3,6 +3,7 @@
+
@@ -16,7 +17,7 @@
-
+
@@ -28,5 +29,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/.project b/server/.project
index c3933cc1..f4f02abb 100644
--- a/server/.project
+++ b/server/.project
@@ -22,7 +22,7 @@
- 1743313759745
+ 1742623224309
30
diff --git a/server/.settings/org.eclipse.jdt.apt.core.prefs b/server/.settings/org.eclipse.jdt.apt.core.prefs
new file mode 100644
index 00000000..dfa4f3ad
--- /dev/null
+++ b/server/.settings/org.eclipse.jdt.apt.core.prefs
@@ -0,0 +1,4 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.apt.aptEnabled=true
+org.eclipse.jdt.apt.genSrcDir=target/generated-sources/annotations
+org.eclipse.jdt.apt.genTestSrcDir=target/generated-test-sources/test-annotations
diff --git a/server/.settings/org.eclipse.jdt.core.prefs b/server/.settings/org.eclipse.jdt.core.prefs
index 3a0745fd..e1a03f96 100644
--- a/server/.settings/org.eclipse.jdt.core.prefs
+++ b/server/.settings/org.eclipse.jdt.core.prefs
@@ -1,9 +1,9 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=21
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.compliance=21
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -12,5 +12,6 @@ org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.processAnnotations=enabled
org.eclipse.jdt.core.compiler.release=disabled
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.source=21
diff --git a/server/src/main/java/ctbrec/recorder/server/HttpServer.java b/server/src/main/java/ctbrec/recorder/server/HttpServer.java
index 37cad6d1..1dad789b 100644
--- a/server/src/main/java/ctbrec/recorder/server/HttpServer.java
+++ b/server/src/main/java/ctbrec/recorder/server/HttpServer.java
@@ -16,19 +16,14 @@ import ctbrec.servlet.MarkdownServlet;
import ctbrec.servlet.SearchServlet;
import ctbrec.servlet.StaticFileServlet;
import ctbrec.sites.Site;
-import ctbrec.sites.amateurtv.AmateurTv;
import ctbrec.sites.bonga.BongaCams;
import ctbrec.sites.cam4.Cam4;
import ctbrec.sites.camsoda.Camsoda;
import ctbrec.sites.chaturbate.Chaturbate;
-import ctbrec.sites.cherrytv.CherryTv;
import ctbrec.sites.dreamcam.Dreamcam;
import ctbrec.sites.fc2live.Fc2Live;
import ctbrec.sites.flirt4free.Flirt4Free;
-import ctbrec.sites.jasmin.LiveJasmin;
-import ctbrec.sites.manyvids.MVLive;
import ctbrec.sites.mfc.MyFreeCams;
-import ctbrec.sites.secretfriends.SecretFriends;
import ctbrec.sites.showup.Showup;
import ctbrec.sites.streamate.Streamate;
import ctbrec.sites.streamray.Streamray;
@@ -137,19 +132,14 @@ public class HttpServer {
}
private void createSites() {
- sites.add(new AmateurTv());
sites.add(new BongaCams());
sites.add(new Cam4());
sites.add(new Camsoda());
sites.add(new Chaturbate());
- sites.add(new CherryTv());
sites.add(new Dreamcam());
sites.add(new Fc2Live());
sites.add(new Flirt4Free());
- sites.add(new LiveJasmin());
- sites.add(new MVLive());
sites.add(new MyFreeCams());
- sites.add(new SecretFriends());
sites.add(new Showup());
sites.add(new Streamate());
sites.add(new Stripchat());
diff --git a/server/src/main/resources/html/static/index.html b/server/src/main/resources/html/static/index.html
index 98a96386..2351001f 100644
--- a/server/src/main/resources/html/static/index.html
+++ b/server/src/main/resources/html/static/index.html
@@ -299,7 +299,7 @@
});
} else {
$('#addModelByUrl').autocomplete({
- source: ["AmateurTv:", "BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "CherryTv:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "LiveJasmin:", "MVLive:", "MyFreeCams:", "SecretFriends:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
+ source: ["BongaCams:", "Cam4:", "Camsoda:", "Chaturbate:", "Dreamcam:", "Fc2Live:", "Flirt4Free:", "MyFreeCams:", "Showup:", "Streamate:", "Streamray:", "Stripchat:", "XloveCam:"]
});
}
}