From 7bee404ec1387c55a8753e8ff63872d16bae1501 Mon Sep 17 00:00:00 2001 From: Jafea7 Date: Mon, 7 Apr 2025 12:54:39 +1000 Subject: [PATCH] Remove non-working sites --- .../java/ctbrec/ui/CamrecApplication.java | 10 - .../main/java/ctbrec/ui/SiteUiFactory.java | 42 +-- .../ui/sites/amateurtv/AmateurTvConfigUI.java | 93 ------ .../AmateurTvElectronLoginDialog.java | 126 -------- .../sites/amateurtv/AmateurTvFollowedTab.java | 13 - .../ui/sites/amateurtv/AmateurTvSiteUi.java | 65 ---- .../sites/amateurtv/AmateurTvTabProvider.java | 73 ----- .../amateurtv/AmateurTvUpdateService.java | 132 -------- .../ui/sites/cherrytv/CherryTvConfigUI.java | 88 ------ .../sites/cherrytv/CherryTvFollowedTab.java | 90 ------ .../CherryTvFollowedUpdateService.java | 55 ---- .../ui/sites/cherrytv/CherryTvSiteUi.java | 43 --- .../sites/cherrytv/CherryTvTabProvider.java | 50 --- .../sites/cherrytv/CherryTvUpdateService.java | 151 ---------- .../ui/sites/jasmin/LiveJasminConfigUi.java | 107 ------- .../jasmin/LiveJasminElectronLoginDialog.java | 98 ------ .../sites/jasmin/LiveJasminFollowedTab.java | 51 ---- .../LiveJasminFollowedUpdateService.java | 140 --------- .../ui/sites/jasmin/LiveJasminSiteUi.java | 78 ----- .../ctbrec/ui/sites/jasmin/LiveJasminTab.java | 80 ----- .../sites/jasmin/LiveJasminTabProvider.java | 51 ---- .../sites/jasmin/LiveJasminUpdateService.java | 189 ------------ .../ui/sites/manyvids/MVLiveConfigUi.java | 50 --- .../ui/sites/manyvids/MVLiveSiteUi.java | 41 --- .../ui/sites/manyvids/MVLiveTabProvider.java | 32 -- .../sites/manyvids/MVLiveUpdateService.java | 103 ------- .../secretfriends/SecretFriendsConfigUI.java | 89 ------ .../secretfriends/SecretFriendsSiteUi.java | 40 --- .../SecretFriendsTabProvider.java | 34 --- .../SecretFriendsUpdateService.java | 92 ------ common/src/main/java/ctbrec/Settings.java | 8 - .../ctbrec/sites/amateurtv/AmateurTv.java | 161 ---------- .../sites/amateurtv/AmateurTvDownload.java | 171 ----------- .../sites/amateurtv/AmateurTvHttpClient.java | 50 --- .../sites/amateurtv/AmateurTvModel.java | 190 ------------ .../java/ctbrec/sites/cherrytv/CherryTv.java | 178 ----------- .../sites/cherrytv/CherryTvHttpClient.java | 128 -------- .../ctbrec/sites/cherrytv/CherryTvModel.java | 285 ------------------ .../java/ctbrec/sites/jasmin/LiveJasmin.java | 217 ------------- .../sites/jasmin/LiveJasminHttpClient.java | 98 ------ .../ctbrec/sites/jasmin/LiveJasminModel.java | 255 ---------------- .../sites/jasmin/LiveJasminModelInfo.java | 18 -- .../jasmin/LiveJasminStreamRegistration.java | 258 ---------------- .../sites/jasmin/LiveJasminStreamSource.java | 14 - .../jasmin/LiveJasminTippingWebSocket.java | 176 ----------- .../jasmin/LiveJasminWebrtcDownload.java | 247 --------------- .../java/ctbrec/sites/manyvids/MVLive.java | 212 ------------- .../ctbrec/sites/manyvids/MVLiveClient.java | 219 -------------- .../sites/manyvids/MVLiveHlsDownload.java | 49 --- .../sites/manyvids/MVLiveHttpClient.java | 45 --- .../manyvids/MVLiveMergedHlsDownload.java | 50 --- .../ctbrec/sites/manyvids/MVLiveModel.java | 285 ------------------ .../ctbrec/sites/manyvids/StreamLocation.java | 14 - .../manyvids/wsmsg/GetBroadcastHealth.java | 32 -- .../ctbrec/sites/manyvids/wsmsg/JoinChat.java | 34 --- .../ctbrec/sites/manyvids/wsmsg/Message.java | 19 -- .../ctbrec/sites/manyvids/wsmsg/Ping.java | 9 - .../sites/manyvids/wsmsg/RegisterMessage.java | 28 -- .../ctbrec/sites/manyvids/wsmsg/Response.java | 7 - .../sites/manyvids/wsmsg/SendMessage.java | 33 -- .../sites/secretfriends/SecretFriends.java | 153 ---------- .../SecretFriendsHttpClient.java | 174 ----------- .../secretfriends/SecretFriendsModel.java | 198 ------------ .../SecretFriendsModelParser.java | 75 ----- .../SecretFriendsWebrtcDownload.java | 227 -------------- .../main/resources/docs/ConfigurationFile.md | 14 +- .../ctbrec/recorder/server/HttpServer.java | 10 - .../src/main/resources/html/static/index.html | 2 +- 68 files changed, 9 insertions(+), 6640 deletions(-) delete mode 100644 client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvConfigUI.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvElectronLoginDialog.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvFollowedTab.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvSiteUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvTabProvider.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/amateurtv/AmateurTvUpdateService.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminConfigUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminElectronLoginDialog.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedTab.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminFollowedUpdateService.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminSiteUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTab.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminTabProvider.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/jasmin/LiveJasminUpdateService.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveConfigUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveSiteUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveTabProvider.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/manyvids/MVLiveUpdateService.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsConfigUI.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsSiteUi.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsTabProvider.java delete mode 100644 client/src/main/java/ctbrec/ui/sites/secretfriends/SecretFriendsUpdateService.java delete mode 100644 common/src/main/java/ctbrec/sites/amateurtv/AmateurTv.java delete mode 100644 common/src/main/java/ctbrec/sites/amateurtv/AmateurTvDownload.java delete mode 100644 common/src/main/java/ctbrec/sites/amateurtv/AmateurTvHttpClient.java delete mode 100644 common/src/main/java/ctbrec/sites/amateurtv/AmateurTvModel.java delete mode 100644 common/src/main/java/ctbrec/sites/cherrytv/CherryTv.java delete mode 100644 common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java delete mode 100644 common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasmin.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminHttpClient.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminModel.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminModelInfo.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamRegistration.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminStreamSource.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminTippingWebSocket.java delete mode 100644 common/src/main/java/ctbrec/sites/jasmin/LiveJasminWebrtcDownload.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/MVLive.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/MVLiveClient.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/MVLiveHlsDownload.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/MVLiveHttpClient.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/MVLiveMergedHlsDownload.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/MVLiveModel.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/StreamLocation.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/GetBroadcastHealth.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/JoinChat.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/Message.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/Ping.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/RegisterMessage.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/Response.java delete mode 100644 common/src/main/java/ctbrec/sites/manyvids/wsmsg/SendMessage.java delete mode 100644 common/src/main/java/ctbrec/sites/secretfriends/SecretFriends.java delete mode 100644 common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsHttpClient.java delete mode 100644 common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModel.java delete mode 100644 common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsModelParser.java delete mode 100644 common/src/main/java/ctbrec/sites/secretfriends/SecretFriendsWebrtcDownload.java diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index 517ff09c..4328814e 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -24,19 +24,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; @@ -184,19 +179,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/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index fa8fc5df..f468032e 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -50,8 +50,6 @@ public class Settings { }; public FlaresolverrSettings flaresolverr = new FlaresolverrSettings(); - public String amateurTvUsername = ""; - public String amateurTvPassword = ""; public String bongacamsBaseUrl = "https://bongacams.com"; public String bongaPassword = ""; public String bongaUsername = ""; @@ -65,8 +63,6 @@ public class Settings { @Deprecated public boolean chaturbateUseFlaresolverr = false; public int chaturbateMsBetweenRequests = 1000; - public String cherryTvPassword = ""; - public String cherryTvUsername = ""; public boolean chooseStreamQuality = false; public String colorAccent = "#FFFFFF"; public String colorBase = "#FFFFFF"; @@ -100,10 +96,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 352b542b..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 || onlineState == UNCHECKED) { - 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 5bda0f93..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 NotImplementedExcetion(); - } - - @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 65497a21..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.NotLoggedInExcetion; -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 NotLoggedInExcetion()); - } - } - - @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 360823e3..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 NotImplementedExcetion("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 0530b3a0..8b484435 100644 --- a/common/src/main/resources/docs/ConfigurationFile.md +++ b/common/src/main/resources/docs/ConfigurationFile.md @@ -6,15 +6,15 @@ The configuration file stores all your settings and recorded models. ctbrec application: -- Windows: `C:\Users\{your user name}\AppData\Roaming\ctbrec\settings.json` -- Linux: `~/.config/ctbrec/settings.json` -- macOS: `/Users/{your user name}/Library/Preferences/ctbrec/settings.json` +- Windows: `C:\Users\{your user name}\AppData\Roaming\ctbrec\\settings.json` +- Linux: `~/.config/ctbrec//settings.json` +- macOS: `/Users/{your user name}/Library/Preferences/ctbrec//settings.json` server: -- Windows: `C:\Users\{your user name}\AppData\Roaming\ctbrec\server.json` -- Linux: `~/.config/ctbrec/server.json` -- macOS: `/Users/{your user name}/Library/Preferences/ctbrec/server.json` +- Windows: `C:\Users\{your user name}\AppData\Roaming\ctbrec\\server.json` +- Linux: `~/.config/ctbrec//server.json` +- macOS: `/Users/{your user name}/Library/Preferences/ctbrec//server.json` ##### Values @@ -100,7 +100,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/server/src/main/java/ctbrec/recorder/server/HttpServer.java b/server/src/main/java/ctbrec/recorder/server/HttpServer.java index 4f9e26af..5d7785de 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 58807410..8702a270 100644 --- a/server/src/main/resources/html/static/index.html +++ b/server/src/main/resources/html/static/index.html @@ -306,7 +306,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:"] }); } }