From 8fd09fd521bb347ae78841db27a63131f076a32d Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sun, 28 Oct 2018 21:23:58 +0100 Subject: [PATCH] Implemented followed tab for CamSoda --- src/main/java/ctbrec/Settings.java | 2 + .../java/ctbrec/sites/camsoda/Camsoda.java | 87 +++++++++++++++---- .../sites/camsoda/CamsodaFollowedTab.java | 76 ++++++++++++++++ .../camsoda/CamsodaFollowedUpdateService.java | 73 ++++++++++++++++ .../sites/camsoda/CamsodaHttpClient.java | 38 ++++++++ .../ctbrec/sites/camsoda/CamsodaModel.java | 86 +++++++++++------- .../ctbrec/sites/camsoda/CamsodaShowsTab.java | 16 ++-- .../sites/camsoda/CamsodaTabProvider.java | 8 +- src/main/java/ctbrec/ui/JavaFxModel.java | 6 +- src/main/java/ctbrec/ui/TokenLabel.java | 10 ++- 10 files changed, 340 insertions(+), 62 deletions(-) create mode 100644 src/main/java/ctbrec/sites/camsoda/CamsodaFollowedTab.java create mode 100644 src/main/java/ctbrec/sites/camsoda/CamsodaFollowedUpdateService.java create mode 100644 src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index 97b8bcd4..d1f41691 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -24,6 +24,8 @@ public class Settings { public String password = ""; // chaturbate password TODO maybe rename this onetime public String mfcUsername = ""; public String mfcPassword = ""; + public String camsodaUsername = ""; + public String camsodaPassword = ""; public String lastDownloadDir = ""; public List models = new ArrayList(); diff --git a/src/main/java/ctbrec/sites/camsoda/Camsoda.java b/src/main/java/ctbrec/sites/camsoda/Camsoda.java index c0ce5337..c03139d5 100644 --- a/src/main/java/ctbrec/sites/camsoda/Camsoda.java +++ b/src/main/java/ctbrec/sites/camsoda/Camsoda.java @@ -2,12 +2,26 @@ package ctbrec.sites.camsoda; 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.ui.DesktopIntergation; +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; public class Camsoda extends AbstractSite { @@ -27,7 +41,7 @@ public class Camsoda extends AbstractSite { @Override public String getAffiliateLink() { - return ""; + return BASE_URI; } @Override @@ -51,7 +65,26 @@ public class Camsoda extends AbstractSite { @Override public Integer getTokenBalance() throws IOException { - return 0; + String username = Config.getInstance().getSettings().camsodaUsername; + if (username == null || username.trim().isEmpty()) { + throw new IOException("Not logged in"); + } + + String url = BASE_URI + "/api/v1/user/" + username; + Request request = new Request.Builder().url(url).build(); + Response response = getHttpClient().execute(request, true); + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.has("user")) { + JSONObject user = json.getJSONObject("user"); + if(user.has("tokens")) { + return user.getInt("tokens"); + } + } + } else { + throw new IOException(response.code() + " " + response.message()); + } + throw new RuntimeException("Tokens not found in response"); } @Override @@ -61,23 +94,19 @@ public class Camsoda extends AbstractSite { @Override public void login() throws IOException { - httpClient.login(); + getHttpClient().login(); } @Override public HttpClient getHttpClient() { + if(httpClient == null) { + httpClient = new CamsodaHttpClient(); + } return httpClient; } @Override public void init() throws IOException { - httpClient = new HttpClient() { - @Override - public boolean login() throws IOException { - return false; - } - - }; } @Override @@ -87,12 +116,12 @@ public class Camsoda extends AbstractSite { @Override public boolean supportsTips() { - return false; + return true; } @Override public boolean supportsFollow() { - return false; + return true; } @Override @@ -101,12 +130,38 @@ public class Camsoda extends AbstractSite { } @Override - public Node getConfigurationGui() { - return null; + public boolean credentialsAvailable() { + String username = Config.getInstance().getSettings().camsodaUsername; + return username != null && !username.trim().isEmpty(); } @Override - public boolean credentialsAvailable() { - return false; + 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; } } diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaFollowedTab.java b/src/main/java/ctbrec/sites/camsoda/CamsodaFollowedTab.java new file mode 100644 index 00000000..8ef31c6f --- /dev/null +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaFollowedTab.java @@ -0,0 +1,76 @@ +package ctbrec.sites.camsoda; + +import ctbrec.ui.FollowedTab; +import ctbrec.ui.ThumbOverviewTab; +import javafx.concurrent.WorkerStateEvent; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; + +public class CamsodaFollowedTab extends ThumbOverviewTab implements FollowedTab { + private Label status; + boolean showOnline = true; + + public CamsodaFollowedTab(String title, Camsoda camsoda) { + super(title, new CamsodaFollowedUpdateService(camsoda), camsoda); + status = new Label("Logging in..."); + grid.getChildren().add(status); + } + + @Override + protected void createGui() { + super.createGui(); + addOnlineOfflineSelector(); + } + + private void addOnlineOfflineSelector() { + ToggleGroup group = new ToggleGroup(); + RadioButton online = new RadioButton("online"); + online.setToggleGroup(group); + RadioButton offline = new RadioButton("offline"); + offline.setToggleGroup(group); + pagination.getChildren().add(online); + pagination.getChildren().add(offline); + HBox.setMargin(online, new Insets(5, 5, 5, 40)); + HBox.setMargin(offline, new Insets(5, 5, 5, 5)); + online.setSelected(true); + group.selectedToggleProperty().addListener((e) -> { + queue.clear(); + ((CamsodaFollowedUpdateService)updateService).showOnline(online.isSelected()); + updateService.restart(); + }); + } + + @Override + protected void onSuccess() { + grid.getChildren().remove(status); + super.onSuccess(); + } + + @Override + protected void onFail(WorkerStateEvent event) { + status.setText("Login failed"); + super.onFail(event); + } + + @Override + public void selected() { + status.setText("Logging in..."); + super.selected(); + } + + public void setScene(Scene scene) { + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + if (this.isSelected()) { + if (event.getCode() == KeyCode.DELETE) { + follow(selectedThumbCells, false); + } + } + }); + } +} diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaFollowedUpdateService.java b/src/main/java/ctbrec/sites/camsoda/CamsodaFollowedUpdateService.java new file mode 100644 index 00000000..e9577906 --- /dev/null +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaFollowedUpdateService.java @@ -0,0 +1,73 @@ +package ctbrec.sites.camsoda; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +import org.json.JSONArray; +import org.json.JSONObject; + +import ctbrec.Model; +import ctbrec.ui.PaginatedScheduledService; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.Response; + +public class CamsodaFollowedUpdateService extends PaginatedScheduledService { + private Camsoda camsoda; + private boolean showOnline = true; + + public CamsodaFollowedUpdateService(Camsoda camsoda) { + this.camsoda = camsoda; + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + List models = new ArrayList<>(); + String url = camsoda.getBaseUrl() + "/api/v1/user/current"; + Request request = new Request.Builder().url(url).build(); + Response response = camsoda.getHttpClient().execute(request, true); + if (response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.has("status") && json.getBoolean("status")) { + JSONObject user = json.getJSONObject("user"); + JSONArray following = user.getJSONArray("following"); + for (int i = 0; i < following.length(); i++) { + JSONObject m = following.getJSONObject(i); + CamsodaModel model = (CamsodaModel) camsoda.createModel(m.getString("followname")); + boolean online = m.getInt("online") == 1; + model.setOnlineState(online ? "offline" : "online"); + model.setPreview("https://md.camsoda.com/thumbs/" + model.getName() + ".jpg"); + models.add(model); + } + return models.stream() + .filter((m) -> { + try { + return m.isOnline() == showOnline; + } catch (IOException | ExecutionException | InterruptedException e) { + return false; + } + }).collect(Collectors.toList()); + } else { + response.close(); + return Collections.emptyList(); + } + } else { + int code = response.code(); + response.close(); + throw new IOException("HTTP status " + code); + } + } + }; + } + + void showOnline(boolean online) { + this.showOnline = online; + } +} diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java b/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java new file mode 100644 index 00000000..76e3b98b --- /dev/null +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaHttpClient.java @@ -0,0 +1,38 @@ +package ctbrec.sites.camsoda; + +import java.io.IOException; + +import org.json.JSONObject; + +import ctbrec.Config; +import ctbrec.io.HttpClient; +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.Response; + +public class CamsodaHttpClient extends HttpClient { + + @Override + public boolean login() throws IOException { + String url = Camsoda.BASE_URI + "/api/v1/auth/login"; + FormBody body = new FormBody.Builder() + .add("username", Config.getInstance().getSettings().camsodaUsername) + .add("password", Config.getInstance().getSettings().camsodaPassword) + .build(); + Request request = new Request.Builder() + .url(url) + .post(body) + .build(); + Response response = execute(request); + if(response.isSuccessful()) { + JSONObject resp = new JSONObject(response.body().string()); + if(resp.has("error")) { + throw new IOException(resp.getString("error")); + } else { + return true; + } + } else { + throw new IOException(response.code() + " " + response.message()); + } + } +} diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index 5f0d58bb..fcce75ae 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -25,6 +25,7 @@ import ctbrec.AbstractModel; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.Site; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; public class CamsodaModel extends AbstractModel { @@ -94,32 +95,30 @@ public class CamsodaModel extends AbstractModel { @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { - LOG.trace("Loading master playlist {}", streamUrl); - if(streamSources == null) { - 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(); - PlaylistData playlistData = master.getPlaylists().get(0); - 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 = Collections.singletonList(streamsource); - } finally { - response.close(); + LOG.debug("Loading master playlist {}", streamUrl); + 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(); + PlaylistData playlistData = master.getPlaylists().get(0); + 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 = Collections.singletonList(streamsource); + } finally { + response.close(); } return streamSources; } @@ -153,18 +152,47 @@ public class CamsodaModel extends AbstractModel { @Override public void receiveTip(int tokens) throws IOException { // TODO Auto-generated method stub - + /* + sendTip: function(i, a, r, o, c, d) { + if (!s.isAuthenticated()) return s.showRegister(), t.when(!1); + var u = t.defer(); + return e.post("/api/v1/tip/" + i, { + amount: a, + comment: o, + type: r, + app_data: c, + source_id: d + }).then(function(e) { + 1 == e.data.status ? (s.currentUser.tokens = e.data.total, void 0 != e.data.tipped_performer_last_24hrs && e.data.tipped_performer_last_24hrs >= 25 && (n.$emit("local.allowed_to_rate"), 0 == n.allowedToRate && (n.allowedToRate = !0, l.pop("info", "Voting Unlocked", "You tipped " + i + " 25 tokens in the past 24 hours, you may now vote!"))), u.resolve(e.data)) : (l.pop("error", e.data.error, e.data.message), u.reject(e.data)) + }), u.promise + }, + */ } @Override public boolean follow() throws IOException { - // TODO Auto-generated method stub - return false; + String url = Camsoda.BASE_URI + "/api/v1/follow/" + getName(); + //RequestBody body = new FormBody.Builder().build(); + LOG.debug("Sending follow request {}", url); + Request request = new Request.Builder() + .url(url) + .post(RequestBody.create(null, "")) + .addHeader("Content-Lentgh", "0") + .addHeader("Referer", Camsoda.BASE_URI + '/' + getName()) + .build(); + Response resp = site.getHttpClient().execute(request, true); + if (resp.isSuccessful()) { + System.out.println(resp.body().string()); + return true; + } else { + resp.close(); + throw new IOException("HTTP status " + resp.code() + " " + resp.message()); + } } @Override public boolean unfollow() throws IOException { - // TODO Auto-generated method stub + // TODO /api/v1/unfollow/" + n.slug return false; } diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java b/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java index 53972c87..1eceeae3 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaShowsTab.java @@ -33,13 +33,13 @@ import javafx.scene.control.Alert; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.ScrollPane; import javafx.scene.control.Tab; import javafx.scene.control.TitledPane; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; -import javafx.scene.layout.VBox; import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import okhttp3.Request; @@ -51,7 +51,7 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener { private Camsoda camsoda; private Recorder recorder; - private VBox showList; + private GridPane showList; private ProgressIndicator progressIndicator; public CamsodaShowsTab(Camsoda camsoda, Recorder recorder) { @@ -61,7 +61,10 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener { } private void createGui() { - showList = new VBox(); + showList = new GridPane(); + showList.setPadding(new Insets(5)); + showList.setHgap(5); + showList.setVgap(5); progressIndicator = new ProgressIndicator(); progressIndicator.setPrefSize(100, 100); setContent(progressIndicator); @@ -118,14 +121,15 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener { try { List boxes = get(); showList.getChildren().clear(); + int index = 0; for (ShowBox showBox : boxes) { - showList.getChildren().add(showBox); - VBox.setMargin(showBox, new Insets(20, 20, 0, 20)); + showList.add(showBox, index%2, index++/2); + GridPane.setMargin(showBox, new Insets(20, 20, 0, 20)); } } catch (Exception e) { LOG.error("Couldn't load upcoming camsoda shows", e); } - setContent(showList); + setContent(new ScrollPane(showList)); }); } }; diff --git a/src/main/java/ctbrec/sites/camsoda/CamsodaTabProvider.java b/src/main/java/ctbrec/sites/camsoda/CamsodaTabProvider.java index bb431001..9aa552a7 100644 --- a/src/main/java/ctbrec/sites/camsoda/CamsodaTabProvider.java +++ b/src/main/java/ctbrec/sites/camsoda/CamsodaTabProvider.java @@ -25,11 +25,11 @@ public class CamsodaTabProvider extends TabProvider { public List getTabs(Scene scene) { List tabs = new ArrayList<>(); tabs.add(createTab("Online", BASE_URI + "/api/v1/browse/online")); + CamsodaFollowedTab followedTab = new CamsodaFollowedTab("Followed", camsoda); + followedTab.setRecorder(recorder); + followedTab.setScene(scene); + tabs.add(followedTab); tabs.add(new CamsodaShowsTab(camsoda, recorder)); - // ChaturbateFollowedTab followedTab = new ChaturbateFollowedTab("Followed", BASE_URI + "/followed-cams/", chaturbate); - // followedTab.setRecorder(recorder); - // followedTab.setScene(scene); - // tabs.add(followedTab); return tabs; } diff --git a/src/main/java/ctbrec/ui/JavaFxModel.java b/src/main/java/ctbrec/ui/JavaFxModel.java index 1ede0c64..c713020c 100644 --- a/src/main/java/ctbrec/ui/JavaFxModel.java +++ b/src/main/java/ctbrec/ui/JavaFxModel.java @@ -2,7 +2,6 @@ package ctbrec.ui; import java.io.IOException; import java.util.List; -import java.util.Objects; import java.util.concurrent.ExecutionException; import com.iheartradio.m3u8.ParseException; @@ -20,14 +19,13 @@ import javafx.beans.property.SimpleBooleanProperty; */ public class JavaFxModel extends AbstractModel { private transient BooleanProperty onlineProperty = new SimpleBooleanProperty(); - private Model delegate; public JavaFxModel(Model delegate) { this.delegate = delegate; try { - onlineProperty.set(Objects.equals("public", delegate.getOnlineState(true))); - } catch (IOException | ExecutionException e) {} + onlineProperty.set(delegate.isOnline()); + } catch (IOException | ExecutionException | InterruptedException e) {} } @Override diff --git a/src/main/java/ctbrec/ui/TokenLabel.java b/src/main/java/ctbrec/ui/TokenLabel.java index a3123a34..30471ab8 100644 --- a/src/main/java/ctbrec/ui/TokenLabel.java +++ b/src/main/java/ctbrec/ui/TokenLabel.java @@ -13,6 +13,7 @@ import ctbrec.sites.Site; import javafx.application.Platform; import javafx.concurrent.Task; import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; public class TokenLabel extends Label { @@ -26,10 +27,10 @@ public class TokenLabel extends Label { CamrecApplication.bus.register(new Object() { @Subscribe public void tokensUpdates(Map e) { - if(Objects.equals("tokens", e.get("event"))) { + if (Objects.equals("tokens", e.get("event"))) { tokens = (int) e.get("amount"); updateText(); - } else if(Objects.equals("tokens.sent", e.get("event"))) { + } else if (Objects.equals("tokens.sent", e.get("event"))) { int _tokens = (int) e.get("amount"); tokens -= _tokens; updateText(); @@ -70,7 +71,10 @@ public class TokenLabel extends Label { update(tokens); } catch (InterruptedException | ExecutionException e) { LOG.error("Couldn't retrieve account balance", e); - Platform.runLater(() -> setText("Tokens: error")); + Platform.runLater(() -> { + setText("Tokens: error"); + setTooltip(new Tooltip(e.getMessage())); + }); } } };