diff --git a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateElectronLoginDialog.java b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateElectronLoginDialog.java new file mode 100644 index 00000000..2197a6b9 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateElectronLoginDialog.java @@ -0,0 +1,123 @@ +package ctbrec.ui.sites.chaturbate; + +import ctbrec.Config; +import ctbrec.sites.chaturbate.Chaturbate; +import ctbrec.ui.ExternalBrowser; +import okhttp3.Cookie; +import okhttp3.Cookie.Builder; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +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; + +public class ChaturbateElectronLoginDialog { + + private static final Logger LOG = LoggerFactory.getLogger(ChaturbateElectronLoginDialog.class); + public static final String DOMAIN = "chaturbate.com"; + private final Chaturbate site; + private CookieJar cookieJar; + private ExternalBrowser browser; + + public ChaturbateElectronLoginDialog(Chaturbate site, CookieJar cookieJar) throws IOException { + this.site = site; + this.cookieJar = cookieJar; + browser = ExternalBrowser.getInstance(); + try { + var config = new JSONObject(); + config.put("url", site.getBaseUrl() + "/auth/login/"); + config.put("w", 640); + config.put("h", 480); + config.put("userAgent", Config.getInstance().getSettings().httpUserAgent); + var 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 final Consumer msgHandler = line -> { + if (!line.startsWith("{")) { + LOG.error("Didn't received a JSON object {}", line); + } else { + var json = new JSONObject(line); + if (json.has("url")) { + var url = json.getString("url"); + if (url.endsWith("/auth/login/")) { + try { + Thread.sleep(500); + String username = Config.getInstance().getSettings().chaturbateUsername; + if (username != null && !username.trim().isEmpty()) { + browser.executeJavaScript("document.getElementById('id_username').value = '" + username + "'"); + } + String password = Config.getInstance().getSettings().chaturbatePassword; + if (password != null && !password.trim().isEmpty()) { + password = password.replace("'", "\\'"); + browser.executeJavaScript("document.getElementById('id_password').value = '" + password + "'"); + } + var simplify = new String[]{ + "$('div#header').css('display','none');", + "$('div#footer-holder').css('display','none')", + }; + for (String js : simplify) { + browser.executeJavaScript(js); + } + browser.executeJavaScript("document.querySelector('form[action*=login]').submit()"); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.warn("Couldn't auto fill username and password for Chaturbate", e); + } catch (Exception e) { + LOG.warn("Couldn't auto fill username and password for Chaturbate", e); + } + } + + if (json.has("cookies")) { + var cookiesFromBrowser = json.getJSONArray("cookies"); + for (var i = 0; i < cookiesFromBrowser.length(); i++) { + var cookie = cookiesFromBrowser.getJSONObject(i); + if (cookie.getString("domain").contains(DOMAIN)) { + Builder b = new Builder() + .path(cookie.getString("path")) + .domain(DOMAIN) + .name(cookie.getString("name")) + .value(cookie.getString("value")) + .expiresAt((long) cookie.optDouble("expirationDate")); + 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(ChaturbateElectronLoginDialog.this.site.getBaseUrl()), Collections.singletonList(c)); + } + } + } + + try { + if (Objects.equals(new URL(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); + } + } + } + }; +} diff --git a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateSiteUi.java b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateSiteUi.java index 2437e8fd..90d84740 100644 --- a/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateSiteUi.java +++ b/client/src/main/java/ctbrec/ui/sites/chaturbate/ChaturbateSiteUi.java @@ -1,14 +1,20 @@ package ctbrec.ui.sites.chaturbate; -import java.io.IOException; - import ctbrec.sites.chaturbate.Chaturbate; +import ctbrec.sites.chaturbate.ChaturbateHttpClient; +import ctbrec.ui.controls.Dialogs; import ctbrec.ui.sites.AbstractSiteUi; import ctbrec.ui.sites.ConfigUI; import ctbrec.ui.tabs.TabProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; public class ChaturbateSiteUi extends AbstractSiteUi { + private static final Logger LOG = LoggerFactory.getLogger(ChaturbateSiteUi.class); + private final Chaturbate chaturbate; private ChaturbateTabProvider tabProvider; private ChaturbateConfigUi configUi; @@ -35,7 +41,26 @@ public class ChaturbateSiteUi extends AbstractSiteUi { @Override public synchronized boolean login() throws IOException { - return chaturbate.login(); + boolean automaticLogin = false; + try { + automaticLogin = chaturbate.login(); + } catch (Exception e) { + LOG.debug("Automatic login failed", e); + } + if (automaticLogin) { + return true; + } else { + // login with external browser window + try { + new ChaturbateElectronLoginDialog(chaturbate, chaturbate.getHttpClient().getCookieJar()); + } catch (Exception e1) { + LOG.error("Error logging in with external browser", e1); + Dialogs.showError("Login error", "Couldn't login to " + chaturbate.getName(), e1); + } + + ChaturbateHttpClient httpClient = (ChaturbateHttpClient) chaturbate.getHttpClient(); + return httpClient.checkLogin(); + } } } diff --git a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java index 6cefaffa..bcc54505 100644 --- a/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java +++ b/common/src/main/java/ctbrec/sites/chaturbate/ChaturbateHttpClient.java @@ -1,6 +1,12 @@ package ctbrec.sites.chaturbate; -import static ctbrec.io.HttpConstants.*; +import ctbrec.Config; +import ctbrec.io.HtmlParser; +import ctbrec.io.HttpClient; +import okhttp3.*; +import org.jsoup.nodes.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InterruptedIOException; @@ -8,25 +14,16 @@ import java.util.NoSuchElementException; import java.util.Objects; import java.util.concurrent.Semaphore; -import org.jsoup.nodes.Element; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import ctbrec.Config; -import ctbrec.io.HtmlParser; -import ctbrec.io.HttpClient; -import okhttp3.Cookie; -import okhttp3.FormBody; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; +import static ctbrec.io.HttpConstants.REFERER; +import static ctbrec.io.HttpConstants.USER_AGENT; public class ChaturbateHttpClient extends HttpClient { private static final Logger LOG = LoggerFactory.getLogger(ChaturbateHttpClient.class); + private static final String PATH = "/auth/login/"; // NOSONAR protected String token; - private static Semaphore requestThrottle = new Semaphore(2, true); + private static final Semaphore requestThrottle = new Semaphore(2, true); private static long lastRequest = 0; public ChaturbateHttpClient(Config config) { @@ -37,13 +34,13 @@ public class ChaturbateHttpClient extends HttpClient { try { Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken"); token = csrfToken.value(); - } catch(NoSuchElementException e) { + } catch (NoSuchElementException e) { LOG.trace("CSRF token not found in cookies"); } } public String getToken() throws IOException { - if(token == null) { + if (token == null) { login(); } return token; @@ -51,11 +48,11 @@ public class ChaturbateHttpClient extends HttpClient { @Override public boolean login() throws IOException { - if(loggedIn) { + if (loggedIn) { return true; } - if(checkLogin()) { + if (checkLogin()) { loggedIn = true; LOG.debug("Logged in with cookies"); return true; @@ -63,7 +60,7 @@ public class ChaturbateHttpClient extends HttpClient { try { Request login = new Request.Builder() - .url(Chaturbate.baseUrl + "/auth/login/") + .url(Chaturbate.baseUrl + PATH) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .build(); Response response = client.newCall(login).execute(); @@ -78,23 +75,23 @@ public class ChaturbateHttpClient extends HttpClient { .add("csrfmiddlewaretoken", token) .build(); login = new Request.Builder() - .url(Chaturbate.baseUrl + "/auth/login/") - .header(REFERER, Chaturbate.baseUrl + "/auth/login/") + .url(Chaturbate.baseUrl + PATH) + .header(REFERER, Chaturbate.baseUrl + PATH) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .post(body) .build(); response = client.newCall(login).execute(); - if(response.isSuccessful()) { + if (response.isSuccessful()) { content = response.body().string(); - if(content.contains("Login, Chaturbate login")) { + if (content.contains("Login, Chaturbate login")) { loggedIn = false; } else { loggedIn = true; extractCsrfToken(login); } } else { - if(loginTries++ < 3) { + if (loginTries++ < 3) { login(); } else { throw new IOException("Login failed: " + response.code() + " " + response.message()); @@ -107,24 +104,25 @@ public class ChaturbateHttpClient extends HttpClient { return loggedIn; } - private boolean checkLogin() throws IOException { + public boolean checkLogin() throws IOException { String url = "https://chaturbate.com/p/" + Config.getInstance().getSettings().chaturbateUsername + "/"; Request req = new Request.Builder() .url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .build(); - Response resp = execute(req); - if (resp.isSuccessful()) { - String profilePage = resp.body().string(); - try { - Element userIcon = HtmlParser.getTag(profilePage, "img.user_information_header_icon"); - return !Objects.equals("Anonymous Icon", userIcon.attr("alt")); - } catch(Exception e) { - LOG.debug("Token tag not found. Login failed"); - return false; + try (Response resp = execute(req)) { + if (resp.isSuccessful()) { + String profilePage = resp.body().string(); + try { + Element userIcon = HtmlParser.getTag(profilePage, "img.user_information_header_icon"); + return !Objects.equals("Anonymous Icon", userIcon.attr("alt")); + } catch (Exception e) { + LOG.debug("Token tag not found. Login failed"); + return false; + } + } else { + throw new IOException("HTTP response: " + resp.code() + " - " + resp.message()); } - } else { - throw new IOException("HTTP response: " + resp.code() + " - " + resp.message()); } }