forked from j62/ctbrec
Add followed tab for Cam4
This commit is contained in:
parent
dab3466cf6
commit
2ffdbfa71a
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue