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] Add BongaCams This is the first working version. Follow / unfollow and tipping are not implemented. --- .../ctbrec/recorder/server/HttpServer.java | 2 + .../java/ctbrec/sites/bonga/BongaCams.java | 123 +++++++++++ .../sites/bonga/BongaCamsHttpClient.java | 15 ++ .../ctbrec/sites/bonga/BongaCamsModel.java | 193 ++++++++++++++++++ .../sites/bonga/BongaCamsTabProvider.java | 39 ++++ .../sites/bonga/BongaCamsUpdateService.java | 71 +++++++ .../java/ctbrec/ui/CamrecApplication.java | 2 + 7 files changed, 445 insertions(+) create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCams.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsModel.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java create mode 100644 src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java diff --git a/src/main/java/ctbrec/recorder/server/HttpServer.java b/src/main/java/ctbrec/recorder/server/HttpServer.java index 00537280..5767b72e 100644 --- a/src/main/java/ctbrec/recorder/server/HttpServer.java +++ b/src/main/java/ctbrec/recorder/server/HttpServer.java @@ -20,6 +20,7 @@ import ctbrec.Config; import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.Recorder; import ctbrec.sites.Site; +import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; @@ -66,6 +67,7 @@ public class HttpServer { sites.add(new MyFreeCams()); sites.add(new Camsoda()); sites.add(new Cam4()); + sites.add(new BongaCams()); } private void addShutdownHook() { diff --git a/src/main/java/ctbrec/sites/bonga/BongaCams.java b/src/main/java/ctbrec/sites/bonga/BongaCams.java new file mode 100644 index 00000000..61e46340 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCams.java @@ -0,0 +1,123 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; + +import ctbrec.Model; +import ctbrec.io.HttpClient; +import ctbrec.recorder.Recorder; +import ctbrec.sites.AbstractSite; +import ctbrec.ui.TabProvider; +import javafx.scene.Node; + +public class BongaCams extends AbstractSite { + + public static final String BASE_URL = "https://bongacams.com"; + + private BongaCamsHttpClient httpClient; + + private Recorder recorder; + + @Override + public String getName() { + return "BongaCams"; + } + + @Override + public String getBaseUrl() { + return BASE_URL; + } + + @Override + public String getAffiliateLink() { + return BASE_URL; + } + + @Override + public void setRecorder(Recorder recorder) { + this.recorder = recorder; + } + + @Override + public TabProvider getTabProvider() { + return new BongaCamsTabProvider(recorder, this); + } + + @Override + public Model createModel(String name) { + BongaCamsModel model = new BongaCamsModel(); + model.setName(name); + model.setUrl(BASE_URL + '/' + name); + model.setDescription(""); + model.setSite(this); + return model; + } + + @Override + public Integer getTokenBalance() throws IOException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getBuyTokensLink() { + // TODO Auto-generated method stub + return getBaseUrl(); + } + + @Override + public void login() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public HttpClient getHttpClient() { + if(httpClient == null) { + httpClient = new BongaCamsHttpClient(); + } + return httpClient; + } + + @Override + public void init() throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public void shutdown() { + // TODO Auto-generated method stub + + } + + @Override + public boolean supportsTips() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean supportsFollow() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isSiteForModel(Model m) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Node getConfigurationGui() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean credentialsAvailable() { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java new file mode 100644 index 00000000..a7de6816 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsHttpClient.java @@ -0,0 +1,15 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; + +import ctbrec.io.HttpClient; + +public class BongaCamsHttpClient extends HttpClient { + + @Override + public boolean login() throws IOException { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java new file mode 100644 index 00000000..dba742fe --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsModel.java @@ -0,0 +1,193 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.iheartradio.m3u8.Encoding; +import com.iheartradio.m3u8.Format; +import com.iheartradio.m3u8.ParseException; +import com.iheartradio.m3u8.PlaylistException; +import com.iheartradio.m3u8.PlaylistParser; +import com.iheartradio.m3u8.data.MasterPlaylist; +import com.iheartradio.m3u8.data.Playlist; +import com.iheartradio.m3u8.data.PlaylistData; +import com.iheartradio.m3u8.data.StreamInfo; + +import ctbrec.AbstractModel; +import ctbrec.recorder.download.StreamSource; +import ctbrec.sites.Site; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class BongaCamsModel extends AbstractModel { + + private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsModel.class); + + private BongaCams site; + private int userId; + private String onlineState = "n/a"; + private boolean online = false; + private List streamSources = new ArrayList<>(); + private int[] resolution; + + @Override + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + @Override + public String getOnlineState(boolean failFast) throws IOException, ExecutionException { + return onlineState; + } + + public void setOnlineState(String onlineState) { + this.onlineState = onlineState; + } + + @Override + public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { + String streamUrl = getStreamUrl(); + if (streamUrl == null) { + return Collections.emptyList(); + } + Request req = new Request.Builder().url(streamUrl).build(); + Response response = site.getHttpClient().execute(req); + try { + InputStream inputStream = response.body().byteStream(); + PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); + Playlist playlist = parser.parse(); + MasterPlaylist master = playlist.getMasterPlaylist(); + for (PlaylistData playlistData : master.getPlaylists()) { + + StreamSource streamsource = new StreamSource(); + streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri()); + if (playlistData.hasStreamInfo()) { + StreamInfo info = playlistData.getStreamInfo(); + streamsource.bandwidth = info.getBandwidth(); + streamsource.width = info.hasResolution() ? info.getResolution().width : 0; + streamsource.height = info.hasResolution() ? info.getResolution().height : 0; + } else { + streamsource.bandwidth = 0; + streamsource.width = 0; + streamsource.height = 0; + } + streamSources.add(streamsource); + } + } finally { + response.close(); + } + return streamSources; + } + + private String getStreamUrl() throws IOException { + String url = BongaCams.BASE_URL + "/tools/amf.php"; + RequestBody body = new FormBody.Builder() + .add("method", "getRoomData") + .add("args[]", getName()) + .add("args[]", "false") + .build(); + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", BongaCams.BASE_URL) + .addHeader("X-Requested-With", "XMLHttpRequest") + .post(body) + .build(); + try(Response response = site.getHttpClient().execute(request)) { + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.optString("status").equals("success")) { + JSONObject localData = json.getJSONObject("localData"); + String server = localData.getString("videoServerUrl"); + return "https:" + server + "/hls/stream_" + getName() + "/playlist.m3u8"; + } else { + throw new IOException("Request was not successful: " + json.toString(2)); + } + } else { + throw new IOException(response.code() + " " + response.message()); + } + } + } + + @Override + public void invalidateCacheEntries() { + // TODO Auto-generated method stub + + } + + @Override + public void receiveTip(int tokens) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public int[] getStreamResolution(boolean failFast) throws ExecutionException { + if(resolution == null) { + if(failFast) { + return new int[2]; + } + try { + List streamSources = getStreamSources(); + Collections.sort(streamSources); + StreamSource best = streamSources.get(streamSources.size()-1); + resolution = new int[] {best.width, best.height}; + } catch (ExecutionException | IOException | ParseException | PlaylistException e) { + LOG.error("Couldn't determine stream resolution", e); + } + return resolution; + } else { + return resolution; + } + } + + @Override + public boolean follow() throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean unfollow() throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setSite(Site site) { + if(site instanceof BongaCams) { + this.site = (BongaCams) site; + } else { + throw new IllegalArgumentException("Site has to be an instance of BongaCams"); + } + } + + @Override + public Site getSite() { + return site; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java new file mode 100644 index 00000000..ebe42de3 --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsTabProvider.java @@ -0,0 +1,39 @@ +package ctbrec.sites.bonga; + +import java.util.ArrayList; +import java.util.List; + +import ctbrec.recorder.Recorder; +import ctbrec.ui.PaginatedScheduledService; +import ctbrec.ui.TabProvider; +import ctbrec.ui.ThumbOverviewTab; +import javafx.scene.Scene; +import javafx.scene.control.Tab; + +public class BongaCamsTabProvider extends TabProvider { + + private BongaCams bongaCams; + private Recorder recorder; + + public BongaCamsTabProvider(Recorder recorder, BongaCams bongaCams) { + this.recorder = recorder; + this.bongaCams = bongaCams; + } + + @Override + public List getTabs(Scene scene) { + List tabs = new ArrayList<>(); + + BongaCamsUpdateService updateService = new BongaCamsUpdateService(bongaCams); + tabs.add(createTab("Online", updateService)); + + return tabs; + } + + private Tab createTab(String title, PaginatedScheduledService updateService) { + ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, bongaCams); + tab.setRecorder(recorder); + return tab; + } + +} diff --git a/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java new file mode 100644 index 00000000..4d2164ba --- /dev/null +++ b/src/main/java/ctbrec/sites/bonga/BongaCamsUpdateService.java @@ -0,0 +1,71 @@ +package ctbrec.sites.bonga; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.json.JSONArray; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Model; +import ctbrec.ui.PaginatedScheduledService; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.Response; + +public class BongaCamsUpdateService extends PaginatedScheduledService { + + private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsUpdateService.class); + + private BongaCams bongaCams; + + public BongaCamsUpdateService(BongaCams bongaCams) { + this.bongaCams = bongaCams; + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + String url = BongaCams.BASE_URL + "/tools/listing_v3.php?livetab=female&online_only=true&is_mobile=true&offset=" + ((page-1) * 50); + LOG.debug("Fetching page {}", url); + Request request = new Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:61.0) Gecko/61.0 Firefox/61.0") + .addHeader("Accept", "application/json, text/javascript, */*") + .addHeader("Accept-Language", "en") + .addHeader("Referer", bongaCams.getBaseUrl()) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + Response response = bongaCams.getHttpClient().execute(request); + if (response.isSuccessful()) { + String content = response.body().string(); + response.close(); + List models = new ArrayList<>(); + JSONObject json = new JSONObject(content); + if(json.optString("status").equals("success")) { + JSONArray _models = json.getJSONArray("models"); + for (int i = 0; i < _models.length(); i++) { + JSONObject m = _models.getJSONObject(i); + String name = m.getString("username"); + BongaCamsModel model = (BongaCamsModel) bongaCams.createModel(name); + model.setUserId(m.getInt("user_id")); + model.setOnlineState(m.getString("room")); + model.setOnline(m.optBoolean("online") && !m.optBoolean("is_away")); + model.setPreview("https:" + m.getString("thumb_image")); + models.add(model); + } + } + return models; + } else { + int code = response.code(); + response.close(); + throw new IOException("HTTP status " + code); + } + } + }; + } +} diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java index 90be7e79..3fdf6466 100644 --- a/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/src/main/java/ctbrec/ui/CamrecApplication.java @@ -27,6 +27,7 @@ import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.Recorder; import ctbrec.recorder.RemoteRecorder; import ctbrec.sites.Site; +import ctbrec.sites.bonga.BongaCams; import ctbrec.sites.cam4.Cam4; import ctbrec.sites.camsoda.Camsoda; import ctbrec.sites.chaturbate.Chaturbate; @@ -64,6 +65,7 @@ public class CamrecApplication extends Application { sites.add(new MyFreeCams()); sites.add(new Camsoda()); sites.add(new Cam4()); + sites.add(new BongaCams()); loadConfig(); createHttpClient(); bus = new AsyncEventBus(Executors.newSingleThreadExecutor());