Add login for xLoveCam
The login is not quite working. Probably something with the password encryption or the fnv hash
This commit is contained in:
parent
804d7b0f52
commit
939758403e
|
@ -1,3 +1,9 @@
|
||||||
|
4.4.0
|
||||||
|
========================
|
||||||
|
* Added Amateur.TV
|
||||||
|
* Added XloveCam
|
||||||
|
* Fixed tipping function
|
||||||
|
|
||||||
4.3.1
|
4.3.1
|
||||||
========================
|
========================
|
||||||
* Fixed bug in server communication. The server always returned HTTP 400,
|
* Fixed bug in server communication. The server always returned HTTP 400,
|
||||||
|
|
|
@ -10,7 +10,10 @@ import javafx.scene.Parent;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.PasswordField;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
|
||||||
public class XloveCamConfigUI extends AbstractConfigUI {
|
public class XloveCamConfigUI extends AbstractConfigUI {
|
||||||
private XloveCam site;
|
private XloveCam site;
|
||||||
|
@ -40,10 +43,41 @@ public class XloveCamConfigUI extends AbstractConfigUI {
|
||||||
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
|
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
|
||||||
layout.add(enabled, 1, row++);
|
layout.add(enabled, 1, row++);
|
||||||
|
|
||||||
|
layout.add(new Label("XloveCam User"), 0, row);
|
||||||
|
var username = new TextField(Config.getInstance().getSettings().xlovecamUsername);
|
||||||
|
username.textProperty().addListener((ob, o, n) -> {
|
||||||
|
if(!n.equals(Config.getInstance().getSettings().xlovecamUsername)) {
|
||||||
|
Config.getInstance().getSettings().xlovecamUsername = 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("XloveCam Password"), 0, row);
|
||||||
|
var password = new PasswordField();
|
||||||
|
password.setText(Config.getInstance().getSettings().xlovecamPassword);
|
||||||
|
password.textProperty().addListener((ob, o, n) -> {
|
||||||
|
if(!n.equals(Config.getInstance().getSettings().xlovecamPassword)) {
|
||||||
|
Config.getInstance().getSettings().xlovecamPassword = password.getText();
|
||||||
|
site.getHttpClient().logout();
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
GridPane.setFillWidth(password, true);
|
||||||
|
GridPane.setHgrow(password, Priority.ALWAYS);
|
||||||
|
GridPane.setColumnSpan(password, 2);
|
||||||
|
layout.add(password, 1, row++);
|
||||||
|
|
||||||
var createAccount = new Button("Create new Account");
|
var createAccount = new Button("Create new Account");
|
||||||
createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
|
createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
|
||||||
layout.add(createAccount, 1, row);
|
layout.add(createAccount, 1, row);
|
||||||
GridPane.setColumnSpan(createAccount, 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));
|
GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
|
||||||
return layout;
|
return layout;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ public class XloveCamSiteUi extends AbstractSiteUi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean login() throws IOException {
|
public synchronized boolean login() throws IOException {
|
||||||
|
if (!site.credentialsAvailable()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return site.getHttpClient().login();
|
return site.getHttpClient().login();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,4 +182,6 @@ public class Settings {
|
||||||
public boolean webinterface = false;
|
public boolean webinterface = false;
|
||||||
public String webinterfaceUsername = "ctbrec";
|
public String webinterfaceUsername = "ctbrec";
|
||||||
public String webinterfacePassword = "sucks";
|
public String webinterfacePassword = "sucks";
|
||||||
|
public String xlovecamUsername = "";
|
||||||
|
public String xlovecamPassword = "";
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ public class HttpConstants {
|
||||||
public static final String CONTENT_ENCODING = "Content-Encoding";
|
public static final String CONTENT_ENCODING = "Content-Encoding";
|
||||||
public static final String CONTENT_TYPE = "Content-Type";
|
public static final String CONTENT_TYPE = "Content-Type";
|
||||||
public static final String COOKIE = "Cookie";
|
public static final String COOKIE = "Cookie";
|
||||||
|
public static final String FORM_ENCODED = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||||
public static final String KEEP_ALIVE = "keep-alive";
|
public static final String KEEP_ALIVE = "keep-alive";
|
||||||
public static final String MIMETYPE_APPLICATION_JSON = "application/json";
|
public static final String MIMETYPE_APPLICATION_JSON = "application/json";
|
||||||
public static final String MIMETYPE_TEXT_HTML = "text/html";
|
public static final String MIMETYPE_TEXT_HTML = "text/html";
|
||||||
|
|
|
@ -7,13 +7,16 @@ import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
|
import ctbrec.StringUtil;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.sites.AbstractSite;
|
import ctbrec.sites.AbstractSite;
|
||||||
|
|
||||||
public class XloveCam extends AbstractSite {
|
public class XloveCam extends AbstractSite {
|
||||||
|
|
||||||
public static String baseUrl = "https://mobile.xlovecam.com";
|
public static String baseUrl = "https://www.xlovecam.com";
|
||||||
|
public static String mobileUrl = "https://mobile.xlovecam.com";
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -28,7 +31,7 @@ public class XloveCam extends AbstractSite {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return baseUrl;
|
return mobileUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,7 +112,7 @@ public class XloveCam extends AbstractSite {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean credentialsAvailable() {
|
public boolean credentialsAvailable() {
|
||||||
return false;
|
return StringUtil.isNotBlank(Config.getInstance().getSettings().xlovecamUsername);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,17 +1,201 @@
|
||||||
package ctbrec.sites.xlovecam;
|
package ctbrec.sites.xlovecam;
|
||||||
|
|
||||||
import java.io.IOException;
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class XloveCamHttpClient extends HttpClient {
|
public class XloveCamHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(XloveCamHttpClient.class);
|
||||||
|
private static final Pattern CSRF_PATTERN = Pattern.compile("CSRFToken\s*=\s*\"(.*?)\";");
|
||||||
|
private final Random rng = new Random();
|
||||||
|
|
||||||
public XloveCamHttpClient() {
|
public XloveCamHttpClient() {
|
||||||
super("xlovecam");
|
super("xlovecam");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean login() throws IOException {
|
public boolean login() throws IOException {
|
||||||
return false;
|
String username = Config.getInstance().getSettings().xlovecamUsername;
|
||||||
|
String csrfToken = getCsrfToken();
|
||||||
|
JSONObject config = getConfig();
|
||||||
|
String token = config.getString("token");
|
||||||
|
byte[] passwordKey = getPasswordKey(config);
|
||||||
|
byte[] encryptedPassword = encryptPassword(Config.getInstance().getSettings().xlovecamPassword, passwordKey);
|
||||||
|
String base64EncryptedPassword = Base64.getEncoder().encodeToString(encryptedPassword);
|
||||||
|
LOG.debug("csrf:{} token:{} key:{}", csrfToken, token, Arrays.toString(passwordKey));
|
||||||
|
LOG.debug("encrypted password: {}", base64EncryptedPassword);
|
||||||
|
|
||||||
|
long time = System.currentTimeMillis() / 1000;
|
||||||
|
long rnd = rng.nextInt(100_000_000);
|
||||||
|
String rndTokenString = token + ':' + Long.toString(rnd) + ":\u20ac\u2716\u21aa:" + time + ':' + username;
|
||||||
|
long rndToken = fnv32a(rndTokenString.getBytes(UTF_8));
|
||||||
|
|
||||||
|
RequestBody body = new FormBody.Builder() // @formatter:off
|
||||||
|
.add("pseudo", username)
|
||||||
|
.add("passwd", "")
|
||||||
|
.add("keep", "1")
|
||||||
|
.add("isLayer", "1")
|
||||||
|
.add("token", token)
|
||||||
|
.add("grecaptchaLoaded", "0")
|
||||||
|
.add("extra[screen][width]", "1920")
|
||||||
|
.add("extra[screen][height]", "1080")
|
||||||
|
.add("extra[tmz]", "0")
|
||||||
|
.add("extra[blng]", "en")
|
||||||
|
.add("extra[clientTimezone]", "GMT")
|
||||||
|
.add("extra[flash][installed]", "false")
|
||||||
|
.add("extra[flash][raw]", "")
|
||||||
|
.add("extra[flash][major]", "-1")
|
||||||
|
.add("extra[flash][minor]", "-1")
|
||||||
|
.add("extra[flash][revision]", "-1")
|
||||||
|
.add("extra[flash][revisionStr]", "")
|
||||||
|
.add("extra[rnd][rnd]", Long.toString(rnd))
|
||||||
|
.add("extra[rnd][time]", Long.toString(time))
|
||||||
|
.add("extra[rnd][token]", Integer.toString((int)rndToken))
|
||||||
|
.add("csrf", csrfToken)
|
||||||
|
.add("passwdEncoded", "b64:" + base64EncryptedPassword)
|
||||||
|
.add("g-recaptcha-response", "")
|
||||||
|
.build(); // @formatter:on
|
||||||
|
|
||||||
|
String url = XloveCam.baseUrl + "/en/ajax/client/login2";
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.method("POST", body)
|
||||||
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
||||||
|
.header(REFERER, XloveCam.baseUrl + "/en/login")
|
||||||
|
.header(ORIGIN, XloveCam.baseUrl)
|
||||||
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
|
.header(CONTENT_TYPE, FORM_ENCODED)
|
||||||
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||||
|
.build();
|
||||||
|
try (Response resp = execute(req)) {
|
||||||
|
if (resp.isSuccessful()) {
|
||||||
|
String msg = resp.body().string();
|
||||||
|
JSONObject json = new JSONObject(msg);
|
||||||
|
LOG.debug(json.toString(2));
|
||||||
|
return json.optBoolean("success");
|
||||||
|
} else {
|
||||||
|
throw new HttpException(resp.code(), resp.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long fnv32a(byte[] a) {
|
||||||
|
long b = 2166136261l;
|
||||||
|
for (int d = 0; d < a.length; d++) {
|
||||||
|
b ^= (a[d] & 0xFF);
|
||||||
|
b += (b << 1) + (b << 4) + (b << 7) + (b << 8) + (b << 24) & 4294967295l;
|
||||||
|
}
|
||||||
|
return b >>> 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] encryptPassword(String xlovecamPassword, byte[] passwordKey) {
|
||||||
|
byte[] password = xlovecamPassword.getBytes(UTF_8);
|
||||||
|
byte d = (byte) password.length;
|
||||||
|
byte[] c = new byte[password.length + rng.nextInt(20)];
|
||||||
|
System.arraycopy(password, 0, c, 0, password.length);
|
||||||
|
for (int i = password.length; i < c.length; i++) {
|
||||||
|
c[i] = (byte)rng.nextInt(255);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] b = new byte[c.length + 1];
|
||||||
|
b[0] = (byte)(d ^ (passwordKey[0] & 0xFF));
|
||||||
|
|
||||||
|
d = 1;
|
||||||
|
for (int e = 0; e < c.length; e++) {
|
||||||
|
b[e+1] = (byte)(c[e] ^ passwordKey[d]);
|
||||||
|
d++;
|
||||||
|
if(d >= passwordKey.length) {
|
||||||
|
d = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getPasswordKey(JSONObject config) {
|
||||||
|
String passwordKeyString = config.getString("passwordKey");
|
||||||
|
LOG.debug(passwordKeyString);
|
||||||
|
String[] numbers = passwordKeyString.split(",");
|
||||||
|
byte[] passwordKey = new byte[numbers.length];
|
||||||
|
for (int i = 0; i < numbers.length; i++) {
|
||||||
|
passwordKey[i] = (byte)(Integer.parseInt(numbers[i]) & 0xFF);
|
||||||
|
}
|
||||||
|
return passwordKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCsrfToken() throws IOException {
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url(XloveCam.baseUrl)
|
||||||
|
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
||||||
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
|
.build();
|
||||||
|
try (Response resp = execute(req)) {
|
||||||
|
if (resp.isSuccessful()) {
|
||||||
|
String body = resp.body().string();
|
||||||
|
Matcher m = CSRF_PATTERN.matcher(body);
|
||||||
|
if (m.find()) {
|
||||||
|
return m.group(1);
|
||||||
|
} else {
|
||||||
|
throw new IOException("CSRF token not found in landing page");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new HttpException(resp.code(), resp.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject getConfig() throws IOException {
|
||||||
|
String url = XloveCam.baseUrl + "/en/popup/login";
|
||||||
|
LOG.debug("Calling {}", url);
|
||||||
|
RequestBody body = new FormBody.Builder()
|
||||||
|
.add("referrer", "https://www.xlovecam.com/en/")
|
||||||
|
.add("referrer_is_layer", "0")
|
||||||
|
.add("referrer_model_id", "")
|
||||||
|
.add("referrer_model_name", "")
|
||||||
|
.build();
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.method("POST", body)
|
||||||
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
|
||||||
|
.header(REFERER, XloveCam.baseUrl)
|
||||||
|
.header(ORIGIN, XloveCam.baseUrl)
|
||||||
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
|
.header(CONTENT_TYPE, FORM_ENCODED)
|
||||||
|
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||||
|
.build();
|
||||||
|
try (Response resp = execute(req)) {
|
||||||
|
if (resp.isSuccessful()) {
|
||||||
|
String msg = resp.body().string();
|
||||||
|
JSONObject json = new JSONObject(msg);
|
||||||
|
if (json.has("config")) {
|
||||||
|
return json.getJSONObject("config");
|
||||||
|
} else {
|
||||||
|
throw new IOException("JSON doesn't contain \"config\":\n" + json.toString(2));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new HttpException(resp.code(), resp.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class XloveCamModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getModelPage() throws IOException {
|
private String getModelPage() throws IOException {
|
||||||
String url = XloveCam.baseUrl + "/en/model/" + getName() + '/';
|
String url = XloveCam.mobileUrl + "/en/model/" + getName() + '/';
|
||||||
Request req = new Request.Builder()
|
Request req = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||||
|
|
Loading…
Reference in New Issue