forked from j62/ctbrec
1
0
Fork 0

Add followed tab for Cam4

This commit is contained in:
0xboobface 2018-10-29 21:53:41 +01:00
parent dab3466cf6
commit 2ffdbfa71a
7 changed files with 319 additions and 21 deletions

View File

@ -2,17 +2,31 @@ package ctbrec.sites.cam4;
import java.io.IOException;
import org.slf4j.LoggerFactory;
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;
public class Cam4 extends AbstractSite {
public static final String BASE_URI = "https://www.cam4.com";
public static final String AFFILIATE_LINK = BASE_URI + "/?referrerId=1514a80d87b5effb456cca02f6743aa1";
private HttpClient httpClient;
private Recorder recorder;
@ -28,7 +42,7 @@ public class Cam4 extends AbstractSite {
@Override
public String getAffiliateLink() {
return getBaseUrl() + "/?referrerId=1514a80d87b5effb456cca02f6743aa1";
return AFFILIATE_LINK;
}
@Override
@ -62,7 +76,10 @@ public class Cam4 extends AbstractSite {
@Override
public void login() throws IOException {
getHttpClient().login();
if (credentialsAvailable()) {
boolean success = getHttpClient().login();
LoggerFactory.getLogger(getClass()).debug("Login success: {}", success);
}
}
@Override
@ -89,7 +106,7 @@ public class Cam4 extends AbstractSite {
@Override
public boolean supportsFollow() {
return false;
return true;
}
@Override
@ -98,13 +115,38 @@ public class Cam4 extends AbstractSite {
}
@Override
public Node getConfigurationGui() {
return null;
public boolean credentialsAvailable() {
String username = Config.getInstance().getSettings().cam4Username;
return username != null && !username.trim().isEmpty();
}
@Override
public boolean credentialsAvailable() {
return false;
}
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;
}
}

View File

@ -0,0 +1,75 @@
package ctbrec.sites.cam4;
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 Cam4FollowedTab extends ThumbOverviewTab implements FollowedTab {
private Label status;
public Cam4FollowedTab(Cam4 cam4) {
super("Followed", new Cam4FollowedUpdateService(cam4), cam4);
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) -> {
((Cam4FollowedUpdateService)updateService).setShowOnline(online.isSelected());
queue.clear();
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,94 @@
package ctbrec.sites.cam4;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.stream.Collectors;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ui.HtmlParser;
import ctbrec.ui.PaginatedScheduledService;
import javafx.concurrent.Task;
import okhttp3.Request;
import okhttp3.Response;
public class Cam4FollowedUpdateService extends PaginatedScheduledService {
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4FollowedUpdateService.class);
private Cam4 site;
private boolean showOnline = true;
public Cam4FollowedUpdateService(Cam4 site) {
this.site = site;
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("ThumbOverviewTab UpdateService");
return t;
}
});
setExecutor(executor);
}
@Override
protected Task<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
List<Model> models = new ArrayList<>();
String username = Config.getInstance().getSettings().cam4Username;
String url = site.getBaseUrl() + '/' + username + "/edit/friends_favorites";
Request req = new Request.Builder().url(url).build();
Response response = site.getHttpClient().execute(req, true);
if(response.isSuccessful()) {
String content = response.body().string();
Elements cells = HtmlParser.getTags(content, "div#favorites div.ff_thumb");
for (Element cell : cells) {
String cellHtml = cell.html();
Element link = HtmlParser.getTag(cellHtml, "div.ff_img a");
String path = link.attr("href");
String modelName = path.substring(1);
Cam4Model model = (Cam4Model) site.createModel(modelName);
model.setPreview("https://snapshots.xcdnpro.com/thumbnails/"+model.getName()+"?s=" + System.currentTimeMillis());
model.setOnlineState(parseOnlineState(cellHtml));
models.add(model);
}
return models.stream()
.filter(m -> {
try {
return m.isOnline() == showOnline;
} catch (IOException | ExecutionException | InterruptedException e) {
LOG.error("Couldn't determine online state", e);
return false;
}
}).collect(Collectors.toList());
} else {
IOException e = new IOException(response.code() + " " + response.message());
response.close();
throw e;
}
}
private String parseOnlineState(String cellHtml) {
Element state = HtmlParser.getTag(cellHtml, "div.ff_name div");
return state.attr("class").equals("online") ? "NORMAL" : "OFFLINE";
}
};
}
public void setShowOnline(boolean online) {
this.showOnline = online;
}
}

View File

@ -5,10 +5,15 @@ import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.io.HttpClient;
import javafx.application.Platform;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
import okhttp3.Request;
@ -16,15 +21,43 @@ import okhttp3.Response;
public class Cam4HttpClient extends HttpClient {
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4HttpClient.class);
@Override
public boolean login() throws IOException {
// login with javafx WebView
Cam4LoginDialog loginDialog = new Cam4LoginDialog();
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
LOG.debug("Launching dialog");
// transfer cookies from WebView to OkHttp cookie jar
transferCookies(loginDialog);
Runnable showDialog = () -> {
// login with javafx WebView
Cam4LoginDialog loginDialog = new Cam4LoginDialog();
return checkLoginSuccess();
// 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);
LOG.debug("waiting for queue");
try {
queue.take();
} catch (InterruptedException e) {
LOG.error("Error while waiting for login dialog to close", e);
throw new IOException(e);
}
}
loggedIn = checkLoginSuccess();
return loggedIn;
}
/**

View File

@ -9,6 +9,7 @@ import java.util.concurrent.ExecutionException;
import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -22,9 +23,13 @@ import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.PlaylistData;
import ctbrec.AbstractModel;
import ctbrec.Config;
import ctbrec.recorder.download.StreamSource;
import ctbrec.sites.Site;
import ctbrec.ui.HtmlParser;
import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class Cam4Model extends AbstractModel {
@ -147,14 +152,57 @@ public class Cam4Model extends AbstractModel {
@Override
public boolean follow() throws IOException {
// TODO Auto-generated method stub
return false;
String url = site.getBaseUrl() + "/profiles/addFriendFavorite?action=addFavorite&object=" + getName() + "&_=" + System.currentTimeMillis();
Request req = new Request.Builder()
.url(url)
.addHeader("X-Requested-With", "XMLHttpRequest")
.build();
Response response = site.getHttpClient().execute(req, true);
boolean success = response.isSuccessful();
response.close();
return success;
}
@Override
public boolean unfollow() throws IOException {
// TODO Auto-generated method stub
return false;
// get model user id
String url = site.getBaseUrl() + '/' + getName();
Request req = new Request.Builder().url(url).build();
Response response = site.getHttpClient().execute(req, true);
String broadCasterId = null;
if(response.isSuccessful()) {
String content = response.body().string();
try {
Element tag = HtmlParser.getTag(content, "input[name=\"broadcasterId\"]");
broadCasterId = tag.attr("value");
} catch(Exception e) {
LOG.debug(content);
throw new IOException(e);
}
// send unfollow request
String username = Config.getInstance().getSettings().cam4Username;
url = site.getBaseUrl() + '/' + username + "/edit/friends_favorites";
RequestBody body = new FormBody.Builder()
.add("deleteFavorites", broadCasterId)
.add("simpleresult", "true")
.build();
req = new Request.Builder()
.url(url)
.post(body)
.addHeader("X-Requested-With", "XMLHttpRequest")
.build();
response = site.getHttpClient().execute(req, true);
if(response.isSuccessful()) {
return Objects.equals(response.body().string(), "Ok");
} else {
response.close();
return false;
}
} else {
response.close();
return false;
}
}
@Override
@ -174,4 +222,8 @@ public class Cam4Model extends AbstractModel {
public void setPlaylistUrl(String playlistUrl) {
this.playlistUrl = playlistUrl;
}
public void setOnlineState(String onlineState) {
this.onlineState = onlineState;
}
}

View File

@ -25,7 +25,11 @@ public class Cam4TabProvider extends TabProvider {
tabs.add(createTab("Female", cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS"));
tabs.add(createTab("HD", cam4.getBaseUrl() + "/directoryResults?online=true&hd=true&orderBy=VIDEO_QUALITY"));
if(cam4.credentialsAvailable()) {
Cam4FollowedTab followed = new Cam4FollowedTab(cam4);
followed.setRecorder(recorder);
tabs.add(followed);
}
return tabs;
}

View File

@ -52,7 +52,7 @@ public class Cam4UpdateService extends PaginatedScheduledService {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().username)) { // FIXME change to cam4 username
if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().cam4Username)) {
return Collections.emptyList();
} else {
String url = Cam4UpdateService.this.url + "&page=" + page;
@ -72,10 +72,8 @@ public class Cam4UpdateService extends PaginatedScheduledService {
Cam4Model model = (Cam4Model) site.createModel(slug);
String playlistUrl = profileLink.attr("data-hls-preview-url");
model.setPlaylistUrl(playlistUrl);
//model.setPreview(HtmlParser.getTag(boxHtml, "a img").attr("data-src"));
model.setPreview("https://snapshots.xcdnpro.com/thumbnails/"+model.getName()+"?s=" + System.currentTimeMillis());
model.setDescription(parseDesription(boxHtml));
//model.setOnlineState(parseOnlineState(boxHtml));
models.add(model);
}
response.close();