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/11] 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/11] 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<StreamSource> 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<StreamSource> 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<StreamSource> 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<Tab> getTabs(Scene scene) {
+        List<Tab> 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<List<Model>> createTask() {
+        return new Task<List<Model>>() {
+            @Override
+            public List<Model> 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<Model> 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/11] 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/11] 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 2f1ebabf00de9824594876f4ff7a67d5ac045a48 Mon Sep 17 00:00:00 2001
From: 0xboobface <0xboobface@gmail.com>
Date: Mon, 5 Nov 2018 19:50:26 +0100
Subject: [PATCH 05/11] 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<Tab> getTabs(Scene scene) {
         List<Tab> 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<List<Model>>() {
             @Override
             public List<Model> 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 06/11] 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<Boolean> 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<Cookie> 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<HttpCookie> 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<HttpCookie> 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 a5ddf4f50989d52925c98e3fa5c96b58a14c3247 Mon Sep 17 00:00:00 2001
From: 0xboobface <0xboobface@gmail.com>
Date: Tue, 6 Nov 2018 18:36:28 +0100
Subject: [PATCH 07/11] 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<Model> 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 08/11] 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[]", <userId>) // 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<Cookie> 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 09/11] 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<Model> 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 10/11] 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 11/11] 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<String, List<Cookie>> entry : cookieStore.entrySet()) {
+            sb.append(entry.getKey()).append(": ").append(entry.getValue()).append('\n');
+        }
+        return sb.toString();
+    }
 
+    protected Map<String, List<Cookie>> 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<Cookie> {
+
+    @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<String, List<Cookie>> cookies = cookieJar.getCookies();
+            Moshi moshi = new Moshi.Builder().add(Cookie.class, new CookieJsonAdapter()).build();
+            @SuppressWarnings("rawtypes")
+            JsonAdapter<Map> 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;