Implemented followed tab for CamSoda

This commit is contained in:
0xboobface 2018-10-28 21:23:58 +01:00
parent 7442ddd3e4
commit 8fd09fd521
10 changed files with 340 additions and 62 deletions

View File

@ -24,6 +24,8 @@ public class Settings {
public String password = ""; // chaturbate password TODO maybe rename this onetime public String password = ""; // chaturbate password TODO maybe rename this onetime
public String mfcUsername = ""; public String mfcUsername = "";
public String mfcPassword = ""; public String mfcPassword = "";
public String camsodaUsername = "";
public String camsodaPassword = "";
public String lastDownloadDir = ""; public String lastDownloadDir = "";
public List<Model> models = new ArrayList<Model>(); public List<Model> models = new ArrayList<Model>();

View File

@ -2,12 +2,26 @@ package ctbrec.sites.camsoda;
import java.io.IOException; import java.io.IOException;
import org.json.JSONObject;
import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.AbstractSite; import ctbrec.sites.AbstractSite;
import ctbrec.ui.DesktopIntergation;
import ctbrec.ui.SettingsTab;
import ctbrec.ui.TabProvider; import ctbrec.ui.TabProvider;
import javafx.geometry.Insets;
import javafx.scene.Node; 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 { public class Camsoda extends AbstractSite {
@ -27,7 +41,7 @@ public class Camsoda extends AbstractSite {
@Override @Override
public String getAffiliateLink() { public String getAffiliateLink() {
return ""; return BASE_URI;
} }
@Override @Override
@ -51,7 +65,26 @@ public class Camsoda extends AbstractSite {
@Override @Override
public Integer getTokenBalance() throws IOException { 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 @Override
@ -61,23 +94,19 @@ public class Camsoda extends AbstractSite {
@Override @Override
public void login() throws IOException { public void login() throws IOException {
httpClient.login(); getHttpClient().login();
} }
@Override @Override
public HttpClient getHttpClient() { public HttpClient getHttpClient() {
if(httpClient == null) {
httpClient = new CamsodaHttpClient();
}
return httpClient; return httpClient;
} }
@Override @Override
public void init() throws IOException { public void init() throws IOException {
httpClient = new HttpClient() {
@Override
public boolean login() throws IOException {
return false;
}
};
} }
@Override @Override
@ -87,12 +116,12 @@ public class Camsoda extends AbstractSite {
@Override @Override
public boolean supportsTips() { public boolean supportsTips() {
return false; return true;
} }
@Override @Override
public boolean supportsFollow() { public boolean supportsFollow() {
return false; return true;
} }
@Override @Override
@ -101,12 +130,38 @@ public class Camsoda extends AbstractSite {
} }
@Override @Override
public Node getConfigurationGui() { public boolean credentialsAvailable() {
return null; String username = Config.getInstance().getSettings().camsodaUsername;
return username != null && !username.trim().isEmpty();
} }
@Override @Override
public boolean credentialsAvailable() { public Node getConfigurationGui() {
return false; 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;
} }
} }

View File

@ -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);
}
}
});
}
}

View File

@ -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<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
List<Model> 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;
}
}

View File

@ -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());
}
}
}

View File

@ -25,6 +25,7 @@ import ctbrec.AbstractModel;
import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
public class CamsodaModel extends AbstractModel { public class CamsodaModel extends AbstractModel {
@ -94,32 +95,30 @@ public class CamsodaModel extends AbstractModel {
@Override @Override
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
LOG.trace("Loading master playlist {}", streamUrl); LOG.debug("Loading master playlist {}", streamUrl);
if(streamSources == null) { Request req = new Request.Builder().url(streamUrl).build();
Request req = new Request.Builder().url(streamUrl).build(); Response response = site.getHttpClient().execute(req);
Response response = site.getHttpClient().execute(req); try {
try { InputStream inputStream = response.body().byteStream();
InputStream inputStream = response.body().byteStream(); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); Playlist playlist = parser.parse();
Playlist playlist = parser.parse(); MasterPlaylist master = playlist.getMasterPlaylist();
MasterPlaylist master = playlist.getMasterPlaylist(); PlaylistData playlistData = master.getPlaylists().get(0);
PlaylistData playlistData = master.getPlaylists().get(0); StreamSource streamsource = new StreamSource();
StreamSource streamsource = new StreamSource(); streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri());
streamsource.mediaPlaylistUrl = streamUrl.replace("playlist.m3u8", playlistData.getUri()); if(playlistData.hasStreamInfo()) {
if(playlistData.hasStreamInfo()) { StreamInfo info = playlistData.getStreamInfo();
StreamInfo info = playlistData.getStreamInfo(); streamsource.bandwidth = info.getBandwidth();
streamsource.bandwidth = info.getBandwidth(); streamsource.width = info.hasResolution() ? info.getResolution().width : 0;
streamsource.width = info.hasResolution() ? info.getResolution().width : 0; streamsource.height = info.hasResolution() ? info.getResolution().height : 0;
streamsource.height = info.hasResolution() ? info.getResolution().height : 0; } else {
} else { streamsource.bandwidth = 0;
streamsource.bandwidth = 0; streamsource.width = 0;
streamsource.width = 0; streamsource.height = 0;
streamsource.height = 0;
}
streamSources = Collections.singletonList(streamsource);
} finally {
response.close();
} }
streamSources = Collections.singletonList(streamsource);
} finally {
response.close();
} }
return streamSources; return streamSources;
} }
@ -153,18 +152,47 @@ public class CamsodaModel extends AbstractModel {
@Override @Override
public void receiveTip(int tokens) throws IOException { public void receiveTip(int tokens) throws IOException {
// TODO Auto-generated method stub // 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 @Override
public boolean follow() throws IOException { public boolean follow() throws IOException {
// TODO Auto-generated method stub String url = Camsoda.BASE_URI + "/api/v1/follow/" + getName();
return false; //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 @Override
public boolean unfollow() throws IOException { public boolean unfollow() throws IOException {
// TODO Auto-generated method stub // TODO /api/v1/unfollow/" + n.slug
return false; return false;
} }

View File

@ -33,13 +33,13 @@ import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator; import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TitledPane; import javafx.scene.control.TitledPane;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane; import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane; import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.FontWeight; import javafx.scene.text.FontWeight;
import okhttp3.Request; import okhttp3.Request;
@ -51,7 +51,7 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
private Camsoda camsoda; private Camsoda camsoda;
private Recorder recorder; private Recorder recorder;
private VBox showList; private GridPane showList;
private ProgressIndicator progressIndicator; private ProgressIndicator progressIndicator;
public CamsodaShowsTab(Camsoda camsoda, Recorder recorder) { public CamsodaShowsTab(Camsoda camsoda, Recorder recorder) {
@ -61,7 +61,10 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
} }
private void createGui() { private void createGui() {
showList = new VBox(); showList = new GridPane();
showList.setPadding(new Insets(5));
showList.setHgap(5);
showList.setVgap(5);
progressIndicator = new ProgressIndicator(); progressIndicator = new ProgressIndicator();
progressIndicator.setPrefSize(100, 100); progressIndicator.setPrefSize(100, 100);
setContent(progressIndicator); setContent(progressIndicator);
@ -118,14 +121,15 @@ public class CamsodaShowsTab extends Tab implements TabSelectionListener {
try { try {
List<ShowBox> boxes = get(); List<ShowBox> boxes = get();
showList.getChildren().clear(); showList.getChildren().clear();
int index = 0;
for (ShowBox showBox : boxes) { for (ShowBox showBox : boxes) {
showList.getChildren().add(showBox); showList.add(showBox, index%2, index++/2);
VBox.setMargin(showBox, new Insets(20, 20, 0, 20)); GridPane.setMargin(showBox, new Insets(20, 20, 0, 20));
} }
} catch (Exception e) { } catch (Exception e) {
LOG.error("Couldn't load upcoming camsoda shows", e); LOG.error("Couldn't load upcoming camsoda shows", e);
} }
setContent(showList); setContent(new ScrollPane(showList));
}); });
} }
}; };

View File

@ -25,11 +25,11 @@ public class CamsodaTabProvider extends TabProvider {
public List<Tab> getTabs(Scene scene) { public List<Tab> getTabs(Scene scene) {
List<Tab> tabs = new ArrayList<>(); List<Tab> tabs = new ArrayList<>();
tabs.add(createTab("Online", BASE_URI + "/api/v1/browse/online")); 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)); 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; return tabs;
} }

View File

@ -2,7 +2,6 @@ package ctbrec.ui;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.ParseException;
@ -20,14 +19,13 @@ import javafx.beans.property.SimpleBooleanProperty;
*/ */
public class JavaFxModel extends AbstractModel { public class JavaFxModel extends AbstractModel {
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty(); private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
private Model delegate; private Model delegate;
public JavaFxModel(Model delegate) { public JavaFxModel(Model delegate) {
this.delegate = delegate; this.delegate = delegate;
try { try {
onlineProperty.set(Objects.equals("public", delegate.getOnlineState(true))); onlineProperty.set(delegate.isOnline());
} catch (IOException | ExecutionException e) {} } catch (IOException | ExecutionException | InterruptedException e) {}
} }
@Override @Override

View File

@ -13,6 +13,7 @@ import ctbrec.sites.Site;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.concurrent.Task; import javafx.concurrent.Task;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
public class TokenLabel extends Label { public class TokenLabel extends Label {
@ -26,10 +27,10 @@ public class TokenLabel extends Label {
CamrecApplication.bus.register(new Object() { CamrecApplication.bus.register(new Object() {
@Subscribe @Subscribe
public void tokensUpdates(Map<String, Object> e) { public void tokensUpdates(Map<String, Object> e) {
if(Objects.equals("tokens", e.get("event"))) { if (Objects.equals("tokens", e.get("event"))) {
tokens = (int) e.get("amount"); tokens = (int) e.get("amount");
updateText(); 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"); int _tokens = (int) e.get("amount");
tokens -= _tokens; tokens -= _tokens;
updateText(); updateText();
@ -70,7 +71,10 @@ public class TokenLabel extends Label {
update(tokens); update(tokens);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
LOG.error("Couldn't retrieve account balance", e); LOG.error("Couldn't retrieve account balance", e);
Platform.runLater(() -> setText("Tokens: error")); Platform.runLater(() -> {
setText("Tokens: error");
setTooltip(new Tooltip(e.getMessage()));
});
} }
} }
}; };