Preparation for Showup login

This commit is contained in:
0xboobface 2020-05-16 21:30:25 +02:00
parent 6c85a2a493
commit 1a5c32167e
9 changed files with 534 additions and 10 deletions

View File

@ -0,0 +1,86 @@
package ctbrec.ui.sites.showup;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.sites.showup.Showup;
import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.settings.SettingsTab;
import ctbrec.ui.sites.AbstractConfigUI;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
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 ShowupConfigUI extends AbstractConfigUI {
private Showup site;
public ShowupConfigUI(Showup site) {
this.site = site;
}
@Override
public Parent createConfigPanel() {
GridPane layout = SettingsTab.createGridLayout();
Settings settings = Config.getInstance().getSettings();
int row = 0;
Label l = new Label("Active");
layout.add(l, 0, row);
CheckBox enabled = new CheckBox();
enabled.setSelected(!settings.disabledSites.contains(site.getName()));
enabled.setOnAction(e -> {
if(enabled.isSelected()) {
settings.disabledSites.remove(site.getName());
} else {
settings.disabledSites.add(site.getName());
}
save();
});
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
layout.add(enabled, 1, row++);
layout.add(new Label("Showup User"), 0, row);
TextField username = new TextField(Config.getInstance().getSettings().showupUsername);
username.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().showupUsername)) {
Config.getInstance().getSettings().showupUsername = username.getText();
site.getHttpClient().logout();
save();
}
});
GridPane.setFillWidth(username, true);
GridPane.setHgrow(username, Priority.ALWAYS);
GridPane.setColumnSpan(username, 2);
layout.add(username, 1, row++);
layout.add(new Label("Showup Password"), 0, row);
PasswordField password = new PasswordField();
password.setText(Config.getInstance().getSettings().showupPassword);
password.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().showupPassword)) {
Config.getInstance().getSettings().showupPassword = password.getText();
site.getHttpClient().logout();
save();
}
});
GridPane.setFillWidth(password, true);
GridPane.setHgrow(password, Priority.ALWAYS);
GridPane.setColumnSpan(password, 2);
layout.add(password, 1, row++);
Button createAccount = new Button("Create new Account");
createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
layout.add(createAccount, 1, row);
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,133 @@
package ctbrec.ui.sites.showup;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Objects;
import java.util.function.Consumer;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.sites.showup.Showup;
import ctbrec.ui.ExternalBrowser;
import okhttp3.Cookie;
import okhttp3.Cookie.Builder;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class ShowupElectronLoginDialog {
private static final Logger LOG = LoggerFactory.getLogger(ShowupElectronLoginDialog.class);
public static final String DOMAIN = "showup.tv";
public static final String URL = Showup.BASE_URL;
private CookieJar cookieJar;
private ExternalBrowser browser;
public ShowupElectronLoginDialog(CookieJar cookieJar) throws IOException {
this.cookieJar = cookieJar;
browser = ExternalBrowser.getInstance();
try {
JSONObject config = new JSONObject();
config.put("url", URL);
config.put("w", 640);
config.put("h", 480);
JSONObject msg = new JSONObject();
msg.put("config", config);
browser.run(msg, msgHandler);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Couldn't wait for login dialog", e);
} finally {
browser.close();
}
}
private Consumer<String> msgHandler = line -> {
if(!line.startsWith("{")) {
System.err.println(line); // NOSONAR
} else {
JSONObject json = new JSONObject(line);
//LOG.debug("Browser: {}", json.toString(2));
if(json.has("url")) {
String url = json.getString("url");
if(url.contains("/site/accept_rules")) {
try {
Thread.sleep(500);
String[] simplify = new String[] {
"document.getElementById(\"acceptrules\").submit();"
};
for (String js : simplify) {
browser.executeJavaScript(js);
}
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password for Showup", e);
}
} else if(url.equals(URL + '/')) {
try {
Thread.sleep(500);
String[] simplify = new String[] {
//"document.ready(function() {$('a[class~=\"login-btn\"]').click()});",
"console.log('Hello CTBREC');",
"user.openLoginPopUp();"
};
for (String js : simplify) {
browser.executeJavaScript(js);
}
String username = Config.getInstance().getSettings().showupUsername;
if (username != null && !username.trim().isEmpty()) {
browser.executeJavaScript("$('input[name=\"email\"]').attr('value','" + username + "')");
}
String password = Config.getInstance().getSettings().showupPassword;
if (password != null && !password.trim().isEmpty()) {
browser.executeJavaScript("$('input[name=\"password\"]').attr('value','" + password + "')");
}
browser.executeJavaScript("$('input[name=\"remember\"]').attr('value','true')");
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password for Showup", e);
}
}
if(json.has("cookies")) {
JSONArray _cookies = json.getJSONArray("cookies");
for (int i = 0; i < _cookies.length(); i++) {
JSONObject cookie = _cookies.getJSONObject(i);
if(cookie.getString("domain").contains(DOMAIN)) {
Builder b = new Cookie.Builder()
.path(cookie.getString("path"))
.domain(DOMAIN)
.name(cookie.getString("name"))
.value(cookie.getString("value"))
.expiresAt(Double.valueOf(cookie.optDouble("expirationDate")).longValue()); // NOSONAR
if(cookie.optBoolean("hostOnly")) {
b.hostOnlyDomain(DOMAIN);
}
if(cookie.optBoolean("httpOnly")) {
b.httpOnly();
}
if(cookie.optBoolean("secure")) {
b.secure();
}
Cookie c = b.build();
cookieJar.saveFromResponse(HttpUrl.parse(Showup.BASE_URL), Collections.singletonList(c));
}
}
}
try {
if (Objects.equals(new URL(url).getPath(), "/site/log_in")) {
browser.close();
}
} catch (MalformedURLException e) {
LOG.error("Couldn't parse new url {}", url, e);
} catch (IOException e) {
LOG.error("Couldn't send shutdown request to external browser", e);
}
}
}
};
}

View File

@ -0,0 +1,79 @@
package ctbrec.ui.sites.showup;
import ctbrec.sites.showup.Showup;
import ctbrec.ui.tabs.FollowedTab;
import ctbrec.ui.tabs.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 ShowupFollowedTab extends ThumbOverviewTab implements FollowedTab {
private Label status;
boolean showOnline = true;
public ShowupFollowedTab(String title, Showup showup) {
super(title, new ShowupFollowedUpdateService(showup), showup);
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();
((ShowupFollowedUpdateService)updateService).showOnline(online.isSelected());
updateService.restart();
});
}
@Override
protected void onSuccess() {
grid.getChildren().remove(status);
super.onSuccess();
}
@Override
protected void onFail(WorkerStateEvent event) {
String msg = "";
if (event.getSource().getException() != null) {
msg = ": " + event.getSource().getException().getMessage();
}
status.setText("Login failed" + msg);
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() && event.getCode() == KeyCode.DELETE) {
follow(selectedThumbCells, false);
}
});
}
}

View File

@ -0,0 +1,97 @@
package ctbrec.ui.sites.showup;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.json.JSONArray;
import org.json.JSONObject;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpException;
import ctbrec.sites.showup.Showup;
import ctbrec.sites.showup.ShowupModel;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.tabs.PaginatedScheduledService;
import javafx.concurrent.Task;
import okhttp3.Request;
import okhttp3.Response;
public class ShowupFollowedUpdateService extends PaginatedScheduledService {
private Showup site;
private boolean showOnline = true;
public ShowupFollowedUpdateService(Showup site) {
this.site = site;
}
@Override
protected Task<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
SiteUiFactory.getUi(site).login();
Request request = new Request.Builder()
.url(site.getBaseUrl() + "/site/favorites")
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.build();
try (Response response = site.getHttpClient().execute(request)) {
if (response.isSuccessful()) {
String body = response.body().string();
JSONObject json = new JSONObject(body);
if (json.optString("status").equalsIgnoreCase("success")) {
Map<Long, String> onlineModels = parseOnlineModels(json);
return parseFavorites(json).stream()
.filter(m -> onlineModels.containsKey(m.getUid()) == showOnline)
.collect(Collectors.toList());
} else {
throw new RuntimeException("Request was not successful: " + body);
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private List<ShowupModel> parseFavorites(JSONObject json) {
var favorites = new ArrayList<ShowupModel>();
JSONArray list = json.getJSONArray("list");
for (int i = 0; i < list.length(); i++) {
JSONObject m = list.getJSONObject(i);
ShowupModel model = new ShowupModel();
model.setSite(site);
model.setUid(m.optLong("fav_uid"));
model.setName(m.optString("username"));
model.setUrl(site.getBaseUrl() + '/' + model.getName());
}
return favorites;
}
private Map<Long, String> parseOnlineModels(JSONObject json) {
var onlineModels = new HashMap<Long, String>();
JSONArray online = json.getJSONArray("online");
for (int i = 0; i < online.length(); i++) {
JSONObject m = online.getJSONObject(i);
String preview = site.getBaseUrl() + "/files/" + m.optString("big_img") + ".jpg";
onlineModels.put(m.optLong("uid"), preview);
}
return onlineModels;
}
};
}
void showOnline(boolean online) {
this.showOnline = online;
}
}

View File

@ -1,33 +1,82 @@
package ctbrec.ui.sites.showup; package ctbrec.ui.sites.showup;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.sites.ConfigUI; import ctbrec.sites.ConfigUI;
import ctbrec.sites.showup.Showup; import ctbrec.sites.showup.Showup;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.sites.AbstractSiteUi; import ctbrec.ui.sites.AbstractSiteUi;
import ctbrec.ui.tabs.TabProvider; import ctbrec.ui.tabs.TabProvider;
public class ShowupSiteUi extends AbstractSiteUi { public class ShowupSiteUi extends AbstractSiteUi {
private static final Logger LOG = LoggerFactory.getLogger(ShowupSiteUi.class);
private Showup site; private Showup site;
private ConfigUI configUi;
private TabProvider tabProvider;
public ShowupSiteUi(Showup site) { public ShowupSiteUi(Showup site) {
this.site = site; this.site = site;
configUi = new ShowupConfigUI(site);
tabProvider = new ShowupTabProvider(site);
} }
@Override @Override
public TabProvider getTabProvider() { public TabProvider getTabProvider() {
return new ShowupTabProvider(site); return tabProvider;
} }
@Override @Override
public ConfigUI getConfigUI() { public ConfigUI getConfigUI() {
return null; return configUi;
} }
@Override @Override
public boolean login() throws IOException { public boolean login() throws IOException {
return false; boolean automaticLogin = site.login();
if(automaticLogin) {
return true;
} else {
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
try {
new Thread(() -> {
// login with external browser window
try {
new ShowupElectronLoginDialog(site.getHttpClient().getCookieJar());
} catch (Exception e1) {
LOG.error("Error logging in with external browser", e1);
Dialogs.showError("Login error", "Couldn't login to " + site.getName(), e1);
}
try {
queue.put(true);
} catch (InterruptedException e) {
LOG.error("Error while signaling termination", e);
Thread.currentThread().interrupt();
}
}).start();
queue.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException(e);
}
// ShowupHttpClient httpClient = (ShowupHttpClient)site.getHttpClient();
// boolean loggedIn = httpClient.checkLoginSuccess();
// if(loggedIn) {
// LOG.info("Logged in. User ID is {}", httpClient.getUserId());
// } else {
// LOG.info("Login failed");
// }
// return loggedIn;
return true;
}
} }
} }

View File

@ -23,6 +23,7 @@ public class ShowupTabProvider extends TabProvider {
tabs.add(createTab("Women", "female")); tabs.add(createTab("Women", "female"));
tabs.add(createTab("Men", "male")); tabs.add(createTab("Men", "male"));
tabs.add(createTab("All", "all")); tabs.add(createTab("All", "all"));
//tabs.add(new ShowupFollowedTab("Favorites", site));
return tabs; return tabs;
} }

View File

@ -110,6 +110,8 @@ public class Settings {
public boolean requireAuthentication = false; public boolean requireAuthentication = false;
public String servletContext = ""; public String servletContext = "";
public boolean showPlayerStarting = false; public boolean showPlayerStarting = false;
public String showupUsername = "";
public String showupPassword = "";
public boolean singlePlayer = true; public boolean singlePlayer = true;
public int splitRecordings = 0; public int splitRecordings = 0;
public String startTab = "Settings"; public String startTab = "Settings";

View File

@ -28,7 +28,9 @@ import okhttp3.Response;
public class Showup extends AbstractSite { public class Showup extends AbstractSite {
private static final transient Logger LOG = LoggerFactory.getLogger(Showup.class); private static final Logger LOG = LoggerFactory.getLogger(Showup.class);
public static final String BASE_URL = "https://showup.tv";
private ShowupHttpClient httpClient; private ShowupHttpClient httpClient;
@Override @Override
@ -38,7 +40,7 @@ public class Showup extends AbstractSite {
@Override @Override
public String getBaseUrl() { public String getBaseUrl() {
return "https://showup.tv"; return BASE_URL;
} }
@Override @Override
@ -113,7 +115,7 @@ public class Showup extends AbstractSite {
@Override @Override
public boolean login() throws IOException { public boolean login() throws IOException {
return false; return getHttpClient().login();
} }
@Override @Override

View File

@ -1,18 +1,35 @@
package ctbrec.sites.showup; package ctbrec.sites.showup;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.io.HttpClient; import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import okhttp3.Cookie; import okhttp3.Cookie;
import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.Response;
public class ShowupHttpClient extends HttpClient { public class ShowupHttpClient extends HttpClient {
private static final Logger LOG = LoggerFactory.getLogger(ShowupHttpClient.class);
private String csrfToken;
protected ShowupHttpClient() { protected ShowupHttpClient() {
super("showup"); super("showup");
setCookie("accept_rules", "true"); setCookie("accept_rules", "true");
@ -20,7 +37,11 @@ public class ShowupHttpClient extends HttpClient {
} }
public void setCookie(String name, String value) { public void setCookie(String name, String value) {
Cookie cookie = new Cookie.Builder().domain("showup.tv").name(name).value(value).build(); Cookie cookie = new Cookie.Builder()
.domain("showup.tv")
.name(name)
.value(value)
.build();
Map<String, List<Cookie>> cookies = cookieJar.getCookies(); Map<String, List<Cookie>> cookies = cookieJar.getCookies();
List<Cookie> cookiesForDomain = cookies.computeIfAbsent(cookie.domain(), k -> new ArrayList<Cookie>()); List<Cookie> cookiesForDomain = cookies.computeIfAbsent(cookie.domain(), k -> new ArrayList<Cookie>());
@ -33,9 +54,63 @@ public class ShowupHttpClient extends HttpClient {
cookiesForDomain.add(cookie); cookiesForDomain.add(cookie);
} }
@Override public String getCsrfToken() throws IOException {
public boolean login() throws IOException { if (csrfToken == null) {
return false; Request req = new Request.Builder()
.url(Showup.BASE_URL + "/site/log_in")
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.build();
try (Response response = execute(req)) {
if (response.isSuccessful()) {
String body = response.body().string();
Matcher m = Pattern.compile("var csrf = '(.*?)';").matcher(body);
if (m.matches()) {
csrfToken = m.group(1);
} else {
throw new RuntimeException("CSRF Token not found"); // NOSONAR
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
return csrfToken;
} }
@Override
public boolean login() throws IOException {
Settings settings = Config.getInstance().getSettings();
FormBody body = new FormBody.Builder()
.add("is_ajax", "1")
.add("email", settings.showupUsername)
.add("password", settings.showupPassword)
.add("remember", "1")
.build();
Request req = new Request.Builder()
.url(Showup.BASE_URL)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(REFERER, Showup.BASE_URL + '/')
.header(ORIGIN, Showup.BASE_URL)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.post(body)
.build();
try (Response response = execute(req)) {
if (response.isSuccessful()) {
String responseBody = response.body().string();
if (responseBody.startsWith("{")) {
JSONObject json = new JSONObject(responseBody);
return json.optString("status").equalsIgnoreCase("success");
} else {
String msg = "Login was not successful";
LOG.warn("{}\n{}", msg, responseBody);
return false;
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
} }