forked from j62/ctbrec
1
0
Fork 0

Introduce external browser for logins

Since the JavaFX integrated browser does not work reliably for recaptcha and in general does not behave like standard browsers, I now use an external browser for the logins. The dependency to javafx-web has been removed.

The external browser is based on electron, which uses chromium as internal browser. The implementation can be found at https://github.com/0xboobface/ctbrec-minimal-browser

The browser is a minimal browser, which only shows the web page content without any other controls. It is launched by ctbrec in a new process and remote controlled over a socket connection. I first tried to control it via
stdin/stdout, but it turns out, that stdin is not supported by electron on windows.
This commit is contained in:
0xboobface 2019-01-08 14:26:22 +01:00
parent 77753bd377
commit a6709bd6db
35 changed files with 767 additions and 863 deletions

1
client/.gitignore vendored
View File

@ -6,3 +6,4 @@
/ctbrec-tunnel.sh
/jre/
/server-local.sh
/browser/

View File

@ -80,7 +80,7 @@
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<artifactId>javafx-media</artifactId>
</dependency>
</dependencies>

View File

@ -38,5 +38,13 @@
<outputDirectory>ctbrec/jre</outputDirectory>
<filtered>false</filtered>
</fileSet>
<fileSet>
<directory>browser/ctbrec-minimal-browser-linux-x64</directory>
<includes>
<include>**/*</include>
</includes>
<outputDirectory>ctbrec/browser</outputDirectory>
<filtered>false</filtered>
</fileSet>
</fileSets>
</assembly>

View File

@ -29,4 +29,14 @@
<outputDirectory>ctbrec</outputDirectory>
</file>
</files>
<fileSets>
<fileSet>
<directory>browser/ctbrec-minimal-browser-linux-x64</directory>
<includes>
<include>**/*</include>
</includes>
<outputDirectory>ctbrec/browser</outputDirectory>
<filtered>false</filtered>
</fileSet>
</fileSets>
</assembly>

View File

@ -38,5 +38,13 @@
<outputDirectory>ctbrec/jre</outputDirectory>
<filtered>false</filtered>
</fileSet>
<fileSet>
<directory>browser/ctbrec-minimal-browser-darwin-x64</directory>
<includes>
<include>**/*</include>
</includes>
<outputDirectory>ctbrec/browser</outputDirectory>
<filtered>false</filtered>
</fileSet>
</fileSets>
</assembly>

View File

@ -29,4 +29,14 @@
<outputDirectory>ctbrec</outputDirectory>
</file>
</files>
<fileSets>
<fileSet>
<directory>browser/ctbrec-minimal-browser-darwin-x64</directory>
<includes>
<include>**/*</include>
</includes>
<outputDirectory>ctbrec/browser</outputDirectory>
<filtered>false</filtered>
</fileSet>
</fileSets>
</assembly>

View File

@ -40,5 +40,13 @@
<outputDirectory>ctbrec/jre</outputDirectory>
<filtered>false</filtered>
</fileSet>
<fileSet>
<directory>browser/ctbrec-minimal-browser-win32-x64</directory>
<includes>
<include>**/*</include>
</includes>
<outputDirectory>ctbrec/browser</outputDirectory>
<filtered>false</filtered>
</fileSet>
</fileSets>
</assembly>

View File

@ -31,4 +31,14 @@
<outputDirectory>ctbrec</outputDirectory>
</file>
</files>
<fileSets>
<fileSet>
<directory>browser/ctbrec-minimal-browser-win32-x64</directory>
<includes>
<include>**/*</include>
</includes>
<outputDirectory>ctbrec/browser</outputDirectory>
<filtered>false</filtered>
</fileSet>
</fileSets>
</assembly>

View File

@ -196,6 +196,10 @@ public class CamrecApplication extends Application {
System.exit(1);
});
}
try {
ExternalBrowser.getInstance().close();
} catch (IOException e) {
}
}
}.start();
});

View File

@ -1,33 +0,0 @@
package ctbrec.ui;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.scene.control.Tab;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
public class DonateTabHtml extends Tab {
private static final transient Logger LOG = LoggerFactory.getLogger(DonateTabHtml.class);
private WebView browser;
public DonateTabHtml() {
setClosable(false);
setText("Donate");
browser = new WebView();
try {
WebEngine webEngine = browser.getEngine();
webEngine.load("https://0xboobface.github.io/ctbrec/#donate");
webEngine.setJavaScriptEnabled(true);
webEngine.setOnAlert((e) -> {
System.out.println(e.getData());
});
setContent(browser);
} catch (Exception e) {
LOG.error("Couldn't load donate.html", e);
}
}
}

View File

@ -0,0 +1,141 @@
package ctbrec.ui;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.OS;
import ctbrec.io.StreamRedirectThread;
public class ExternalBrowser implements AutoCloseable {
private static final transient Logger LOG = LoggerFactory.getLogger(ExternalBrowser.class);
private static final ExternalBrowser INSTANCE = new ExternalBrowser();
private Lock lock = new ReentrantLock();
private Process p;
private Consumer<String> messageListener;
private InputStream in;
private OutputStream out;
private Socket socket;
private Thread reader;
private volatile boolean stopped = true;
public static ExternalBrowser getInstance() {
return INSTANCE;
}
public void run(String jsonConfig, Consumer<String> messageListener) throws InterruptedException, IOException {
lock.lock();
stopped = false;
this.messageListener = messageListener;
p = new ProcessBuilder(OS.getBrowserCommand()).start();
new StreamRedirectThread(p.getInputStream(), System.err);
new StreamRedirectThread(p.getErrorStream(), System.err);
LOG.debug("Browser started");
connectToRemoteControlSocket();
LOG.debug("Connected to remote control server. Sending config {}", jsonConfig);
out.write(jsonConfig.getBytes("utf-8"));
out.write('\n');
out.flush();
LOG.debug("Waiting for browser to terminate");
p.waitFor();
int exitValue = p.exitValue();
p = null;
LOG.debug("Browser Process terminated with {}", exitValue);
}
private void connectToRemoteControlSocket() {
for (int i = 0; i < 20; i++) {
try {
socket = new Socket("localhost", 3202);
in = socket.getInputStream();
out = socket.getOutputStream();
reader = new Thread(this::readBrowserOutput);
reader.start();
return;
} catch (IOException e) {
if(i == 19) {
LOG.error("Connection to remote control socket failed", e);
return;
}
}
// wait a bit, so that the remote server socket can be opened by electron
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
public void executeJavaScript(String javaScript) throws IOException {
//LOG.debug("Executing JS {}", javaScript);
JSONObject script = new JSONObject();
script.put("execute", javaScript);
out.write(script.toString().getBytes("utf-8"));
out.write('\n');
out.flush();
if(javaScript.equals("quit")) {
stopped = true;
}
}
@Override
public void close() throws IOException {
if(stopped) {
return;
}
stopped = true;
executeJavaScript("quit");
if(socket != null) {
socket.close();
socket = null;
}
messageListener = null;
reader = null;
in = null;
out = null;
if(p != null) {
p.destroy();
}
}
private void readBrowserOutput() {
LOG.debug("Browser output reader started");
try {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while( !Thread.interrupted() && (line = br.readLine()) != null ) {
if(!line.startsWith("{")) {
System.err.println(line);
} else {
if(messageListener != null) {
messageListener.accept(line);
}
}
}
} catch (IOException e) {
if(!stopped) {
LOG.error("Couldn't read browser output", e);
}
}
}
public void release() {
lock.unlock();
}
}

View File

@ -1,31 +0,0 @@
package ctbrec.ui;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.OS;
import ctbrec.ui.controls.Dialogs;
import javafx.scene.control.Tab;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
public class WebbrowserTab extends Tab {
private static final transient Logger LOG = LoggerFactory.getLogger(WebbrowserTab.class);
public WebbrowserTab(String uri) {
WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine"));
webEngine.setJavaScriptEnabled(true);
webEngine.load(uri);
setContent(browser);
webEngine.setOnError(evt -> {
LOG.error("Couldn't load {}", uri, evt.getException());
Dialogs.showError("Error", "Couldn't load " + uri, evt.getException());
});
}
}

View File

@ -0,0 +1,124 @@
package ctbrec.ui.sites.bonga;
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.bonga.BongaCams;
import ctbrec.ui.ExternalBrowser;
import okhttp3.Cookie;
import okhttp3.Cookie.Builder;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class BongaCamsElectronLoginDialog {
private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsElectronLoginDialog.class);
public static final String DOMAIN = "bongacams.com";
public static final String URL = BongaCams.BASE_URL + "/login";
private CookieJar cookieJar;
private ExternalBrowser browser;
public BongaCamsElectronLoginDialog(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.toString(), msgHandler);
} catch (InterruptedException e) {
throw new IOException("Couldn't wait for login dialog", e);
} finally {
browser.close();
browser.release();
}
}
private Consumer<String> msgHandler = (line) -> {
if(!line.startsWith("{")) {
System.err.println(line);
} else {
JSONObject json = new JSONObject(line);
//LOG.debug("Browser: {}", json.toString(2));
if(json.has("url")) {
String url = json.getString("url");
if(url.endsWith("/login")) {
try {
Thread.sleep(500);
String username = Config.getInstance().getSettings().bongaUsername;
if (username != null && !username.trim().isEmpty()) {
browser.executeJavaScript("$('input[name=\"log_in[username]\"]').attr('value','" + username + "')");
}
String password = Config.getInstance().getSettings().bongaPassword;
if (password != null && !password.trim().isEmpty()) {
browser.executeJavaScript("$('input[name=\"log_in[password]\"]').attr('value','" + password + "')");
}
String[] simplify = new String[] {
"$('div#header').css('display','none');",
"$('div.footer').css('display','none');",
"$('div.footer_copy').css('display','none')",
"$('div[class~=\"banner_top_index\"]').css('display','none');",
"$('td.menu_container').css('display','none');",
"$('div[class~=\"fancybox-overlay\"]').css('display','none');"
};
for (String js : simplify) {
browser.executeJavaScript(js);
}
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password for BongaCams", 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());
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(BongaCams.BASE_URL), Collections.singletonList(c));
}
}
}
try {
URL _url = new URL(url);
if (Objects.equals(_url.getPath(), "/")) {
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

@ -1,120 +0,0 @@
package ctbrec.ui.sites.bonga;
import java.io.File;
import java.io.InputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.OS;
import ctbrec.sites.bonga.BongaCams;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class BongaCamsLoginDialog {
private static final transient Logger LOG = LoggerFactory.getLogger(BongaCamsLoginDialog.class);
public static final String URL = BongaCams.BASE_URL + "/login";
private List<HttpCookie> cookies = null;
private String url;
private Region veil;
private ProgressIndicator p;
public BongaCamsLoginDialog() {
Stage stage = new Stage();
stage.setTitle("BongaCams Login");
InputStream icon = getClass().getResourceAsStream("/icon.png");
stage.getIcons().add(new Image(icon));
CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
WebView webView = createWebView(stage);
veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
p = new ProgressIndicator();
p.setMaxSize(140, 140);
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(webView, veil, p);
stage.setScene(new Scene(stackPane, 640, 480));
stage.showAndWait();
cookies = cookieManager.getCookieStore().getCookies();
}
private WebView createWebView(Stage stage) {
WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.setJavaScriptEnabled(true);
webEngine.setUserAgent(Config.getInstance().getSettings().httpUserAgent);
webEngine.locationProperty().addListener((obs, oldV, newV) -> {
try {
URL _url = new URL(newV);
if (Objects.equals(_url.getPath(), "/")) {
stage.close();
}
} catch (MalformedURLException e) {
LOG.error("Couldn't parse new url {}", newV, e);
}
url = newV.toString();
});
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
veil.setVisible(false);
p.setVisible(false);
//System.out.println("############# " + webEngine.getLocation());
//System.out.println(webEngine.getDocument().getDocumentElement().getTextContent());
try {
String username = Config.getInstance().getSettings().bongaUsername;
if (username != null && !username.trim().isEmpty()) {
webEngine.executeScript("$('input[name=\"log_in[username]\"]').attr('value','" + username + "')");
}
String password = Config.getInstance().getSettings().bongaPassword;
if (password != null && !password.trim().isEmpty()) {
webEngine.executeScript("$('input[name=\"log_in[password]\"]').attr('value','" + password + "')");
}
webEngine.executeScript("$('div[class~=\"fancybox-overlay\"]').css('display','none')");
webEngine.executeScript("$('div#header').css('display','none')");
webEngine.executeScript("$('div.footer').css('display','none')");
webEngine.executeScript("$('div.footer_copy').css('display','none')");
webEngine.executeScript("$('div[class~=\"banner_top_index\"]').css('display','none')");
webEngine.executeScript("$('td.menu_container').css('display','none')");
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password for BongaCams", e);
}
} else if (newState == State.CANCELLED || newState == State.FAILED) {
veil.setVisible(false);
p.setVisible(false);
}
});
webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine"));
webEngine.load(URL);
return browser;
}
public List<HttpCookie> getCookies() {
// for (HttpCookie httpCookie : cookies) {
// LOG.debug("Cookie: {}", httpCookie);
// }
return cookies;
}
public String getUrl() {
return url;
}
}

View File

@ -1,9 +1,6 @@
package ctbrec.ui.sites.bonga;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@ -15,10 +12,6 @@ import ctbrec.sites.bonga.BongaCams;
import ctbrec.sites.bonga.BongaCamsHttpClient;
import ctbrec.ui.SiteUI;
import ctbrec.ui.TabProvider;
import javafx.application.Platform;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class BongaCamsSiteUi implements SiteUI {
@ -50,32 +43,26 @@ public class BongaCamsSiteUi implements SiteUI {
return true;
} else {
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
Runnable showDialog = () -> {
// login with javafx WebView
BongaCamsLoginDialog loginDialog = new BongaCamsLoginDialog();
// transfer cookies from WebView to OkHttp cookie jar
transferCookies(loginDialog);
try {
new Thread(() -> {
// login with external browser window
try {
new BongaCamsElectronLoginDialog(bongaCams.getHttpClient().getCookieJar());
} catch (Exception e1) {
LOG.error("Error logging in with external browser", e1);
}
try {
queue.put(true);
} catch (InterruptedException e) {
LOG.error("Error while signaling termination", e);
}
};
if(Platform.isFxApplicationThread()) {
showDialog.run();
} else {
Platform.runLater(showDialog);
try {
}).start();
queue.take();
} catch (InterruptedException e) {
LOG.error("Error while waiting for login dialog to close", e);
throw new IOException(e);
}
}
BongaCamsHttpClient httpClient = (BongaCamsHttpClient)bongaCams.getHttpClient();
boolean loggedIn = httpClient.checkLoginSuccess();
@ -87,26 +74,4 @@ public class BongaCamsSiteUi implements SiteUI {
return loggedIn;
}
}
private void transferCookies(BongaCamsLoginDialog loginDialog) {
BongaCamsHttpClient httpClient = (BongaCamsHttpClient)bongaCams.getHttpClient();
CookieJar cookieJar = httpClient.getCookieJar();
HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl());
List<Cookie> cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
Cookie cookie = Cookie.parse(redirectedUrl, webViewCookie.toString());
cookies.add(cookie);
}
cookieJar.saveFromResponse(redirectedUrl, cookies);
HttpUrl origUrl = HttpUrl.parse(BongaCamsLoginDialog.URL);
cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
Cookie cookie = Cookie.parse(origUrl, webViewCookie.toString());
cookies.add(cookie);
}
cookieJar.saveFromResponse(origUrl, cookies);
}
}

View File

@ -0,0 +1,127 @@
package ctbrec.ui.sites.cam4;
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.cam4.Cam4;
import ctbrec.ui.ExternalBrowser;
import okhttp3.Cookie;
import okhttp3.Cookie.Builder;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class Cam4ElectronLoginDialog {
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4ElectronLoginDialog.class);
public static final String DOMAIN = "cam4.com";
public static final String URL = Cam4.BASE_URI + "/login";
private CookieJar cookieJar;
private ExternalBrowser browser;
public Cam4ElectronLoginDialog(CookieJar cookieJar) throws IOException {
this.cookieJar = cookieJar;
browser = ExternalBrowser.getInstance();
try {
JSONObject config = new JSONObject();
config.put("url", URL);
config.put("w", 480);
config.put("h", 640);
JSONObject msg = new JSONObject();
msg.put("config", config);
browser.run(msg.toString(), msgHandler);
} catch (InterruptedException e) {
throw new IOException("Couldn't wait for login dialog", e);
} finally {
browser.close();
browser.release();
}
}
private Consumer<String> msgHandler = (line) -> {
if(!line.startsWith("{")) {
System.err.println(line);
} else {
JSONObject json = new JSONObject(line);
if(json.has("url")) {
String url = json.getString("url");
if(url.endsWith("/login")) {
try {
String username = Config.getInstance().getSettings().cam4Username;
if (username != null && !username.trim().isEmpty()) {
browser.executeJavaScript("document.querySelector('#loginPageForm input[name=\"username\"]').value = '" + username + "';");
}
String password = Config.getInstance().getSettings().cam4Password;
if (password != null && !password.trim().isEmpty()) {
browser.executeJavaScript("document.querySelector('#loginPageForm input[name=\"password\"]').value = '" + password + "';");
}
browser.executeJavaScript("document.getElementById('footer').setAttribute('style', 'display:none');");
browser.executeJavaScript("document.getElementById('promptArea').setAttribute('style', 'display:none');");
browser.executeJavaScript("document.getElementById('content').setAttribute('style', 'padding: 0');");
browser.executeJavaScript("document.querySelector('div[class~=\"navbar\"]').setAttribute('style', 'display:none');");
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password for Cam4", e);
}
}
if(json.has("cookies")) {
JSONArray _cookies = json.getJSONArray("cookies");
try {
URL _url = new URL(url);
for (int i = 0; i < _cookies.length(); i++) {
JSONObject cookie = _cookies.getJSONObject(i);
if(cookie.getString("domain").contains("cam4")) {
String domain = cookie.getString("domain");
if(domain.startsWith(".")) {
domain = domain.substring(1);
}
Cookie c = createCookie(domain, cookie);
cookieJar.saveFromResponse(HttpUrl.parse(url), Collections.singletonList(c));
c = createCookie("cam4.com", cookie);
cookieJar.saveFromResponse(HttpUrl.parse(Cam4.BASE_URI), Collections.singletonList(c));
}
}
if (Objects.equals(_url.getPath(), "/")) {
try {
browser.close();
} catch(IOException e) {
LOG.error("Couldn't send close request to browser", e);
}
}
} catch (MalformedURLException e) {
LOG.error("Couldn't parse new url {}", url, e);
}
}
}
}
};
private Cookie createCookie(String domain, JSONObject cookie) {
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());
if(cookie.optBoolean("hostOnly")) {
b.hostOnlyDomain(domain);
}
if(cookie.optBoolean("httpOnly")) {
b.httpOnly();
}
if(cookie.optBoolean("secure")) {
b.secure();
}
return b.build();
}
}

View File

@ -1,112 +0,0 @@
package ctbrec.ui.sites.cam4;
import java.io.File;
import java.io.InputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.OS;
import ctbrec.sites.cam4.Cam4;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
public class Cam4LoginDialog {
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4LoginDialog.class);
public static final String URL = Cam4.BASE_URI + "/login";
private List<HttpCookie> cookies = null;
private String url;
private Region veil;
private ProgressIndicator p;
public Cam4LoginDialog() {
Stage stage = new Stage();
stage.setTitle("Cam4 Login");
InputStream icon = getClass().getResourceAsStream("/icon.png");
stage.getIcons().add(new Image(icon));
CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
WebView webView = createWebView(stage);
veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
p = new ProgressIndicator();
p.setMaxSize(140, 140);
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(webView, veil, p);
stage.setScene(new Scene(stackPane, 480, 854));
stage.showAndWait();
cookies = cookieManager.getCookieStore().getCookies();
}
private WebView createWebView(Stage stage) {
WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.setJavaScriptEnabled(true);
webEngine.setUserAgent(Config.getInstance().getSettings().httpUserAgent);
webEngine.locationProperty().addListener((obs, oldV, newV) -> {
try {
URL _url = new URL(newV);
if (Objects.equals(_url.getPath(), "/")) {
stage.close();
}
} catch (MalformedURLException e) {
LOG.error("Couldn't parse new url {}", newV, e);
}
url = newV.toString();
});
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
veil.setVisible(false);
p.setVisible(false);
try {
String username = Config.getInstance().getSettings().cam4Username;
if (username != null && !username.trim().isEmpty()) {
webEngine.executeScript("$('input[name=username]').attr('value','" + username + "')");
}
String password = Config.getInstance().getSettings().cam4Password;
if (password != null && !password.trim().isEmpty()) {
webEngine.executeScript("$('input[name=password]').attr('value','" + password + "')");
}
webEngine.executeScript("$('div[class~=navbar]').css('display','none')");
webEngine.executeScript("$('div#footer').css('display','none')");
webEngine.executeScript("$('div#content').css('padding','0')");
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password for Cam4", e);
}
} else if (newState == State.CANCELLED || newState == State.FAILED) {
veil.setVisible(false);
p.setVisible(false);
}
});
webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine"));
webEngine.load(URL);
return browser;
}
public List<HttpCookie> getCookies() {
return cookies;
}
public String getUrl() {
return url;
}
}

View File

@ -1,9 +1,6 @@
package ctbrec.ui.sites.cam4;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@ -16,9 +13,6 @@ import ctbrec.sites.cam4.Cam4HttpClient;
import ctbrec.ui.SiteUI;
import ctbrec.ui.TabProvider;
import javafx.application.Platform;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class Cam4SiteUi implements SiteUI {
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4SiteUi.class);
@ -53,11 +47,12 @@ public class Cam4SiteUi implements SiteUI {
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
Runnable showDialog = () -> {
// login with javafx WebView
Cam4LoginDialog loginDialog = new Cam4LoginDialog();
// transfer cookies from WebView to OkHttp cookie jar
transferCookies(loginDialog);
// login with external browser
try {
new Cam4ElectronLoginDialog(cam4.getHttpClient().getCookieJar());
} catch (Exception e1) {
LOG.error("Error logging in with external browser", e1);
}
try {
queue.put(true);
@ -66,9 +61,6 @@ public class Cam4SiteUi implements SiteUI {
}
};
if(Platform.isFxApplicationThread()) {
showDialog.run();
} else {
Platform.runLater(showDialog);
try {
queue.take();
@ -76,38 +68,10 @@ public class Cam4SiteUi implements SiteUI {
LOG.error("Error while waiting for login dialog to close", e);
throw new IOException(e);
}
}
Cam4HttpClient httpClient = (Cam4HttpClient) cam4.getHttpClient();
boolean loggedIn = httpClient.checkLoginSuccess();
return loggedIn;
}
}
private void transferCookies(Cam4LoginDialog loginDialog) {
Cam4HttpClient httpClient = (Cam4HttpClient) cam4.getHttpClient();
CookieJar cookieJar = httpClient.getCookieJar();
HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl());
List<Cookie> cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
if(webViewCookie.getDomain().contains("cam4")) {
Cookie cookie = Cookie.parse(redirectedUrl, webViewCookie.toString());
LOG.debug("{} {} {}", webViewCookie.getDomain(), webViewCookie.getName(), webViewCookie.getValue());
cookies.add(cookie);
}
}
cookieJar.saveFromResponse(redirectedUrl, cookies);
HttpUrl origUrl = HttpUrl.parse(Cam4LoginDialog.URL);
cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
if(webViewCookie.getDomain().contains("cam4")) {
Cookie cookie = Cookie.parse(origUrl, webViewCookie.toString());
cookies.add(cookie);
}
}
cookieJar.saveFromResponse(origUrl, cookies);
}
}

View File

@ -1,110 +0,0 @@
package ctbrec.ui.sites.camsoda;
import java.io.File;
import java.io.InputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.util.Base64;
import java.util.List;
import ctbrec.OS;
import ctbrec.sites.camsoda.Camsoda;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
// FIXME this dialog does not help, because google's recaptcha does not work
// with WebView even though it does work in Cam4LoginDialog
public class CamsodaLoginDialog {
public static final String URL = Camsoda.BASE_URI;
private List<HttpCookie> cookies = null;
private String url;
private Region veil;
private ProgressIndicator p;
public CamsodaLoginDialog() {
Stage stage = new Stage();
stage.setTitle("CamSoda Login");
InputStream icon = getClass().getResourceAsStream("/icon.png");
stage.getIcons().add(new Image(icon));
CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
WebView webView = createWebView(stage);
veil = new Region();
veil.setStyle("-fx-background-color: rgba(1, 1, 1)");
p = new ProgressIndicator();
p.setMaxSize(140, 140);
p.setVisible(true);
veil.visibleProperty().bind(p.visibleProperty());
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(webView, veil, p);
stage.setScene(new Scene(stackPane, 400, 358));
stage.showAndWait();
cookies = cookieManager.getCookieStore().getCookies();
}
private WebView createWebView(Stage stage) {
WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.setJavaScriptEnabled(true);
webEngine.locationProperty().addListener((obs, oldV, newV) -> {
// try {
// URL _url = new URL(newV);
// if (Objects.equals(_url.getPath(), "/")) {
// stage.close();
// }
// } catch (MalformedURLException e) {
// LOG.error("Couldn't parse new url {}", newV, e);
// }
url = newV.toString();
System.out.println(newV.toString());
});
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
webEngine.executeScript("document.querySelector('a[ng-click=\"signin();\"]').click()");
p.setVisible(false);
// TODO make this work
// String username = Config.getInstance().getSettings().camsodaUsername;
// if (username != null && !username.trim().isEmpty()) {
// webEngine.executeScript("document.querySelector('input[name=\"loginUsername\"]').value = '" + username + "'");
// }
// String password = Config.getInstance().getSettings().camsodaPassword;
// if (password != null && !password.trim().isEmpty()) {
// webEngine.executeScript("document.querySelector('input[name=\"loginPassword\"]').value = '" + password + "'");
// }
} else if (newState == State.CANCELLED || newState == State.FAILED) {
p.setVisible(false);
}
});
webEngine.setUserStyleSheetLocation("data:text/css;base64," + Base64.getEncoder().encodeToString(CUSTOM_STYLE.getBytes()));
webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine"));
webEngine.load(URL);
return browser;
}
public List<HttpCookie> getCookies() {
return cookies;
}
public String getUrl() {
return url;
}
private static final String CUSTOM_STYLE = ""
+ ".ngdialog.ngdialog-theme-custom { padding: 0 !important }"
+ ".ngdialog-overlay { background: black !important; }";
}

View File

@ -1,24 +1,14 @@
package ctbrec.ui.sites.camsoda;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.List;
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.camsoda.Camsoda;
import ctbrec.sites.camsoda.CamsodaHttpClient;
import ctbrec.ui.SiteUI;
import ctbrec.ui.TabProvider;
import ctbrec.ui.sites.cam4.Cam4LoginDialog;
import javafx.application.Platform;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
public class CamsodaSiteUi implements SiteUI {
@ -50,58 +40,57 @@ public class CamsodaSiteUi implements SiteUI {
return automaticLogin;
}
@SuppressWarnings("unused")
private boolean loginWithDialog() throws IOException {
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
Runnable showDialog = () -> {
// login with javafx WebView
CamsodaLoginDialog loginDialog = new CamsodaLoginDialog();
// 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);
try {
queue.take();
} catch (InterruptedException e) {
LOG.error("Error while waiting for login dialog to close", e);
throw new IOException(e);
}
}
CamsodaHttpClient httpClient = (CamsodaHttpClient)camsoda.getHttpClient();
boolean loggedIn = httpClient.checkLoginSuccess();
return loggedIn;
}
private void transferCookies(CamsodaLoginDialog loginDialog) {
HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl());
List<Cookie> cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
Cookie cookie = Cookie.parse(redirectedUrl, webViewCookie.toString());
cookies.add(cookie);
}
camsoda.getHttpClient().getCookieJar().saveFromResponse(redirectedUrl, cookies);
HttpUrl origUrl = HttpUrl.parse(Cam4LoginDialog.URL);
cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
Cookie cookie = Cookie.parse(origUrl, webViewCookie.toString());
cookies.add(cookie);
}
camsoda.getHttpClient().getCookieJar().saveFromResponse(origUrl, cookies);
}
// @SuppressWarnings("unused")
// private boolean loginWithDialog() throws IOException {
// BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
//
// Runnable showDialog = () -> {
// // login with external browser
// CamsodaLoginDialog loginDialog = new CamsodaLoginDialog();
//
// // 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);
// try {
// queue.take();
// } catch (InterruptedException e) {
// LOG.error("Error while waiting for login dialog to close", e);
// throw new IOException(e);
// }
// }
//
// CamsodaHttpClient httpClient = (CamsodaHttpClient)camsoda.getHttpClient();
// boolean loggedIn = httpClient.checkLoginSuccess();
// return loggedIn;
// }
//
// private void transferCookies(CamsodaLoginDialog loginDialog) {
// HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl());
// List<Cookie> cookies = new ArrayList<>();
// for (HttpCookie webViewCookie : loginDialog.getCookies()) {
// Cookie cookie = Cookie.parse(redirectedUrl, webViewCookie.toString());
// cookies.add(cookie);
// }
// camsoda.getHttpClient().getCookieJar().saveFromResponse(redirectedUrl, cookies);
//
// HttpUrl origUrl = HttpUrl.parse(Camsoda.BASE_URI);
// cookies = new ArrayList<>();
// for (HttpCookie webViewCookie : loginDialog.getCookies()) {
// Cookie cookie = Cookie.parse(origUrl, webViewCookie.toString());
// cookies.add(cookie);
// }
// camsoda.getHttpClient().getCookieJar().saveFromResponse(origUrl, cookies);
// }
}

View File

@ -73,19 +73,20 @@ public class LiveJasminConfigUi extends AbstractConfigUI {
GridPane.setColumnSpan(password, 2);
layout.add(password, 1, row++);
layout.add(new Label("LiveJasmin Session ID"), 0, row);
TextField sessionId = new TextField();
sessionId.setText(Config.getInstance().getSettings().livejasminSession);
sessionId.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().livejasminSession)) {
Config.getInstance().getSettings().livejasminSession = n;
save();
}
});
GridPane.setFillWidth(sessionId, true);
GridPane.setHgrow(sessionId, Priority.ALWAYS);
GridPane.setColumnSpan(sessionId, 2);
layout.add(sessionId, 1, row++);
// layout.add(new Label("LiveJasmin Session ID"), 0, row);
// TextField sessionId = new TextField();
// sessionId.setText(Config.getInstance().getSettings().livejasminSession);
// sessionId.textProperty().addListener((ob, o, n) -> {
// if(!n.equals(Config.getInstance().getSettings().livejasminSession)) {
// Config.getInstance().getSettings().livejasminSession = n;
// save();
// }
// });
// GridPane.setFillWidth(sessionId, true);
// GridPane.setHgrow(sessionId, Priority.ALWAYS);
// GridPane.setColumnSpan(sessionId, 2);
// GridPane.setMargin(sessionId, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
// layout.add(sessionId, 1, row++);
Button createAccount = new Button("Create new Account");
createAccount.setOnAction((e) -> DesktopIntegration.open(liveJasmin.getAffiliateLink()));
@ -93,7 +94,6 @@ public class LiveJasminConfigUi extends AbstractConfigUI {
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(sessionId, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
username.setPrefWidth(300);

View File

@ -0,0 +1,97 @@
package ctbrec.ui.sites.jasmin;
import java.io.IOException;
import java.util.Collections;
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.jasmin.LiveJasmin;
import ctbrec.ui.ExternalBrowser;
import okhttp3.Cookie;
import okhttp3.Cookie.Builder;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class LiveJasminElectronLoginDialog {
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminElectronLoginDialog.class);
public static final String URL = LiveJasmin.BASE_URL + "/en/auth/login";
private CookieJar cookieJar;
private ExternalBrowser browser;
public LiveJasminElectronLoginDialog(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", 720);
JSONObject msg = new JSONObject();
msg.put("config", config);
browser.run(msg.toString(), msgHandler);
} catch (InterruptedException e) {
throw new IOException("Couldn't wait for login dialog", e);
} finally {
browser.close();
browser.release();
}
}
private Consumer<String> msgHandler = (line) -> {
//LOG.debug("Browser: {}", line);
if(!line.startsWith("{")) {
System.err.println(line);
} else {
JSONObject json = new JSONObject(line);
if(json.has("url")) {
String url = json.getString("url");
if(url.endsWith("/auth/login")) {
try {
String username = Config.getInstance().getSettings().livejasminUsername;
if (username != null && !username.trim().isEmpty()) {
browser.executeJavaScript("document.querySelector('#login_form input[name=\"username\"]').value = '" + username + "';");
}
String password = Config.getInstance().getSettings().livejasminPassword;
if (password != null && !password.trim().isEmpty()) {
browser.executeJavaScript("document.querySelector('#login_form input[name=\"password\"]').value = '" + password + "';");
}
browser.executeJavaScript("document.getElementById('header_container').setAttribute('style', 'display:none');");
browser.executeJavaScript("document.getElementById('footer').setAttribute('style', 'display:none');");
browser.executeJavaScript("document.getElementById('react-container').setAttribute('style', 'display:none');");
browser.executeJavaScript("document.getElementById('inner_container').setAttribute('style', 'padding: 0; margin: 1em');");
browser.executeJavaScript("document.querySelector('div[class~=\"content_box\"]').setAttribute('style', 'margin: 1em');");
} catch(Exception e) {
LOG.warn("Couldn't auto fill username and password", e);
}
}
if(json.has("cookies")) {
JSONArray _cookies = json.getJSONArray("cookies");
for (int i = 0; i < _cookies.length(); i++) {
JSONObject cookie = _cookies.getJSONObject(i);
Builder b = new Cookie.Builder()
.path("/")
.domain("livejasmin.com")
.name(cookie.getString("name"))
.value(cookie.getString("value"))
.expiresAt(0);
Cookie c = b.build();
cookieJar.saveFromResponse(HttpUrl.parse(LiveJasmin.BASE_URL), Collections.singletonList(c));
}
}
if(url.contains("/member/")) {
try {
browser.close();
} catch(IOException e) {
LOG.error("Couldn't send close request to browser", e);
}
}
}
}
};
}

View File

@ -15,6 +15,7 @@ import ctbrec.io.HttpException;
import ctbrec.sites.jasmin.LiveJasmin;
import ctbrec.sites.jasmin.LiveJasminModel;
import ctbrec.ui.PaginatedScheduledService;
import ctbrec.ui.SiteUiFactory;
import javafx.concurrent.Task;
import okhttp3.Request;
import okhttp3.Response;
@ -37,6 +38,10 @@ public class LiveJasminFollowedUpdateService extends PaginatedScheduledService {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
boolean loggedIn = SiteUiFactory.getUi(liveJasmin).login();
if(!loggedIn) {
throw new RuntimeException("Couldn't login on livejasmin.com");
}
//String _url = url + ((page-1) * 36); // TODO find out how to switch pages
//LOG.debug("Fetching page {}", url);
Request request = new Request.Builder()

View File

@ -1,170 +0,0 @@
package ctbrec.ui.sites.jasmin;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Objects;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.OS;
import ctbrec.io.HttpException;
import ctbrec.sites.jasmin.LiveJasmin;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import okhttp3.Request;
import okhttp3.Response;
public class LiveJasminLoginDialog {
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminLoginDialog.class);
public static final String URL = "https://m.livejasmin.com/en/list"; // #login-modal
private List<HttpCookie> cookies = null;
private String url;
private Region veil;
private ProgressIndicator p;
private LiveJasmin liveJasmin;
public LiveJasminLoginDialog(LiveJasmin liveJasmin) throws IOException {
this.liveJasmin = liveJasmin;
Stage stage = new Stage();
stage.setTitle("LiveJasmin Login");
InputStream icon = getClass().getResourceAsStream("/icon.png");
stage.getIcons().add(new Image(icon));
CookieManager cookieManager = new CookieManager();
CookieHandler.setDefault(cookieManager);
WebView webView = createWebView(stage);
veil = new Region();
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
p = new ProgressIndicator();
p.setMaxSize(140, 140);
StackPane stackPane = new StackPane();
stackPane.getChildren().addAll(webView, veil, p);
stage.setScene(new Scene(stackPane, 360, 480));
stage.showAndWait();
cookies = cookieManager.getCookieStore().getCookies();
}
private WebView createWebView(Stage stage) throws IOException {
WebView browser = new WebView();
WebEngine webEngine = browser.getEngine();
webEngine.setJavaScriptEnabled(true);
//webEngine.setUserAgent("Mozilla/5.0 (Android 9.0; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0");
webEngine.setUserAgent("Mozilla/5.0 (Mobile; rv:30.0) Gecko/20100101 Firefox/30.0");
webEngine.locationProperty().addListener((obs, oldV, newV) -> {
try {
URL _url = new URL(newV);
if (Objects.equals(_url.getPath(), "/")) {
stage.close();
}
} catch (MalformedURLException e) {
LOG.error("Couldn't parse new url {}", newV, e);
}
url = newV.toString();
});
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
if (newState == State.SUCCEEDED) {
veil.setVisible(false);
p.setVisible(false);
// try {
// //webEngine.executeScript("$('#eighteen-plus-modal').hide();");
// //webEngine.executeScript("$('body').html('"+loginForm+"');");
// //webEngine.executeScript("$('#listpage').append('"+loginForm+"');");
// // webEngine.executeScript("$('#main-menu-button').click();");
// // webEngine.executeScript("$('#login-menu').click();");
// String username = Config.getInstance().getSettings().livejasminUsername;
// if (username != null && !username.trim().isEmpty()) {
// webEngine.executeScript("$('#username').attr('value','" + username + "')");
// }
// String password = Config.getInstance().getSettings().livejasminPassword;
// if (password != null && !password.trim().isEmpty()) {
// webEngine.executeScript("$('#password').attr('value','" + password + "')");
// }
// } catch(Exception e) {
// LOG.warn("Couldn't auto fill username and password for LiveJasmin", e);
// }
} else if (newState == State.CANCELLED || newState == State.FAILED) {
veil.setVisible(false);
p.setVisible(false);
}
});
webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine"));
webEngine.load(URL);
return browser;
}
private String getLoginForm() throws IOException {
callBaseUrl(); // to get cookies
String url = "https://m.livejasmin.com/en/auth/window/get-login-window?isAjax=1";
Request request = new Request.Builder()
.url(url)
.addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0")
.addHeader("Accept", "application/json, text/javascript, */*")
.addHeader("Accept-Language", "en")
.addHeader("Referer", LiveJasmin.BASE_URL)
.addHeader("X-Requested-With", "XMLHttpRequest")
.build();
try(Response response = liveJasmin.getHttpClient().execute(request)) {
if(response.isSuccessful()) {
String body = response.body().string();
JSONObject json = new JSONObject(body);
System.out.println(json.toString(2));
if(json.optBoolean("success")) {
JSONObject data = json.getJSONObject("data");
return data.getString("content");
} else {
throw new IOException("Request was not successful: " + body);
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private void callBaseUrl() throws IOException {
String url = liveJasmin.getBaseUrl();
Request request = new Request.Builder()
.url(url)
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.build();
try(Response response = liveJasmin.getHttpClient().execute(request)) {
if(response.isSuccessful()) {
} else {
throw new HttpException(response.code(), response.message());
}
}
}
public List<HttpCookie> getCookies() {
for (HttpCookie httpCookie : cookies) {
LOG.debug("Cookie: {}", httpCookie);
}
return cookies;
}
public String getUrl() {
return url;
}
}

View File

@ -1,9 +1,8 @@
package ctbrec.ui.sites.jasmin;
import java.io.IOException;
import java.net.HttpCookie;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -13,9 +12,6 @@ import ctbrec.sites.jasmin.LiveJasmin;
import ctbrec.sites.jasmin.LiveJasminHttpClient;
import ctbrec.ui.SiteUI;
import ctbrec.ui.TabProvider;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class LiveJasminSiteUi implements SiteUI {
@ -28,12 +24,6 @@ public class LiveJasminSiteUi implements SiteUI {
this.liveJasmin = liveJasmin;
tabProvider = new LiveJasminTabProvider(liveJasmin);
configUi = new LiveJasminConfigUi(liveJasmin);
try {
login();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
@ -49,74 +39,42 @@ public class LiveJasminSiteUi implements SiteUI {
@Override
public synchronized boolean login() throws IOException {
boolean automaticLogin = liveJasmin.login();
return automaticLogin;
// if(automaticLogin) {
// return true;
// } else {
// BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
//
// Runnable showDialog = () -> {
// // login with javafx WebView
// LiveJasminLoginDialog loginDialog;
// try {
// loginDialog = new LiveJasminLoginDialog(liveJasmin);
// // transfer cookies from WebView to OkHttp cookie jar
// transferCookies(loginDialog);
// } catch (IOException e1) {
// LOG.error("Couldn't load login dialog", e1);
// }
//
// try {
// queue.put(true);
// } catch (InterruptedException e) {
// LOG.error("Error while signaling termination", e);
// }
// };
//
// if(Platform.isFxApplicationThread()) {
// showDialog.run();
// } else {
// Platform.runLater(showDialog);
// try {
// queue.take();
// } catch (InterruptedException e) {
// LOG.error("Error while waiting for login dialog to close", e);
// throw new IOException(e);
// }
// }
//
// LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
// boolean loggedIn = httpClient.checkLoginSuccess();
// if(loggedIn) {
// LOG.info("Logged in.");
// } else {
// LOG.info("Login failed");
// }
// return loggedIn;
// }
if(automaticLogin) {
return true;
} else {
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
new Thread (() -> {
// login with external browser window
try {
//LiveJasminElectronLoginDialog dialog =
new LiveJasminElectronLoginDialog(liveJasmin.getHttpClient().getCookieJar());
} catch (IOException e1) {
LOG.error("Error logging in with external browser", e1);
}
try {
queue.put(true);
} catch (InterruptedException e) {
LOG.error("Error while signaling termination", e);
}
}).start();
try {
queue.take();
} catch (InterruptedException e) {
LOG.error("Error while waiting for login dialog to close", e);
throw new IOException(e);
}
private void transferCookies(LiveJasminLoginDialog loginDialog) {
LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
CookieJar cookieJar = httpClient.getCookieJar();
String[] urls = {
"https://www.livejasmin.com",
"http://www.livejasmin.com",
"https://m.livejasmin.com",
"http://m.livejasmin.com",
"https://livejasmin.com",
"http://livejasmin.com"
};
for (String u : urls) {
HttpUrl url = HttpUrl.parse(u);
List<Cookie> cookies = new ArrayList<>();
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
Cookie cookie = Cookie.parse(url, webViewCookie.toString());
cookies.add(cookie);
boolean loggedIn = httpClient.checkLoginSuccess();
if(loggedIn) {
LOG.info("Logged in");
} else {
LOG.info("Login failed");
}
cookieJar.saveFromResponse(url, cookies);
return loggedIn;
}
}
}

View File

@ -8,6 +8,7 @@ import ctbrec.ui.TabProvider;
import ctbrec.ui.ThumbOverviewTab;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
import javafx.util.Duration;
public class LiveJasminTabProvider extends TabProvider {
@ -43,6 +44,7 @@ public class LiveJasminTabProvider extends TabProvider {
LiveJasminUpdateService s = new LiveJasminUpdateService(liveJasmin, url);
ThumbOverviewTab tab = new ThumbOverviewTab(title, s, liveJasmin);
tab.setRecorder(liveJasmin.getRecorder());
s.setPeriod(Duration.seconds(60));
return tab;
}
}

View File

@ -48,7 +48,7 @@ public class LiveJasminUpdateService extends PaginatedScheduledService {
.name("listPageOrderType")
.value("most_popular")
.build();
cookieJar.saveFromResponse(HttpUrl.parse("https://www.livejasmin.com"), Collections.singletonList(sortCookie));
cookieJar.saveFromResponse(HttpUrl.parse("https://livejasmin.com"), Collections.singletonList(sortCookie));
LOG.debug("Fetching page {}", url);
Request request = new Request.Builder()
@ -56,7 +56,7 @@ public class LiveJasminUpdateService extends PaginatedScheduledService {
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.addHeader("Accept", "application/json, text/javascript, */*")
.addHeader("Accept-Language", "en")
.addHeader("Referer", liveJasmin.getBaseUrl())
.addHeader("Referer", liveJasmin.getBaseUrl() + "/en/girls/")
.addHeader("X-Requested-With", "XMLHttpRequest")
.build();
try (Response response = liveJasmin.getHttpClient().execute(request)) {

View File

@ -57,7 +57,7 @@
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<artifactId>javafx-media</artifactId>
<scope>provided</scope>
</dependency>
<dependency>

View File

@ -8,8 +8,11 @@ import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map.Entry;
import org.slf4j.Logger;
@ -61,6 +64,46 @@ public class OS {
return configDir;
}
public static String[] getBrowserCommand(String...args) {
if(System.getenv("CTBREC_BROWSER") != null) {
String cmd[] = new String[args.length + 1];
cmd[0] = System.getenv("CTBREC_BROWSER");
System.arraycopy(args, 0, cmd, 1, args.length);
return cmd;
}
try {
URI uri = OS.class.getProtectionDomain().getCodeSource().getLocation().toURI();
File jar = new File(uri.getPath());
File browserDir = new File(jar.getParentFile(), "browser");
String[] cmd;
switch (getOsType()) {
case LINUX:
cmd = new String[args.length + 1];
cmd[0] = new File(browserDir, "ctbrec-minimal-browser").getAbsolutePath();
System.arraycopy(args, 0, cmd, 1, args.length);
break;
case WINDOWS:
cmd = new String[args.length + 1];
cmd[0] = new File(browserDir, "ctbrec-minimal-browser.exe").getAbsolutePath();
System.arraycopy(args, 0, cmd, 1, args.length);
break;
case MAC:
cmd = new String[args.length + 2];
cmd[0] = "open";
cmd[1] = new File(browserDir, "ctbrec-minimal-browser.app").getAbsolutePath();
System.arraycopy(args, 0, cmd, 2, args.length);
break;
default:
throw new RuntimeException("Unsupported operating system " + System.getProperty("os.name"));
}
LOG.debug("Browser command: {}", Arrays.toString(cmd));
return cmd;
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
public static Settings getDefaultSettings() {
Settings settings = new Settings();
if(getOsType() == TYPE.WINDOWS) {

View File

@ -36,6 +36,7 @@ public class Settings {
public boolean localRecording = true;
public int httpPort = 8080;
public int httpTimeout = 10000;
public String httpUserAgentMobile = "Mozilla/5.0 (Android 9.0; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0";
public String httpUserAgent = "Mozilla/5.0 Gecko/20100101 Firefox/62.0";
public String httpServer = "localhost";
public String recordingsDir = System.getProperty("user.home") + File.separator + "ctbrec";

View File

@ -25,7 +25,7 @@ public class CookieJarImpl implements CookieJar {
@Override
public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
String host = getHost(url);
String host = getDomain(url);
List<Cookie> cookiesForUrl = cookieStore.get(host);
if (cookiesForUrl != null) {
cookiesForUrl = new ArrayList<Cookie>(cookiesForUrl); //unmodifiable
@ -52,7 +52,7 @@ public class CookieJarImpl implements CookieJar {
@Override
public List<Cookie> loadForRequest(HttpUrl url) {
String host = getHost(url);
String host = getDomain(url);
List<Cookie> cookies = cookieStore.get(host);
LOG.debug("Cookies for {}", url);
Optional.ofNullable(cookies).ifPresent(cookiez -> cookiez.forEach(c -> {
@ -72,12 +72,13 @@ public class CookieJarImpl implements CookieJar {
throw new NoSuchElementException("No cookie named " + name + " for " + url.host() + " available");
}
private String getHost(HttpUrl url) {
String host = url.host();
if (host.startsWith("www.")) {
host = host.substring(4);
}
return host;
private String getDomain(HttpUrl url) {
// String host = url.host();
// if (host.startsWith("www.")) {
// host = host.substring(4);
// }
// return host;
return url.topPrivateDomain();
}
@Override

View File

@ -151,7 +151,7 @@ public class LiveJasmin extends AbstractSite {
@Override
public boolean credentialsAvailable() {
return !Config.getInstance().getSettings().livejasminSession.isEmpty();
return !Config.getInstance().getSettings().livejasminUsername.isEmpty();
}
}

View File

@ -1,7 +1,6 @@
package ctbrec.sites.jasmin;
import java.io.IOException;
import java.util.Collections;
import java.util.NoSuchElementException;
import org.slf4j.Logger;
@ -29,19 +28,6 @@ public class LiveJasminHttpClient extends HttpClient {
return true;
}
// set session cookie, if session id is available
if(!Config.getInstance().getSettings().livejasminSession.isEmpty()) {
Cookie captchaCookie = new Cookie.Builder()
.domain("livejasmin.com")
.name("session")
.value(Config.getInstance().getSettings().livejasminSession)
.build();
getCookieJar().saveFromResponse(HttpUrl.parse("https://livejasmin.com"), Collections.singletonList(captchaCookie));
getCookieJar().saveFromResponse(HttpUrl.parse("https://www.livejasmin.com"), Collections.singletonList(captchaCookie));
getCookieJar().saveFromResponse(HttpUrl.parse("https://m.livejasmin.com"), Collections.singletonList(captchaCookie));
}
// loadMainPage(); // to get initial cookies
// Cookie captchaCookie = new Cookie.Builder()
// .domain("livejasmin.com")
@ -152,10 +138,17 @@ public class LiveJasminHttpClient extends HttpClient {
.followRedirects(false)
.followSslRedirects(false)
.build();
String url = LiveJasmin.BASE_URL + "/en/free/favourite/get-favourite-list";
// getCookieJar().getCookies().entrySet().forEach((e) -> {
// e.getValue().forEach(c -> {
// LOG.debug("LOGIN CHECK: Cookie {} {}={}", e.getKey(), c.name(), c.value());
// });
// });
String url = "https://m.livejasmin.com/en/member/favourite/get-favourite-list?ajax=1";
Request request = new Request.Builder()
.url(url)
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgentMobile)
.addHeader("Accept", "application/json, text/javascript, */*")
.addHeader("Accept-Language", "en")
.addHeader("Referer", LiveJasmin.BASE_URL)
@ -166,13 +159,18 @@ public class LiveJasminHttpClient extends HttpClient {
if(response.isSuccessful()) {
return true;
} else {
// if(response.code() >= 300 && response.code() < 400) {
// for (String name : response.headers().names()) {
// LOG.debug("{}: {}", name, response.header(name));
// }
// }
return false;
}
}
}
public String getSessionId() {
Cookie sessionCookie = getCookieJar().getCookie(HttpUrl.parse("https://www.livejasmin.com"), "session");
Cookie sessionCookie = getCookieJar().getCookie(HttpUrl.parse(LiveJasmin.BASE_URL), "session");
if(sessionCookie != null) {
return sessionCookie.value();
} else {

View File

@ -280,6 +280,7 @@ public class LiveJasminModel extends AbstractModel {
// } else {
// return new LiveJasminWebSocketDownload(getSite().getHttpClient());
// }
return new LiveJasminChunkedHttpDownload(getSite().getHttpClient());
//return new LiveJasminChunkedHttpDownload(getSite().getHttpClient());
return new LiveJasminWebSocketDownload(getSite().getHttpClient());
}
}

View File

@ -85,6 +85,11 @@
<artifactId>javafx-web</artifactId>
<version>11</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>11</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>