From 29ed8648e4b4562510968248423513282f70582f Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sun, 4 Nov 2018 22:29:15 +0100 Subject: [PATCH 01/40] Fix button width --- src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java b/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java index 44070fcc..3557a55a 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java @@ -195,8 +195,8 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener { root.setCenter(grid); loadImage(model, thumb); - record.prefWidthProperty().bind(openInBrowser.widthProperty()); - follow.prefWidthProperty().bind(openInBrowser.widthProperty()); + record.minWidthProperty().bind(openInBrowser.widthProperty()); + follow.minWidthProperty().bind(openInBrowser.widthProperty()); } private void follow(Model model) { From 1fec124bbcd871609273df605fe5dab3846d7085 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 5 Nov 2018 00:41:22 +0100 Subject: [PATCH 02/40] Add BongaCams This is the first working version. Follow / unfollow and tipping are not implemented. --- .../ctbrec/recorder/server/HttpServer.java | 2 + .../java/ctbrec/sites/bonga/BongaCams.java | 123 +++++++++++ .../sites/bonga/BongaCamsHttpClient.java | 15 ++ .../ctbrec/sites/bonga/BongaCamsModel.java | 193 ++++++++++++++++++ .../sites/bonga/BongaCamsTabProvider.java | 39 ++++ .../sites/bonga/BongaCamsUpdateService.java | 71 +++++++ .../java/ctbrec/ui/CamrecApplication.java | 2 + 7 files changed, 445 insertions(+) create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCams.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsModel.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java diff --git a/src/main/java/ctbrec/recorder/server/HttpServer.java b/src/main/java/ctbrec/recorder/server/HttpServer.java index 00537280..5767b72e 100644 --- a/src/main/java/ctbrec/recorder/server/HttpServer.java +++ b/src/main/java/ctbrec/recorder/server/HttpServer.java @@ -20,6 +20,7 @@ import ctbrec.Config; import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.Recorder; import ctbrec.sites.Site; +import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; @@ -66,6 +67,7 @@ public class HttpServer { sites.add(new MyFreeCams()); sites.add(new Camsoda()); sites.add(new Cam4()); + sites.add(new BongaCams()); } private void addShutdownHook() { diff --git a/src/main/java/ctbrec/sites/bonga/BongaCams.java b/src/main/java/ctbrec/sites/bonga/BongaCams.java new file mode 100644 index 00000000..61e46340 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCams.java @@ -0,0 +1,123 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; + +import ctbrec.Model; +import ctbrec.io.HttpClient; +import ctbrec.recorder.Recorder; +import ctbrec.sites.AbstractSite; +import ctbrec.ui.TabProvider; +import javafx.scene.Node; + +public class BongaCams extends AbstractSite { + + public static final String BASE_URL = "https://bongacams.com"; + + private BongaCamsHttpClient httpClient; + + private Recorder recorder; + + @Override + public String getName() { + return "BongaCams"; + } + + @Override + public String getBaseUrl() { + return BASE_URL; + } + + @Override + public String getAffiliateLink() { + return BASE_URL; + } + + @Override + public void setRecorder(Recorder recorder) { + this.recorder = recorder; + } + + @Override + public TabProvider getTabProvider() { + return new BongaCamsTabProvider(recorder, this); + } + + @Override + public Model createModel(String name) { + BongaCamsModel model = new BongaCamsModel(); + model.setName(name); + model.setUrl(BASE_URL + '/' + name); + model.setDescription(""); + model.setSite(this); + return model; + } + + @Override + public Integer getTokenBalance() throws IOException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getBuyTokensLink() { + // TODO Auto-generated method stub + return getBaseUrl(); + } + + @Override + public void login() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public HttpClient getHttpClient() { + if(httpClient == null) { + httpClient = new BongaCamsHttpClient(); + } + return httpClient; + } + + @Override + public void init() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void shutdown() { + // TODO Auto-generated method stub + + } + + @Override + public boolean supportsTips() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsFollow() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isSiteForModel(Model m) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Node getConfigurationGui() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean credentialsAvailable() { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java new file mode 100644 index 00000000..a7de6816 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -0,0 +1,15 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; + +import ctbrec.io.HttpClient; + +public class BongaCamsHttpClient extends HttpClient { + + @Override + public boolean login() throws IOException { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java new file mode 100644 index 00000000..dba742fe --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -0,0 +1,193 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iheartradio.m3u8.Encoding; +import com.iheartradio.m3u8.Format; +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; +import com.iheartradio.m3u8.PlaylistParser; +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.AbstractModel; +import ctbrec.recorder.download.StreamSource; +import ctbrec.sites.Site; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class BongaCamsModel extends AbstractModel { + + private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsModel.class); + + private BongaCams site; + private int userId; + private String onlineState = "n/a"; + private boolean online = false; + private List streamSources = new ArrayList<>(); + private int[] resolution; + + @Override + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + @Override + public String getOnlineState(boolean failFast) throws IOException, ExecutionException { + return onlineState; + } + + public void setOnlineState(String onlineState) { + this.onlineState = onlineState; + } + + @Override + public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { + String streamUrl = getStreamUrl(); + if (streamUrl == null) { + return Collections.emptyList(); + } + Request req = new Request.Builder().url(streamUrl).build(); + Response response = site.getHttpClient().execute(req); + try { + InputStream inputStream = response.body().byteStream(); + PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); + Playlist playlist = parser.parse(); + MasterPlaylist master = playlist.getMasterPlaylist(); + for (PlaylistData playlistData : master.getPlaylists()) { + + StreamSource streamsource = new StreamSource(); + streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri()); + if (playlistData.hasStreamInfo()) { + StreamInfo info = playlistData.getStreamInfo(); + streamsource.bandwidth = info.getBandwidth(); + streamsource.width = info.hasResolution() ? info.getResolution().width : 0; + streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + } else { + streamsource.bandwidth = 0; + streamsource.width = 0; + streamsource.height = 0; + } + streamSources.add(streamsource); + } + } finally { + response.close(); + } + return streamSources; + } + + private String getStreamUrl() throws IOException { + String url = BongaCams.BASE_URL + "/tools/amf.php"; + RequestBody body = new FormBody.Builder() + .add("method", "getRoomData") + .add("args[]", getName()) + .add("args[]", "false") + .build(); + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", BongaCams.BASE_URL) + .addHeader("X-Requested-With", "XMLHttpRequest") + .post(body) + .build(); + try(Response response = site.getHttpClient().execute(request)) { + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.optString("status").equals("success")) { + JSONObject localData = json.getJSONObject("localData"); + String server = localData.getString("videoServerUrl"); + return "https:" + server + "/hls/stream_" + getName() + "/playlist.m3u8"; + } else { + throw new IOException("Request was not successful: " + json.toString(2)); + } + } else { + throw new IOException(response.code() + " " + response.message()); + } + } + } + + @Override + public void invalidateCacheEntries() { + // TODO Auto-generated method stub + + } + + @Override + public void receiveTip(int tokens) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public int[] getStreamResolution(boolean failFast) throws ExecutionException { + if(resolution == null) { + if(failFast) { + return new int[2]; + } + try { + List streamSources = getStreamSources(); + Collections.sort(streamSources); + StreamSource best = streamSources.get(streamSources.size()-1); + resolution = new int[] {best.width, best.height}; + } catch (ExecutionException | IOException | ParseException | PlaylistException e) { + LOG.error("Couldn't determine stream resolution", e); + } + return resolution; + } else { + return resolution; + } + } + + @Override + public boolean follow() throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean unfollow() throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setSite(Site site) { + if(site instanceof BongaCams) { + this.site = (BongaCams) site; + } else { + throw new IllegalArgumentException("Site has to be an instance of BongaCams"); + } + } + + @Override + public Site getSite() { + return site; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java new file mode 100644 index 00000000..ebe42de3 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java @@ -0,0 +1,39 @@ +package ctbrec.sites.bonga; + +import java.util.ArrayList; +import java.util.List; + +import ctbrec.recorder.Recorder; +import ctbrec.ui.PaginatedScheduledService; +import ctbrec.ui.TabProvider; +import ctbrec.ui.ThumbOverviewTab; +import javafx.scene.Scene; +import javafx.scene.control.Tab; + +public class BongaCamsTabProvider extends TabProvider { + + private BongaCams bongaCams; + private Recorder recorder; + + public BongaCamsTabProvider(Recorder recorder, BongaCams bongaCams) { + this.recorder = recorder; + this.bongaCams = bongaCams; + } + + @Override + public List getTabs(Scene scene) { + List tabs = new ArrayList<>(); + + BongaCamsUpdateService updateService = new BongaCamsUpdateService(bongaCams); + tabs.add(createTab("Online", updateService)); + + return tabs; + } + + private Tab createTab(String title, PaginatedScheduledService updateService) { + ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, bongaCams); + tab.setRecorder(recorder); + return tab; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java new file mode 100644 index 00000000..4d2164ba --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -0,0 +1,71 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Model; +import ctbrec.ui.PaginatedScheduledService; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.Response; + +public class BongaCamsUpdateService extends PaginatedScheduledService { + + private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsUpdateService.class); + + private BongaCams bongaCams; + + public BongaCamsUpdateService(BongaCams bongaCams) { + this.bongaCams = bongaCams; + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + String url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=female&online_only=true&is_mobile=true&offset=" + ((page-1) * 50); + LOG.debug("Fetching page {}", url); + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", bongaCams.getBaseUrl()) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + Response response = bongaCams.getHttpClient().execute(request); + if (response.isSuccessful()) { + String content = response.body().string(); + response.close(); + List models = new ArrayList<>(); + JSONObject json = new JSONObject(content); + if(json.optString("status").equals("success")) { + JSONArray _models = json.getJSONArray("models"); + for (int i = 0; i < _models.length(); i++) { + JSONObject m = _models.getJSONObject(i); + String name = m.getString("username"); + BongaCamsModel model = (BongaCamsModel) bongaCams.createModel(name); + model.setUserId(m.getInt("user_id")); + model.setOnlineState(m.getString("room")); + model.setOnline(m.optBoolean("online") && !m.optBoolean("is_away")); + model.setPreview("https:" + m.getString("thumb_image")); + models.add(model); + } + } + return models; + } else { + int code = response.code(); + response.close(); + throw new IOException("HTTP status " + code); + } + } + }; + } +} diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java index 90be7e79..3fdf6466 100644 --- a/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/src/main/java/ctbrec/ui/CamrecApplication.java @@ -27,6 +27,7 @@ import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.Recorder; import ctbrec.recorder.RemoteRecorder; import ctbrec.sites.Site; +import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; @@ -64,6 +65,7 @@ public class CamrecApplication extends Application { sites.add(new MyFreeCams()); sites.add(new Camsoda()); sites.add(new Cam4()); + sites.add(new BongaCams()); loadConfig(); createHttpClient(); bus = new AsyncEventBus(Executors.newSingleThreadExecutor()); From 26bd482eac2c1fe522ffcd623128b81c4cff079c Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 5 Nov 2018 18:59:25 +0100 Subject: [PATCH 03/40] Remove dependency to JavaFX from the server code The sites had a direct depedency to JavaFX, which prevents the server to be started with openjdk. The affected code is no located in ConfigUI, so that the no direct dependency exists. --- src/main/java/ctbrec/Settings.java | 2 + src/main/java/ctbrec/sites/ConfigUI.java | 7 +++ src/main/java/ctbrec/sites/Site.java | 3 +- src/main/java/ctbrec/sites/cam4/Cam4.java | 40 ++------------ .../java/ctbrec/sites/cam4/Cam4ConfigUI.java | 48 +++++++++++++++++ .../java/ctbrec/sites/camsoda/Camsoda.java | 50 +++-------------- .../ctbrec/sites/camsoda/CamsodaConfigUI.java | 54 +++++++++++++++++++ .../ctbrec/sites/chaturbate/Chaturbate.java | 40 ++------------ .../sites/chaturbate/ChaturbateConfigUi.java | 48 +++++++++++++++++ .../java/ctbrec/sites/mfc/MyFreeCams.java | 40 ++------------ .../ctbrec/sites/mfc/MyFreeCamsConfigUI.java | 54 +++++++++++++++++++ src/main/java/ctbrec/ui/SettingsTab.java | 5 +- 12 files changed, 234 insertions(+), 157 deletions(-) create mode 100644 src/main/java/ctbrec/sites/ConfigUI.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java create mode 100644 src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java create mode 100644 src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java create mode 100644 src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index a8809ddf..96e878bc 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -22,6 +22,8 @@ public class Settings { public String mediaPlayer = "/usr/bin/mpv"; public String username = ""; // chaturbate username TODO maybe rename this onetime public String password = ""; // chaturbate password TODO maybe rename this onetime + public String bongaUsername = ""; + public String bongaPassword = ""; public String mfcUsername = ""; public String mfcPassword = ""; public String camsodaUsername = ""; diff --git a/src/main/java/ctbrec/sites/ConfigUI.java b/src/main/java/ctbrec/sites/ConfigUI.java new file mode 100644 index 00000000..33d97ec8 --- /dev/null +++ b/src/main/java/ctbrec/sites/ConfigUI.java @@ -0,0 +1,7 @@ +package ctbrec.sites; + +import javafx.scene.Parent; + +public interface ConfigUI { + public Parent createConfigPanel(); +} diff --git a/src/main/java/ctbrec/sites/Site.java b/src/main/java/ctbrec/sites/Site.java index 411be906..29a0e226 100644 --- a/src/main/java/ctbrec/sites/Site.java +++ b/src/main/java/ctbrec/sites/Site.java @@ -6,7 +6,6 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.ui.TabProvider; -import javafx.scene.Node; public interface Site { public String getName(); @@ -24,7 +23,7 @@ public interface Site { public boolean supportsTips(); public boolean supportsFollow(); public boolean isSiteForModel(Model m); - public Node getConfigurationGui(); + public ConfigUI getConfigurationGui(); public boolean credentialsAvailable(); public void setEnabled(boolean enabled); public boolean isEnabled(); diff --git a/src/main/java/ctbrec/sites/cam4/Cam4.java b/src/main/java/ctbrec/sites/cam4/Cam4.java index 5d791883..7533f96e 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4.java @@ -9,17 +9,8 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; -import ctbrec.ui.SettingsTab; +import ctbrec.sites.ConfigUI; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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 Cam4 extends AbstractSite { @@ -124,32 +115,7 @@ public class Cam4 extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("Cam4 User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().cam4Username); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Username = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("Cam4 Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().cam4Password); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Password = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(Cam4.AFFILIATE_LINK)); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new Cam4ConfigUI(); } } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java b/src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java new file mode 100644 index 00000000..4c1bef12 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java @@ -0,0 +1,48 @@ +package ctbrec.sites.cam4; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 Cam4ConfigUI implements ConfigUI { + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("Cam4 User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().cam4Username); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Username = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("Cam4 Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().cam4Password); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Password = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(Cam4.AFFILIATE_LINK)); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/sites/camsoda/Camsoda.java b/src/main/java/ctbrec/sites/camsoda/Camsoda.java index 3cf7f937..c405b336 100644 --- a/src/main/java/ctbrec/sites/camsoda/Camsoda.java +++ b/src/main/java/ctbrec/sites/camsoda/Camsoda.java @@ -9,17 +9,8 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; -import ctbrec.ui.SettingsTab; +import ctbrec.sites.ConfigUI; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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; import okhttp3.Request; import okhttp3.Response; @@ -44,6 +35,11 @@ public class Camsoda extends AbstractSite { return BASE_URI; } + @Override + public String getBuyTokensLink() { + return BASE_URI; + } + @Override public void setRecorder(Recorder recorder) { this.recorder = recorder; @@ -87,11 +83,6 @@ public class Camsoda extends AbstractSite { throw new RuntimeException("Tokens not found in response"); } - @Override - public String getBuyTokensLink() { - return getBaseUrl(); - } - @Override public void login() throws IOException { if(credentialsAvailable()) { @@ -140,32 +131,7 @@ public class Camsoda extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("CamSoda User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().camsodaUsername); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaUsername = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("CamSoda Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().camsodaPassword); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaPassword = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(getAffiliateLink())); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new CamsodaConfigUI(this); } } diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java b/src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java new file mode 100644 index 00000000..056ecd86 --- /dev/null +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java @@ -0,0 +1,54 @@ +package ctbrec.sites.camsoda; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 CamsodaConfigUI implements ConfigUI { + + private Camsoda camsoda; + + public CamsodaConfigUI(Camsoda camsoda) { + this.camsoda = camsoda; + } + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("CamSoda User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().camsodaUsername); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaUsername = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("CamSoda Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().camsodaPassword); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaPassword = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(camsoda.getAffiliateLink())); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java b/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java index 984e8c69..d06b37b4 100644 --- a/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java +++ b/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java @@ -29,18 +29,9 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; +import ctbrec.sites.ConfigUI; import ctbrec.ui.HtmlParser; -import ctbrec.ui.SettingsTab; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; @@ -316,33 +307,8 @@ public class Chaturbate extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("Chaturbate User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().username); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().username = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("Chaturbate Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().password); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().password = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(Chaturbate.REGISTRATION_LINK)); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new ChaturbateConfigUi(); } @Override diff --git a/src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java b/src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java new file mode 100644 index 00000000..b215b879 --- /dev/null +++ b/src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java @@ -0,0 +1,48 @@ +package ctbrec.sites.chaturbate; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 ChaturbateConfigUi implements ConfigUI { + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + + layout.add(new Label("Chaturbate User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().username); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().username = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("Chaturbate Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().password); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().password = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(Chaturbate.REGISTRATION_LINK)); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + + return layout; + } +} diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java index c06c1218..1d857810 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java @@ -8,18 +8,9 @@ import ctbrec.Config; import ctbrec.Model; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; +import ctbrec.sites.ConfigUI; import ctbrec.ui.HtmlParser; -import ctbrec.ui.SettingsTab; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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; import okhttp3.Request; import okhttp3.Response; @@ -129,33 +120,8 @@ public class MyFreeCams extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("MyFreeCams User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().mfcUsername); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcUsername = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("MyFreeCams Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().mfcPassword); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcPassword = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(getAffiliateLink())); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new MyFreeCamsConfigUI(this); } @Override diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java new file mode 100644 index 00000000..1190f061 --- /dev/null +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java @@ -0,0 +1,54 @@ +package ctbrec.sites.mfc; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 MyFreeCamsConfigUI implements ConfigUI { + + private MyFreeCams myFreeCams; + + public MyFreeCamsConfigUI(MyFreeCams myFreeCams) { + this.myFreeCams = myFreeCams; + } + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("MyFreeCams User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().mfcUsername); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcUsername = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("MyFreeCams Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().mfcPassword); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcPassword = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(myFreeCams.getAffiliateLink())); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index f0066613..6b8dc752 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -14,6 +14,7 @@ import com.sun.javafx.collections.ObservableListWrapper; import ctbrec.Config; import ctbrec.Hmac; import ctbrec.Settings; +import ctbrec.sites.ConfigUI; import ctbrec.sites.Site; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -119,9 +120,9 @@ public class SettingsTab extends Tab implements TabSelectionListener { rightSide.getChildren().add(credentialsAccordion); for (int i = 0; i < sites.size(); i++) { Site site = sites.get(i); - Node siteConfig = site.getConfigurationGui(); + ConfigUI siteConfig = site.getConfigurationGui(); if(siteConfig != null) { - TitledPane pane = new TitledPane(site.getName(), siteConfig); + TitledPane pane = new TitledPane(site.getName(), siteConfig.createConfigPanel()); credentialsAccordion.getPanes().add(pane); } } From 46c3feeb1fc7e444e6753374d52effaf1e06e8c7 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 5 Nov 2018 19:00:26 +0100 Subject: [PATCH 04/40] More stuff for BongaCams --- .../java/ctbrec/sites/bonga/BongaCams.java | 66 +++++++++++++------ .../ctbrec/sites/bonga/BongaCamsConfigUI.java | 54 +++++++++++++++ .../sites/bonga/BongaCamsHttpClient.java | 47 ++++++++++++- .../ctbrec/sites/bonga/BongaCamsModel.java | 1 + .../java/ctbrec/ui/CamrecApplication.java | 6 +- 5 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsConfigUI.java diff --git a/src/main/java/ctbrec/sites/bonga/BongaCams.java b/src/main/java/ctbrec/sites/bonga/BongaCams.java index 61e46340..d152a46c 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCams.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCams.java @@ -2,12 +2,19 @@ package ctbrec.sites.bonga; import java.io.IOException; +import org.json.JSONObject; + +import ctbrec.Config; import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; +import ctbrec.sites.ConfigUI; import ctbrec.ui.TabProvider; -import javafx.scene.Node; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; public class BongaCams extends AbstractSite { @@ -29,7 +36,7 @@ public class BongaCams extends AbstractSite { @Override public String getAffiliateLink() { - return BASE_URL; + return "http://bongacams2.com/track?c=610249"; } @Override @@ -54,20 +61,43 @@ public class BongaCams extends AbstractSite { @Override public Integer getTokenBalance() throws IOException { - // TODO Auto-generated method stub + String url = BongaCams.BASE_URL + "/tools/amf.php"; + RequestBody body = new FormBody.Builder() + .add("method", "ping") + .add("args[]", "66050808") + .build(); + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", BongaCams.BASE_URL) + .addHeader("X-Requested-With", "XMLHttpRequest") + .post(body) + .build(); + try(Response response = getHttpClient().execute(request, true)) { + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.optString("status").equals("success")) { + System.out.println(json.toString(2)); + } else { + throw new IOException("Request was not successful: " + json.toString(2)); + } + } else { + throw new IOException(response.code() + " " + response.message()); + } + } return 0; } @Override public String getBuyTokensLink() { - // TODO Auto-generated method stub - return getBaseUrl(); + return getAffiliateLink(); } @Override public void login() throws IOException { - // TODO Auto-generated method stub - + getHttpClient().login(); } @Override @@ -80,20 +110,18 @@ public class BongaCams extends AbstractSite { @Override public void init() throws IOException { - // TODO Auto-generated method stub - } @Override public void shutdown() { - // TODO Auto-generated method stub - + if(httpClient != null) { + httpClient.shutdown(); + } } @Override public boolean supportsTips() { - // TODO Auto-generated method stub - return false; + return true; } @Override @@ -104,20 +132,18 @@ public class BongaCams extends AbstractSite { @Override public boolean isSiteForModel(Model m) { - // TODO Auto-generated method stub - return false; + return m instanceof BongaCamsModel; } @Override - public Node getConfigurationGui() { - // TODO Auto-generated method stub - return null; + public ConfigUI getConfigurationGui() { + return new BongaCamsConfigUI(this); } @Override public boolean credentialsAvailable() { - // TODO Auto-generated method stub - return false; + String username = Config.getInstance().getSettings().bongaUsername; + return username != null && !username.trim().isEmpty(); } } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsConfigUI.java b/src/main/java/ctbrec/sites/bonga/BongaCamsConfigUI.java new file mode 100644 index 00000000..32ad77a3 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsConfigUI.java @@ -0,0 +1,54 @@ +package ctbrec.sites.bonga; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 BongaCamsConfigUI implements ConfigUI { + + private BongaCams bongaCams; + + public BongaCamsConfigUI(BongaCams bongaCams) { + this.bongaCams = bongaCams; + } + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("BongaCams User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().bongaUsername); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().bongaUsername = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("BongaCams Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().bongaPassword); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().bongaPassword = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(bongaCams.getAffiliateLink())); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index a7de6816..35be111d 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -1,15 +1,58 @@ package ctbrec.sites.bonga; import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; import ctbrec.io.HttpClient; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; public class BongaCamsHttpClient extends HttpClient { + private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsHttpClient.class); + @Override public boolean login() throws IOException { - // TODO Auto-generated method stub - return false; + String url = BongaCams.BASE_URL + "/login"; + String dateTime = new SimpleDateFormat("d.MM.yyyy', 'HH:mm:ss").format(new Date()); + RequestBody body = new FormBody.Builder() + .add("security_log_additional_info","{\"language\":\"en\",\"cookieEnabled\":true,\"javaEnabled\":false,\"flashVersion\":\"31.0.0\",\"dateTime\":\""+dateTime+"\",\"ips\":[\"192.168.0.1\"]}") + .add("log_in[username]", Config.getInstance().getSettings().bongaUsername) + .add("log_in[password]", Config.getInstance().getSettings().bongaPassword) + .add("log_in[remember]", "1") + .add("log_in[bfpt]", "") + .add("header_form", "1") + .build(); + Request request = new Request.Builder() + .url(url) + .post(body) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept","application/json") + .addHeader("Accept-Language", "en") + .addHeader("Referer", BongaCams.BASE_URL) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + try(Response response = execute(request)) { + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.optString("status").equals("success")) { + return true; + } else { + LOG.debug("Login response: {}", json.toString(2)); + throw new IOException("Login not successful"); + } + } else { + throw new IOException(response.code() + " " + response.message()); + } + } } } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index dba742fe..5126b44a 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -113,6 +113,7 @@ public class BongaCamsModel extends AbstractModel { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); if(json.optString("status").equals("success")) { + System.out.println(json.toString(2)); JSONObject localData = json.getJSONObject("localData"); String server = localData.getString("videoServerUrl"); return "https:" + server + "/hls/stream_" + getName() + "/playlist.m3u8"; diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java index 3fdf6466..59faff75 100644 --- a/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/src/main/java/ctbrec/ui/CamrecApplication.java @@ -61,11 +61,11 @@ public class CamrecApplication extends Application { @Override public void start(Stage primaryStage) throws Exception { + sites.add(new BongaCams()); + sites.add(new Cam4()); + sites.add(new Camsoda()); sites.add(new Chaturbate()); sites.add(new MyFreeCams()); - sites.add(new Camsoda()); - sites.add(new Cam4()); - sites.add(new BongaCams()); loadConfig(); createHttpClient(); bus = new AsyncEventBus(Executors.newSingleThreadExecutor()); From 682f78bdacfe827c3d99f3c32b2c3c1d417320be Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 5 Nov 2018 18:59:25 +0100 Subject: [PATCH 05/40] Remove dependency to JavaFX from the server code The sites had a direct depedency to JavaFX, which prevents the server to be started with openjdk. The affected code is no located in ConfigUI, so that the no direct dependency exists. --- src/main/java/ctbrec/Settings.java | 2 + src/main/java/ctbrec/sites/ConfigUI.java | 7 +++ src/main/java/ctbrec/sites/Site.java | 3 +- src/main/java/ctbrec/sites/cam4/Cam4.java | 40 ++------------ .../java/ctbrec/sites/cam4/Cam4ConfigUI.java | 48 +++++++++++++++++ .../java/ctbrec/sites/camsoda/Camsoda.java | 50 +++-------------- .../ctbrec/sites/camsoda/CamsodaConfigUI.java | 54 +++++++++++++++++++ .../ctbrec/sites/chaturbate/Chaturbate.java | 40 ++------------ .../sites/chaturbate/ChaturbateConfigUi.java | 48 +++++++++++++++++ .../java/ctbrec/sites/mfc/MyFreeCams.java | 40 ++------------ .../ctbrec/sites/mfc/MyFreeCamsConfigUI.java | 54 +++++++++++++++++++ src/main/java/ctbrec/ui/SettingsTab.java | 5 +- 12 files changed, 234 insertions(+), 157 deletions(-) create mode 100644 src/main/java/ctbrec/sites/ConfigUI.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java create mode 100644 src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java create mode 100644 src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java create mode 100644 src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index a8809ddf..96e878bc 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -22,6 +22,8 @@ public class Settings { public String mediaPlayer = "/usr/bin/mpv"; public String username = ""; // chaturbate username TODO maybe rename this onetime public String password = ""; // chaturbate password TODO maybe rename this onetime + public String bongaUsername = ""; + public String bongaPassword = ""; public String mfcUsername = ""; public String mfcPassword = ""; public String camsodaUsername = ""; diff --git a/src/main/java/ctbrec/sites/ConfigUI.java b/src/main/java/ctbrec/sites/ConfigUI.java new file mode 100644 index 00000000..33d97ec8 --- /dev/null +++ b/src/main/java/ctbrec/sites/ConfigUI.java @@ -0,0 +1,7 @@ +package ctbrec.sites; + +import javafx.scene.Parent; + +public interface ConfigUI { + public Parent createConfigPanel(); +} diff --git a/src/main/java/ctbrec/sites/Site.java b/src/main/java/ctbrec/sites/Site.java index 411be906..29a0e226 100644 --- a/src/main/java/ctbrec/sites/Site.java +++ b/src/main/java/ctbrec/sites/Site.java @@ -6,7 +6,6 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.ui.TabProvider; -import javafx.scene.Node; public interface Site { public String getName(); @@ -24,7 +23,7 @@ public interface Site { public boolean supportsTips(); public boolean supportsFollow(); public boolean isSiteForModel(Model m); - public Node getConfigurationGui(); + public ConfigUI getConfigurationGui(); public boolean credentialsAvailable(); public void setEnabled(boolean enabled); public boolean isEnabled(); diff --git a/src/main/java/ctbrec/sites/cam4/Cam4.java b/src/main/java/ctbrec/sites/cam4/Cam4.java index 5d791883..7533f96e 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4.java @@ -9,17 +9,8 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; -import ctbrec.ui.SettingsTab; +import ctbrec.sites.ConfigUI; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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 Cam4 extends AbstractSite { @@ -124,32 +115,7 @@ public class Cam4 extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("Cam4 User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().cam4Username); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Username = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("Cam4 Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().cam4Password); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Password = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(Cam4.AFFILIATE_LINK)); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new Cam4ConfigUI(); } } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java b/src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java new file mode 100644 index 00000000..4c1bef12 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4ConfigUI.java @@ -0,0 +1,48 @@ +package ctbrec.sites.cam4; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 Cam4ConfigUI implements ConfigUI { + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("Cam4 User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().cam4Username); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Username = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("Cam4 Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().cam4Password); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Password = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(Cam4.AFFILIATE_LINK)); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/sites/camsoda/Camsoda.java b/src/main/java/ctbrec/sites/camsoda/Camsoda.java index 3cf7f937..c405b336 100644 --- a/src/main/java/ctbrec/sites/camsoda/Camsoda.java +++ b/src/main/java/ctbrec/sites/camsoda/Camsoda.java @@ -9,17 +9,8 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; -import ctbrec.ui.SettingsTab; +import ctbrec.sites.ConfigUI; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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; import okhttp3.Request; import okhttp3.Response; @@ -44,6 +35,11 @@ public class Camsoda extends AbstractSite { return BASE_URI; } + @Override + public String getBuyTokensLink() { + return BASE_URI; + } + @Override public void setRecorder(Recorder recorder) { this.recorder = recorder; @@ -87,11 +83,6 @@ public class Camsoda extends AbstractSite { throw new RuntimeException("Tokens not found in response"); } - @Override - public String getBuyTokensLink() { - return getBaseUrl(); - } - @Override public void login() throws IOException { if(credentialsAvailable()) { @@ -140,32 +131,7 @@ public class Camsoda extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("CamSoda User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().camsodaUsername); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaUsername = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("CamSoda Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().camsodaPassword); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaPassword = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(getAffiliateLink())); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new CamsodaConfigUI(this); } } diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java b/src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java new file mode 100644 index 00000000..056ecd86 --- /dev/null +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaConfigUI.java @@ -0,0 +1,54 @@ +package ctbrec.sites.camsoda; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 CamsodaConfigUI implements ConfigUI { + + private Camsoda camsoda; + + public CamsodaConfigUI(Camsoda camsoda) { + this.camsoda = camsoda; + } + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("CamSoda User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().camsodaUsername); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaUsername = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("CamSoda Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().camsodaPassword); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().camsodaPassword = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(camsoda.getAffiliateLink())); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java b/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java index 984e8c69..d06b37b4 100644 --- a/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java +++ b/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java @@ -29,18 +29,9 @@ import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; +import ctbrec.sites.ConfigUI; import ctbrec.ui.HtmlParser; -import ctbrec.ui.SettingsTab; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; @@ -316,33 +307,8 @@ public class Chaturbate extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("Chaturbate User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().username); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().username = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("Chaturbate Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().password); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().password = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(Chaturbate.REGISTRATION_LINK)); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new ChaturbateConfigUi(); } @Override diff --git a/src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java b/src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java new file mode 100644 index 00000000..b215b879 --- /dev/null +++ b/src/main/java/ctbrec/sites/chaturbate/ChaturbateConfigUi.java @@ -0,0 +1,48 @@ +package ctbrec.sites.chaturbate; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 ChaturbateConfigUi implements ConfigUI { + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + + layout.add(new Label("Chaturbate User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().username); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().username = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("Chaturbate Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().password); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().password = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(Chaturbate.REGISTRATION_LINK)); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + + return layout; + } +} diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java index c06c1218..1d857810 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCams.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCams.java @@ -8,18 +8,9 @@ import ctbrec.Config; import ctbrec.Model; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; -import ctbrec.ui.DesktopIntergation; +import ctbrec.sites.ConfigUI; import ctbrec.ui.HtmlParser; -import ctbrec.ui.SettingsTab; import ctbrec.ui.TabProvider; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Button; -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; import okhttp3.Request; import okhttp3.Response; @@ -129,33 +120,8 @@ public class MyFreeCams extends AbstractSite { } @Override - public Node getConfigurationGui() { - GridPane layout = SettingsTab.createGridLayout(); - layout.add(new Label("MyFreeCams User"), 0, 0); - TextField username = new TextField(Config.getInstance().getSettings().mfcUsername); - username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcUsername = username.getText()); - GridPane.setFillWidth(username, true); - GridPane.setHgrow(username, Priority.ALWAYS); - GridPane.setColumnSpan(username, 2); - layout.add(username, 1, 0); - - layout.add(new Label("MyFreeCams Password"), 0, 1); - PasswordField password = new PasswordField(); - password.setText(Config.getInstance().getSettings().mfcPassword); - password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcPassword = password.getText()); - GridPane.setFillWidth(password, true); - GridPane.setHgrow(password, Priority.ALWAYS); - GridPane.setColumnSpan(password, 2); - layout.add(password, 1, 1); - - Button createAccount = new Button("Create new Account"); - createAccount.setOnAction((e) -> DesktopIntergation.open(getAffiliateLink())); - layout.add(createAccount, 1, 2); - GridPane.setColumnSpan(createAccount, 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)); - return layout; + public ConfigUI getConfigurationGui() { + return new MyFreeCamsConfigUI(this); } @Override diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java new file mode 100644 index 00000000..1190f061 --- /dev/null +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsConfigUI.java @@ -0,0 +1,54 @@ +package ctbrec.sites.mfc; + +import ctbrec.Config; +import ctbrec.sites.ConfigUI; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.control.Button; +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 MyFreeCamsConfigUI implements ConfigUI { + + private MyFreeCams myFreeCams; + + public MyFreeCamsConfigUI(MyFreeCams myFreeCams) { + this.myFreeCams = myFreeCams; + } + + @Override + public Parent createConfigPanel() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("MyFreeCams User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().mfcUsername); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcUsername = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + + layout.add(new Label("MyFreeCams Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().mfcPassword); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().mfcPassword = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(myFreeCams.getAffiliateLink())); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 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)); + return layout; + } + +} diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index f0066613..6b8dc752 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -14,6 +14,7 @@ import com.sun.javafx.collections.ObservableListWrapper; import ctbrec.Config; import ctbrec.Hmac; import ctbrec.Settings; +import ctbrec.sites.ConfigUI; import ctbrec.sites.Site; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -119,9 +120,9 @@ public class SettingsTab extends Tab implements TabSelectionListener { rightSide.getChildren().add(credentialsAccordion); for (int i = 0; i < sites.size(); i++) { Site site = sites.get(i); - Node siteConfig = site.getConfigurationGui(); + ConfigUI siteConfig = site.getConfigurationGui(); if(siteConfig != null) { - TitledPane pane = new TitledPane(site.getName(), siteConfig); + TitledPane pane = new TitledPane(site.getName(), siteConfig.createConfigPanel()); credentialsAccordion.getPanes().add(pane); } } From 2f1ebabf00de9824594876f4ff7a67d5ac045a48 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 5 Nov 2018 19:50:26 +0100 Subject: [PATCH 06/40] Add more tabs to BongaCams --- .../sites/bonga/BongaCamsTabProvider.java | 26 +++++++++++++++++-- .../sites/bonga/BongaCamsUpdateService.java | 8 +++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java index ebe42de3..50a6bd63 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java @@ -24,8 +24,30 @@ public class BongaCamsTabProvider extends TabProvider { public List getTabs(Scene scene) { List tabs = new ArrayList<>(); - BongaCamsUpdateService updateService = new BongaCamsUpdateService(bongaCams); - tabs.add(createTab("Online", updateService)); + // female + String url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=female&online_only=true&is_mobile=true&offset="; + BongaCamsUpdateService updateService = new BongaCamsUpdateService(bongaCams, url); + tabs.add(createTab("Female", updateService)); + + // male + url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=male&online_only=true&is_mobile=true&offset="; + updateService = new BongaCamsUpdateService(bongaCams, url); + tabs.add(createTab("Male", updateService)); + + // couples + url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=couples&online_only=true&is_mobile=true&offset="; + updateService = new BongaCamsUpdateService(bongaCams, url); + tabs.add(createTab("Couples", updateService)); + + // trans + url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=transsexual&online_only=true&is_mobile=true&offset="; + updateService = new BongaCamsUpdateService(bongaCams, url); + tabs.add(createTab("Transsexual", updateService)); + + // new + url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=new-models&online_only=true&is_mobile=true&offset="; + updateService = new BongaCamsUpdateService(bongaCams, url); + tabs.add(createTab("New", updateService)); return tabs; } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java index 4d2164ba..5230b736 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -20,9 +20,11 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsUpdateService.class); private BongaCams bongaCams; + private String url; - public BongaCamsUpdateService(BongaCams bongaCams) { + public BongaCamsUpdateService(BongaCams bongaCams, String url) { this.bongaCams = bongaCams; + this.url = url; } @Override @@ -30,10 +32,10 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { return new Task>() { @Override public List call() throws IOException { - String url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=female&online_only=true&is_mobile=true&offset=" + ((page-1) * 50); + String _url = url + ((page-1) * 50); LOG.debug("Fetching page {}", url); Request request = new Request.Builder() - .url(url) + .url(_url) .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") From 69194e2800eef450498ac8b2f570a67060006299 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 00:17:41 +0100 Subject: [PATCH 07/40] Add login dialog for BongaCams --- .../java/ctbrec/sites/bonga/BongaCams.java | 6 +- .../sites/bonga/BongaCamsHttpClient.java | 192 ++++++++++++++++-- .../sites/bonga/BongaCamsLoginDialog.java | 118 +++++++++++ src/main/java/ctbrec/ui/ThumbCell.java | 2 +- 4 files changed, 297 insertions(+), 21 deletions(-) create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java diff --git a/src/main/java/ctbrec/sites/bonga/BongaCams.java b/src/main/java/ctbrec/sites/bonga/BongaCams.java index d152a46c..075c83b0 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCams.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCams.java @@ -78,8 +78,9 @@ public class BongaCams extends AbstractSite { try(Response response = getHttpClient().execute(request, true)) { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); - if(json.optString("status").equals("success")) { - System.out.println(json.toString(2)); + if(json.optString("status").equals("online")) { + JSONObject userData = json.getJSONObject("userData"); + return userData.getInt("balance"); } else { throw new IOException("Request was not successful: " + json.toString(2)); } @@ -87,7 +88,6 @@ public class BongaCams extends AbstractSite { throw new IOException(response.code() + " " + response.message()); } } - return 0; } @Override diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index 35be111d..4a16ff98 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -1,53 +1,156 @@ package ctbrec.sites.bonga; import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.net.HttpCookie; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import ctbrec.Config; import ctbrec.io.HttpClient; +import javafx.application.Platform; +import okhttp3.Cookie; import okhttp3.FormBody; +import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; +import okio.ByteString; public class BongaCamsHttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsHttpClient.class); @Override - public boolean login() throws IOException { - String url = BongaCams.BASE_URL + "/login"; - String dateTime = new SimpleDateFormat("d.MM.yyyy', 'HH:mm:ss").format(new Date()); + public synchronized boolean login() throws IOException { + if(loggedIn) { + return true; + } + + BlockingQueue queue = new LinkedBlockingQueue<>(); + + Runnable showDialog = () -> { + // login with javafx WebView + BongaCamsLoginDialog loginDialog = new BongaCamsLoginDialog(); + + // transfer cookies from WebView to OkHttp cookie jar + transferCookies(loginDialog); + + try { + queue.put(true); + } catch (InterruptedException e) { + LOG.error("Error while signaling termination", e); + } + }; + + if(Platform.isFxApplicationThread()) { + showDialog.run(); + } else { + Platform.runLater(showDialog); + try { + queue.take(); + } catch (InterruptedException e) { + LOG.error("Error while waiting for login dialog to close", e); + throw new IOException(e); + } + } + + loggedIn = checkLoginSuccess(); + createWebSocket(); + return loggedIn; + } + + private void createWebSocket() { + // $.noticeSocket = new SocketAdapter('wss://notice.bcrncdn.com:443/ws'); + // $.noticeSocket.onopen = function(){ + // this.send({type: 'identify', data: '0387db666178a863395c49f5f912cf070055482716514804'}); + // $(document).trigger('onNoticeSocketOpen'); + // }; + // $.noticeSocket.onmessage = function(e){$(document).trigger('onNoticeSocketMessage', [e])}; + // $(function() { + // window.setTimeout($.checkAuth, 3600 * 5 * 1000); + // if ($('#email_confirmed_popup').length > 0) { + // $('#email_confirmed_popup').show(); + // setTimeout(function() { $('#email_confirmed_popup').fadeOut('fast');}, 5000); + // } + // }); + Request req = new Request.Builder() + .url("wss://notice.bcrncdn.com:443/ws") + .build(); + LOG.debug("Creating websocket"); + WebSocket ws = super.client.newWebSocket(req, new WebSocketListener() { + @Override + public void onOpen(WebSocket webSocket, Response response) { + super.onOpen(webSocket, response); + try { + LOG.trace("open: [{}]", response.body().string()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onClosed(WebSocket webSocket, int code, String reason) { + super.onClosed(webSocket, code, reason); + LOG.info("Bonga websocket closed: {} {}", code, reason); + } + + @Override + public void onFailure(WebSocket webSocket, Throwable t, Response response) { + super.onFailure(webSocket, t, response); + LOG.error("Bonga websocket failure: {} {}", response.code(), response.message(), t); + } + + @Override + public void onMessage(WebSocket webSocket, String text) { + super.onMessage(webSocket, text); + LOG.debug("onMessage {}", text); + } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + super.onMessage(webSocket, bytes); + LOG.debug("msgb: {}", bytes.hex()); + } + }); + } + + + /** + * check, if the login by sending a ping request + * @throws IOException + */ + private boolean checkLoginSuccess() throws IOException { + String url = BongaCams.BASE_URL + "/tools/amf.php"; RequestBody body = new FormBody.Builder() - .add("security_log_additional_info","{\"language\":\"en\",\"cookieEnabled\":true,\"javaEnabled\":false,\"flashVersion\":\"31.0.0\",\"dateTime\":\""+dateTime+"\",\"ips\":[\"192.168.0.1\"]}") - .add("log_in[username]", Config.getInstance().getSettings().bongaUsername) - .add("log_in[password]", Config.getInstance().getSettings().bongaPassword) - .add("log_in[remember]", "1") - .add("log_in[bfpt]", "") - .add("header_form", "1") + // .add("method", "getRoomData") + // .add("args[]", name) + // .add("args[]", "false") + .add("method", "ping") + .add("args[]", "66050808") // TODO where to get the userId .build(); Request request = new Request.Builder() .url(url) - .post(body) .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") - .addHeader("Accept","application/json") + .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", BongaCams.BASE_URL) .addHeader("X-Requested-With", "XMLHttpRequest") + .post(body) .build(); try(Response response = execute(request)) { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); - if(json.optString("status").equals("success")) { + if(json.optString("status").equals("online")) { return true; } else { - LOG.debug("Login response: {}", json.toString(2)); - throw new IOException("Login not successful"); + throw new IOException("Request was not successful: " + json.toString(2)); } } else { throw new IOException(response.code() + " " + response.message()); @@ -55,4 +158,59 @@ public class BongaCamsHttpClient extends HttpClient { } } + private void transferCookies(BongaCamsLoginDialog loginDialog) { + HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl()); + List cookies = new ArrayList<>(); + for (HttpCookie webViewCookie : loginDialog.getCookies()) { + Cookie cookie = Cookie.parse(redirectedUrl, webViewCookie.toString()); + cookies.add(cookie); + } + cookieJar.saveFromResponse(redirectedUrl, cookies); + + HttpUrl origUrl = HttpUrl.parse(BongaCamsLoginDialog.URL); + cookies = new ArrayList<>(); + for (HttpCookie webViewCookie : loginDialog.getCookies()) { + Cookie cookie = Cookie.parse(origUrl, webViewCookie.toString()); + cookies.add(cookie); + } + cookieJar.saveFromResponse(origUrl, cookies); + } + + // @Override + // public boolean login() throws IOException { + // String url = BongaCams.BASE_URL + "/login"; + // String dateTime = new SimpleDateFormat("d.MM.yyyy', 'HH:mm:ss").format(new Date()); + // RequestBody body = new FormBody.Builder() + // .add("security_log_additional_info","{\"language\":\"en\",\"cookieEnabled\":true,\"javaEnabled\":false,\"flashVersion\":\"31.0.0\",\"dateTime\":\""+dateTime+"\",\"ips\":[\"192.168.0.1\"]}") + // .add("log_in[username]", Config.getInstance().getSettings().bongaUsername) + // .add("log_in[password]", Config.getInstance().getSettings().bongaPassword) + // .add("log_in[remember]", "1") + // .add("log_in[bfpt]", "") + // .add("header_form", "1") + // .build(); + // Request request = new Request.Builder() + // .url(url) + // .post(body) + // .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + // .addHeader("Accept","application/json") + // .addHeader("Accept-Language", "en") + // .addHeader("Referer", BongaCams.BASE_URL) + // .addHeader("X-Requested-With", "XMLHttpRequest") + // .build(); + // try(Response response = execute(request)) { + // if(response.isSuccessful()) { + // JSONObject json = new JSONObject(response.body().string()); + // if(json.optString("status").equals("success")) { + // return true; + // } else { + // LOG.debug("Login response: {}", json.toString(2)); + // Platform.runLater(() -> new BongaCamsLoginDialog()); + // throw new IOException("Login not successful"); + // } + // } else { + // throw new IOException(response.code() + " " + response.message()); + // } + // } + // } + } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java b/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java new file mode 100644 index 00000000..0d609311 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java @@ -0,0 +1,118 @@ +package ctbrec.sites.bonga; + +import java.io.File; +import java.io.InputStream; +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.HttpCookie; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; +import ctbrec.OS; +import javafx.concurrent.Worker.State; +import javafx.scene.Scene; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.image.Image; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import javafx.stage.Stage; + +public class BongaCamsLoginDialog { + + private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsLoginDialog.class); + public static final String URL = BongaCams.BASE_URL + "/login"; + private List cookies = null; + private String url; + private Region veil; + private ProgressIndicator p; + + public BongaCamsLoginDialog() { + Stage stage = new Stage(); + stage.setTitle("BongaCams Login"); + InputStream icon = getClass().getResourceAsStream("/icon.png"); + stage.getIcons().add(new Image(icon)); + CookieManager cookieManager = new CookieManager(); + CookieHandler.setDefault(cookieManager); + WebView webView = createWebView(stage); + + veil = new Region(); + veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)"); + p = new ProgressIndicator(); + p.setMaxSize(140, 140); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(webView, veil, p); + + stage.setScene(new Scene(stackPane, 640, 480)); + stage.showAndWait(); + cookies = cookieManager.getCookieStore().getCookies(); + } + + private WebView createWebView(Stage stage) { + WebView browser = new WebView(); + WebEngine webEngine = browser.getEngine(); + webEngine.setJavaScriptEnabled(true); + webEngine.locationProperty().addListener((obs, oldV, newV) -> { + try { + URL _url = new URL(newV); + if (Objects.equals(_url.getPath(), "/")) { + stage.close(); + } + } catch (MalformedURLException e) { + LOG.error("Couldn't parse new url {}", newV, e); + } + url = newV.toString(); + }); + webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> { + if (newState == State.SUCCEEDED) { + veil.setVisible(false); + p.setVisible(false); + //System.out.println("############# " + webEngine.getLocation()); + //System.out.println(webEngine.getDocument().getDocumentElement().getTextContent()); + try { + String username = Config.getInstance().getSettings().bongaUsername; + if (username != null && !username.trim().isEmpty()) { + webEngine.executeScript("$('input[name=\"log_in[username]\"]').attr('value','" + username + "')"); + } + String password = Config.getInstance().getSettings().bongaPassword; + if (password != null && !password.trim().isEmpty()) { + webEngine.executeScript("$('input[name=\"log_in[password]\"]').attr('value','" + password + "')"); + } + webEngine.executeScript("$('div[class~=\"fancybox-overlay\"]').css('display','none')"); + webEngine.executeScript("$('div#header').css('display','none')"); + webEngine.executeScript("$('div.footer').css('display','none')"); + webEngine.executeScript("$('div.footer_copy').css('display','none')"); + webEngine.executeScript("$('div[class~=\"banner_top_index\"]').css('display','none')"); + webEngine.executeScript("$('td.menu_container').css('display','none')"); + } catch(Exception e) { + LOG.warn("Couldn't auto fill username and password for BongaCams", e); + } + } else if (newState == State.CANCELLED || newState == State.FAILED) { + veil.setVisible(false); + p.setVisible(false); + } + }); + webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine")); + webEngine.load(URL); + return browser; + } + + public List getCookies() { + // for (HttpCookie httpCookie : cookies) { + // LOG.debug("Cookie: {}", httpCookie); + // } + return cookies; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index f579a948..133d37a2 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -208,7 +208,7 @@ public class ThumbCell extends StackPane { LOG.trace("Removing invalid resolution value for {}", model.getName()); model.invalidateCacheEntries(); } - + Thread.sleep(500); } catch (IOException | InterruptedException e1) { LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1); From 6b16a637f0958eefb5fb0e78340a757614c9fe29 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 14:32:35 +0100 Subject: [PATCH 08/40] Add JVM parameter to define the configuration directory Add a new JVM parameter (-Dctbrec.config.dir) to define the directory to save the config to / load it from. If this parameter is not set, the system default directory determined by OS.getConfigDir() is used. --- src/main/java/ctbrec/Config.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/Config.java b/src/main/java/ctbrec/Config.java index 4ecf0f10..de2aaf2b 100644 --- a/src/main/java/ctbrec/Config.java +++ b/src/main/java/ctbrec/Config.java @@ -29,9 +29,16 @@ public class Config { private Settings settings; private String filename; private List sites; + private File configDir; private Config(List sites) throws FileNotFoundException, IOException { this.sites = sites; + if(System.getProperty("ctbrec.config.dir") != null) { + configDir = new File(System.getProperty("ctbrec.config.dir")); + } else { + configDir = OS.getConfigDir(); + } + if(System.getProperty("ctbrec.config") != null) { filename = System.getProperty("ctbrec.config"); } else { @@ -45,7 +52,6 @@ public class Config { .add(Model.class, new ModelJsonAdapter(sites)) .build(); JsonAdapter adapter = moshi.adapter(Settings.class); - File configDir = OS.getConfigDir(); File configFile = new File(configDir, filename); LOG.debug("Loading config from {}", configFile.getAbsolutePath()); if(configFile.exists()) { @@ -86,7 +92,6 @@ public class Config { .build(); JsonAdapter adapter = moshi.adapter(Settings.class).indent(" "); String json = adapter.toJson(settings); - File configDir = OS.getConfigDir(); File configFile = new File(configDir, filename); LOG.debug("Saving config to {}", configFile.getAbsolutePath()); Files.createDirectories(configDir.toPath()); From efc4719018bcab79975d501a2bd0d45830632ef9 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 16:35:41 +0100 Subject: [PATCH 09/40] Add possibility to suspend the recording for model This makes it possible to stop the recording without loosing track of the model. The user can pause/unpause recordings in the recorded models tab. There is also an new column "Paused", which indicates, if the recording is suspended for a model. --- src/main/java/ctbrec/AbstractModel.java | 11 +++ src/main/java/ctbrec/Model.java | 3 + src/main/java/ctbrec/io/ModelJsonAdapter.java | 5 + .../java/ctbrec/recorder/LocalRecorder.java | 47 +++++++++- src/main/java/ctbrec/recorder/Recorder.java | 3 + .../java/ctbrec/recorder/RemoteRecorder.java | 12 ++- .../recorder/server/RecorderServlet.java | 12 +++ src/main/java/ctbrec/ui/JavaFxModel.java | 40 +++++++- .../java/ctbrec/ui/RecordedModelsTab.java | 92 +++++++++++++++++-- 9 files changed, 211 insertions(+), 14 deletions(-) diff --git a/src/main/java/ctbrec/AbstractModel.java b/src/main/java/ctbrec/AbstractModel.java index bf0395d0..ce62e3cd 100644 --- a/src/main/java/ctbrec/AbstractModel.java +++ b/src/main/java/ctbrec/AbstractModel.java @@ -16,6 +16,7 @@ public abstract class AbstractModel implements Model { private String description; private List tags = new ArrayList<>(); private int streamUrlIndex = -1; + private boolean suspended = false; @Override public boolean isOnline() throws IOException, ExecutionException, InterruptedException { @@ -92,6 +93,16 @@ public abstract class AbstractModel implements Model { // noop default implementation, can be overriden by concrete models } + @Override + public boolean isSuspended() { + return suspended; + } + + @Override + public void setSuspended(boolean suspended) { + this.suspended = suspended; + } + @Override public int hashCode() { final int prime = 31; diff --git a/src/main/java/ctbrec/Model.java b/src/main/java/ctbrec/Model.java index 351dda3e..3144f777 100644 --- a/src/main/java/ctbrec/Model.java +++ b/src/main/java/ctbrec/Model.java @@ -38,4 +38,7 @@ public interface Model { public Site getSite(); public void writeSiteSpecificData(JsonWriter writer) throws IOException; public void readSiteSpecificData(JsonReader reader) throws IOException; + public boolean isSuspended(); + public void setSuspended(boolean suspended); + } \ No newline at end of file diff --git a/src/main/java/ctbrec/io/ModelJsonAdapter.java b/src/main/java/ctbrec/io/ModelJsonAdapter.java index 804c77fa..2a900282 100644 --- a/src/main/java/ctbrec/io/ModelJsonAdapter.java +++ b/src/main/java/ctbrec/io/ModelJsonAdapter.java @@ -32,6 +32,7 @@ public class ModelJsonAdapter extends JsonAdapter { String url = null; String type = null; int streamUrlIndex = -1; + boolean suspended = false; Model model = null; while(reader.hasNext()) { @@ -55,6 +56,9 @@ public class ModelJsonAdapter extends JsonAdapter { } else if(key.equals("streamUrlIndex")) { streamUrlIndex = reader.nextInt(); model.setStreamUrlIndex(streamUrlIndex); + } else if(key.equals("suspended")) { + suspended = reader.nextBoolean(); + model.setSuspended(suspended); } else if(key.equals("siteSpecific")) { reader.beginObject(); model.readSiteSpecificData(reader); @@ -87,6 +91,7 @@ public class ModelJsonAdapter extends JsonAdapter { writeValueIfSet(writer, "description", model.getDescription()); writeValueIfSet(writer, "url", model.getUrl()); writer.name("streamUrlIndex").value(model.getStreamUrlIndex()); + writer.name("suspended").value(model.isSuspended()); writer.name("siteSpecific"); writer.beginObject(); model.writeSiteSpecificData(writer); diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index e1916d93..1b8cdf5c 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -112,7 +112,12 @@ public class LocalRecorder implements Recorder { } private void startRecordingProcess(Model model) throws IOException { - LOG.debug("Restart recording for model {}", model.getName()); + if(model.isSuspended()) { + LOG.info("Recording for model {} is suspended.", model); + return; + } + + LOG.debug("Starting recording for model {}", model.getName()); if (recordingProcesses.containsKey(model)) { LOG.error("A recording for model {} is already running", model); return; @@ -315,7 +320,7 @@ public class LocalRecorder implements Recorder { while (running) { for (Model model : getModelsRecording()) { try { - if (!recordingProcesses.containsKey(model)) { + if (!model.isSuspended() && !recordingProcesses.containsKey(model)) { boolean isOnline = model.isOnline(IGNORE_CACHE); LOG.trace("Checking online state for {}: {}", model, (isOnline ? "online" : "offline")); if (isOnline) { @@ -529,4 +534,42 @@ public class LocalRecorder implements Recorder { stopRecordingProcess(model); tryRestartRecording(model); } + + @Override + public void suspendRecording(Model model) { + lock.lock(); + try { + if (models.contains(model)) { + int index = models.indexOf(model); + models.get(index).setSuspended(true); + } else { + return; + } + } finally { + lock.unlock(); + } + + Download download = recordingProcesses.get(model); + if(download != null) { + download.stop(); + recordingProcesses.remove(model); + } + } + + @Override + public void resumeRecording(Model model) throws IOException { + lock.lock(); + try { + if (models.contains(model)) { + int index = models.indexOf(model); + Model m = models.get(index); + m.setSuspended(false); + startRecordingProcess(m); + } else { + return; + } + } finally { + lock.unlock(); + } + } } diff --git a/src/main/java/ctbrec/recorder/Recorder.java b/src/main/java/ctbrec/recorder/Recorder.java index 9effa208..e216f668 100644 --- a/src/main/java/ctbrec/recorder/Recorder.java +++ b/src/main/java/ctbrec/recorder/Recorder.java @@ -28,4 +28,7 @@ public interface Recorder { public void delete(Recording recording) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; public void shutdown(); + + public void suspendRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; + public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; } diff --git a/src/main/java/ctbrec/recorder/RemoteRecorder.java b/src/main/java/ctbrec/recorder/RemoteRecorder.java index 2cb7b216..ec0f22a5 100644 --- a/src/main/java/ctbrec/recorder/RemoteRecorder.java +++ b/src/main/java/ctbrec/recorder/RemoteRecorder.java @@ -88,7 +88,7 @@ public class RemoteRecorder implements Recorder { if("start".equals(action)) { models.add(model); - } else { + } else if("stop".equals(action)) { models.remove(model); } } else { @@ -276,4 +276,14 @@ public class RemoteRecorder implements Recorder { public void switchStreamSource(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { sendRequest("switch", model); } + + @Override + public void suspendRecording(Model model) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, IOException { + sendRequest("suspend", model); + } + + @Override + public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { + sendRequest("resume", model); + } } diff --git a/src/main/java/ctbrec/recorder/server/RecorderServlet.java b/src/main/java/ctbrec/recorder/server/RecorderServlet.java index 4d258906..16a4210f 100644 --- a/src/main/java/ctbrec/recorder/server/RecorderServlet.java +++ b/src/main/java/ctbrec/recorder/server/RecorderServlet.java @@ -112,6 +112,18 @@ public class RecorderServlet extends AbstractCtbrecServlet { response = "{\"status\": \"success\", \"msg\": \"Resolution switched\"}"; resp.getWriter().write(response); break; + case "suspend": + LOG.debug("Suspend recording for model {} - {}", request.model.getName(), request.model.getUrl()); + recorder.suspendRecording(request.model); + response = "{\"status\": \"success\", \"msg\": \"Recording suspended\"}"; + resp.getWriter().write(response); + break; + case "resume": + LOG.debug("Resume recording for model {} - {}", request.model.getName(), request.model.getUrl()); + recorder.resumeRecording(request.model); + response = "{\"status\": \"success\", \"msg\": \"Recording resumed\"}"; + resp.getWriter().write(response); + break; default: resp.setStatus(SC_BAD_REQUEST); response = "{\"status\": \"error\", \"msg\": \"Unknown action\"}"; diff --git a/src/main/java/ctbrec/ui/JavaFxModel.java b/src/main/java/ctbrec/ui/JavaFxModel.java index a1c9d399..2a2ece56 100644 --- a/src/main/java/ctbrec/ui/JavaFxModel.java +++ b/src/main/java/ctbrec/ui/JavaFxModel.java @@ -9,7 +9,6 @@ import com.iheartradio.m3u8.PlaylistException; import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; -import ctbrec.AbstractModel; import ctbrec.Model; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.Site; @@ -19,14 +18,16 @@ import javafx.beans.property.SimpleBooleanProperty; /** * Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly */ -public class JavaFxModel extends AbstractModel { +public class JavaFxModel implements Model { private transient BooleanProperty onlineProperty = new SimpleBooleanProperty(); + private transient BooleanProperty pausedProperty = new SimpleBooleanProperty(); private Model delegate; public JavaFxModel(Model delegate) { this.delegate = delegate; try { onlineProperty.set(delegate.isOnline()); + pausedProperty.set(delegate.isSuspended()); } catch (IOException | ExecutionException | InterruptedException e) {} } @@ -89,6 +90,10 @@ public class JavaFxModel extends AbstractModel { return onlineProperty; } + public BooleanProperty getPausedProperty() { + return pausedProperty; + } + Model getDelegate() { return delegate; } @@ -157,4 +162,35 @@ public class JavaFxModel extends AbstractModel { public void writeSiteSpecificData(JsonWriter writer) throws IOException { delegate.writeSiteSpecificData(writer); } + + @Override + public String getDescription() { + return delegate.getDescription(); + } + + @Override + public void setDescription(String description) { + delegate.setDescription(description); + } + + @Override + public int getStreamUrlIndex() { + return delegate.getStreamUrlIndex(); + } + + @Override + public void setStreamUrlIndex(int streamUrlIndex) { + delegate.setStreamUrlIndex(streamUrlIndex); + } + + @Override + public boolean isSuspended() { + return delegate.isSuspended(); + } + + @Override + public void setSuspended(boolean suspended) { + delegate.setSuspended(suspended); + pausedProperty.set(suspended); + } } diff --git a/src/main/java/ctbrec/ui/RecordedModelsTab.java b/src/main/java/ctbrec/ui/RecordedModelsTab.java index 5a37b229..8702603c 100644 --- a/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -67,7 +67,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { ScrollPane scrollPane = new ScrollPane(); TableView table = new TableView(); ObservableList observableModels = FXCollections.observableArrayList(); - ContextMenu popup = createContextMenu(); + ContextMenu popup; Label modelLabel = new Label("Model"); TextField model = new TextField(); @@ -104,11 +104,17 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { online.setCellValueFactory((cdf) -> cdf.getValue().getOnlineProperty()); online.setCellFactory(CheckBoxTableCell.forTableColumn(online)); online.setPrefWidth(60); - table.getColumns().addAll(name, url, online); + TableColumn paused = new TableColumn<>("Paused"); + paused.setCellValueFactory((cdf) -> cdf.getValue().getPausedProperty()); + paused.setCellFactory(CheckBoxTableCell.forTableColumn(paused)); + paused.setPrefWidth(60); + table.getColumns().addAll(name, url, online, paused); table.setItems(observableModels); table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> { popup = createContextMenu(); - popup.show(table, event.getScreenX(), event.getScreenY()); + if(popup != null) { + popup.show(table, event.getScreenX(), event.getScreenY()); + } event.consume(); }); table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> { @@ -194,6 +200,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { threadPool.submit(() -> { try { javaFxModel.getOnlineProperty().set(javaFxModel.isOnline()); + javaFxModel.setSuspended(model.isSuspended()); } catch (IOException | ExecutionException | InterruptedException e) {} }); } @@ -253,26 +260,37 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { } private ContextMenu createContextMenu() { - MenuItem stop = new MenuItem("Stop Recording"); + JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem(); + if(selectedModel == null) { + return null; + } + MenuItem stop = new MenuItem("Remove Model"); stop.setOnAction((e) -> stopAction()); MenuItem copyUrl = new MenuItem("Copy URL"); copyUrl.setOnAction((e) -> { - Model selected = table.getSelectionModel().getSelectedItem(); + Model selected = selectedModel; final Clipboard clipboard = Clipboard.getSystemClipboard(); final ClipboardContent content = new ClipboardContent(); content.putString(selected.getUrl()); clipboard.setContent(content); }); + MenuItem pauseRecording = new MenuItem("Pause Recording"); + pauseRecording.setOnAction((e) -> pauseRecording()); + MenuItem resumeRecording = new MenuItem("Resume Recording"); + resumeRecording.setOnAction((e) -> resumeRecording()); MenuItem openInBrowser = new MenuItem("Open in Browser"); - openInBrowser.setOnAction((e) -> DesktopIntergation.open(table.getSelectionModel().getSelectedItem().getUrl())); + openInBrowser.setOnAction((e) -> DesktopIntergation.open(selectedModel.getUrl())); MenuItem openInPlayer = new MenuItem("Open in Player"); - openInPlayer.setOnAction((e) -> Player.play(table.getSelectionModel().getSelectedItem().getUrl())); + openInPlayer.setOnAction((e) -> Player.play(selectedModel.getUrl())); MenuItem switchStreamSource = new MenuItem("Switch resolution"); - switchStreamSource.setOnAction((e) -> switchStreamSource(table.getSelectionModel().getSelectedItem())); + switchStreamSource.setOnAction((e) -> switchStreamSource(selectedModel)); - return new ContextMenu(stop, copyUrl, openInBrowser, switchStreamSource); + ContextMenu menu = new ContextMenu(stop); + menu.getItems().add(selectedModel.isSuspended() ? resumeRecording : pauseRecording); + menu.getItems().addAll(copyUrl, openInBrowser, switchStreamSource); + return menu; } private void switchStreamSource(JavaFxModel fxModel) { @@ -345,4 +363,60 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { }.start(); } }; + + private void pauseRecording() { + JavaFxModel model = table.getSelectionModel().getSelectedItem(); + Model delegate = table.getSelectionModel().getSelectedItem().getDelegate(); + if (delegate != null) { + table.setCursor(Cursor.WAIT); + new Thread() { + @Override + public void run() { + try { + recorder.suspendRecording(delegate); + Platform.runLater(() -> model.setSuspended(true)); + } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { + LOG.error("Couldn't pause recording", e1); + Platform.runLater(() -> { + Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText("Couldn't pause recording"); + alert.setContentText("Error while pausing the recording: " + e1.getLocalizedMessage()); + alert.showAndWait(); + }); + } finally { + table.setCursor(Cursor.DEFAULT); + } + } + }.start(); + } + }; + + private void resumeRecording() { + JavaFxModel model = table.getSelectionModel().getSelectedItem(); + Model delegate = table.getSelectionModel().getSelectedItem().getDelegate(); + if (delegate != null) { + table.setCursor(Cursor.WAIT); + new Thread() { + @Override + public void run() { + try { + recorder.resumeRecording(delegate); + Platform.runLater(() -> model.setSuspended(false)); + } catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) { + LOG.error("Couldn't resume recording", e1); + Platform.runLater(() -> { + Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText("Couldn't resume recording"); + alert.setContentText("Error while resuming the recording: " + e1.getLocalizedMessage()); + alert.showAndWait(); + }); + } finally { + table.setCursor(Cursor.DEFAULT); + } + } + }.start(); + } + }; } From 60334e9f0b35b908968b9b153862d88a29f3ec10 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 17:07:40 +0100 Subject: [PATCH 10/40] Update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a025d00..f37025ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +1.7.1 +======================== +* Added possibility to suspend the recording for a model. The model stays in + the list of recorded models, but the actual recording is suspended +* Server can run now run on OpenJRE +* Added JVM parameter to define the configuration directory + (``-Dctbrec.config.dir``) + 1.7.0 ======================== * Added CamSoda From a5ddf4f50989d52925c98e3fa5c96b58a14c3247 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 18:36:28 +0100 Subject: [PATCH 11/40] Remove unnecessary response.close() --- src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java index 5230b736..dce39a23 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -45,7 +45,6 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { Response response = bongaCams.getHttpClient().execute(request); if (response.isSuccessful()) { String content = response.body().string(); - response.close(); List models = new ArrayList<>(); JSONObject json = new JSONObject(content); if(json.optString("status").equals("success")) { From d768cbb1ffaf703b8b49f3579cc3858aafcc7280 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 18:38:20 +0100 Subject: [PATCH 12/40] Implement tipping for bongacams --- .../java/ctbrec/sites/bonga/BongaCams.java | 3 +- .../sites/bonga/BongaCamsHttpClient.java | 66 ++++++++++++++++--- .../ctbrec/sites/bonga/BongaCamsModel.java | 38 ++++++++++- 3 files changed, 94 insertions(+), 13 deletions(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCams.java b/src/main/java/ctbrec/sites/bonga/BongaCams.java index 075c83b0..cead29b9 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCams.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCams.java @@ -61,10 +61,11 @@ public class BongaCams extends AbstractSite { @Override public Integer getTokenBalance() throws IOException { + int userId = ((BongaCamsHttpClient)getHttpClient()).getUserId(); String url = BongaCams.BASE_URL + "/tools/amf.php"; RequestBody body = new FormBody.Builder() .add("method", "ping") - .add("args[]", "66050808") + .add("args[]", Integer.toString(userId)) .build(); Request request = new Request.Builder() .url(url) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index 4a16ff98..c3af3b20 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,6 +27,7 @@ import okio.ByteString; public class BongaCamsHttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsHttpClient.class); + private int userId = 0; @Override public synchronized boolean login() throws IOException { @@ -62,7 +64,12 @@ public class BongaCamsHttpClient extends HttpClient { } loggedIn = checkLoginSuccess(); - createWebSocket(); + if(loggedIn) { + LOG.info("Logged in. User ID is {}", userId); + createWebSocket(); + } else { + LOG.info("Login failed"); + } return loggedIn; } @@ -123,17 +130,20 @@ public class BongaCamsHttpClient extends HttpClient { /** - * check, if the login by sending a ping request + * Check, if the login worked by requesting roomdata and looking * @throws IOException */ private boolean checkLoginSuccess() throws IOException { + String modelName = getAnyModelName(); + // we request the roomData of a random model, because it contains + // user data, if the user is logged in, which we can use to verify, that the login worked String url = BongaCams.BASE_URL + "/tools/amf.php"; RequestBody body = new FormBody.Builder() - // .add("method", "getRoomData") - // .add("args[]", name) - // .add("args[]", "false") - .add("method", "ping") - .add("args[]", "66050808") // TODO where to get the userId + .add("method", "getRoomData") + .add("args[]", modelName) + .add("args[]", "false") + //.add("method", "ping") // TODO alternative request, but + //.add("args[]", ) // where to get the userId .build(); Request request = new Request.Builder() .url(url) @@ -147,8 +157,10 @@ public class BongaCamsHttpClient extends HttpClient { try(Response response = execute(request)) { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); - if(json.optString("status").equals("online")) { - return true; + if(json.optString("status").equals("success")) { + JSONObject userData = json.getJSONObject("userData"); + userId = userData.optInt("userId"); + return userId > 0; } else { throw new IOException("Request was not successful: " + json.toString(2)); } @@ -158,6 +170,36 @@ public class BongaCamsHttpClient extends HttpClient { } } + /** + * Fetches the list of online models and returns the name of the first model + */ + private String getAnyModelName() throws IOException { + Request request = new Request.Builder() + .url(BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=female&online_only=true&is_mobile=true&offset=0") + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", BongaCams.BASE_URL) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + try(Response response = execute(request)) { + if (response.isSuccessful()) { + String content = response.body().string(); + JSONObject json = new JSONObject(content); + if(json.optString("status").equals("success")) { + JSONArray _models = json.getJSONArray("models"); + JSONObject m = _models.getJSONObject(0); + String name = m.getString("username"); + return name; + } else { + throw new IOException("Request was not successful: " + content); + } + } else { + throw new IOException(response.code() + ' ' + response.message()); + } + } + } + private void transferCookies(BongaCamsLoginDialog loginDialog) { HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl()); List cookies = new ArrayList<>(); @@ -213,4 +255,10 @@ public class BongaCamsHttpClient extends HttpClient { // } // } + public int getUserId() throws IOException { + if(userId == 0) { + login(); + } + return userId; + } } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index 5126b44a..2f29563b 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -113,7 +113,6 @@ public class BongaCamsModel extends AbstractModel { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); if(json.optString("status").equals("success")) { - System.out.println(json.toString(2)); JSONObject localData = json.getJSONObject("localData"); String server = localData.getString("videoServerUrl"); return "https:" + server + "/hls/stream_" + getName() + "/playlist.m3u8"; @@ -134,8 +133,41 @@ public class BongaCamsModel extends AbstractModel { @Override public void receiveTip(int tokens) throws IOException { - // TODO Auto-generated method stub - + // method=tipModel&args[]=Sweetsexbia&args[]=1&args[]=66050808&args[3]=&_csrf_token=dd304a3876025127cc487e71d44a5843 + String url = BongaCams.BASE_URL + "/chat-ajax-amf-service?" + System.currentTimeMillis(); + int userId = ((BongaCamsHttpClient)site.getHttpClient()).getUserId(); + RequestBody body = new FormBody.Builder() + .add("method", "tipModel") + .add("args[]", getName()) + .add("args[]", Integer.toString(tokens)) + .add("args[]", Integer.toString(userId)) + .add("args[3]", "") + .build(); + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", BongaCams.BASE_URL + '/' + getName()) + .addHeader("X-Requested-With", "XMLHttpRequest") + .post(body) + .build(); + try(Response response = site.getHttpClient().execute(request, true)) { + if(response.isSuccessful()) { + // { + // "dataKey": "d40f579faf592324c1b0b97bd711039f", + // "amount": "1", + // "balance": 11, + // "actionKey": "b60f780c472e83b95167efe9bc9512bf", + // "description": "Sie haben erfolgreich Sweetsexbia 1 Token Trinkgeld gegeben!", + // "status": "success" + // } + JSONObject json = new JSONObject(response.body().string()); + System.out.println(json.toString(2)); + } else { + throw new IOException(response.code() + ' ' + response.message()); + } + } } @Override From f8f0d5082efaeea00ac144ede734d86ad293e89d Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 18:56:21 +0100 Subject: [PATCH 13/40] Add friends tab to BongaCams --- src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java | 5 +++++ src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java index 50a6bd63..5a5bf55a 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java @@ -49,6 +49,11 @@ public class BongaCamsTabProvider extends TabProvider { updateService = new BongaCamsUpdateService(bongaCams, url); tabs.add(createTab("New", updateService)); + // friends + url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=friends&online_only=true&offset="; + updateService = new BongaCamsUpdateService(bongaCams, url); + tabs.add(createTab("Friends", updateService)); + return tabs; } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java index dce39a23..e1f19274 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -33,7 +33,7 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { @Override public List call() throws IOException { String _url = url + ((page-1) * 50); - LOG.debug("Fetching page {}", url); + LOG.debug("Fetching page {}", _url); Request request = new Request.Builder() .url(_url) .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") From f15b57ce9ab3cc96a9e459273c48d7189e81486c Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 18:56:31 +0100 Subject: [PATCH 14/40] Remove websocket stuff --- .../sites/bonga/BongaCamsHttpClient.java | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index c3af3b20..6d0a23dd 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -20,9 +20,6 @@ import okhttp3.HttpUrl; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; -import okhttp3.WebSocket; -import okhttp3.WebSocketListener; -import okio.ByteString; public class BongaCamsHttpClient extends HttpClient { @@ -66,69 +63,12 @@ public class BongaCamsHttpClient extends HttpClient { loggedIn = checkLoginSuccess(); if(loggedIn) { LOG.info("Logged in. User ID is {}", userId); - createWebSocket(); } else { LOG.info("Login failed"); } return loggedIn; } - private void createWebSocket() { - // $.noticeSocket = new SocketAdapter('wss://notice.bcrncdn.com:443/ws'); - // $.noticeSocket.onopen = function(){ - // this.send({type: 'identify', data: '0387db666178a863395c49f5f912cf070055482716514804'}); - // $(document).trigger('onNoticeSocketOpen'); - // }; - // $.noticeSocket.onmessage = function(e){$(document).trigger('onNoticeSocketMessage', [e])}; - // $(function() { - // window.setTimeout($.checkAuth, 3600 * 5 * 1000); - // if ($('#email_confirmed_popup').length > 0) { - // $('#email_confirmed_popup').show(); - // setTimeout(function() { $('#email_confirmed_popup').fadeOut('fast');}, 5000); - // } - // }); - Request req = new Request.Builder() - .url("wss://notice.bcrncdn.com:443/ws") - .build(); - LOG.debug("Creating websocket"); - WebSocket ws = super.client.newWebSocket(req, new WebSocketListener() { - @Override - public void onOpen(WebSocket webSocket, Response response) { - super.onOpen(webSocket, response); - try { - LOG.trace("open: [{}]", response.body().string()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void onClosed(WebSocket webSocket, int code, String reason) { - super.onClosed(webSocket, code, reason); - LOG.info("Bonga websocket closed: {} {}", code, reason); - } - - @Override - public void onFailure(WebSocket webSocket, Throwable t, Response response) { - super.onFailure(webSocket, t, response); - LOG.error("Bonga websocket failure: {} {}", response.code(), response.message(), t); - } - - @Override - public void onMessage(WebSocket webSocket, String text) { - super.onMessage(webSocket, text); - LOG.debug("onMessage {}", text); - } - - @Override - public void onMessage(WebSocket webSocket, ByteString bytes) { - super.onMessage(webSocket, bytes); - LOG.debug("msgb: {}", bytes.hex()); - } - }); - } - - /** * Check, if the login worked by requesting roomdata and looking * @throws IOException From a136c9ccd23be1df1dda86d19e9f24f33041ae12 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 19:32:21 +0100 Subject: [PATCH 15/40] Prepare code to persist http cookies Save and reload the cookies might help to avoid logins between sessions. --- src/main/java/ctbrec/io/CookieJarImpl.java | 13 +++ .../java/ctbrec/io/CookieJsonAdapter.java | 79 +++++++++++++++++++ src/main/java/ctbrec/io/HttpClient.java | 28 ++++++- .../recorder/server/RecorderHttpClient.java | 4 + .../sites/bonga/BongaCamsHttpClient.java | 4 + .../ctbrec/sites/cam4/Cam4HttpClient.java | 4 + .../sites/camsoda/CamsodaHttpClient.java | 4 + .../chaturbate/ChaturbateHttpClient.java | 4 + .../sites/mfc/MyFreeCamsHttpClient.java | 4 + .../java/ctbrec/ui/CamrecApplication.java | 2 +- 10 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ctbrec/io/CookieJsonAdapter.java diff --git a/src/main/java/ctbrec/io/CookieJarImpl.java b/src/main/java/ctbrec/io/CookieJarImpl.java index 712ff30c..8baf7a25 100644 --- a/src/main/java/ctbrec/io/CookieJarImpl.java +++ b/src/main/java/ctbrec/io/CookieJarImpl.java @@ -4,6 +4,8 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Optional; @@ -78,5 +80,16 @@ public class CookieJarImpl implements CookieJar { return host; } + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Entry> entry : cookieStore.entrySet()) { + sb.append(entry.getKey()).append(": ").append(entry.getValue()).append('\n'); + } + return sb.toString(); + } + protected Map> getCookies() { + return cookieStore; + } } diff --git a/src/main/java/ctbrec/io/CookieJsonAdapter.java b/src/main/java/ctbrec/io/CookieJsonAdapter.java new file mode 100644 index 00000000..c94df9b6 --- /dev/null +++ b/src/main/java/ctbrec/io/CookieJsonAdapter.java @@ -0,0 +1,79 @@ +package ctbrec.io; + +import java.io.IOException; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; + +import okhttp3.Cookie; +import okhttp3.Cookie.Builder; + +public class CookieJsonAdapter extends JsonAdapter { + + @Override + public Cookie fromJson(JsonReader reader) throws IOException { + Builder builder = new Cookie.Builder(); + // domain + reader.nextName(); + String domain = reader.nextString(); + builder.domain(domain); + + // expiresAt + reader.nextName(); + builder.expiresAt(reader.nextLong()); + + // host only + reader.nextName(); + if(reader.nextBoolean()) { + builder.hostOnlyDomain(domain); + } + + // http only + reader.nextName(); + if(reader.nextBoolean()) { + builder.httpOnly(); + } + + // name + reader.nextName(); + builder.name(reader.nextString()); + + // path + reader.nextName(); + builder.path(reader.nextString()); + + // persistent + reader.nextName(); + if(reader.nextBoolean()) { + // noop + } + + // secure + reader.nextName(); + if(reader.nextBoolean()) { + builder.secure(); + } + + // value + reader.nextName(); + builder.value(reader.nextString()); + + return builder.build(); + } + + @Override + public void toJson(JsonWriter writer, Cookie cookie) throws IOException { + writer.beginObject(); + writer.name("domain").value(cookie.domain()); + writer.name("expiresAt").value(cookie.expiresAt()); + writer.name("hostOnly").value(cookie.hostOnly()); + writer.name("httpOnly").value(cookie.httpOnly()); + writer.name("name").value(cookie.name()); + writer.name("path").value(cookie.path()); + writer.name("persistent").value(cookie.persistent()); + writer.name("secure").value(cookie.secure()); + writer.name("value").value(cookie.value()); + writer.endObject(); + } +} diff --git a/src/main/java/ctbrec/io/HttpClient.java b/src/main/java/ctbrec/io/HttpClient.java index 02c8f818..843d65a4 100644 --- a/src/main/java/ctbrec/io/HttpClient.java +++ b/src/main/java/ctbrec/io/HttpClient.java @@ -3,11 +3,20 @@ package ctbrec.io; import java.io.IOException; import java.net.Authenticator; import java.net.PasswordAuthentication; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + import ctbrec.Config; import ctbrec.Settings.ProxyType; import okhttp3.ConnectionPool; +import okhttp3.Cookie; import okhttp3.Credentials; import okhttp3.OkHttpClient; import okhttp3.OkHttpClient.Builder; @@ -16,12 +25,16 @@ import okhttp3.Response; import okhttp3.Route; public abstract class HttpClient { + private static final transient Logger LOG = LoggerFactory.getLogger(HttpClient.class); + protected OkHttpClient client; protected CookieJarImpl cookieJar = new CookieJarImpl(); protected boolean loggedIn = false; protected int loginTries = 0; + private String name; - protected HttpClient() { + protected HttpClient(String name) { + this.name = name; reconfigure(); } @@ -112,10 +125,23 @@ public abstract class HttpClient { } public void shutdown() { + persistCookies(); client.connectionPool().evictAll(); client.dispatcher().executorService().shutdown(); } + private void persistCookies() { + try { + Map> cookies = cookieJar.getCookies(); + Moshi moshi = new Moshi.Builder().add(Cookie.class, new CookieJsonAdapter()).build(); + @SuppressWarnings("rawtypes") + JsonAdapter adapter = moshi.adapter(Map.class).indent(" "); + String json = adapter.toJson(cookies); + } catch (Exception e) { + LOG.error("Couldn't persist cookies for {}", name, e); + } + } + private okhttp3.Authenticator createHttpProxyAuthenticator(String username, String password) { return new okhttp3.Authenticator() { @Override diff --git a/src/main/java/ctbrec/recorder/server/RecorderHttpClient.java b/src/main/java/ctbrec/recorder/server/RecorderHttpClient.java index 22027ba9..7b3170e7 100644 --- a/src/main/java/ctbrec/recorder/server/RecorderHttpClient.java +++ b/src/main/java/ctbrec/recorder/server/RecorderHttpClient.java @@ -6,6 +6,10 @@ import ctbrec.io.HttpClient; public class RecorderHttpClient extends HttpClient { + public RecorderHttpClient() { + super("recorder"); + } + @Override public boolean login() throws IOException { return false; diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index 6d0a23dd..26e08381 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -26,6 +26,10 @@ public class BongaCamsHttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsHttpClient.class); private int userId = 0; + public BongaCamsHttpClient() { + super("bongacams"); + } + @Override public synchronized boolean login() throws IOException { if(loggedIn) { diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java index 3a40c555..f6434401 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -23,6 +23,10 @@ public class Cam4HttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(Cam4HttpClient.class); + public Cam4HttpClient() { + super("cam4"); + } + @Override public synchronized boolean login() throws IOException { if(loggedIn) { diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java b/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java index 9ab57b9e..b6148863 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java @@ -29,6 +29,10 @@ public class CamsodaHttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(CamsodaHttpClient.class); private String csrfToken = null; + public CamsodaHttpClient() { + super("camsoda"); + } + @Override public boolean login() throws IOException { if(loggedIn) { diff --git a/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java b/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java index 33cdbcd8..06ce6262 100644 --- a/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java +++ b/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java @@ -20,6 +20,10 @@ public class ChaturbateHttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateHttpClient.class); protected String token; + public ChaturbateHttpClient() { + super("chaturbate"); + } + private void extractCsrfToken(Request request) { try { Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken"); diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java index 762d3dd6..7d49237b 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java @@ -24,6 +24,10 @@ public class MyFreeCamsHttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsHttpClient.class); + public MyFreeCamsHttpClient() { + super("myfreecams"); + } + @Override public boolean login() throws IOException { if(loggedIn) { diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java index 59faff75..5856873e 100644 --- a/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/src/main/java/ctbrec/ui/CamrecApplication.java @@ -200,7 +200,7 @@ public class CamrecApplication extends Application { } private void createHttpClient() { - httpClient = new HttpClient() { + httpClient = new HttpClient("camrec") { @Override public boolean login() throws IOException { return false; From 1fc22876fd7c487af66182e6ea6a9162c2cfd295 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 19:43:15 +0100 Subject: [PATCH 16/40] Reduce size of received message history to save memory --- src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index 17191f65..563c72ec 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -45,6 +45,8 @@ public class MyFreeCamsClient { private Moshi moshi; private volatile boolean running = false; + // TODO use caches for sessionStates and models to avoid OOME + // maybe write own eviction (if possible) to evict all offline models private Map sessionStates = new HashMap<>(); private Map models = new HashMap<>(); private Lock lock = new ReentrantLock(); @@ -59,7 +61,7 @@ public class MyFreeCamsClient { private int sessionId; private long heartBeat; - private EvictingQueue receivedTextHistory = EvictingQueue.create(10000); + private EvictingQueue receivedTextHistory = EvictingQueue.create(100); private MyFreeCamsClient() { moshi = new Moshi.Builder().build(); From 43793f37282e7ccd6b58f22ca2230e9b379b1400 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 20:27:07 +0100 Subject: [PATCH 17/40] Use guava caches to store SessionStates and models Use caches with a maximum size of 4000 elements instead of maps to avoid running out of heap memory --- .../ctbrec/sites/mfc/MyFreeCamsClient.java | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index 563c72ec..40f228a2 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -7,9 +7,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -23,6 +21,8 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.common.collect.EvictingQueue; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.Moshi; @@ -45,10 +45,8 @@ public class MyFreeCamsClient { private Moshi moshi; private volatile boolean running = false; - // TODO use caches for sessionStates and models to avoid OOME - // maybe write own eviction (if possible) to evict all offline models - private Map sessionStates = new HashMap<>(); - private Map models = new HashMap<>(); + private Cache sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build(); + private Cache models = CacheBuilder.newBuilder().maximumSize(4000).build(); private Lock lock = new ReentrantLock(); private ExecutorService executor = Executors.newSingleThreadExecutor(); private ServerConfig serverConfig; @@ -120,7 +118,7 @@ public class MyFreeCamsClient { lock.lock(); try { LOG.trace("Models: {}", models.size()); - return new ArrayList<>(this.models.values()); + return new ArrayList<>(this.models.asMap().values()); } finally { lock.unlock(); } @@ -210,7 +208,7 @@ public class MyFreeCamsClient { JSONObject json = new JSONObject(message.getMessage()); String[] names = JSONObject.getNames(json); Integer uid = Integer.parseInt(names[0]); - SessionState sessionState = sessionStates.get(uid); + SessionState sessionState = sessionStates.getIfPresent(uid); if (sessionState != null) { JSONArray tags = json.getJSONArray(names[0]); for (Object obj : tags) { @@ -360,7 +358,7 @@ public class MyFreeCamsClient { if (newState.getUid() <= 0) { return; } - SessionState storedState = sessionStates.get(newState.getUid()); + SessionState storedState = sessionStates.getIfPresent(newState.getUid()); if (storedState != null) { storedState.merge(newState); updateModel(storedState); @@ -386,7 +384,7 @@ public class MyFreeCamsClient { return; } - MyFreeCamsModel model = models.get(state.getUid()); + MyFreeCamsModel model = models.getIfPresent(state.getUid()); if(model == null) { model = mfc.createModel(state.getNm()); model.setUid(state.getUid()); @@ -496,7 +494,7 @@ public class MyFreeCamsClient { public void update(MyFreeCamsModel model) { lock.lock(); try { - for (SessionState state : sessionStates.values()) { + for (SessionState state : sessionStates.asMap().values()) { if(Objects.equals(state.getNm(), model.getName()) || Objects.equals(model.getUid(), state.getUid())) { model.update(state, getStreamUrl(state)); return; @@ -534,7 +532,7 @@ public class MyFreeCamsClient { } public MyFreeCamsModel getModel(int uid) { - return models.get(uid); + return models.getIfPresent(uid); } public void execute(Runnable r) { @@ -542,7 +540,7 @@ public class MyFreeCamsClient { } public void getSessionState(ctbrec.Model model) { - for (SessionState state : sessionStates.values()) { + for (SessionState state : sessionStates.asMap().values()) { if(Objects.equals(state.getNm(), model.getName())) { JsonAdapter adapter = moshi.adapter(SessionState.class).indent(" "); System.out.println(adapter.toJson(state)); From b73627c0fb4ee0492db932e185f8526d4dd7990c Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 20:36:09 +0100 Subject: [PATCH 18/40] Put settings tab into ScrollPane ... to support small screens --- src/main/java/ctbrec/ui/SettingsTab.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index 6b8dc752..2c9bb604 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -28,6 +28,7 @@ import javafx.scene.control.CheckBox; import javafx.scene.control.ComboBox; import javafx.scene.control.Label; import javafx.scene.control.RadioButton; +import javafx.scene.control.ScrollPane; import javafx.scene.control.Tab; import javafx.scene.control.TextField; import javafx.scene.control.TextInputDialog; @@ -89,7 +90,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { ColumnConstraints cc = new ColumnConstraints(); cc.setPercentWidth(50); mainLayout.getColumnConstraints().setAll(cc, cc); - setContent(mainLayout); + setContent(new ScrollPane(mainLayout)); VBox leftSide = new VBox(15); VBox rightSide = new VBox(15); GridPane.setHgrow(leftSide, Priority.ALWAYS); From 75351cedb52f5f6a532831e3c5167194ee5b67a1 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 21:54:11 +0100 Subject: [PATCH 19/40] Cookies are now persisted in the config dir --- src/main/java/ctbrec/Config.java | 4 ++ .../ctbrec/io/CookieContainerJsonAdapter.java | 64 +++++++++++++++++++ .../java/ctbrec/io/CookieJsonAdapter.java | 2 + src/main/java/ctbrec/io/HttpClient.java | 53 +++++++++++++-- 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 src/main/java/ctbrec/io/CookieContainerJsonAdapter.java diff --git a/src/main/java/ctbrec/Config.java b/src/main/java/ctbrec/Config.java index de2aaf2b..c421d96f 100644 --- a/src/main/java/ctbrec/Config.java +++ b/src/main/java/ctbrec/Config.java @@ -101,4 +101,8 @@ public class Config { public boolean isServerMode() { return Objects.equals(System.getProperty("ctbrec.server.mode"), "1"); } + + public File getConfigDir() { + return configDir; + } } diff --git a/src/main/java/ctbrec/io/CookieContainerJsonAdapter.java b/src/main/java/ctbrec/io/CookieContainerJsonAdapter.java new file mode 100644 index 00000000..9498e85d --- /dev/null +++ b/src/main/java/ctbrec/io/CookieContainerJsonAdapter.java @@ -0,0 +1,64 @@ +package ctbrec.io; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonReader.Token; +import com.squareup.moshi.JsonWriter; + +import ctbrec.io.HttpClient.CookieContainer; +import okhttp3.Cookie; + +public class CookieContainerJsonAdapter extends JsonAdapter { + + private CookieJsonAdapter cookieAdapter = new CookieJsonAdapter(); + + @Override + public CookieContainer fromJson(JsonReader reader) throws IOException { + CookieContainer cookies = new CookieContainer(); + reader.beginArray(); + while(reader.hasNext()) { + reader.beginObject(); + reader.nextName(); // "domain" + String domain = reader.nextString(); + reader.nextName(); // "cookies" + reader.beginArray(); + List cookieList = new ArrayList<>(); + while(reader.hasNext()) { + Token token = reader.peek(); + if(token == Token.END_ARRAY) { + break; + } + Cookie cookie = cookieAdapter.fromJson(reader); + cookieList.add(cookie); + } + reader.endArray(); + reader.endObject(); + cookies.put(domain, cookieList); + } + reader.endArray(); + return cookies; + } + + @Override + public void toJson(JsonWriter writer, CookieContainer cookieContainer) throws IOException { + writer.beginArray(); + for (Entry> entry : cookieContainer.entrySet()) { + writer.beginObject(); + writer.name("domain").value(entry.getKey()); + writer.name("cookies"); + writer.beginArray(); + for (Cookie cookie : entry.getValue()) { + cookieAdapter.toJson(writer, cookie); + } + writer.endArray(); + writer.endObject(); + } + writer.endArray(); + } + +} diff --git a/src/main/java/ctbrec/io/CookieJsonAdapter.java b/src/main/java/ctbrec/io/CookieJsonAdapter.java index c94df9b6..fa549814 100644 --- a/src/main/java/ctbrec/io/CookieJsonAdapter.java +++ b/src/main/java/ctbrec/io/CookieJsonAdapter.java @@ -13,6 +13,7 @@ public class CookieJsonAdapter extends JsonAdapter { @Override public Cookie fromJson(JsonReader reader) throws IOException { + reader.beginObject(); Builder builder = new Cookie.Builder(); // domain reader.nextName(); @@ -59,6 +60,7 @@ public class CookieJsonAdapter extends JsonAdapter { reader.nextName(); builder.value(reader.nextString()); + reader.endObject(); return builder.build(); } diff --git a/src/main/java/ctbrec/io/HttpClient.java b/src/main/java/ctbrec/io/HttpClient.java index 843d65a4..f1afed9b 100644 --- a/src/main/java/ctbrec/io/HttpClient.java +++ b/src/main/java/ctbrec/io/HttpClient.java @@ -1,10 +1,16 @@ package ctbrec.io; +import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.net.Authenticator; import java.net.PasswordAuthentication; +import java.nio.file.Files; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; @@ -105,6 +111,7 @@ public abstract class HttpClient { public void reconfigure() { loadProxySettings(); + loadCookies(); Builder builder = new OkHttpClient.Builder() .cookieJar(cookieJar) .connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS) @@ -132,16 +139,54 @@ public abstract class HttpClient { private void persistCookies() { try { - Map> cookies = cookieJar.getCookies(); - Moshi moshi = new Moshi.Builder().add(Cookie.class, new CookieJsonAdapter()).build(); - @SuppressWarnings("rawtypes") - JsonAdapter adapter = moshi.adapter(Map.class).indent(" "); + CookieContainer cookies = new CookieContainer(); + cookies.putAll(cookieJar.getCookies()); + Moshi moshi = new Moshi.Builder() + .add(CookieContainer.class, new CookieContainerJsonAdapter()) + .build(); + JsonAdapter adapter = moshi.adapter(CookieContainer.class).indent(" "); String json = adapter.toJson(cookies); + + File cookieFile = new File(Config.getInstance().getConfigDir(), "cookies-" + name + ".json"); + try(FileOutputStream fout = new FileOutputStream(cookieFile)) { + fout.write(json.getBytes("utf-8")); + } } catch (Exception e) { LOG.error("Couldn't persist cookies for {}", name, e); } } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private void loadCookies() { + try { + File cookieFile = new File(Config.getInstance().getConfigDir(), "cookies-" + name + ".json"); + if(!cookieFile.exists()) { + return; + } + byte[] jsonBytes = Files.readAllBytes(cookieFile.toPath()); + String json = new String(jsonBytes, "utf-8"); + + Map> cookies = cookieJar.getCookies(); + Moshi moshi = new Moshi.Builder() + .add(CookieContainer.class, new CookieContainerJsonAdapter()) + .build(); + JsonAdapter adapter = moshi.adapter(CookieContainer.class).indent(" "); + CookieContainer fromJson = adapter.fromJson(json); + Set entries = fromJson.entrySet(); + for (Object _entry : entries) { + Entry entry = (Entry) _entry; + cookies.put((String)entry.getKey(), (List)entry.getValue()); + } + + } catch (Exception e) { + LOG.error("Couldn't load cookies for {}", name, e); + } + } + + public static class CookieContainer extends HashMap> { + + } + private okhttp3.Authenticator createHttpProxyAuthenticator(String username, String password) { return new okhttp3.Authenticator() { @Override From 1b11af8872fbd6d0c27f0a3a09e000f9490118ee Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 22:17:30 +0100 Subject: [PATCH 20/40] Try to restore HTTP session with cookies before login in All the HTTP clients now check, if loading the cookies was sufficient to restore the HTTP session. If yes, we assume that we are logged in. If not, the normal login procedure is done. --- .../sites/bonga/BongaCamsHttpClient.java | 7 +++++ .../ctbrec/sites/cam4/Cam4HttpClient.java | 7 +++++ .../sites/camsoda/CamsodaHttpClient.java | 7 +++++ .../chaturbate/ChaturbateHttpClient.java | 28 +++++++++++++++++++ .../sites/mfc/MyFreeCamsHttpClient.java | 27 ++++++++++++++++++ 5 files changed, 76 insertions(+) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index 26e08381..80f4c303 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -36,6 +36,13 @@ public class BongaCamsHttpClient extends HttpClient { return true; } + boolean cookiesWorked = checkLoginSuccess(); + if(cookiesWorked) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + BlockingQueue queue = new LinkedBlockingQueue<>(); Runnable showDialog = () -> { diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java index f6434401..968d1de4 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -33,6 +33,13 @@ public class Cam4HttpClient extends HttpClient { return true; } + boolean cookiesWorked = checkLoginSuccess(); + if(cookiesWorked) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + BlockingQueue queue = new LinkedBlockingQueue<>(); Runnable showDialog = () -> { diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java b/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java index b6148863..fbb90d6c 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java @@ -39,6 +39,13 @@ public class CamsodaHttpClient extends HttpClient { return true; } + // persisted cookies might let us log in + if(checkLoginSuccess()) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + String url = Camsoda.BASE_URI + "/api/v1/auth/login"; FormBody body = new FormBody.Builder() .add("username", Config.getInstance().getSettings().camsodaUsername) diff --git a/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java b/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java index 06ce6262..58e1f059 100644 --- a/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java +++ b/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java @@ -42,6 +42,16 @@ public class ChaturbateHttpClient extends HttpClient { @Override public boolean login() throws IOException { + if(loggedIn) { + return true; + } + + if(checkLogin()) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + try { Request login = new Request.Builder() .url(Chaturbate.BASE_URI + "/auth/login/") @@ -86,6 +96,24 @@ public class ChaturbateHttpClient extends HttpClient { return loggedIn; } + private boolean checkLogin() throws IOException { + String url = "https://chaturbate.com/p/" + Config.getInstance().getSettings().username + "/"; + Request req = new Request.Builder().url(url).build(); + Response resp = execute(req); + if (resp.isSuccessful()) { + String profilePage = resp.body().string(); + try { + HtmlParser.getText(profilePage, "span.tokencount"); + return true; + } catch(Exception e) { + LOG.debug("Token tag not found. Login failed"); + return false; + } + } else { + throw new IOException("HTTP response: " + resp.code() + " - " + resp.message()); + } + } + @Override public Response execute(Request req, boolean requiresLogin) throws IOException { Response resp = super.execute(req, requiresLogin); diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java index 7d49237b..62c6229c 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsHttpClient.java @@ -5,11 +5,13 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; +import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ctbrec.Config; import ctbrec.io.HttpClient; +import ctbrec.ui.HtmlParser; import okhttp3.Cookie; import okhttp3.CookieJar; import okhttp3.FormBody; @@ -34,6 +36,12 @@ public class MyFreeCamsHttpClient extends HttpClient { return true; } + if(checkLogin()) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + String username = Config.getInstance().getSettings().mfcUsername; String password = Config.getInstance().getSettings().mfcPassword; RequestBody body = new FormBody.Builder() @@ -65,6 +73,25 @@ public class MyFreeCamsHttpClient extends HttpClient { } } + private boolean checkLogin() throws IOException { + Request req = new Request.Builder().url(MyFreeCams.BASE_URI + "/php/account.php?request=status").build(); + Response resp = execute(req); + if(resp.isSuccessful()) { + String content = resp.body().string(); + try { + Elements tags = HtmlParser.getTags(content, "div.content > p > b"); + tags.get(2).text(); + return true; + } catch(Exception e) { + LOG.debug("Token tag not found. Login failed"); + return false; + } + } else { + resp.close(); + throw new IOException(resp.code() + " " + resp.message()); + } + } + public WebSocket newWebSocket(Request req, WebSocketListener webSocketListener) { return client.newWebSocket(req, webSocketListener); } From 091628b4866ab1534030926accbcc874dc5bffeb Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 22:36:02 +0100 Subject: [PATCH 21/40] Check, if tipping was successful --- .../java/ctbrec/sites/bonga/BongaCamsModel.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index 2f29563b..463b4643 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -127,13 +127,11 @@ public class BongaCamsModel extends AbstractModel { @Override public void invalidateCacheEntries() { - // TODO Auto-generated method stub - + resolution = null; } @Override public void receiveTip(int tokens) throws IOException { - // method=tipModel&args[]=Sweetsexbia&args[]=1&args[]=66050808&args[3]=&_csrf_token=dd304a3876025127cc487e71d44a5843 String url = BongaCams.BASE_URL + "/chat-ajax-amf-service?" + System.currentTimeMillis(); int userId = ((BongaCamsHttpClient)site.getHttpClient()).getUserId(); RequestBody body = new FormBody.Builder() @@ -154,16 +152,11 @@ public class BongaCamsModel extends AbstractModel { .build(); try(Response response = site.getHttpClient().execute(request, true)) { if(response.isSuccessful()) { - // { - // "dataKey": "d40f579faf592324c1b0b97bd711039f", - // "amount": "1", - // "balance": 11, - // "actionKey": "b60f780c472e83b95167efe9bc9512bf", - // "description": "Sie haben erfolgreich Sweetsexbia 1 Token Trinkgeld gegeben!", - // "status": "success" - // } JSONObject json = new JSONObject(response.body().string()); - System.out.println(json.toString(2)); + if(!json.optString("status").equals("success")) { + LOG.error("Sending tip failed {}", json.toString(2)); + throw new IOException("Sending tip failed"); + } } else { throw new IOException(response.code() + ' ' + response.message()); } From 2a5c0ccd43bbfbd46918a15b3eea8c705192d54f Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 23:15:44 +0100 Subject: [PATCH 22/40] Set offset to be a multiple of 36 The server returns lists of 36 models, so we have to align our offset to that. --- src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java index e1f19274..9a230f1e 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -32,7 +32,7 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { return new Task>() { @Override public List call() throws IOException { - String _url = url + ((page-1) * 50); + String _url = url + ((page-1) * 36); LOG.debug("Fetching page {}", _url); Request request = new Request.Builder() .url(_url) From a2f048fe0dbd30495e57a3b8823c10e045d6f336 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 6 Nov 2018 23:17:01 +0100 Subject: [PATCH 23/40] Add a cookie to define the sorting for BongaCams The cookie is set to sort by popularity --- src/main/java/ctbrec/io/CookieJarImpl.java | 2 +- .../sites/bonga/BongaCamsHttpClient.java | 27 +++++++++++++++++++ .../ctbrec/sites/bonga/BongaCamsModel.java | 2 +- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/io/CookieJarImpl.java b/src/main/java/ctbrec/io/CookieJarImpl.java index 8baf7a25..deaaab2c 100644 --- a/src/main/java/ctbrec/io/CookieJarImpl.java +++ b/src/main/java/ctbrec/io/CookieJarImpl.java @@ -89,7 +89,7 @@ public class CookieJarImpl implements CookieJar { return sb.toString(); } - protected Map> getCookies() { + public Map> getCookies() { return cookieStore; } } diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index 80f4c303..165ef4ea 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -3,7 +3,10 @@ package ctbrec.sites.bonga; import java.io.IOException; import java.net.HttpCookie; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -28,6 +31,30 @@ public class BongaCamsHttpClient extends HttpClient { public BongaCamsHttpClient() { super("bongacams"); + addSortByPopularCookie(); + } + + /** + * Adds a cookie, which defines the sort order for returned model lists + */ + private void addSortByPopularCookie() { + Cookie sortByCookie = new Cookie.Builder() + .domain("bongacams.com") + .name("bcmlsf9") + .value("%7B%22limit%22%3A20%2C%22c_limit%22%3A10%2C%22th_type%22%3A%22live%22%2C%22sorting%22%3A%22popular%22%2C%22display%22%3A%22auto%22%7D") + .build(); + + Map> cookies = cookieJar.getCookies(); + for (Entry> entry : cookies.entrySet()) { + List cookieList = entry.getValue(); + for (Iterator iterator = cookieList.iterator(); iterator.hasNext();) { + Cookie cookie = iterator.next(); + if(cookie.name().equals("bcmlsf9")) { + iterator.remove(); + } + } + entry.getValue().add(sortByCookie); + } } @Override diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index 463b4643..f4bd92e4 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -175,7 +175,7 @@ public class BongaCamsModel extends AbstractModel { StreamSource best = streamSources.get(streamSources.size()-1); resolution = new int[] {best.width, best.height}; } catch (ExecutionException | IOException | ParseException | PlaylistException e) { - LOG.error("Couldn't determine stream resolution", e); + LOG.error("Couldn't determine stream resolution for {}", getName(), e); } return resolution; } else { From ba0cc591d38f34713860527f3d6e9b087fb13460 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 13:49:56 +0100 Subject: [PATCH 24/40] Add setting for HTTP User-Agent header Replace all occurences of the User-Agent header with the user-agent string from the settings --- src/main/java/ctbrec/Settings.java | 1 + src/main/java/ctbrec/sites/bonga/BongaCams.java | 2 +- src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java | 7 ++++--- src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java | 1 + src/main/java/ctbrec/sites/bonga/BongaCamsModel.java | 5 +++-- .../java/ctbrec/sites/bonga/BongaCamsUpdateService.java | 3 ++- src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java | 1 + src/main/java/ctbrec/sites/camsoda/CamsodaModel.java | 7 ++++--- src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java | 3 ++- 9 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index 96e878bc..a92327a4 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -17,6 +17,7 @@ public class Settings { public boolean localRecording = true; public int httpPort = 8080; public int httpTimeout = 10000; + public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/62.0"; public String httpServer = "localhost"; public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec"; public String mediaPlayer = "/usr/bin/mpv"; diff --git a/src/main/java/ctbrec/sites/bonga/BongaCams.java b/src/main/java/ctbrec/sites/bonga/BongaCams.java index cead29b9..b518c3ff 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCams.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCams.java @@ -69,7 +69,7 @@ public class BongaCams extends AbstractSite { .build(); Request request = new Request.Builder() .url(url) - .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", BongaCams.BASE_URL) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java index 165ef4ea..4606f830 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -15,6 +15,7 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ctbrec.Config; import ctbrec.io.HttpClient; import javafx.application.Platform; import okhttp3.Cookie; @@ -125,7 +126,7 @@ public class BongaCamsHttpClient extends HttpClient { .build(); Request request = new Request.Builder() .url(url) - .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", BongaCams.BASE_URL) @@ -154,7 +155,7 @@ public class BongaCamsHttpClient extends HttpClient { private String getAnyModelName() throws IOException { Request request = new Request.Builder() .url(BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=female&online_only=true&is_mobile=true&offset=0") - .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", BongaCams.BASE_URL) @@ -211,7 +212,7 @@ public class BongaCamsHttpClient extends HttpClient { // Request request = new Request.Builder() // .url(url) // .post(body) - // .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + // .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) // .addHeader("Accept","application/json") // .addHeader("Accept-Language", "en") // .addHeader("Referer", BongaCams.BASE_URL) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java b/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java index 0d609311..626667d3 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsLoginDialog.java @@ -60,6 +60,7 @@ public class BongaCamsLoginDialog { WebView browser = new WebView(); WebEngine webEngine = browser.getEngine(); webEngine.setJavaScriptEnabled(true); + webEngine.setUserAgent(Config.getInstance().getSettings().httpUserAgent); webEngine.locationProperty().addListener((obs, oldV, newV) -> { try { URL _url = new URL(newV); diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index f4bd92e4..da19f5b0 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -22,6 +22,7 @@ import com.iheartradio.m3u8.data.PlaylistData; import com.iheartradio.m3u8.data.StreamInfo; import ctbrec.AbstractModel; +import ctbrec.Config; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.Site; import okhttp3.FormBody; @@ -102,7 +103,7 @@ public class BongaCamsModel extends AbstractModel { .build(); Request request = new Request.Builder() .url(url) - .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", BongaCams.BASE_URL) @@ -143,7 +144,7 @@ public class BongaCamsModel extends AbstractModel { .build(); Request request = new Request.Builder() .url(url) - .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", BongaCams.BASE_URL + '/' + getName()) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java index 9a230f1e..d58cf651 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -9,6 +9,7 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ctbrec.Config; import ctbrec.Model; import ctbrec.ui.PaginatedScheduledService; import javafx.concurrent.Task; @@ -36,7 +37,7 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { LOG.debug("Fetching page {}", _url); Request request = new Request.Builder() .url(_url) - .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/javascript, */*") .addHeader("Accept-Language", "en") .addHeader("Referer", bongaCams.getBaseUrl()) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java b/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java index c170af83..bb862bed 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java @@ -60,6 +60,7 @@ public class Cam4LoginDialog { WebView browser = new WebView(); WebEngine webEngine = browser.getEngine(); webEngine.setJavaScriptEnabled(true); + webEngine.setUserAgent(Config.getInstance().getSettings().httpUserAgent); webEngine.locationProperty().addListener((obs, oldV, newV) -> { try { URL _url = new URL(newV); diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index 1fe5beeb..fb2b7aff 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -25,6 +25,7 @@ import com.iheartradio.m3u8.data.PlaylistData; import com.iheartradio.m3u8.data.StreamInfo; import ctbrec.AbstractModel; +import ctbrec.Config; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.Site; import okhttp3.FormBody; @@ -181,7 +182,7 @@ public class CamsodaModel extends AbstractModel { .url(url) .post(body) .addHeader("Referer", Camsoda.BASE_URI + '/' + getName()) - .addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/plain, */*") .addHeader("Accept-Language", "en") .addHeader("X-CSRF-Token", csrfToken) @@ -203,7 +204,7 @@ public class CamsodaModel extends AbstractModel { .url(url) .post(RequestBody.create(null, "")) .addHeader("Referer", Camsoda.BASE_URI + '/' + getName()) - .addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/plain, */*") .addHeader("Accept-Language", "en") .addHeader("X-CSRF-Token", csrfToken) @@ -227,7 +228,7 @@ public class CamsodaModel extends AbstractModel { .url(url) .post(RequestBody.create(null, "")) .addHeader("Referer", Camsoda.BASE_URI + '/' + getName()) - .addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:62.0) Gecko/20100101 Firefox/62.0") + .addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent) .addHeader("Accept", "application/json, text/plain, */*") .addHeader("Accept-Language", "en") .addHeader("X-CSRF-Token", csrfToken) diff --git a/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java b/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java index 9b573fb2..d635994b 100644 --- a/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java +++ b/src/main/java/ctbrec/sites/chaturbate/ChaturbateModel.java @@ -17,6 +17,7 @@ import com.iheartradio.m3u8.data.MasterPlaylist; import com.iheartradio.m3u8.data.PlaylistData; import ctbrec.AbstractModel; +import ctbrec.Config; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.Site; import okhttp3.Request; @@ -147,7 +148,7 @@ public class ChaturbateModel extends AbstractModel { .header("Accept", "*/*") .header("Accept-Language", "en-US,en;q=0.5") .header("Referer", getUrl()) - .header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0") + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("X-CSRFToken", ((ChaturbateHttpClient)site.getHttpClient()).getToken()) .header("X-Requested-With", "XMLHttpRequest") .build(); From 8b7bb79d8cef8f4e2cfe8b047c462d064eb7f3eb Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 13:51:26 +0100 Subject: [PATCH 25/40] Change the URL if the name changed --- src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index db794ae2..0c8ac197 100644 --- a/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -213,6 +213,7 @@ public class MyFreeCamsModel extends AbstractModel { public void setName(String name) { if(getName() != null && name != null && !getName().equals(name)) { LOG.debug("Model name changed {} -> {}", getName(), name); + setUrl("https://profiles.myfreecams.com/" + name); } super.setName(name); } From 2a3e9c0634470f4d62d958a613b61909eaaeadc4 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 17:22:19 +0100 Subject: [PATCH 26/40] Add padding to description, if model is recording --- src/main/java/ctbrec/ui/ThumbCell.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 133d37a2..735f5462 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -118,7 +118,10 @@ public class ThumbCell extends StackPane { StackPane.setAlignment(name, Pos.BOTTOM_CENTER); getChildren().add(name); - topic = new Text(model.getDescription()); + topic = new Text(); + String txt = recording ? " " : ""; + txt += model.getDescription(); + topic.setText(txt); topic.setFill(Color.WHITE); topic.setFont(new Font("Sansserif", 13)); From c884c3b248bcd8fe9f080e61b48a2b282a41c259 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 17:24:20 +0100 Subject: [PATCH 27/40] Sped-up inital loading od RecordedModelsTab --- src/main/java/ctbrec/ui/JavaFxModel.java | 4 ---- .../java/ctbrec/ui/RecordedModelsTab.java | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/java/ctbrec/ui/JavaFxModel.java b/src/main/java/ctbrec/ui/JavaFxModel.java index 2a2ece56..9cd8420a 100644 --- a/src/main/java/ctbrec/ui/JavaFxModel.java +++ b/src/main/java/ctbrec/ui/JavaFxModel.java @@ -25,10 +25,6 @@ public class JavaFxModel implements Model { public JavaFxModel(Model delegate) { this.delegate = delegate; - try { - onlineProperty.set(delegate.isOnline()); - pausedProperty.set(delegate.isSuspended()); - } catch (IOException | ExecutionException | InterruptedException e) {} } @Override diff --git a/src/main/java/ctbrec/ui/RecordedModelsTab.java b/src/main/java/ctbrec/ui/RecordedModelsTab.java index 8702603c..3d5dfd37 100644 --- a/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -192,18 +192,20 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { queue.clear(); for (Model model : models) { int index = observableModels.indexOf(model); + final JavaFxModel javaFxModel; if (index == -1) { - observableModels.add(new JavaFxModel(model)); + javaFxModel = new JavaFxModel(model); + observableModels.add(javaFxModel); } else { // make sure to update the JavaFX online property, so that the table cell is updated - JavaFxModel javaFxModel = observableModels.get(index); - threadPool.submit(() -> { - try { - javaFxModel.getOnlineProperty().set(javaFxModel.isOnline()); - javaFxModel.setSuspended(model.isSuspended()); - } catch (IOException | ExecutionException | InterruptedException e) {} - }); + javaFxModel = observableModels.get(index); } + threadPool.submit(() -> { + try { + javaFxModel.getOnlineProperty().set(javaFxModel.isOnline()); + javaFxModel.setSuspended(model.isSuspended()); + } catch (IOException | ExecutionException | InterruptedException e) {} + }); } for (Iterator iterator = observableModels.iterator(); iterator.hasNext();) { Model model = iterator.next(); @@ -211,7 +213,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { iterator.remove(); } } - }); updateService.setOnFailed((event) -> { LOG.info("Couldn't get list of models from recorder", event.getSource().getException()); From 9e40d4412815bfadeac97a31f28955a0ac3ec6df Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 18:08:04 +0100 Subject: [PATCH 28/40] Implement pause/resume in ThumbCell Add pause/resume item to the thumbnail overview context menu. Change the recording indicator to a paused indicator, if the recording is paused --- .../java/ctbrec/recorder/LocalRecorder.java | 16 +++++++ src/main/java/ctbrec/recorder/Recorder.java | 2 + .../java/ctbrec/recorder/RemoteRecorder.java | 25 +++++++++++ src/main/java/ctbrec/ui/PauseIndicator.java | 18 ++++++++ src/main/java/ctbrec/ui/ThumbCell.java | 44 ++++++++++++++++++- src/main/java/ctbrec/ui/ThumbOverviewTab.java | 14 +++++- 6 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 src/main/java/ctbrec/ui/PauseIndicator.java diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index 1b8cdf5c..dfa3093d 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -169,6 +169,22 @@ public class LocalRecorder implements Recorder { } } + @Override + public boolean isSuspended(Model model) { + lock.lock(); + try { + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + return m.isSuspended(); + } else { + return false; + } + } finally { + lock.unlock(); + } + } + @Override public List getModelsRecording() { lock.lock(); diff --git a/src/main/java/ctbrec/recorder/Recorder.java b/src/main/java/ctbrec/recorder/Recorder.java index e216f668..b22fe1a4 100644 --- a/src/main/java/ctbrec/recorder/Recorder.java +++ b/src/main/java/ctbrec/recorder/Recorder.java @@ -31,4 +31,6 @@ public interface Recorder { public void suspendRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException; + + public boolean isSuspended(Model model); } diff --git a/src/main/java/ctbrec/recorder/RemoteRecorder.java b/src/main/java/ctbrec/recorder/RemoteRecorder.java index ec0f22a5..2e07d10c 100644 --- a/src/main/java/ctbrec/recorder/RemoteRecorder.java +++ b/src/main/java/ctbrec/recorder/RemoteRecorder.java @@ -109,6 +109,17 @@ public class RemoteRecorder implements Recorder { return models != null && models.contains(model); } + @Override + public boolean isSuspended(Model model) { + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + return m.isSuspended(); + } else { + return false; + } + } + @Override public List getModelsRecording() { if(lastSync.isBefore(Instant.now().minusSeconds(60))) { @@ -280,10 +291,24 @@ public class RemoteRecorder implements Recorder { @Override public void suspendRecording(Model model) throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, IOException { sendRequest("suspend", model); + model.setSuspended(true); + // update cached model + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + m.setSuspended(true); + } } @Override public void resumeRecording(Model model) throws IOException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException { sendRequest("resume", model); + model.setSuspended(false); + // update cached model + int index = models.indexOf(model); + if(index >= 0) { + Model m = models.get(index); + m.setSuspended(false); + } } } diff --git a/src/main/java/ctbrec/ui/PauseIndicator.java b/src/main/java/ctbrec/ui/PauseIndicator.java new file mode 100644 index 00000000..f716b871 --- /dev/null +++ b/src/main/java/ctbrec/ui/PauseIndicator.java @@ -0,0 +1,18 @@ +package ctbrec.ui; + +import javafx.scene.layout.HBox; +import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; + +public class PauseIndicator extends HBox { + + public PauseIndicator(Color c, int size) { + spacingProperty().setValue(size*1/5); + Rectangle left = new Rectangle(size*2/5, size); + left.setFill(c); + Rectangle right = new Rectangle(size*2/5, size); + right.setFill(c); + getChildren().add(left); + getChildren().add(right); + } +} diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 735f5462..87ca2ce8 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -64,6 +64,7 @@ public class ThumbCell extends StackPane { private Text resolutionTag; private Recorder recorder; private Circle recordingIndicator; + private PauseIndicator pausedIndicator; private int index = 0; ContextMenu popup; private final Color colorNormal = Color.BLACK; @@ -81,6 +82,7 @@ public class ThumbCell extends StackPane { this.model = model; this.recorder = recorder; recording = recorder.isRecording(model); + model.setSuspended(recorder.isSuspended(model)); this.setStyle("-fx-background-color: lightgray"); iv = new ImageView(); @@ -145,6 +147,12 @@ public class ThumbCell extends StackPane { StackPane.setAlignment(recordingIndicator, Pos.TOP_LEFT); getChildren().add(recordingIndicator); + pausedIndicator = new PauseIndicator(colorRecording, 16); + pausedIndicator.setVisible(false); + StackPane.setMargin(pausedIndicator, new Insets(3)); + StackPane.setAlignment(pausedIndicator, Pos.TOP_LEFT); + getChildren().add(pausedIndicator); + selectionOverlay = new Rectangle(); selectionOverlay.setOpacity(0); StackPane.setAlignment(selectionOverlay, Pos.TOP_LEFT); @@ -324,7 +332,14 @@ public class ThumbCell extends StackPane { Color c = mouseHovering ? colorHighlight : colorNormal; nameBackground.setFill(c); } - recordingIndicator.setVisible(recording); + + if(recording) { + recordingIndicator.setVisible(!model.isSuspended()); + pausedIndicator.setVisible(model.isSuspended()); + } else { + recordingIndicator.setVisible(false); + pausedIndicator.setVisible(false); + } } void startStopAction(boolean start) { @@ -350,6 +365,31 @@ public class ThumbCell extends StackPane { } } + void pauseResumeAction(boolean pause) { + setCursor(Cursor.WAIT); + new Thread(() -> { + try { + if(pause) { + recorder.suspendRecording(model); + } else { + recorder.resumeRecording(model); + } + setRecording(recording); + } catch (Exception e1) { + LOG.error("Couldn't pause/resume recording", e1); + Platform.runLater(() -> { + Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText("Couldn't pause/resume recording"); + alert.setContentText("I/O error while pausing/resuming the recording: " + e1.getLocalizedMessage()); + alert.showAndWait(); + }); + } finally { + setCursor(Cursor.DEFAULT); + } + }).start(); + } + private void _startStopAction(Model model, boolean start) { new Thread(() -> { try { @@ -429,6 +469,7 @@ public class ThumbCell extends StackPane { this.model.setPreview(model.getPreview()); this.model.setTags(model.getTags()); this.model.setUrl(model.getUrl()); + this.model.setSuspended(model.isSuspended()); update(); } @@ -441,6 +482,7 @@ public class ThumbCell extends StackPane { } private void update() { + model.setSuspended(recorder.isSuspended(model)); setRecording(recorder.isRecording(model)); setImage(model.getPreview()); String txt = recording ? " " : ""; diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java index 447336c6..54a14e64 100644 --- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -324,6 +324,12 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { stop.setOnAction((e) -> startStopAction(getSelectedThumbCells(cell), false)); MenuItem startStop = recorder.isRecording(cell.getModel()) ? stop : start; + MenuItem pause = new MenuItem("Pause Recording"); + pause.setOnAction((e) -> pauseResumeAction(getSelectedThumbCells(cell), true)); + MenuItem resume = new MenuItem("Resume Recording"); + resume.setOnAction((e) -> pauseResumeAction(getSelectedThumbCells(cell), false)); + MenuItem pauseResume = recorder.isSuspended(cell.getModel()) ? resume : pause; + MenuItem follow = new MenuItem("Follow"); follow.setOnAction((e) -> follow(getSelectedThumbCells(cell), true)); MenuItem unfollow = new MenuItem("Unfollow"); @@ -388,7 +394,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { contextMenu.setAutoHide(true); contextMenu.setHideOnEscape(true); contextMenu.setAutoFix(true); - contextMenu.getItems().addAll(openInPlayer, startStop); + contextMenu.getItems().addAll(openInPlayer, startStop, pauseResume); if(site.supportsFollow()) { MenuItem followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow; followOrUnFollow.setDisable(!site.credentialsAvailable()); @@ -431,6 +437,12 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { } } + private void pauseResumeAction(List selection, boolean pause) { + for (ThumbCell thumbCell : selection) { + thumbCell.pauseResumeAction(pause); + } + } + private void startPlayer(List selection) { for (ThumbCell thumbCell : selection) { thumbCell.startPlayer(); From 39a7c5f79b9959aff401a1d8d714cf262af39d48 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 20:08:06 +0100 Subject: [PATCH 29/40] Avoid NPE when context menu is triggered in empty table --- src/main/java/ctbrec/ui/RecordingsTab.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/ctbrec/ui/RecordingsTab.java b/src/main/java/ctbrec/ui/RecordingsTab.java index e64184f0..13a0701b 100644 --- a/src/main/java/ctbrec/ui/RecordingsTab.java +++ b/src/main/java/ctbrec/ui/RecordingsTab.java @@ -139,9 +139,11 @@ public class RecordingsTab extends Tab implements TabSelectionListener { table.setItems(observableRecordings); table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> { Recording recording = table.getSelectionModel().getSelectedItem(); - popup = createContextMenu(recording); - if(!popup.getItems().isEmpty()) { - popup.show(table, event.getScreenX(), event.getScreenY()); + if(recording != null) { + popup = createContextMenu(recording); + if(!popup.getItems().isEmpty()) { + popup.show(table, event.getScreenX(), event.getScreenY()); + } } event.consume(); }); From 77a1b4f3ac97f036e7d8739425da0a6edd4e094c Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 20:08:40 +0100 Subject: [PATCH 30/40] Show pause/resume only when model is recorded --- src/main/java/ctbrec/recorder/LocalRecorder.java | 4 ++++ src/main/java/ctbrec/ui/ThumbOverviewTab.java | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index dfa3093d..15c63e69 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -558,7 +558,9 @@ public class LocalRecorder implements Recorder { if (models.contains(model)) { int index = models.indexOf(model); models.get(index).setSuspended(true); + model.setSuspended(true); } else { + LOG.warn("Couldn't suspend model {}. Not found in list", model.getName()); return; } } finally { @@ -581,7 +583,9 @@ public class LocalRecorder implements Recorder { Model m = models.get(index); m.setSuspended(false); startRecordingProcess(m); + model.setSuspended(false); } else { + LOG.warn("Couldn't resume model {}. Not found in list", model.getName()); return; } } finally { diff --git a/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/src/main/java/ctbrec/ui/ThumbOverviewTab.java index 54a14e64..b7e8a7f2 100644 --- a/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -394,7 +394,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { contextMenu.setAutoHide(true); contextMenu.setHideOnEscape(true); contextMenu.setAutoFix(true); - contextMenu.getItems().addAll(openInPlayer, startStop, pauseResume); + contextMenu.getItems().addAll(openInPlayer, startStop); + if(recorder.isRecording(cell.getModel())) { + contextMenu.getItems().add(pauseResume); + } if(site.supportsFollow()) { MenuItem followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow; followOrUnFollow.setDisable(!site.credentialsAvailable()); From 8ee3d8b5884b4664977e01761489b6dac14ba186 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Wed, 7 Nov 2018 23:05:06 +0100 Subject: [PATCH 31/40] Add setting to run post-processing script The post-processing script is executed, after a local recording is finished. The script is executed in the directory of the recording with the following parameters in given order: directory (absolute path), file (absolute path), model name, site name, unixtime --- src/main/java/ctbrec/Settings.java | 1 + .../java/ctbrec/recorder/LocalRecorder.java | 95 ++++++++++++++----- .../download/AbstractHlsDownload.java | 13 +++ .../ctbrec/recorder/download/Download.java | 3 + .../ctbrec/recorder/download/HlsDownload.java | 3 + .../recorder/download/MergedHlsDownload.java | 10 +- src/main/java/ctbrec/ui/SettingsTab.java | 62 ++++++++++++ src/main/resources/pp_example.sh | 28 ++++++ 8 files changed, 190 insertions(+), 25 deletions(-) create mode 100755 src/main/resources/pp_example.sh diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index a92327a4..2d5f7866 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -21,6 +21,7 @@ public class Settings { public String httpServer = "localhost"; public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec"; public String mediaPlayer = "/usr/bin/mpv"; + public String postProcessing = ""; public String username = ""; // chaturbate username TODO maybe rename this onetime public String password = ""; // chaturbate password TODO maybe rename this onetime public String bongaUsername = ""; diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index 15c63e69..a35f2f56 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -10,6 +10,7 @@ import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -28,7 +29,9 @@ import com.iheartradio.m3u8.PlaylistException; import ctbrec.Config; import ctbrec.Model; +import ctbrec.OS; import ctbrec.Recording; +import ctbrec.io.StreamRedirectThread; import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException; import ctbrec.recorder.download.Download; import ctbrec.recorder.download.HlsDownload; @@ -46,7 +49,7 @@ public class LocalRecorder implements Recorder { private Config config; private ProcessMonitor processMonitor; private OnlineMonitor onlineMonitor; - private PlaylistGeneratorTrigger playlistGenTrigger; + private PostProcessingTrigger postProcessingTrigger; private volatile boolean recording = true; private List deleteInProgress = Collections.synchronizedList(new ArrayList<>()); private RecorderHttpClient client = new RecorderHttpClient(); @@ -68,9 +71,9 @@ public class LocalRecorder implements Recorder { onlineMonitor = new OnlineMonitor(); onlineMonitor.start(); - playlistGenTrigger = new PlaylistGeneratorTrigger(); + postProcessingTrigger = new PostProcessingTrigger(); if(Config.getInstance().isServerMode()) { - playlistGenTrigger.start(); + postProcessingTrigger.start(); } LOG.debug("Recorder initialized"); @@ -153,10 +156,51 @@ public class LocalRecorder implements Recorder { }.start(); } - private void stopRecordingProcess(Model model) throws IOException { + private void stopRecordingProcess(Model model) { Download download = recordingProcesses.get(model); download.stop(); recordingProcesses.remove(model); + if(!Config.getInstance().isServerMode()) { + postprocess(download); + } + } + + private void postprocess(Download download) { + if(!(download instanceof MergedHlsDownload)) { + throw new IllegalArgumentException("Download should be of type MergedHlsDownload"); + } + String postProcessing = Config.getInstance().getSettings().postProcessing; + if (postProcessing != null && !postProcessing.isEmpty()) { + new Thread(() -> { + Runtime rt = Runtime.getRuntime(); + try { + MergedHlsDownload d = (MergedHlsDownload) download; + String[] args = new String[] { + postProcessing, + d.getDirectory().getAbsolutePath(), + d.getTargetFile().getAbsolutePath(), + d.getModel().getName(), + d.getModel().getSite().getName(), + Long.toString(download.getStartTime().getEpochSecond()) + }; + LOG.debug("Running {}", Arrays.toString(args)); + Process process = rt.exec(args, OS.getEnvironment(), download.getDirectory()); + Thread std = new Thread(new StreamRedirectThread(process.getInputStream(), System.out)); + std.setName("Process stdout pipe"); + std.setDaemon(true); + std.start(); + Thread err = new Thread(new StreamRedirectThread(process.getErrorStream(), System.err)); + err.setName("Process stderr pipe"); + err.setDaemon(true); + err.start(); + + process.waitFor(); + LOG.debug("Process finished."); + } catch (Exception e) { + LOG.error("Error in process thread", e); + } + }).start(); + } } @Override @@ -202,7 +246,7 @@ public class LocalRecorder implements Recorder { LOG.debug("Stopping monitor threads"); onlineMonitor.running = false; processMonitor.running = false; - playlistGenTrigger.running = false; + postProcessingTrigger.running = false; LOG.debug("Stopping all recording processes"); stopRecordingProcesses(); client.shutdown(); @@ -267,10 +311,14 @@ public class LocalRecorder implements Recorder { LOG.debug("Recording terminated for model {}", m.getName()); iterator.remove(); restart.add(m); - try { - finishRecording(d.getDirectory()); - } catch(Exception e) { - LOG.error("Error while finishing recording for model {}", m.getName(), e); + if(config.isServerMode()) { + try { + finishRecording(d.getDirectory()); + } catch(Exception e) { + LOG.error("Error while finishing recording for model {}", m.getName(), e); + } + } else { + postprocess(d); } } } @@ -290,17 +338,17 @@ public class LocalRecorder implements Recorder { } private void finishRecording(File directory) { - Thread t = new Thread() { - @Override - public void run() { - if(Config.getInstance().isServerMode()) { + if(Config.getInstance().isServerMode()) { + Thread t = new Thread() { + @Override + public void run() { generatePlaylist(directory); } - } - }; - t.setDaemon(true); - t.setName("Postprocessing" + directory.toString()); - t.start(); + }; + t.setDaemon(true); + t.setName("Post-Processing " + directory.toString()); + t.start(); + } } private void generatePlaylist(File recDir) { @@ -360,11 +408,11 @@ public class LocalRecorder implements Recorder { } } - private class PlaylistGeneratorTrigger extends Thread { + private class PostProcessingTrigger extends Thread { private volatile boolean running = false; - public PlaylistGeneratorTrigger() { - setName("PlaylistGeneratorTrigger"); + public PostProcessingTrigger() { + setName("PostProcessingTrigger"); setDaemon(true); } @@ -386,7 +434,7 @@ public class LocalRecorder implements Recorder { } if (!recordingProcessFound) { if (deleteInProgress.contains(recDir)) { - LOG.debug("{} is being deleted. Not going to generate a playlist", recDir); + LOG.debug("{} is being deleted. Not going to start post-processing", recDir); } else { finishRecording(recDir); } @@ -569,8 +617,7 @@ public class LocalRecorder implements Recorder { Download download = recordingProcesses.get(model); if(download != null) { - download.stop(); - recordingProcesses.remove(model); + stopRecordingProcess(model); } } diff --git a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java index 84a3744d..62a8f897 100644 --- a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Path; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -41,6 +42,8 @@ public abstract class AbstractHlsDownload implements Download { volatile boolean running = false; volatile boolean alive = true; Path downloadDir; + Instant startTime; + Model model; public AbstractHlsDownload(HttpClient client) { this.client = client; @@ -117,6 +120,16 @@ public abstract class AbstractHlsDownload implements Download { return downloadDir.toFile(); } + @Override + public Instant getStartTime() { + return startTime; + } + + @Override + public Model getModel() { + return model; + } + public static class SegmentPlaylist { public int seq = 0; public float totalDuration = 0; diff --git a/src/main/java/ctbrec/recorder/download/Download.java b/src/main/java/ctbrec/recorder/download/Download.java index 4148a362..76a71f0e 100644 --- a/src/main/java/ctbrec/recorder/download/Download.java +++ b/src/main/java/ctbrec/recorder/download/Download.java @@ -2,6 +2,7 @@ package ctbrec.recorder.download; import java.io.File; import java.io.IOException; +import java.time.Instant; import ctbrec.Config; import ctbrec.Model; @@ -11,4 +12,6 @@ public interface Download { public void stop(); public boolean isAlive(); public File getDirectory(); + public Model getModel(); + public Instant getStartTime(); } diff --git a/src/main/java/ctbrec/recorder/download/HlsDownload.java b/src/main/java/ctbrec/recorder/download/HlsDownload.java index 6974056a..ab1804b6 100644 --- a/src/main/java/ctbrec/recorder/download/HlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/HlsDownload.java @@ -12,6 +12,7 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Date; import java.util.concurrent.Callable; @@ -39,6 +40,8 @@ public class HlsDownload extends AbstractHlsDownload { public void start(Model model, Config config) throws IOException { try { running = true; + startTime = Instant.now(); + super.model = model; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); String startTime = sdf.format(new Date()); Path modelDir = FileSystems.getDefault().getPath(config.getSettings().recordingsDir, model.getName()); diff --git a/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java b/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java index 51e7b422..59668dbd 100644 --- a/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/MergedHlsDownload.java @@ -16,6 +16,7 @@ import java.nio.file.Path; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.time.Duration; +import java.time.Instant; import java.time.ZonedDateTime; import java.util.Date; import java.util.LinkedList; @@ -58,9 +59,14 @@ public class MergedHlsDownload extends AbstractHlsDownload { super(client); } + public File getTargetFile() { + return targetFile; + } + public void start(String segmentPlaylistUri, File targetFile, ProgressListener progressListener) throws IOException { try { running = true; + super.startTime = Instant.now(); downloadDir = targetFile.getParentFile().toPath(); mergeThread = createMergeThread(targetFile, progressListener, false); mergeThread.start(); @@ -75,7 +81,7 @@ public class MergedHlsDownload extends AbstractHlsDownload { } finally { alive = false; streamer.stop(); - LOG.debug("Download for terminated"); + LOG.debug("Download terminated for {}", segmentPlaylistUri); } } @@ -84,6 +90,8 @@ public class MergedHlsDownload extends AbstractHlsDownload { this.config = config; try { running = true; + super.startTime = Instant.now(); + super.model = model; startTime = ZonedDateTime.now(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm"); String startTime = sdf.format(new Date()); diff --git a/src/main/java/ctbrec/ui/SettingsTab.java b/src/main/java/ctbrec/ui/SettingsTab.java index 2c9bb604..9cc910d7 100644 --- a/src/main/java/ctbrec/ui/SettingsTab.java +++ b/src/main/java/ctbrec/ui/SettingsTab.java @@ -56,7 +56,9 @@ public class SettingsTab extends Tab implements TabSelectionListener { public static final int CHECKBOX_MARGIN = 6; private TextField recordingsDirectory; private Button recordingsDirectoryButton; + private Button postProcessingDirectoryButton; private TextField mediaPlayer; + private TextField postProcessing; private TextField server; private TextField port; private CheckBox loadResolution; @@ -267,6 +269,17 @@ public class SettingsTab extends Tab implements TabSelectionListener { layout.add(mediaPlayer, 1, 1); layout.add(createMpvBrowseButton(), 3, 1); + layout.add(new Label("Post-Processing"), 0, 2); + postProcessing = new TextField(Config.getInstance().getSettings().postProcessing); + postProcessing.focusedProperty().addListener(createPostProcessingFocusListener()); + GridPane.setFillWidth(postProcessing, true); + GridPane.setHgrow(postProcessing, Priority.ALWAYS); + GridPane.setColumnSpan(postProcessing, 2); + GridPane.setMargin(postProcessing, new Insets(0, 0, 0, CHECKBOX_MARGIN)); + layout.add(postProcessing, 1, 2); + postProcessingDirectoryButton = createPostProcessingBrowseButton(); + layout.add(postProcessingDirectoryButton, 3, 2); + TitledPane locations = new TitledPane("Locations", layout); locations.setCollapsible(false); return locations; @@ -380,6 +393,8 @@ public class SettingsTab extends Tab implements TabSelectionListener { recordingsDirectoryButton.setDisable(!local); splitAfter.setDisable(!local); maxResolution.setDisable(!local); + postProcessing.setDisable(!local); + postProcessingDirectoryButton.setDisable(!local); } private ChangeListener createRecordingsDirectoryFocusListener() { @@ -414,6 +429,22 @@ public class SettingsTab extends Tab implements TabSelectionListener { }; } + private ChangeListener createPostProcessingFocusListener() { + return new ChangeListener() { + @Override + public void changed(ObservableValue arg0, Boolean oldPropertyValue, Boolean newPropertyValue) { + if (newPropertyValue) { + postProcessing.setBorder(Border.EMPTY); + postProcessing.setTooltip(null); + } else { + String input = postProcessing.getText(); + File program = new File(input); + setPostProcessing(program); + } + } + }; + } + private void setMpv(File program) { String msg = validateProgram(program); if (msg != null) { @@ -424,6 +455,16 @@ public class SettingsTab extends Tab implements TabSelectionListener { } } + private void setPostProcessing(File program) { + String msg = validateProgram(program); + if (msg != null) { + postProcessing.setBorder(new Border(new BorderStroke(Color.RED, BorderStrokeStyle.DASHED, new CornerRadii(2), new BorderWidths(2)))); + postProcessing.setTooltip(new Tooltip(msg)); + } else { + Config.getInstance().getSettings().postProcessing = postProcessing.getText(); + } + } + private String validateProgram(File program) { if (program == null || !program.exists()) { return "File does not exist"; @@ -470,6 +511,27 @@ public class SettingsTab extends Tab implements TabSelectionListener { return button; } + private Button createPostProcessingBrowseButton() { + Button button = new Button("Select"); + button.setOnAction((e) -> { + FileChooser chooser = new FileChooser(); + File program = chooser.showOpenDialog(null); + if(program != null) { + try { + postProcessing.setText(program.getCanonicalPath()); + } catch (IOException e1) { + LOG.error("Couldn't determine path", e1); + Alert alert = new AutosizeAlert(Alert.AlertType.ERROR); + alert.setTitle("Whoopsie"); + alert.setContentText("Couldn't determine path"); + alert.showAndWait(); + } + setPostProcessing(program); + } + }); + return button; + } + private void setRecordingsDir(File dir) { if (dir != null && dir.isDirectory()) { try { diff --git a/src/main/resources/pp_example.sh b/src/main/resources/pp_example.sh new file mode 100755 index 00000000..b4e829e4 --- /dev/null +++ b/src/main/resources/pp_example.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# $1 directory (absolute path) +# $2 file (absolute path) +# $3 model name +# $4 site name +# $5 unixtime + +# get the filename without path +FILE=`basename $2` + +# format unixtime to human readable +TIME=$(date --date="@$5" +%d.%m.%Y_%H:%M) + +# define filename of end result +MP4=$(echo "$1/$4_$3_$TIME.mp4") + +# remux ts to mp4 +ffmpeg -i $2 -c:v copy -c:a copy -f mp4 $MP4 + +# move mp4 to target directory +mv $MP4 /tmp + +# delete the original .ts file +rm $2 + +# delete the directory of the recording +rm -r $1 From b447c76dac23101da098ea42d795369b1dd75eda Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 14:30:30 +0100 Subject: [PATCH 32/40] Don't fetch stream resolution if model is offline --- src/main/java/ctbrec/sites/bonga/BongaCamsModel.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java index da19f5b0..4014ae8c 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -171,12 +171,15 @@ public class BongaCamsModel extends AbstractModel { return new int[2]; } try { + if(!isOnline()) { + return new int[2]; + } List streamSources = getStreamSources(); Collections.sort(streamSources); StreamSource best = streamSources.get(streamSources.size()-1); resolution = new int[] {best.width, best.height}; - } catch (ExecutionException | IOException | ParseException | PlaylistException e) { - LOG.error("Couldn't determine stream resolution for {}", getName(), e); + } catch (ExecutionException | IOException | ParseException | PlaylistException | InterruptedException e) { + LOG.warn("Couldn't determine stream resolution for {} - {}", getName(), e.getMessage()); } return resolution; } else { From 2d3a4c87be27a90719a0c04809477c775ca7ab93 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 14:31:13 +0100 Subject: [PATCH 33/40] Be more precise regarding the online state of the model --- .../ctbrec/sites/bonga/BongaCamsUpdateService.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java index d58cf651..9761be92 100644 --- a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -55,8 +55,18 @@ public class BongaCamsUpdateService extends PaginatedScheduledService { String name = m.getString("username"); BongaCamsModel model = (BongaCamsModel) bongaCams.createModel(name); model.setUserId(m.getInt("user_id")); - model.setOnlineState(m.getString("room")); - model.setOnline(m.optBoolean("online") && !m.optBoolean("is_away")); + boolean away = m.optBoolean("is_away"); + boolean online = m.optBoolean("online") && !away; + model.setOnline(online); + if(online) { + if(away) { + model.setOnlineState("away"); + } else { + model.setOnlineState(m.getString("room")); + } + } else { + model.setOnlineState("offline"); + } model.setPreview("https:" + m.getString("thumb_image")); models.add(model); } From 1d632e6ce17f9b18bdde9d3fb35641aba7db4d09 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 14:31:36 +0100 Subject: [PATCH 34/40] Remove JRE version from classpath --- .classpath | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.classpath b/.classpath index b9241a5e..0f3dcb1f 100644 --- a/.classpath +++ b/.classpath @@ -1,6 +1,6 @@ - + @@ -11,7 +11,7 @@ - + From 9db844b5d1fd538213177e091d87c2eda6d440c8 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 18:19:37 +0100 Subject: [PATCH 35/40] Add post-processing examples for Windows and Linux --- src/main/resources/pp.bat | 17 +++++++++++++++++ src/main/resources/pp.ps1 | 17 +++++++++++++++++ src/main/resources/{pp_example.sh => pp.sh} | 0 3 files changed, 34 insertions(+) create mode 100644 src/main/resources/pp.bat create mode 100644 src/main/resources/pp.ps1 rename src/main/resources/{pp_example.sh => pp.sh} (100%) diff --git a/src/main/resources/pp.bat b/src/main/resources/pp.bat new file mode 100644 index 00000000..74d6c000 --- /dev/null +++ b/src/main/resources/pp.bat @@ -0,0 +1,17 @@ +REM This script is just a wrapper to call the actual powershell script. +REM But you do something complete different here, too. +REM +REM If you want to use powershell, make sure, that your system allows the execution of powershell scripts: +REM 1. Open cmd.exe as administrator (Click on start, type cmd.exe, right-click on it and select "Run as administrator") +REM 2. Execute powershell +REM 3. Execute Set-ExecutionPolicy Unrestricted + +@echo off + +set directory=%1 +set file=%2 +set model=%3 +set site=%4 +set unixtime=%5 + +powershell -F C:\Users\henrik\Desktop\ctbrec\pp.ps1 -dir "%directory%" -file "%file%" -model "%model%" -site "%site%" -time "%unixtime%" \ No newline at end of file diff --git a/src/main/resources/pp.ps1 b/src/main/resources/pp.ps1 new file mode 100644 index 00000000..47550440 --- /dev/null +++ b/src/main/resources/pp.ps1 @@ -0,0 +1,17 @@ +# parse command line parameters +param ( + [Parameter(Mandatory=$true)][string]$dir, + [Parameter(Mandatory=$true)][string]$file, + [Parameter(Mandatory=$true)][string]$model, + [Parameter(Mandatory=$true)][string]$site, + [Parameter(Mandatory=$true)][string]$time +) + +# convert unixtime into a date object +$epoch = get-date "1/1/1970" +$date = $epoch.AddSeconds($time) + +# print out a theoretical new file name, you could use "rename" here, to rename the file +# or move it somewhere or ... +$newname = "$($model)_$($site)_$($date.toString("yyyyMMdd-HHmm")).ts" +ren $file $newname \ No newline at end of file diff --git a/src/main/resources/pp_example.sh b/src/main/resources/pp.sh similarity index 100% rename from src/main/resources/pp_example.sh rename to src/main/resources/pp.sh From dc540c795ac80df4ade4bd98ae086605335ed4f1 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 18:19:59 +0100 Subject: [PATCH 36/40] Avoid NPE in SessionState.X.merge --- src/main/java/ctbrec/sites/mfc/X.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/sites/mfc/X.java b/src/main/java/ctbrec/sites/mfc/X.java index db5175db..8b0b99ee 100644 --- a/src/main/java/ctbrec/sites/mfc/X.java +++ b/src/main/java/ctbrec/sites/mfc/X.java @@ -37,8 +37,12 @@ public class X { if(x == null) { return; } - fcext.merge(x.fcext); - share.merge(x.share); + if (fcext != null) { + fcext.merge(x.fcext); + } + if (share != null) { + share.merge(x.share); + } additionalProperties.putAll(x.additionalProperties); } From 6982bceb57a51fcc430397b2374696c4d9e00866 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 18:20:28 +0100 Subject: [PATCH 37/40] Reduce verbosity of logging --- src/main/java/ctbrec/ui/ThumbCell.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 87ca2ce8..1865844b 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -226,6 +226,8 @@ public class ThumbCell extends StackPane { } catch(ExecutionException e) { if(e.getCause() instanceof EOFException) { LOG.warn("Couldn't update resolution tag for model {}. Playlist empty", model.getName()); + } else if(e.getCause() instanceof ParseException) { + LOG.warn("Couldn't update resolution tag for model {} - {}", model.getName(), e.getMessage()); } else { LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e); } From e34cba01e6734e60e4b6153713a418a8dcb0b8f8 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 18:27:01 +0100 Subject: [PATCH 38/40] Add example post-processing scripts to distribution archives --- src/assembly/linux.xml | 4 ++++ src/assembly/macos-jre.xml | 4 ++++ src/assembly/win32-jre.xml | 8 ++++++++ src/assembly/win64-jre.xml | 8 ++++++++ src/assembly/win64.xml | 8 ++++++++ src/main/resources/pp.bat | 3 ++- 6 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/assembly/linux.xml b/src/assembly/linux.xml index 26c75a93..83413326 100644 --- a/src/assembly/linux.xml +++ b/src/assembly/linux.xml @@ -16,6 +16,10 @@ ctbrec true + + ${project.basedir}/src/main/resources/pp.sh + ctbrec + ${project.build.directory}/${name.final}.jar ctbrec diff --git a/src/assembly/macos-jre.xml b/src/assembly/macos-jre.xml index c8a1c261..84a87a90 100644 --- a/src/assembly/macos-jre.xml +++ b/src/assembly/macos-jre.xml @@ -16,6 +16,10 @@ ctbrec true + + ${project.basedir}/src/main/resources/pp.sh + ctbrec + ${project.build.directory}/${name.final}.jar ctbrec diff --git a/src/assembly/win32-jre.xml b/src/assembly/win32-jre.xml index 7efe1c9d..c76fff23 100644 --- a/src/assembly/win32-jre.xml +++ b/src/assembly/win32-jre.xml @@ -15,6 +15,14 @@ ctbrec true + + ${project.basedir}/src/main/resources/pp.bat + ctbrec + + + ${project.basedir}/src/main/resources/pp.ps1 + ctbrec + ${project.build.directory}/${name.final}.jar ctbrec diff --git a/src/assembly/win64-jre.xml b/src/assembly/win64-jre.xml index 65b22409..81000d7e 100644 --- a/src/assembly/win64-jre.xml +++ b/src/assembly/win64-jre.xml @@ -15,6 +15,14 @@ ctbrec true + + ${project.basedir}/src/main/resources/pp.bat + ctbrec + + + ${project.basedir}/src/main/resources/pp.ps1 + ctbrec + ${project.build.directory}/${name.final}.jar ctbrec diff --git a/src/assembly/win64.xml b/src/assembly/win64.xml index 62bb8a6f..f637dd44 100644 --- a/src/assembly/win64.xml +++ b/src/assembly/win64.xml @@ -15,6 +15,14 @@ ctbrec true + + ${project.basedir}/src/main/resources/pp.bat + ctbrec + + + ${project.basedir}/src/main/resources/pp.ps1 + ctbrec + ${project.build.directory}/${name.final}.jar ctbrec diff --git a/src/main/resources/pp.bat b/src/main/resources/pp.bat index 74d6c000..d8587126 100644 --- a/src/main/resources/pp.bat +++ b/src/main/resources/pp.bat @@ -1,5 +1,6 @@ +REM This is an post-processing example script REM This script is just a wrapper to call the actual powershell script. -REM But you do something complete different here, too. +REM But you can do something completly different here, too. REM REM If you want to use powershell, make sure, that your system allows the execution of powershell scripts: REM 1. Open cmd.exe as administrator (Click on start, type cmd.exe, right-click on it and select "Run as administrator") From 37b78854f4668634fa93246065c7e6aad8e21fac Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 18:27:25 +0100 Subject: [PATCH 39/40] Update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f37025ee..13a13303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ -1.7.1 +1.8.0 ======================== +* Added BongaCams * Added possibility to suspend the recording for a model. The model stays in the list of recorded models, but the actual recording is suspended +* HTTP sessions are restored on startup. This should reduce the number of + logins needed (especially for Cam4, BongaCams and CamSoda). * Server can run now run on OpenJRE * Added JVM parameter to define the configuration directory (``-Dctbrec.config.dir``) +* Improved memory management for MyFreeCams 1.7.0 ======================== From a1cefb4be7ccf3d1e798f1a09ffe0db951a9f764 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 9 Nov 2018 18:27:43 +0100 Subject: [PATCH 40/40] Bumb version to 1.8.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5994a6db..e0c984d2 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 ctbrec ctbrec - 1.7.0 + 1.8.0 UTF-8