forked from j62/ctbrec
Merge branch 'dev' into fc2
# Conflicts: # client/src/main/java/ctbrec/ui/CamrecApplication.java # client/src/main/java/ctbrec/ui/SiteUiFactory.java
This commit is contained in:
commit
55fc6729f8
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -1,3 +1,19 @@
|
|||
1.17.0
|
||||
========================
|
||||
* Added LiveJasmin
|
||||
There are some issues, though:
|
||||
* live previews don't work
|
||||
* it's best to have an account and to be logged in, otherwise you might get
|
||||
errors after some time
|
||||
* the pagination and sorting of the models is random, because the
|
||||
pagination LiveJasmin uses is quite obscure
|
||||
* Added an electron based external browser component, which makes logins, which are
|
||||
secured by Google's recaptcha, more reliable. This should also fix the login problems
|
||||
with BongaCams (#58)
|
||||
* Added a docker file for the server (thanks to bounty1342)
|
||||
* Fixed Streamate favorites tab
|
||||
* Added a setting for the thumbnail overview update interval
|
||||
|
||||
1.16.0
|
||||
========================
|
||||
* Thumbnails can show a live preview. Can be switched on in the settings.
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
/ctbrec-tunnel.sh
|
||||
/jre/
|
||||
/server-local.sh
|
||||
/browser/
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>client</artifactId>
|
||||
|
||||
|
||||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>1.16.0</version>
|
||||
<version>1.17.0</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -80,7 +80,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -38,6 +38,7 @@ import ctbrec.sites.cam4.Cam4;
|
|||
import ctbrec.sites.camsoda.Camsoda;
|
||||
import ctbrec.sites.chaturbate.Chaturbate;
|
||||
import ctbrec.sites.fc2live.Fc2Live;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import ctbrec.sites.mfc.MyFreeCams;
|
||||
import ctbrec.sites.streamate.Streamate;
|
||||
import ctbrec.ui.settings.SettingsTab;
|
||||
|
@ -78,6 +79,7 @@ public class CamrecApplication extends Application {
|
|||
sites.add(new Camsoda());
|
||||
sites.add(new Chaturbate());
|
||||
sites.add(new Fc2Live());
|
||||
sites.add(new LiveJasmin());
|
||||
sites.add(new MyFreeCams());
|
||||
sites.add(new Streamate());
|
||||
loadConfig();
|
||||
|
@ -196,6 +198,10 @@ public class CamrecApplication extends Application {
|
|||
System.exit(1);
|
||||
});
|
||||
}
|
||||
try {
|
||||
ExternalBrowser.getInstance().close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
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.Config;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.Settings.ProxyType;
|
||||
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(JSONObject jsonConfig, Consumer<String> messageListener) throws InterruptedException, IOException {
|
||||
LOG.debug("Running browser with config {}", jsonConfig);
|
||||
lock.lock();
|
||||
try {
|
||||
stopped = false;
|
||||
this.messageListener = messageListener;
|
||||
|
||||
addProxyConfig(jsonConfig.getJSONObject("config"));
|
||||
|
||||
p = new ProcessBuilder(OS.getBrowserCommand()).start();
|
||||
new StreamRedirectThread(p.getInputStream(), System.err);
|
||||
new StreamRedirectThread(p.getErrorStream(), System.err);
|
||||
LOG.debug("Browser started");
|
||||
|
||||
connectToRemoteControlSocket();
|
||||
if(LOG.isTraceEnabled()) {
|
||||
LOG.debug("Connected to remote control server. Sending config {}", jsonConfig);
|
||||
} else {
|
||||
LOG.debug("Connected to remote control server. Sending config");
|
||||
}
|
||||
out.write(jsonConfig.toString().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);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void connectToRemoteControlSocket() throws IOException {
|
||||
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);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addProxyConfig(JSONObject jsonConfig) {
|
||||
ProxyType proxyType = Config.getInstance().getSettings().proxyType;
|
||||
switch (proxyType) {
|
||||
case HTTP:
|
||||
JSONObject proxy = new JSONObject();
|
||||
proxy.put("address",
|
||||
"http=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort
|
||||
+ ";https=" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
||||
String username = Config.getInstance().getSettings().proxyUser;
|
||||
String password = Config.getInstance().getSettings().proxyPassword;
|
||||
proxy.put("user", username);
|
||||
proxy.put("password", password);
|
||||
}
|
||||
jsonConfig.put("proxy", proxy);
|
||||
break;
|
||||
case SOCKS4:
|
||||
proxy = new JSONObject();
|
||||
proxy.put("address", "socks4://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
jsonConfig.put("proxy", proxy);
|
||||
break;
|
||||
case SOCKS5:
|
||||
proxy = new JSONObject();
|
||||
proxy.put("address", "socks5://" + Config.getInstance().getSettings().proxyHost + ':' + Config.getInstance().getSettings().proxyPort);
|
||||
if(Config.getInstance().getSettings().proxyUser != null && !Config.getInstance().getSettings().proxyUser.isEmpty()) {
|
||||
String username = Config.getInstance().getSettings().proxyUser;
|
||||
String password = Config.getInstance().getSettings().proxyPassword;
|
||||
proxy.put("user", username);
|
||||
proxy.put("password", password);
|
||||
}
|
||||
jsonConfig.put("proxy", proxy);
|
||||
break;
|
||||
case DIRECT:
|
||||
default:
|
||||
// nothing to do here
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -126,7 +126,7 @@ public class JavaFxModel implements Model {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
SiteUiFactory.getUi(getSite()).login();
|
||||
delegate.receiveTip(tokens);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import ctbrec.sites.cam4.Cam4;
|
|||
import ctbrec.sites.camsoda.Camsoda;
|
||||
import ctbrec.sites.chaturbate.Chaturbate;
|
||||
import ctbrec.sites.fc2live.Fc2Live;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import ctbrec.sites.mfc.MyFreeCams;
|
||||
import ctbrec.sites.streamate.Streamate;
|
||||
import ctbrec.ui.sites.bonga.BongaCamsSiteUi;
|
||||
|
@ -13,6 +14,7 @@ import ctbrec.ui.sites.cam4.Cam4SiteUi;
|
|||
import ctbrec.ui.sites.camsoda.CamsodaSiteUi;
|
||||
import ctbrec.ui.sites.chaturbate.ChaturbateSiteUi;
|
||||
import ctbrec.ui.sites.fc2live.Fc2LiveUi;
|
||||
import ctbrec.ui.sites.jasmin.LiveJasminSiteUi;
|
||||
import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi;
|
||||
import ctbrec.ui.sites.streamate.StreamateSiteUi;
|
||||
|
||||
|
@ -23,6 +25,7 @@ public class SiteUiFactory {
|
|||
private static CamsodaSiteUi camsodaSiteUi;
|
||||
private static ChaturbateSiteUi ctbSiteUi;
|
||||
private static Fc2LiveUi fc2SiteUi;
|
||||
private static LiveJasminSiteUi jasminSiteUi;
|
||||
private static MyFreeCamsSiteUi mfcSiteUi;
|
||||
private static StreamateSiteUi streamateSiteUi;
|
||||
|
||||
|
@ -62,6 +65,11 @@ public class SiteUiFactory {
|
|||
streamateSiteUi = new StreamateSiteUi((Streamate) site);
|
||||
}
|
||||
return streamateSiteUi;
|
||||
} else if (site instanceof LiveJasmin) {
|
||||
if (jasminSiteUi == null) {
|
||||
jasminSiteUi = new LiveJasminSiteUi((LiveJasmin) site);
|
||||
}
|
||||
return jasminSiteUi;
|
||||
}
|
||||
throw new RuntimeException("Unknown site " + site.getName());
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import static ctbrec.ui.controls.Dialogs.*;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
|
@ -470,20 +471,19 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
tipDialog.showAndWait();
|
||||
String tipText = tipDialog.getResult();
|
||||
if(tipText != null) {
|
||||
if(tipText.matches("[1-9]\\d*")) {
|
||||
int tokens = Integer.parseInt(tipText);
|
||||
try {
|
||||
SiteUiFactory.getUi(site).login();
|
||||
cell.getModel().receiveTip(tokens);
|
||||
Map<String, Object> event = new HashMap<>();
|
||||
event.put("event", "tokens.sent");
|
||||
event.put("amount", tokens);
|
||||
EventBusHolder.BUS.post(event);
|
||||
} catch (Exception e1) {
|
||||
LOG.error("An error occured while sending tip", e1);
|
||||
showError("Couldn't send tip", "An error occured while sending tip:", e1);
|
||||
}
|
||||
} else {
|
||||
DecimalFormat df = new DecimalFormat("0.##");
|
||||
try {
|
||||
Number tokens = df.parse(tipText);
|
||||
SiteUiFactory.getUi(site).login();
|
||||
cell.getModel().receiveTip(tokens.doubleValue());
|
||||
Map<String, Object> event = new HashMap<>();
|
||||
event.put("event", "tokens.sent");
|
||||
event.put("amount", tokens.doubleValue());
|
||||
EventBusHolder.BUS.post(event);
|
||||
} catch (IOException ex) {
|
||||
LOG.error("An error occured while sending tip", ex);
|
||||
showError("Couldn't send tip", "An error occured while sending tip:", ex);
|
||||
} catch (Exception ex) {
|
||||
showError("Couldn't send tip", "You entered an invalid amount of tokens", null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
|
@ -30,21 +31,21 @@ public class TipDialog extends TextInputDialog {
|
|||
}
|
||||
|
||||
private void loadTokenBalance() {
|
||||
Task<Integer> task = new Task<Integer>() {
|
||||
Task<Double> task = new Task<Double>() {
|
||||
@Override
|
||||
protected Integer call() throws Exception {
|
||||
protected Double call() throws Exception {
|
||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
SiteUiFactory.getUi(site).login();
|
||||
return site.getTokenBalance();
|
||||
} else {
|
||||
return 1_000_000;
|
||||
return 1_000_000d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
int tokens = get();
|
||||
double tokens = get();
|
||||
Platform.runLater(() -> {
|
||||
if (tokens <= 0) {
|
||||
String msg = "Do you want to buy tokens now?\n\nIf you agree, "+site.getName()+" will open in a browser. "
|
||||
|
@ -59,7 +60,8 @@ public class TipDialog extends TextInputDialog {
|
|||
}
|
||||
} else {
|
||||
getEditor().setDisable(false);
|
||||
setHeaderText("Current token balance: " + tokens);
|
||||
DecimalFormat df = new DecimalFormat("0.##");
|
||||
setHeaderText("Current token balance: " + df.format(tokens));
|
||||
}
|
||||
});
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
@ -19,7 +20,7 @@ import javafx.scene.control.Tooltip;
|
|||
public class TokenLabel extends Label {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(TokenLabel.class);
|
||||
private int tokens = -1;
|
||||
private double tokens = -1;
|
||||
private Site site;
|
||||
|
||||
public TokenLabel(Site site) {
|
||||
|
@ -29,11 +30,10 @@ public class TokenLabel extends Label {
|
|||
@Subscribe
|
||||
public void tokensUpdates(Map<String, Object> e) {
|
||||
if (Objects.equals("tokens", e.get("event"))) {
|
||||
tokens = (int) e.get("amount");
|
||||
tokens = (double) e.get("amount");
|
||||
updateText();
|
||||
} else if (Objects.equals("tokens.sent", e.get("event"))) {
|
||||
int _tokens = (int) e.get("amount");
|
||||
tokens -= _tokens;
|
||||
tokens -= (double) e.get("amount");
|
||||
updateText();
|
||||
}
|
||||
}
|
||||
|
@ -45,31 +45,34 @@ public class TokenLabel extends Label {
|
|||
updateText();
|
||||
}
|
||||
|
||||
public void update(int tokens) {
|
||||
public void update(double tokens) {
|
||||
this.tokens = tokens;
|
||||
updateText();
|
||||
}
|
||||
|
||||
private void updateText() {
|
||||
Platform.runLater(() -> setText("Tokens: " + tokens));
|
||||
Platform.runLater(() -> {
|
||||
DecimalFormat df = new DecimalFormat("0.##");
|
||||
setText("Tokens: " + df.format(tokens));
|
||||
});
|
||||
}
|
||||
|
||||
public void loadBalance() {
|
||||
Task<Integer> task = new Task<Integer>() {
|
||||
Task<Double> task = new Task<Double>() {
|
||||
@Override
|
||||
protected Integer call() throws Exception {
|
||||
protected Double call() throws Exception {
|
||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
SiteUiFactory.getUi(site).login();
|
||||
return site.getTokenBalance();
|
||||
} else {
|
||||
return 1_000_000;
|
||||
return 1_000_000d;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
int tokens = get();
|
||||
double tokens = get();
|
||||
update(tokens);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOG.error("Couldn't retrieve account balance", e);
|
||||
|
|
|
@ -3,23 +3,25 @@ package ctbrec.ui;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.ui.CamrecApplication.Release;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class UpdateTab extends Tab {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(UpdateTab.class);
|
||||
|
||||
private WebView browser;
|
||||
private TextArea changelog;
|
||||
|
||||
public UpdateTab(Release latest) {
|
||||
setText("Update Available");
|
||||
|
@ -32,18 +34,24 @@ public class UpdateTab extends Tab {
|
|||
vbox.getChildren().add(button);
|
||||
VBox.setMargin(button, new Insets(0, 0, 10, 0));
|
||||
vbox.setAlignment(Pos.CENTER);
|
||||
|
||||
browser = new WebView();
|
||||
try {
|
||||
WebEngine webEngine = browser.getEngine();
|
||||
webEngine.load("https://raw.githubusercontent.com/0xboobface/ctbrec/master/CHANGELOG.md");
|
||||
webEngine.setUserDataDirectory(Config.getInstance().getConfigDir());
|
||||
vbox.getChildren().add(browser);
|
||||
VBox.setVgrow(browser, Priority.ALWAYS);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Couldn't load changelog", e);
|
||||
}
|
||||
|
||||
changelog = new TextArea();
|
||||
changelog.setEditable(false);
|
||||
vbox.getChildren().add(changelog);
|
||||
VBox.setVgrow(changelog, Priority.ALWAYS);
|
||||
setContent(vbox);
|
||||
|
||||
new Thread(() -> {
|
||||
Request req = new Request.Builder().url("https://raw.githubusercontent.com/0xboobface/ctbrec/master/CHANGELOG.md").build();
|
||||
try(Response resp = CamrecApplication.httpClient.execute(req)) {
|
||||
if(resp.isSuccessful()) {
|
||||
changelog.setText(resp.body().string());
|
||||
} else {
|
||||
throw new HttpException(resp.code(), resp.message());
|
||||
}
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Couldn't download the changelog", e1);
|
||||
Dialogs.showError("Communication error", "Couldn't download the changelog", e1);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ package ctbrec.ui.controls;
|
|||
import java.io.InterruptedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
@ -162,10 +161,6 @@ public class StreamPreview extends StackPane {
|
|||
|
||||
private void onError(MediaPlayer videoPlayer) {
|
||||
LOG.error("Error while starting preview stream", videoPlayer.getError());
|
||||
Optional<Throwable> cause = Optional.ofNullable(videoPlayer).map(v -> v.getError()).map(e -> e.getCause());
|
||||
if(cause.isPresent()) {
|
||||
LOG.error("Error while starting preview stream root cause:", cause.get());
|
||||
}
|
||||
showTestImage();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
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, msgHandler);
|
||||
} catch (InterruptedException e) {
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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,7 @@ 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;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
|
||||
public class BongaCamsSiteUi implements SiteUI {
|
||||
|
||||
|
@ -50,31 +44,26 @@ public class BongaCamsSiteUi implements SiteUI {
|
|||
return true;
|
||||
} else {
|
||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
||||
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);
|
||||
Dialogs.showError("Login error", "Couldn't login to " + bongaCams.getName(), e1);
|
||||
}
|
||||
|
||||
Runnable showDialog = () -> {
|
||||
// login with javafx WebView
|
||||
BongaCamsLoginDialog loginDialog = new BongaCamsLoginDialog();
|
||||
|
||||
// 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);
|
||||
}
|
||||
try {
|
||||
queue.put(true);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Error while signaling termination", e);
|
||||
}
|
||||
}).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();
|
||||
|
@ -87,26 +76,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
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, msgHandler);
|
||||
} catch (InterruptedException e) {
|
||||
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);
|
||||
} 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
@ -15,10 +12,8 @@ import ctbrec.sites.cam4.Cam4;
|
|||
import ctbrec.sites.cam4.Cam4HttpClient;
|
||||
import ctbrec.ui.SiteUI;
|
||||
import ctbrec.ui.TabProvider;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
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);
|
||||
|
@ -46,18 +41,20 @@ public class Cam4SiteUi implements SiteUI {
|
|||
@Override
|
||||
public synchronized boolean login() throws IOException {
|
||||
boolean automaticLogin = cam4.login();
|
||||
if(automaticLogin) {
|
||||
if (automaticLogin) {
|
||||
return true;
|
||||
} else {
|
||||
|
||||
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);
|
||||
Dialogs.showError("Login error", "Couldn't login to " + cam4.getName(), e1);
|
||||
}
|
||||
|
||||
try {
|
||||
queue.put(true);
|
||||
|
@ -66,16 +63,12 @@ public class Cam4SiteUi implements SiteUI {
|
|||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
Platform.runLater(showDialog);
|
||||
try {
|
||||
queue.take();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.error("Error while waiting for login dialog to close", e);
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
Cam4HttpClient httpClient = (Cam4HttpClient) cam4.getHttpClient();
|
||||
|
@ -83,31 +76,4 @@ public class Cam4SiteUi implements SiteUI {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }";
|
||||
}
|
|
@ -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);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Settings;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
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 LiveJasminConfigUi extends AbstractConfigUI {
|
||||
private LiveJasmin liveJasmin;
|
||||
|
||||
public LiveJasminConfigUi(LiveJasmin liveJasmin) {
|
||||
this.liveJasmin = liveJasmin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parent createConfigPanel() {
|
||||
Settings settings = Config.getInstance().getSettings();
|
||||
GridPane layout = SettingsTab.createGridLayout();
|
||||
|
||||
int row = 0;
|
||||
Label l = new Label("Active");
|
||||
layout.add(l, 0, row);
|
||||
CheckBox enabled = new CheckBox();
|
||||
enabled.setSelected(!settings.disabledSites.contains(liveJasmin.getName()));
|
||||
enabled.setOnAction((e) -> {
|
||||
if(enabled.isSelected()) {
|
||||
settings.disabledSites.remove(liveJasmin.getName());
|
||||
} else {
|
||||
settings.disabledSites.add(liveJasmin.getName());
|
||||
}
|
||||
save();
|
||||
});
|
||||
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
|
||||
layout.add(enabled, 1, row++);
|
||||
|
||||
layout.add(new Label("LiveJasmin User"), 0, row);
|
||||
TextField username = new TextField(Config.getInstance().getSettings().livejasminUsername);
|
||||
username.textProperty().addListener((ob, o, n) -> {
|
||||
if(!n.equals(Config.getInstance().getSettings().livejasminUsername)) {
|
||||
Config.getInstance().getSettings().livejasminUsername = n;
|
||||
liveJasmin.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("LiveJasmin Password"), 0, row);
|
||||
PasswordField password = new PasswordField();
|
||||
password.setText(Config.getInstance().getSettings().livejasminPassword);
|
||||
password.textProperty().addListener((ob, o, n) -> {
|
||||
if(!n.equals(Config.getInstance().getSettings().livejasminPassword)) {
|
||||
Config.getInstance().getSettings().livejasminPassword = n;
|
||||
liveJasmin.getHttpClient().logout();
|
||||
save();
|
||||
}
|
||||
});
|
||||
GridPane.setFillWidth(password, true);
|
||||
GridPane.setHgrow(password, Priority.ALWAYS);
|
||||
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);
|
||||
// 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()));
|
||||
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));
|
||||
|
||||
username.setPrefWidth(300);
|
||||
|
||||
return layout;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
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 Exception {
|
||||
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, msgHandler);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Couldn't wait for login dialog", e);
|
||||
} catch (IOException e) {
|
||||
LOG.debug("Error while starting the browser or communication to it", e);
|
||||
} finally {
|
||||
browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import ctbrec.ui.FollowedTab;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Scene;
|
||||
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 LiveJasminFollowedTab extends LiveJasminTab implements FollowedTab {
|
||||
|
||||
public LiveJasminFollowedTab(LiveJasmin liveJasmin) {
|
||||
super("Followed", new LiveJasminFollowedUpdateService(liveJasmin), liveJasmin);
|
||||
}
|
||||
|
||||
@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) -> {
|
||||
((LiveJasminFollowedUpdateService)updateService).setShowOnline(online.isSelected());
|
||||
queue.clear();
|
||||
updateService.restart();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScene(Scene scene) {
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
if(this.isSelected()) {
|
||||
if(event.getCode() == KeyCode.DELETE) {
|
||||
follow(selectedThumbCells, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
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;
|
||||
|
||||
public class LiveJasminFollowedUpdateService extends PaginatedScheduledService {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminFollowedUpdateService.class);
|
||||
private LiveJasmin liveJasmin;
|
||||
private String url;
|
||||
private boolean showOnline = true;
|
||||
|
||||
public LiveJasminFollowedUpdateService(LiveJasmin liveJasmin) {
|
||||
this.liveJasmin = liveJasmin;
|
||||
long ts = System.currentTimeMillis();
|
||||
this.url = liveJasmin.getBaseUrl() + "/en/free/favourite/get-favourite-list?_dc=" + ts;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<List<Model>> createTask() {
|
||||
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()
|
||||
.url(url)
|
||||
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.header("Accept", "*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", liveJasmin.getBaseUrl() + "/en/free/favorite")
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = liveJasmin.getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
List<Model> models = new ArrayList<>();
|
||||
JSONObject json = new JSONObject(body);
|
||||
//LOG.debug(json.toString(2));
|
||||
if(json.has("success")) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
JSONArray performers = data.getJSONArray("performers");
|
||||
for (int i = 0; i < performers.length(); i++) {
|
||||
JSONObject m = performers.getJSONObject(i);
|
||||
String name = m.optString("pid");
|
||||
if(name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
LiveJasminModel model = (LiveJasminModel) liveJasmin.createModel(name);
|
||||
model.setId(m.getString("id"));
|
||||
model.setPreview(m.getString("profilePictureUrl"));
|
||||
Model.State onlineState = LiveJasminModel.mapStatus(m.getInt("status"));
|
||||
boolean online = onlineState == Model.State.ONLINE;
|
||||
model.setOnlineState(onlineState);
|
||||
if(online == showOnline) {
|
||||
models.add(model);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.error("Request failed:\n{}", body);
|
||||
throw new IOException("Response was not successful");
|
||||
}
|
||||
return models;
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void setShowOnline(boolean showOnline) {
|
||||
this.showOnline = showOnline;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.sites.ConfigUI;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import ctbrec.sites.jasmin.LiveJasminHttpClient;
|
||||
import ctbrec.ui.SiteUI;
|
||||
import ctbrec.ui.TabProvider;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
|
||||
public class LiveJasminSiteUi implements SiteUI {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminSiteUi.class);
|
||||
private LiveJasmin liveJasmin;
|
||||
private LiveJasminTabProvider tabProvider;
|
||||
private LiveJasminConfigUi configUi;
|
||||
private long lastLoginTime = 0;
|
||||
|
||||
public LiveJasminSiteUi(LiveJasmin liveJasmin) {
|
||||
this.liveJasmin = liveJasmin;
|
||||
tabProvider = new LiveJasminTabProvider(liveJasmin);
|
||||
configUi = new LiveJasminConfigUi(liveJasmin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TabProvider getTabProvider() {
|
||||
return tabProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigUI getConfigUI() {
|
||||
return configUi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean login() throws IOException {
|
||||
// renew login every 30 min
|
||||
long now = System.currentTimeMillis();
|
||||
boolean renew = false;
|
||||
if((now - lastLoginTime) > TimeUnit.MINUTES.toMillis(30)) {
|
||||
renew = true;
|
||||
}
|
||||
|
||||
boolean automaticLogin = liveJasmin.login();
|
||||
if(automaticLogin && !renew) {
|
||||
return true;
|
||||
} else {
|
||||
lastLoginTime = System.currentTimeMillis();
|
||||
BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
||||
|
||||
new Thread (() -> {
|
||||
// login with external browser window
|
||||
try {
|
||||
new LiveJasminElectronLoginDialog(liveJasmin.getHttpClient().getCookieJar());
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Error logging in with external browser", e1);
|
||||
Dialogs.showError("Login error", "Couldn't login to " + liveJasmin.getName(), 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);
|
||||
}
|
||||
|
||||
LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
|
||||
boolean loggedIn = httpClient.checkLoginSuccess();
|
||||
if(loggedIn) {
|
||||
LOG.info("Logged in");
|
||||
} else {
|
||||
LOG.info("Login failed");
|
||||
}
|
||||
return loggedIn;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.ui.DesktopIntegration;
|
||||
import ctbrec.ui.PaginatedScheduledService;
|
||||
import ctbrec.ui.ThumbOverviewTab;
|
||||
import javafx.concurrent.WorkerStateEvent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
|
||||
public class LiveJasminTab extends ThumbOverviewTab {
|
||||
protected Label status;
|
||||
protected Button acknowledge = new Button("That's alright");
|
||||
private Button createAccount = new Button("Create Account");
|
||||
private boolean betaAcknowledged = Config.getInstance().getSettings().livejasminBetaAcknowledged;
|
||||
|
||||
public LiveJasminTab(String title, PaginatedScheduledService updateService, Site site) {
|
||||
super(title, updateService, site);
|
||||
if(!betaAcknowledged) {
|
||||
status = new Label("LiveJasmin is not fully functional. Live previews do not work.\n"
|
||||
+ "Also make sure, that you have an account and that you have entered your credentials.\n"
|
||||
+ "Otherwise you might get errors.");
|
||||
grid.getChildren().add(status);
|
||||
grid.getChildren().add(acknowledge);
|
||||
grid.getChildren().add(createAccount);
|
||||
} else {
|
||||
status = new Label("Logging in...");
|
||||
grid.getChildren().add(status);
|
||||
}
|
||||
|
||||
acknowledge.setOnAction(e -> {
|
||||
betaAcknowledged = true;
|
||||
Config.getInstance().getSettings().livejasminBetaAcknowledged = true;
|
||||
try {
|
||||
Config.getInstance().save();
|
||||
} catch (IOException e1) {
|
||||
}
|
||||
status.setText("Logging in...");
|
||||
grid.getChildren().remove(acknowledge);
|
||||
grid.getChildren().remove(createAccount);
|
||||
if(updateService != null) {
|
||||
updateService.cancel();
|
||||
updateService.reset();
|
||||
updateService.restart();
|
||||
}
|
||||
});
|
||||
|
||||
createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createGui() {
|
||||
super.createGui();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSuccess() {
|
||||
if(Config.getInstance().getSettings().livejasminBetaAcknowledged) {
|
||||
grid.getChildren().remove(status);
|
||||
grid.getChildren().remove(acknowledge);
|
||||
grid.getChildren().remove(createAccount);
|
||||
super.onSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFail(WorkerStateEvent event) {
|
||||
status.setText("Login failed");
|
||||
super.onFail(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selected() {
|
||||
super.selected();
|
||||
}
|
||||
|
||||
public void setScene(Scene scene) {
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
if(this.isSelected()) {
|
||||
if(event.getCode() == KeyCode.DELETE) {
|
||||
follow(selectedThumbCells, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
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 {
|
||||
|
||||
private LiveJasmin liveJasmin;
|
||||
private LiveJasminFollowedTab followedTab;
|
||||
|
||||
public LiveJasminTabProvider(LiveJasmin liveJasmin) {
|
||||
this.liveJasmin = liveJasmin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tab> getTabs(Scene scene) {
|
||||
List<Tab> tabs = new ArrayList<>();
|
||||
|
||||
tabs.add(createTab("Girls", liveJasmin.getBaseUrl() + "/en/girls/?listPageOrderType=most_popular"));
|
||||
tabs.add(createTab("Girls HD", liveJasmin.getBaseUrl() + "/en/girls/hd/?listPageOrderType=most_popular"));
|
||||
tabs.add(createTab("Boys", liveJasmin.getBaseUrl() + "/en/boys/?listPageOrderType=most_popular"));
|
||||
tabs.add(createTab("Boys HD", liveJasmin.getBaseUrl() + "/en/boys/hd/?listPageOrderType=most_popular"));
|
||||
|
||||
followedTab = new LiveJasminFollowedTab(liveJasmin);
|
||||
followedTab.setRecorder(liveJasmin.getRecorder());
|
||||
tabs.add(followedTab);
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tab getFollowedTab() {
|
||||
return followedTab;
|
||||
}
|
||||
|
||||
private ThumbOverviewTab createTab(String title, String url) {
|
||||
LiveJasminUpdateService s = new LiveJasminUpdateService(liveJasmin, url);
|
||||
ThumbOverviewTab tab = new LiveJasminTab(title, s, liveJasmin);
|
||||
tab.setRecorder(liveJasmin.getRecorder());
|
||||
s.setPeriod(Duration.seconds(60));
|
||||
return tab;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.CookieJarImpl;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import ctbrec.sites.jasmin.LiveJasminModel;
|
||||
import ctbrec.ui.PaginatedScheduledService;
|
||||
import ctbrec.ui.SiteUI;
|
||||
import ctbrec.ui.SiteUiFactory;
|
||||
import javafx.concurrent.Task;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class LiveJasminUpdateService extends PaginatedScheduledService {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminUpdateService.class);
|
||||
private String url;
|
||||
private LiveJasmin liveJasmin;
|
||||
|
||||
public LiveJasminUpdateService(LiveJasmin liveJasmin, String url) {
|
||||
this.liveJasmin = liveJasmin;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Task<List<Model>> createTask() {
|
||||
return new Task<List<Model>>() {
|
||||
@Override
|
||||
public List<Model> call() throws IOException, NotLoggedInExcetion {
|
||||
//String _url = url + ((page-1) * 36); // TODO find out how to switch pages
|
||||
if(!SiteUiFactory.getUi(liveJasmin).login()) {
|
||||
throw new NotLoggedInExcetion();
|
||||
}
|
||||
|
||||
// sort by popularity
|
||||
CookieJarImpl cookieJar = liveJasmin.getHttpClient().getCookieJar();
|
||||
Cookie sortCookie = new Cookie.Builder()
|
||||
.domain("livejasmin.com")
|
||||
.name("listPageOrderType")
|
||||
.value("most_popular")
|
||||
.build();
|
||||
cookieJar.saveFromResponse(HttpUrl.parse("https://livejasmin.com"), Collections.singletonList(sortCookie));
|
||||
|
||||
LOG.debug("Fetching page {}", url);
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.addHeader("Accept", "application/json, text/javascript, */*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", liveJasmin.getBaseUrl() + "/en/girls/")
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = liveJasmin.getHttpClient().execute(request)) {
|
||||
LOG.debug("Response {} {}", response.code(), response.message());
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
List<Model> models = new ArrayList<>();
|
||||
JSONObject json = new JSONObject(body);
|
||||
//LOG.debug(json.toString(2));
|
||||
if(json.optBoolean("success")) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
JSONObject content = data.getJSONObject("content");
|
||||
JSONArray performers = content.getJSONArray("performers");
|
||||
for (int i = 0; i < performers.length(); i++) {
|
||||
JSONObject m = performers.getJSONObject(i);
|
||||
String name = m.optString("pid");
|
||||
if(name.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
LiveJasminModel model = (LiveJasminModel) liveJasmin.createModel(name);
|
||||
model.setId(m.getString("id"));
|
||||
model.setPreview(m.getString("profilePictureUrl"));
|
||||
model.setOnlineState(LiveJasminModel.mapStatus(m.optInt("status")));
|
||||
models.add(model);
|
||||
}
|
||||
} else if(json.optString("error").equals("Please login.")) {
|
||||
SiteUI siteUI = SiteUiFactory.getUi(liveJasmin);
|
||||
if(siteUI.login()) {
|
||||
return call();
|
||||
} else {
|
||||
LOG.error("Request failed:\n{}", body);
|
||||
throw new IOException("Response was not successful");
|
||||
}
|
||||
} else {
|
||||
LOG.error("Request failed:\n{}", body);
|
||||
throw new IOException("Response was not successful");
|
||||
}
|
||||
return models;
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
public class NotLoggedInExcetion extends Exception {
|
||||
|
||||
}
|
|
@ -47,7 +47,8 @@ public class StreamateFollowedService extends PaginatedScheduledService {
|
|||
public List<Model> call() throws IOException, SAXException, ParserConfigurationException, XPathExpressionException {
|
||||
httpClient.login();
|
||||
String saKey = httpClient.getSaKey();
|
||||
String _url = url + "&page_number=" + page + "&results_per_page=" + MODELS_PER_PAGE + "&sakey=" + saKey;
|
||||
Long userId = httpClient.getUserId();
|
||||
String _url = url + "&page_number=" + page + "&results_per_page=" + MODELS_PER_PAGE + "&sakey=" + saKey + "&userid=" + userId;
|
||||
LOG.debug("Fetching page {}", _url);
|
||||
Request request = new Request.Builder()
|
||||
.url(_url)
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>1.16.0</version>
|
||||
<version>1.17.0</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -57,7 +57,7 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-web</artifactId>
|
||||
<artifactId>javafx-media</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -72,7 +72,7 @@ public interface Model extends Comparable<Model> {
|
|||
|
||||
public void invalidateCacheEntries();
|
||||
|
||||
public void receiveTip(int tokens) throws IOException;
|
||||
public void receiveTip(Double tokens) throws IOException;
|
||||
|
||||
/**
|
||||
* Determines the stream resolution for this model
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package ctbrec;
|
||||
|
||||
public class NotLoggedInExcetion extends Exception {
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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";
|
||||
|
@ -62,6 +63,9 @@ public class Settings {
|
|||
public String camsodaPassword = "";
|
||||
public String cam4Username = "";
|
||||
public String cam4Password = "";
|
||||
public String livejasminUsername = "";
|
||||
public String livejasminPassword = "";
|
||||
public boolean livejasminBetaAcknowledged = false;
|
||||
public String streamateUsername = "";
|
||||
public String streamatePassword = "";
|
||||
public String lastDownloadDir = "";
|
||||
|
@ -99,4 +103,5 @@ public class Settings {
|
|||
public String recordingsSortColumn = "";
|
||||
public String recordingsSortType = "";
|
||||
public double[] recordingsColumnWidths = new double[0];
|
||||
public boolean generatePlaylist = true;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -35,10 +35,10 @@ import okhttp3.WebSocketListener;
|
|||
public abstract class HttpClient {
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
||||
|
||||
protected OkHttpClient client;
|
||||
protected OkHttpClient client;
|
||||
protected CookieJarImpl cookieJar = new CookieJarImpl();
|
||||
protected boolean loggedIn = false;
|
||||
protected int loginTries = 0;
|
||||
protected boolean loggedIn = false;
|
||||
protected int loginTries = 0;
|
||||
private String name;
|
||||
|
||||
protected HttpClient(String name) {
|
||||
|
@ -93,19 +93,7 @@ public abstract class HttpClient {
|
|||
}
|
||||
}
|
||||
|
||||
// public Response execute(Request request) throws IOException {
|
||||
// Response resp = execute(request, false);
|
||||
// return resp;
|
||||
// }
|
||||
|
||||
// public Response execute(Request req, boolean requiresLogin) throws IOException {
|
||||
public Response execute(Request req) throws IOException {
|
||||
// if(requiresLogin && !loggedIn) {
|
||||
// loggedIn = login();
|
||||
// if(!loggedIn) {
|
||||
// throw new IOException("403 Unauthorized");
|
||||
// }
|
||||
// }
|
||||
Response resp = client.newCall(req).execute();
|
||||
return resp;
|
||||
}
|
||||
|
@ -222,8 +210,8 @@ public abstract class HttpClient {
|
|||
loggedIn = false;
|
||||
}
|
||||
|
||||
public WebSocket newWebSocket(String url, WebSocketListener l) {
|
||||
Request request = new Request.Builder().url(url).build();
|
||||
public WebSocket newWebSocket(Request request, WebSocketListener l) {
|
||||
//Request request = new Request.Builder().url(url).build();
|
||||
return client.newWebSocket(request, l);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -193,6 +193,7 @@ public class LocalRecorder implements Recorder {
|
|||
|
||||
LOG.debug("Starting recording for model {}", model.getName());
|
||||
Download download = model.createDownload();
|
||||
LOG.debug("Downloading with {}", download.getClass().getSimpleName());
|
||||
recordingProcesses.put(model, download);
|
||||
new Thread() {
|
||||
@Override
|
||||
|
@ -397,6 +398,10 @@ public class LocalRecorder implements Recorder {
|
|||
}
|
||||
|
||||
private void generatePlaylist(File recDir) {
|
||||
if(!config.getSettings().generatePlaylist) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlaylistGenerator playlistGenerator = new PlaylistGenerator();
|
||||
playlistGenerators.put(recDir, playlistGenerator);
|
||||
try {
|
||||
|
@ -461,16 +466,17 @@ public class LocalRecorder implements Recorder {
|
|||
private List<Recording> listMergedRecordings() {
|
||||
File recordingsDir = new File(config.getSettings().recordingsDir);
|
||||
List<File> possibleRecordings = new LinkedList<>();
|
||||
listRecursively(recordingsDir, possibleRecordings, (dir, name) -> name.matches(".*?_\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}\\.ts"));
|
||||
listRecursively(recordingsDir, possibleRecordings, (dir, name) -> name.matches(".*?_\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}\\.(ts|mp4)"));
|
||||
SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT);
|
||||
List<Recording> recordings = new ArrayList<>();
|
||||
for (File ts: possibleRecordings) {
|
||||
try {
|
||||
String filename = ts.getName();
|
||||
String dateString = filename.substring(filename.length() - 3 - DATE_FORMAT.length(), filename.length() - 3);
|
||||
int extLength = filename.length() - filename.lastIndexOf('.');
|
||||
String dateString = filename.substring(filename.length() - extLength - DATE_FORMAT.length(), filename.length() - extLength);
|
||||
Date startDate = sdf.parse(dateString);
|
||||
Recording recording = new Recording();
|
||||
recording.setModelName(filename.substring(0, filename.length() - 4 - DATE_FORMAT.length()));
|
||||
recording.setModelName(filename.substring(0, filename.length() - extLength - 1 - DATE_FORMAT.length()));
|
||||
recording.setStartDate(Instant.ofEpochMilli(startDate.getTime()));
|
||||
String path = ts.getAbsolutePath().replace(config.getSettings().recordingsDir, "");
|
||||
if(!path.startsWith("/")) {
|
||||
|
@ -731,6 +737,9 @@ public class LocalRecorder implements Recorder {
|
|||
|
||||
private FileStore getRecordingsFileStore() throws IOException {
|
||||
File recordingsDir = new File(config.getSettings().recordingsDir);
|
||||
if(!recordingsDir.exists()) {
|
||||
Files.createDirectories(recordingsDir.toPath());
|
||||
}
|
||||
FileStore store = Files.getFileStore(recordingsDir.toPath());
|
||||
return store;
|
||||
}
|
||||
|
@ -774,6 +783,10 @@ public class LocalRecorder implements Recorder {
|
|||
try {
|
||||
LOG.debug("Determining video length for {}", download.getTarget());
|
||||
File target = download.getTarget();
|
||||
if(!target.exists() || target.length() == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
double duration = 0;
|
||||
if(target.isDirectory()) {
|
||||
File playlist = new File(target, "playlist.m3u8");
|
||||
|
|
|
@ -453,7 +453,7 @@ public class RemoteRecorder implements Recorder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -532,8 +532,8 @@ public class RemoteRecorder implements Recorder {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
return 0;
|
||||
public Double getTokenBalance() throws IOException {
|
||||
return 0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,9 +8,12 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -37,12 +40,13 @@ public abstract class AbstractHlsDownload implements Download {
|
|||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(AbstractHlsDownload.class);
|
||||
|
||||
ExecutorService downloadThreadPool = Executors.newFixedThreadPool(5);
|
||||
HttpClient client;
|
||||
volatile boolean running = false;
|
||||
volatile boolean alive = true;
|
||||
Instant startTime;
|
||||
Model model;
|
||||
protected HttpClient client;
|
||||
protected volatile boolean running = false;
|
||||
protected volatile boolean alive = true;
|
||||
protected Instant startTime;
|
||||
protected Model model;
|
||||
protected BlockingQueue<Runnable> downloadQueue = new LinkedBlockingQueue<>(50);
|
||||
protected ExecutorService downloadThreadPool = new ThreadPoolExecutor(5, 5, 2, TimeUnit.MINUTES, downloadQueue);
|
||||
|
||||
public AbstractHlsDownload(HttpClient client) {
|
||||
this.client = client;
|
||||
|
|
|
@ -22,13 +22,9 @@ import java.time.ZonedDateTime;
|
|||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -63,8 +59,6 @@ public class MergedHlsDownload extends AbstractHlsDownload {
|
|||
private ZonedDateTime splitRecStartTime;
|
||||
private Config config;
|
||||
private File targetFile;
|
||||
private BlockingQueue<Runnable> downloadQueue = new LinkedBlockingQueue<>(50);
|
||||
private ExecutorService downloadThreadPool = new ThreadPoolExecutor(5, 5, 2, TimeUnit.MINUTES, downloadQueue);
|
||||
private FileChannel fileChannel = null;
|
||||
private Object downloadFinished = new Object();
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ public interface Site {
|
|||
public void setRecorder(Recorder recorder);
|
||||
public Recorder getRecorder();
|
||||
public Model createModel(String name);
|
||||
public Integer getTokenBalance() throws IOException;
|
||||
public Double getTokenBalance() throws IOException;
|
||||
public String getBuyTokensLink();
|
||||
public boolean login() throws IOException;
|
||||
public HttpClient getHttpClient();
|
||||
|
|
|
@ -57,7 +57,7 @@ public class BongaCams extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
public Double getTokenBalance() throws IOException {
|
||||
int userId = ((BongaCamsHttpClient)getHttpClient()).getUserId();
|
||||
String url = BongaCams.BASE_URL + "/tools/amf.php";
|
||||
RequestBody body = new FormBody.Builder()
|
||||
|
@ -78,7 +78,7 @@ public class BongaCams extends AbstractSite {
|
|||
JSONObject json = new JSONObject(response.body().string());
|
||||
if(json.optString("status").equals("online")) {
|
||||
JSONObject userData = json.getJSONObject("userData");
|
||||
return userData.getInt("balance");
|
||||
return (double) userData.getInt("balance");
|
||||
} else {
|
||||
throw new IOException("Request was not successful: " + json.toString(2));
|
||||
}
|
||||
|
|
|
@ -154,13 +154,13 @@ public class BongaCamsModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
String url = BongaCams.BASE_URL + "/chat-ajax-amf-service?" + System.currentTimeMillis();
|
||||
int userId = ((BongaCamsHttpClient)site.getHttpClient()).getUserId();
|
||||
RequestBody body = new FormBody.Builder()
|
||||
.add("method", "tipModel")
|
||||
.add("args[]", getName())
|
||||
.add("args[]", Integer.toString(tokens))
|
||||
.add("args[]", Integer.toString(tokens.intValue()))
|
||||
.add("args[]", Integer.toString(userId))
|
||||
.add("args[3]", "")
|
||||
.build();
|
||||
|
|
|
@ -51,7 +51,7 @@ public class Cam4 extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
public Double getTokenBalance() throws IOException {
|
||||
if (!credentialsAvailable()) {
|
||||
throw new IOException("Not logged in");
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class Cam4HttpClient extends HttpClient {
|
|||
}
|
||||
}
|
||||
|
||||
protected int getTokenBalance() throws IOException {
|
||||
protected double getTokenBalance() throws IOException {
|
||||
if(!loggedIn) {
|
||||
login();
|
||||
}
|
||||
|
|
|
@ -176,7 +176,7 @@ public class Cam4Model extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
throw new RuntimeException("Not implemented for Cam4, yet");
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ public class Camsoda extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
public Double getTokenBalance() throws IOException {
|
||||
if (!credentialsAvailable()) {
|
||||
throw new IOException("Account settings not available");
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ public class Camsoda extends AbstractSite {
|
|||
if(json.has("user")) {
|
||||
JSONObject user = json.getJSONObject("user");
|
||||
if(user.has("tokens")) {
|
||||
return user.getInt("tokens");
|
||||
return (double) user.getInt("tokens");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -179,13 +179,13 @@ public class CamsodaModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
String csrfToken = ((CamsodaHttpClient)site.getHttpClient()).getCsrfToken();
|
||||
String url = site.getBaseUrl() + "/api/v1/tip/" + getName();
|
||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||
LOG.debug("Sending tip {}", url);
|
||||
RequestBody body = new FormBody.Builder()
|
||||
.add("amount", Integer.toString(tokens))
|
||||
.add("amount", Integer.toString(tokens.intValue()))
|
||||
.add("comment", "")
|
||||
.build();
|
||||
Request request = new Request.Builder()
|
||||
|
|
|
@ -80,7 +80,7 @@ public class Chaturbate extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
public Double getTokenBalance() throws IOException {
|
||||
String username = Config.getInstance().getSettings().username;
|
||||
if (username == null || username.trim().isEmpty()) {
|
||||
throw new IOException("Not logged in");
|
||||
|
@ -93,7 +93,7 @@ public class Chaturbate extends AbstractSite {
|
|||
String profilePage = resp.body().string();
|
||||
String tokenText = HtmlParser.getText(profilePage, "span.tokencount");
|
||||
int tokens = Integer.parseInt(tokenText);
|
||||
return tokens;
|
||||
return (double) tokens;
|
||||
} else {
|
||||
throw new IOException("HTTP response: " + resp.code() + " - " + resp.message());
|
||||
}
|
||||
|
|
|
@ -127,8 +127,8 @@ public class ChaturbateModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
getChaturbate().sendTip(getName(), tokens);
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
getChaturbate().sendTip(getName(), tokens.intValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -35,8 +35,8 @@ public class Fc2Live extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
return 0;
|
||||
public Double getTokenBalance() throws IOException {
|
||||
return 0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -188,7 +188,7 @@ public class Fc2Model extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,7 +5,9 @@ import org.json.JSONObject;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
@ -26,7 +28,14 @@ public class Fc2WebSocketClient {
|
|||
public String getPlaylistUrl() throws InterruptedException {
|
||||
LOG.debug("Connecting to {}", url);
|
||||
Object monitor = new Object();
|
||||
client.newWebSocket(url, new WebSocketListener() {
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
.header("Accept-Language", "de,en-US;q=0.7,en;q=0.3")
|
||||
.build();
|
||||
client.newWebSocket(request, new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
response.close();
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.NotLoggedInExcetion;
|
||||
import ctbrec.io.HtmlParser;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.sites.AbstractSite;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class LiveJasmin extends AbstractSite {
|
||||
|
||||
public static final String BASE_URL = "https://www.livejasmin.com";
|
||||
private HttpClient httpClient;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "LiveJasmin";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBaseUrl() {
|
||||
return BASE_URL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAffiliateLink() {
|
||||
return "https://awejmp.com/?siteId=jasmin&categoryName=girl&pageName=listpage&performerName=&prm[psid]=0xb00bface&prm[pstool]=205_1&prm[psprogram]=pps&prm[campaign_id]=&subAffId={SUBAFFID}&filters=";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model createModel(String name) {
|
||||
LiveJasminModel model = new LiveJasminModel();
|
||||
model.setName(name);
|
||||
model.setDescription("");
|
||||
model.setSite(this);
|
||||
model.setUrl(getBaseUrl() + "/en/chat/" + name);
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getTokenBalance() throws IOException {
|
||||
if(getLiveJasminHttpClient().login()) {
|
||||
String sessionId = getLiveJasminHttpClient().getSessionId();
|
||||
String url = getBaseUrl() + "/en/offline-surprise/get-member-balance?session=" + sessionId;
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.addHeader("Accept", "*/*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", getBaseUrl())
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = getHttpClient().execute(request)) {
|
||||
if(response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
JSONObject json = new JSONObject(body);
|
||||
if(json.optBoolean("success")) {
|
||||
return json.optDouble("result");
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + url + "\n" + body);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IOException(new NotLoggedInExcetion());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBuyTokensLink() {
|
||||
return getAffiliateLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean login() throws IOException {
|
||||
return getHttpClient().login();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getHttpClient() {
|
||||
if (httpClient == null) {
|
||||
httpClient = new LiveJasminHttpClient();
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (httpClient != null) {
|
||||
httpClient.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTips() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsFollow() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Model> search(String q) throws IOException, InterruptedException {
|
||||
String query = URLEncoder.encode(q, "utf-8");
|
||||
long ts = System.currentTimeMillis();
|
||||
String url = getBaseUrl() + "/en/auto-suggest-search/auto-suggest?category=girls&searchText=" + query + "&_dc=" + ts + "&appletType=html5";
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.addHeader("Accept", "*/*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", getBaseUrl())
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
JSONObject json = new JSONObject(body);
|
||||
if(json.optBoolean("success")) {
|
||||
List<Model> models = new ArrayList<>();
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
String html = data.getString("content");
|
||||
Elements items = HtmlParser.getTags(html, "li.name");
|
||||
for (Element item : items) {
|
||||
String itemHtml = item.html();
|
||||
Element link = HtmlParser.getTag(itemHtml, "a");
|
||||
LiveJasminModel model = (LiveJasminModel) createModel(link.attr("title"));
|
||||
Element pic = HtmlParser.getTag(itemHtml, "span.pic i");
|
||||
String style = pic.attr("style");
|
||||
Matcher m = Pattern.compile("url\\('(.*?)'\\)").matcher(style);
|
||||
if(m.find()) {
|
||||
model.setPreview(m.group(1));
|
||||
}
|
||||
models.add(model);
|
||||
}
|
||||
return models;
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + url + "\n" + body);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSiteForModel(Model m) {
|
||||
return m instanceof LiveJasminModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean credentialsAvailable() {
|
||||
return !Config.getInstance().getSettings().livejasminUsername.isEmpty();
|
||||
}
|
||||
|
||||
private LiveJasminHttpClient getLiveJasminHttpClient() {
|
||||
return (LiveJasminHttpClient) httpClient;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
import java.util.Random;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.download.Download;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
public class LiveJasminChunkedHttpDownload implements Download {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminChunkedHttpDownload.class);
|
||||
private static final transient String USER_AGENT = "Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15";
|
||||
|
||||
private HttpClient client;
|
||||
private Model model;
|
||||
private Instant startTime;
|
||||
private File targetFile;
|
||||
|
||||
private String applicationId;
|
||||
private String sessionId;
|
||||
private String jsm2SessionId;
|
||||
private String sb_ip;
|
||||
private String sb_hash;
|
||||
private String relayHost;
|
||||
private String hlsHost;
|
||||
private String clientInstanceId = newClientInstanceId(); // generate a 32 digit random number
|
||||
private String streamPath = "streams/clonedLiveStream";
|
||||
private boolean isAlive = true;
|
||||
|
||||
public LiveJasminChunkedHttpDownload(HttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
private String newClientInstanceId() {
|
||||
return new java.math.BigInteger(256, new Random()).toString().substring(0, 32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Model model, Config config) throws IOException {
|
||||
this.model = model;
|
||||
startTime = Instant.now();
|
||||
File _targetFile = config.getFileForRecording(model);
|
||||
targetFile = new File(_targetFile.getAbsolutePath().replace(".ts", ".mp4"));
|
||||
|
||||
getPerformerDetails(model.getName());
|
||||
try {
|
||||
getStreamPath();
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("Couldn't determine stream path", e);
|
||||
}
|
||||
|
||||
LOG.debug("appid: {}", applicationId);
|
||||
LOG.debug("sessionid: {}", sessionId);
|
||||
LOG.debug("jsm2sessionid: {}", jsm2SessionId);
|
||||
LOG.debug("sb_ip: {}", sb_ip);
|
||||
LOG.debug("sb_hash: {}", sb_hash);
|
||||
LOG.debug("hls host: {}", hlsHost);
|
||||
LOG.debug("clientinstanceid {}", clientInstanceId);
|
||||
LOG.debug("stream path {}", streamPath);
|
||||
|
||||
String rtmpUrl = "rtmp://" + sb_ip + "/" + applicationId + "?sessionId-" + sessionId + "|clientInstanceId-" + clientInstanceId;
|
||||
|
||||
String m3u8 = "https://" + hlsHost + "/h5live/http/playlist.m3u8?url=" + URLEncoder.encode(rtmpUrl, "utf-8");
|
||||
m3u8 = m3u8 += "&stream=" + URLEncoder.encode(streamPath, "utf-8");
|
||||
|
||||
Request req = new Request.Builder()
|
||||
.url(m3u8)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", model.getUrl())
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = client.execute(req)) {
|
||||
if (response.isSuccessful()) {
|
||||
System.out.println(response.body().string());
|
||||
} else {
|
||||
throw new IOException(response.code() + " - " + response.message());
|
||||
}
|
||||
}
|
||||
|
||||
String url = "https://" + hlsHost + "/h5live/http/stream.mp4?url=" + URLEncoder.encode(rtmpUrl, "utf-8");
|
||||
url = url += "&stream=" + URLEncoder.encode(streamPath, "utf-8");
|
||||
|
||||
LOG.debug("Downloading {}", url);
|
||||
req = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", model.getUrl())
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = client.execute(req)) {
|
||||
if (response.isSuccessful()) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
Files.createDirectories(targetFile.getParentFile().toPath());
|
||||
fos = new FileOutputStream(targetFile);
|
||||
|
||||
InputStream in = response.body().byteStream();
|
||||
byte[] b = new byte[10240];
|
||||
int len = -1;
|
||||
while (isAlive && (len = in.read(b)) >= 0) {
|
||||
fos.write(b, 0, len);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't create video file", e);
|
||||
} finally {
|
||||
isAlive = false;
|
||||
if(fos != null) {
|
||||
fos.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IOException(response.code() + " - " + response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getStreamPath() throws InterruptedException {
|
||||
Object lock = new Object();
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://" + relayHost + "/?random=" + newClientInstanceId())
|
||||
.header("Origin", "https://www.livejasmin.com")
|
||||
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0")
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
.header("Accept-Language", "de,en-US;q=0.7,en;q=0.3")
|
||||
.build();
|
||||
client.newWebSocket(request, new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
LOG.debug("relay open {}", model.getName());
|
||||
webSocket.send("{\"event\":\"register\",\"applicationId\":\"" + applicationId
|
||||
+ "\",\"connectionData\":{\"jasmin2App\":true,\"isMobileClient\":false,\"platform\":\"desktop\",\"chatID\":\"freechat\","
|
||||
+ "\"sessionID\":\"" + sessionId + "\"," + "\"jsm2SessionId\":\"" + jsm2SessionId + "\",\"userType\":\"user\"," + "\"performerId\":\""
|
||||
+ model
|
||||
+ "\",\"clientRevision\":\"\",\"proxyIP\":\"\",\"playerVer\":\"nanoPlayerVersion: 3.10.3 appCodeName: Mozilla appName: Netscape appVersion: 5.0 (X11) platform: Linux x86_64\",\"livejasminTvmember\":false,\"newApplet\":true,\"livefeedtype\":null,\"gravityCookieId\":\"\",\"passparam\":\"\",\"brandID\":\"jasmin\",\"cobrandId\":\"\",\"subbrand\":\"livejasmin\",\"siteName\":\"LiveJasmin\",\"siteUrl\":\"https://www.livejasmin.com\","
|
||||
+ "\"clientInstanceId\":\"" + clientInstanceId + "\",\"armaVersion\":\"34.10.0\",\"isPassive\":false}}");
|
||||
response.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
LOG.debug("relay <-- {} T{}", model.getName(), text);
|
||||
JSONObject event = new JSONObject(text);
|
||||
if (event.optString("event").equals("accept")) {
|
||||
webSocket.send("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
|
||||
} else if (event.optString("event").equals("updateSharedObject")) {
|
||||
JSONArray list = event.getJSONArray("list");
|
||||
for (int i = 0; i < list.length(); i++) {
|
||||
JSONObject obj = list.getJSONObject(i);
|
||||
if (obj.optString("name").equals("streamList")) {
|
||||
LOG.debug(obj.toString(2));
|
||||
streamPath = getStreamPath(obj.getJSONObject("newValue"));
|
||||
LOG.debug("Stream Path: {}", streamPath);
|
||||
webSocket.send("{\"event\":\"call\",\"funcName\":\"makeActive\",\"data\":[]}");
|
||||
webSocket.close(1000, "");
|
||||
synchronized (lock) {
|
||||
lock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}else if(event.optString("event").equals("call")) {
|
||||
String func = event.optString("funcName");
|
||||
if(func.equals("closeConnection")) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getStreamPath(JSONObject obj) {
|
||||
String streamName = "streams/clonedLiveStream";
|
||||
int height = 0;
|
||||
if(obj.has("streams")) {
|
||||
JSONArray streams = obj.getJSONArray("streams");
|
||||
for (int i = 0; i < streams.length(); i++) {
|
||||
JSONObject stream = streams.getJSONObject(i);
|
||||
int h = stream.optInt("height");
|
||||
if(h > height) {
|
||||
height = h;
|
||||
streamName = stream.getString("streamNameWithFolder");
|
||||
streamName = "free/" + stream.getString("name");
|
||||
}
|
||||
}
|
||||
}
|
||||
return streamName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
LOG.debug("relay <-- {} B{}", model.getName(), bytes.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
LOG.debug("relay closed {} {} {}", code, reason, model.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
LOG.debug("relay failure {}", model.getName(), t);
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (lock) {
|
||||
lock.wait();
|
||||
}
|
||||
}
|
||||
|
||||
protected void getPerformerDetails(String name) throws IOException {
|
||||
String url = "https://m.livejasmin.com/en/chat-html5/" + name;
|
||||
Request req = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", "https://www.livejasmin.com")
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = client.execute(req)) {
|
||||
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");
|
||||
JSONObject config = data.getJSONObject("config");
|
||||
JSONObject armageddonConfig = config.getJSONObject("armageddonConfig");
|
||||
JSONObject chatRoom = config.getJSONObject("chatRoom");
|
||||
sessionId = armageddonConfig.getString("sessionid");
|
||||
jsm2SessionId = armageddonConfig.getString("jsm2session");
|
||||
sb_hash = chatRoom.getString("sb_hash");
|
||||
sb_ip = chatRoom.getString("sb_ip");
|
||||
applicationId = "memberChat/jasmin" + name + sb_hash;
|
||||
hlsHost = "dss-hls-" + sb_ip.replace('.', '-') + ".dditscdn.com";
|
||||
relayHost = "dss-relay-" + sb_ip.replace('.', '-') + ".dditscdn.com";
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + body);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(response.code() + " - " + response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
isAlive = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return isAlive ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getTarget() {
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.download.HlsDownload;
|
||||
|
||||
public class LiveJasminHlsDownload extends HlsDownload {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminHlsDownload.class);
|
||||
private long lastMasterPlaylistUpdate = 0;
|
||||
private String segmentUrl;
|
||||
|
||||
public LiveJasminHlsDownload(HttpClient client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
|
||||
if(this.segmentUrl == null) {
|
||||
this.segmentUrl = segments;
|
||||
}
|
||||
SegmentPlaylist playlist = super.getNextSegments(segmentUrl);
|
||||
long now = System.currentTimeMillis();
|
||||
if( (now - lastMasterPlaylistUpdate) > TimeUnit.SECONDS.toMillis(60)) {
|
||||
super.downloadThreadPool.submit(this::updatePlaylistUrl);
|
||||
lastMasterPlaylistUpdate = now;
|
||||
}
|
||||
return playlist;
|
||||
}
|
||||
|
||||
private void updatePlaylistUrl() {
|
||||
try {
|
||||
LOG.debug("Updating segment playlist URL for {}", getModel());
|
||||
segmentUrl = getSegmentPlaylistUrl(getModel());
|
||||
} catch (IOException | ExecutionException | ParseException | PlaylistException e) {
|
||||
LOG.error("Couldn't update segment playlist url. This might cause a premature download termination", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpClient;
|
||||
import okhttp3.Cookie;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class LiveJasminHttpClient extends HttpClient {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminHttpClient.class);
|
||||
|
||||
protected LiveJasminHttpClient() {
|
||||
super("livejasmin");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean login() throws IOException {
|
||||
if (loggedIn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean cookiesWorked = checkLoginSuccess();
|
||||
if (cookiesWorked) {
|
||||
loggedIn = true;
|
||||
LOG.debug("Logged in with cookies");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean checkLoginSuccess() throws IOException {
|
||||
OkHttpClient temp = client.newBuilder()
|
||||
.followRedirects(false)
|
||||
.followSslRedirects(false)
|
||||
.build();
|
||||
|
||||
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().httpUserAgentMobile)
|
||||
.addHeader("Accept", "application/json, text/javascript, */*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", LiveJasmin.BASE_URL)
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try(Response response = temp.newCall(request).execute()) {
|
||||
LOG.debug("Login Check {}: {} - {}", url, response.code(), response.message());
|
||||
if(response.isSuccessful()) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getSessionId() {
|
||||
Cookie sessionCookie = getCookieJar().getCookie(HttpUrl.parse(LiveJasmin.BASE_URL), "session");
|
||||
if(sessionCookie != null) {
|
||||
return sessionCookie.value();
|
||||
} else {
|
||||
throw new NoSuchElementException("session cookie not found");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.download.MergedHlsDownload;
|
||||
|
||||
public class LiveJasminMergedHlsDownload extends MergedHlsDownload {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminMergedHlsDownload.class);
|
||||
private long lastMasterPlaylistUpdate = 0;
|
||||
private String segmentUrl;
|
||||
|
||||
public LiveJasminMergedHlsDownload(HttpClient client) {
|
||||
super(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
|
||||
if(this.segmentUrl == null) {
|
||||
this.segmentUrl = segments;
|
||||
}
|
||||
SegmentPlaylist playlist = super.getNextSegments(segmentUrl);
|
||||
long now = System.currentTimeMillis();
|
||||
if( (now - lastMasterPlaylistUpdate) > TimeUnit.SECONDS.toMillis(60)) {
|
||||
super.downloadThreadPool.submit(this::updatePlaylistUrl);
|
||||
lastMasterPlaylistUpdate = now;
|
||||
}
|
||||
return playlist;
|
||||
}
|
||||
|
||||
private void updatePlaylistUrl() {
|
||||
try {
|
||||
LOG.debug("Updating segment playlist URL for {}", getModel());
|
||||
segmentUrl = getSegmentPlaylistUrl(getModel());
|
||||
} catch (IOException | ExecutionException | ParseException | PlaylistException e) {
|
||||
LOG.error("Couldn't update segment playlist url. This might cause a premature download termination", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,291 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.iheartradio.m3u8.Encoding;
|
||||
import com.iheartradio.m3u8.Format;
|
||||
import com.iheartradio.m3u8.ParseException;
|
||||
import com.iheartradio.m3u8.ParsingMode;
|
||||
import com.iheartradio.m3u8.PlaylistException;
|
||||
import com.iheartradio.m3u8.PlaylistParser;
|
||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||
import com.iheartradio.m3u8.data.Playlist;
|
||||
import com.iheartradio.m3u8.data.PlaylistData;
|
||||
import com.iheartradio.m3u8.data.StreamInfo;
|
||||
import com.squareup.moshi.JsonReader;
|
||||
import com.squareup.moshi.JsonWriter;
|
||||
|
||||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.recorder.download.Download;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class LiveJasminModel extends AbstractModel {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminModel.class);
|
||||
private String id;
|
||||
private boolean online = false;
|
||||
private int[] resolution;
|
||||
|
||||
@Override
|
||||
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
||||
if (ignoreCache) {
|
||||
loadModelInfo();
|
||||
}
|
||||
return online;
|
||||
}
|
||||
|
||||
protected void loadModelInfo() throws IOException {
|
||||
String url = "https://m.livejasmin.com/en/chat-html5/" + getName();
|
||||
Request req = new Request.Builder().url(url).header("User-Agent",
|
||||
"Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15")
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", getSite().getBaseUrl())
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = getSite().getHttpClient().execute(req)) {
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
JSONObject json = new JSONObject(body);
|
||||
// LOG.debug(json.toString(2));
|
||||
if (json.optBoolean("success")) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
JSONObject config = data.getJSONObject("config");
|
||||
JSONObject chatRoom = config.getJSONObject("chatRoom");
|
||||
setId(chatRoom.getString("p_id"));
|
||||
if (chatRoom.has("profile_picture_url")) {
|
||||
setPreview(chatRoom.getString("profile_picture_url"));
|
||||
}
|
||||
int status = chatRoom.optInt("status", -1);
|
||||
onlineState = mapStatus(status);
|
||||
if (chatRoom.optInt("is_on_private", 0) == 1) {
|
||||
onlineState = State.PRIVATE;
|
||||
}
|
||||
resolution = new int[2];
|
||||
resolution[0] = config.optInt("streamWidth");
|
||||
resolution[1] = config.optInt("streamHeight");
|
||||
online = onlineState == State.ONLINE;
|
||||
LOG.trace("{} - status:{} {} {} {}", getName(), online, onlineState, Arrays.toString(resolution), getUrl());
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + body);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static State mapStatus(int status) {
|
||||
switch (status) {
|
||||
case 0:
|
||||
return State.OFFLINE;
|
||||
case 1:
|
||||
return State.ONLINE;
|
||||
case 2:
|
||||
case 3:
|
||||
return State.PRIVATE;
|
||||
default:
|
||||
LOG.debug("Unkown state {}", status);
|
||||
return State.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineState(State status) {
|
||||
super.setOnlineState(status);
|
||||
online = status == State.ONLINE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||
String masterUrl = getMasterPlaylistUrl();
|
||||
LOG.debug("Master playlist: {}", masterUrl);
|
||||
List<StreamSource> streamSources = new ArrayList<>();
|
||||
Request req = new Request.Builder().url(masterUrl).build();
|
||||
try (Response response = site.getHttpClient().execute(req)) {
|
||||
if (response.isSuccessful()) {
|
||||
InputStream inputStream = response.body().byteStream();
|
||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
|
||||
Playlist playlist = parser.parse();
|
||||
MasterPlaylist master = playlist.getMasterPlaylist();
|
||||
streamSources.clear();
|
||||
for (PlaylistData playlistData : master.getPlaylists()) {
|
||||
StreamSource streamsource = new StreamSource();
|
||||
String baseUrl = masterUrl.toString();
|
||||
baseUrl = baseUrl.substring(0, baseUrl.lastIndexOf('/') + 1);
|
||||
streamsource.mediaPlaylistUrl = baseUrl + playlistData.getUri();
|
||||
if (playlistData.hasStreamInfo()) {
|
||||
StreamInfo info = playlistData.getStreamInfo();
|
||||
streamsource.bandwidth = info.getBandwidth();
|
||||
streamsource.width = info.hasResolution() ? info.getResolution().width : 0;
|
||||
streamsource.height = info.hasResolution() ? info.getResolution().height : 0;
|
||||
} else {
|
||||
streamsource.bandwidth = 0;
|
||||
streamsource.width = 0;
|
||||
streamsource.height = 0;
|
||||
}
|
||||
streamSources.add(streamsource);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
return streamSources;
|
||||
}
|
||||
|
||||
private String getMasterPlaylistUrl() throws IOException {
|
||||
loadModelInfo();
|
||||
String url = site.getBaseUrl() + "/en/stream/hls/free/" + getName();
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.addHeader("Accept", "application/json, text/javascript, */*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", site.getBaseUrl())
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = site.getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
JSONObject json = new JSONObject(body);
|
||||
if (json.optBoolean("success")) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
JSONObject hlsStream = data.getJSONObject("hls_stream");
|
||||
return hlsStream.getString("url");
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + url + "\n" + body);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCacheEntries() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
// tips are send over the relay websocket, e.g:
|
||||
// {"event":"call","funcName":"sendSurprise","data":[1,"SurpriseGirlFlower"]}
|
||||
// response:
|
||||
// {"event":"call","funcName":"startSurprise","userId":"xyz_hash_gibberish","data":[{"memberid":"userxyz","amount":1,"tipName":"SurpriseGirlFlower","err_desc":"OK","err_text":"OK"}]}
|
||||
LiveJasminTippingWebSocket tippingSocket = new LiveJasminTippingWebSocket(site.getHttpClient());
|
||||
try {
|
||||
tippingSocket.sendTip(this, Config.getInstance(), tokens);
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
||||
if (resolution == null) {
|
||||
if (failFast) {
|
||||
return new int[2];
|
||||
}
|
||||
try {
|
||||
loadModelInfo();
|
||||
} catch (IOException e) {
|
||||
throw new ExecutionException(e);
|
||||
}
|
||||
return resolution;
|
||||
} else {
|
||||
return resolution;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean follow() throws IOException {
|
||||
return follow(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unfollow() throws IOException {
|
||||
return follow(false);
|
||||
}
|
||||
|
||||
private boolean follow(boolean follow) throws IOException {
|
||||
if (id == null) {
|
||||
loadModelInfo();
|
||||
}
|
||||
|
||||
String sessionId = ((LiveJasminHttpClient) site.getHttpClient()).getSessionId();
|
||||
String url;
|
||||
if (follow) {
|
||||
url = site.getBaseUrl() + "/en/free/favourite/add-favourite?session=" + sessionId + "&performerId=" + id;
|
||||
} else {
|
||||
url = site.getBaseUrl() + "/en/free/favourite/delete-favourite?session=" + sessionId + "&performerId=" + id;
|
||||
}
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.addHeader("Accept", "*/*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", getUrl())
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = site.getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
JSONObject json = new JSONObject(body);
|
||||
return json.optString("status").equalsIgnoreCase("ok");
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSiteSpecificData(JsonReader reader) throws IOException {
|
||||
reader.nextName();
|
||||
id = reader.nextString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
if (id == null) {
|
||||
try {
|
||||
loadModelInfo();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName());
|
||||
}
|
||||
}
|
||||
writer.name("id").value(id);
|
||||
}
|
||||
|
||||
public void setOnline(boolean online) {
|
||||
this.online = online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Download createDownload() {
|
||||
if(Config.isServerMode()) {
|
||||
return new LiveJasminHlsDownload(getSite().getHttpClient());
|
||||
} else {
|
||||
return new LiveJasminMergedHlsDownload(getSite().getHttpClient());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
public class LiveJasminTippingWebSocket {
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminTippingWebSocket.class);
|
||||
|
||||
private String applicationId;
|
||||
private String sessionId;
|
||||
private String jsm2SessionId;
|
||||
private String sb_ip;
|
||||
private String sb_hash;
|
||||
private String relayHost;
|
||||
private String streamHost;
|
||||
private String clientInstanceId = "01234567890123456789012345678901"; // TODO where to get or generate a random id?
|
||||
private WebSocket relay;
|
||||
private Throwable exception;
|
||||
|
||||
private HttpClient client;
|
||||
private Model model;
|
||||
|
||||
public LiveJasminTippingWebSocket(HttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void sendTip(Model model, Config config, double amount) throws IOException, InterruptedException {
|
||||
this.model = model;
|
||||
getPerformerDetails(model.getName());
|
||||
LOG.debug("appid: {}", applicationId);
|
||||
LOG.debug("sessionid: {}",sessionId);
|
||||
LOG.debug("jsm2sessionid: {}",jsm2SessionId);
|
||||
LOG.debug("sb_ip: {}",sb_ip);
|
||||
LOG.debug("sb_hash: {}",sb_hash);
|
||||
LOG.debug("relay host: {}",relayHost);
|
||||
LOG.debug("stream host: {}",streamHost);
|
||||
LOG.debug("clientinstanceid {}",clientInstanceId);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://" + relayHost + "/")
|
||||
.header("Origin", "https://www.livejasmin.com")
|
||||
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0")
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
.header("Accept-Language", "de,en-US;q=0.7,en;q=0.3")
|
||||
.build();
|
||||
Object monitor = new Object();
|
||||
relay = client.newWebSocket(request, new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
LOG.trace("relay open {}", model.getName());
|
||||
sendToRelay("{\"event\":\"register\",\"applicationId\":\"" + applicationId
|
||||
+ "\",\"connectionData\":{\"jasmin2App\":true,\"isMobileClient\":false,\"platform\":\"desktop\",\"chatID\":\"freechat\","
|
||||
+ "\"sessionID\":\"" + sessionId + "\"," + "\"jsm2SessionId\":\"" + jsm2SessionId + "\",\"userType\":\"user\"," + "\"performerId\":\""
|
||||
+ model
|
||||
+ "\",\"clientRevision\":\"\",\"proxyIP\":\"\",\"playerVer\":\"nanoPlayerVersion: 3.10.3 appCodeName: Mozilla appName: Netscape appVersion: 5.0 (X11) platform: Linux x86_64\",\"livejasminTvmember\":false,\"newApplet\":true,\"livefeedtype\":null,\"gravityCookieId\":\"\",\"passparam\":\"\",\"brandID\":\"jasmin\",\"cobrandId\":\"\",\"subbrand\":\"livejasmin\",\"siteName\":\"LiveJasmin\",\"siteUrl\":\"https://www.livejasmin.com\","
|
||||
+ "\"clientInstanceId\":\"" + clientInstanceId + "\",\"armaVersion\":\"34.10.0\",\"isPassive\":false}}");
|
||||
response.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
LOG.trace("relay <-- {} T{}", model.getName(), text);
|
||||
JSONObject event = new JSONObject(text);
|
||||
if (event.optString("event").equals("accept")) {
|
||||
new Thread(() -> {
|
||||
sendToRelay("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
|
||||
}).start();
|
||||
} else if(event.optString("event").equals("call")) {
|
||||
String func = event.optString("funcName");
|
||||
if (func.equals("setName")) {
|
||||
LOG.debug("Entered chat -> Sending tip of {}", amount);
|
||||
sendToRelay("{\"event\":\"call\",\"funcName\":\"sendSurprise\",\"data\":["+amount+",\"SurpriseGirlFlower\"]}");
|
||||
} else if (func.equals("startSurprise")) {
|
||||
// {"event":"call","funcName":"startSurprise","userId":"xyz_hash_gibberish","data":[{"memberid":"userxyz","amount":1,"tipName":"SurpriseGirlFlower","err_desc":"OK","err_text":"OK"}]}
|
||||
JSONArray dataArray = event.getJSONArray("data");
|
||||
JSONObject data = dataArray.getJSONObject(0);
|
||||
String errText = data.optString("err_text");
|
||||
String errDesc = data.optString("err_desc");
|
||||
LOG.debug("Tip response {} - {}", errText, errDesc);
|
||||
if(!errText.equalsIgnoreCase("OK")) {
|
||||
exception = new IOException(errText + " - " + errDesc);
|
||||
}
|
||||
synchronized (monitor) {
|
||||
monitor.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
LOG.trace("relay <-- {} B{}", model.getName(), bytes.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
LOG.trace("relay closed {} {} {}", code, reason, model.getName());
|
||||
exception = new IOException("Socket closed by server - " + code + " " + reason);
|
||||
synchronized (monitor) {
|
||||
monitor.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
exception = t;
|
||||
synchronized (monitor) {
|
||||
monitor.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
synchronized (monitor) {
|
||||
monitor.wait();
|
||||
}
|
||||
if(exception != null) {
|
||||
throw new IOException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToRelay(String msg) {
|
||||
LOG.trace("relay --> {} {}", model.getName(), msg);
|
||||
relay.send(msg);
|
||||
}
|
||||
|
||||
protected void getPerformerDetails(String name) throws IOException {
|
||||
String url = "https://m.livejasmin.com/en/chat-html5/" + name;
|
||||
Request req = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", "Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15")
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", "https://www.livejasmin.com")
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = client.execute(req)) {
|
||||
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");
|
||||
JSONObject config = data.getJSONObject("config");
|
||||
JSONObject armageddonConfig = config.getJSONObject("armageddonConfig");
|
||||
JSONObject chatRoom = config.getJSONObject("chatRoom");
|
||||
sessionId = armageddonConfig.getString("sessionid");
|
||||
jsm2SessionId = armageddonConfig.getString("jsm2session");
|
||||
sb_hash = chatRoom.getString("sb_hash");
|
||||
sb_ip = chatRoom.getString("sb_ip");
|
||||
applicationId = "memberChat/jasmin" + name + sb_hash;
|
||||
relayHost = "dss-relay-" + sb_ip.replace('.', '-') + ".dditscdn.com";
|
||||
streamHost = "dss-live-" + sb_ip.replace('.', '-') + ".dditscdn.com";
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + body);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(response.code() + " - " + response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,357 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.event.Event;
|
||||
import ctbrec.event.EventBusHolder;
|
||||
import ctbrec.event.ModelStateChangedEvent;
|
||||
import ctbrec.io.HttpClient;
|
||||
import ctbrec.recorder.download.Download;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
import okio.ByteString;
|
||||
|
||||
public class LiveJasminWebSocketDownload implements Download {
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminWebSocketDownload.class);
|
||||
|
||||
private String applicationId;
|
||||
private String sessionId;
|
||||
private String jsm2SessionId;
|
||||
private String sb_ip;
|
||||
private String sb_hash;
|
||||
private String relayHost;
|
||||
private String streamHost;
|
||||
private String clientInstanceId = "01234567890123456789012345678901"; // TODO where to get or generate a random id?
|
||||
private String streamPath = "streams/clonedLiveStream";
|
||||
private WebSocket relay;
|
||||
private WebSocket stream;
|
||||
|
||||
protected boolean connectionClosed;
|
||||
private volatile boolean isAlive = true;
|
||||
|
||||
private HttpClient client;
|
||||
private Model model;
|
||||
private Instant startTime;
|
||||
private File targetFile;
|
||||
|
||||
public LiveJasminWebSocketDownload(HttpClient client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Model model, Config config) throws IOException {
|
||||
this.model = model;
|
||||
startTime = Instant.now();
|
||||
File _targetFile = config.getFileForRecording(model);
|
||||
targetFile = new File(_targetFile.getAbsolutePath().replace(".ts", ".mp4"));
|
||||
|
||||
getPerformerDetails(model.getName());
|
||||
LOG.debug("appid: {}", applicationId);
|
||||
LOG.debug("sessionid: {}",sessionId);
|
||||
LOG.debug("jsm2sessionid: {}",jsm2SessionId);
|
||||
LOG.debug("sb_ip: {}",sb_ip);
|
||||
LOG.debug("sb_hash: {}",sb_hash);
|
||||
LOG.debug("relay host: {}",relayHost);
|
||||
LOG.debug("stream host: {}",streamHost);
|
||||
LOG.debug("clientinstanceid {}",clientInstanceId);
|
||||
|
||||
EventBusHolder.BUS.register(this);
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://" + relayHost + "/")
|
||||
.header("Origin", "https://www.livejasmin.com")
|
||||
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0")
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
|
||||
.header("Accept-Language", "de,en-US;q=0.7,en;q=0.3")
|
||||
.build();
|
||||
relay = client.newWebSocket(request, new WebSocketListener() {
|
||||
boolean streamSocketStarted = false;
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
LOG.trace("relay open {}", model.getName());
|
||||
sendToRelay("{\"event\":\"register\",\"applicationId\":\"" + applicationId
|
||||
+ "\",\"connectionData\":{\"jasmin2App\":true,\"isMobileClient\":false,\"platform\":\"desktop\",\"chatID\":\"freechat\","
|
||||
+ "\"sessionID\":\"" + sessionId + "\"," + "\"jsm2SessionId\":\"" + jsm2SessionId + "\",\"userType\":\"user\"," + "\"performerId\":\""
|
||||
+ model
|
||||
+ "\",\"clientRevision\":\"\",\"proxyIP\":\"\",\"playerVer\":\"nanoPlayerVersion: 3.10.3 appCodeName: Mozilla appName: Netscape appVersion: 5.0 (X11) platform: Linux x86_64\",\"livejasminTvmember\":false,\"newApplet\":true,\"livefeedtype\":null,\"gravityCookieId\":\"\",\"passparam\":\"\",\"brandID\":\"jasmin\",\"cobrandId\":\"\",\"subbrand\":\"livejasmin\",\"siteName\":\"LiveJasmin\",\"siteUrl\":\"https://www.livejasmin.com\","
|
||||
+ "\"clientInstanceId\":\"" + clientInstanceId + "\",\"armaVersion\":\"34.10.0\",\"isPassive\":false}}");
|
||||
response.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
LOG.trace("relay <-- {} T{}", model.getName(), text);
|
||||
JSONObject event = new JSONObject(text);
|
||||
if (event.optString("event").equals("accept")) {
|
||||
new Thread(() -> {
|
||||
sendToRelay("{\"event\":\"connectSharedObject\",\"name\":\"data/chat_so\"}");
|
||||
}).start();
|
||||
} else if (event.optString("event").equals("updateSharedObject")) {
|
||||
JSONArray list = event.getJSONArray("list");
|
||||
for (int i = 0; i < list.length(); i++) {
|
||||
JSONObject obj = list.getJSONObject(i);
|
||||
if (obj.optString("name").equals("streamList")) {
|
||||
//LOG.debug(obj.toString(2));
|
||||
streamPath = getStreamPath(obj.getJSONObject("newValue"));
|
||||
} else if(obj.optString("name").equals("isPrivate")
|
||||
|| obj.optString("name").equals("onPrivate")
|
||||
|| obj.optString("name").equals("onPrivateAll")
|
||||
|| obj.optString("name").equals("onPrivateLJ"))
|
||||
{
|
||||
if(obj.optBoolean("newValue")) {
|
||||
// model went private, stop recording
|
||||
LOG.debug("Model {} state changed to private -> stopping download", model.getName());
|
||||
stop();
|
||||
}
|
||||
} else if(obj.optString("name").equals("recommendedBandwidth") || obj.optString("name").equals("realQualityData")) {
|
||||
// stream quality related -> do nothing
|
||||
} else {
|
||||
LOG.debug("{} -{}", model.getName(), obj.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (!streamSocketStarted) {
|
||||
streamSocketStarted = true;
|
||||
sendToRelay("{\"event\":\"call\",\"funcName\":\"makeActive\",\"data\":[]}");
|
||||
new Thread(() -> {
|
||||
try {
|
||||
startStreamSocket();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Couldn't start stream websocket", e);
|
||||
stop();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
} else if(event.optString("event").equals("call")) {
|
||||
String func = event.optString("funcName");
|
||||
if (func.equals("closeConnection")) {
|
||||
connectionClosed = true;
|
||||
// System.out.println(event.get("data"));
|
||||
stop();
|
||||
} else if (func.equals("addLine")) {
|
||||
// chat message -> ignore
|
||||
} else if (func.equals("receiveInvitation")) {
|
||||
// invitation to private show -> ignore
|
||||
} else {
|
||||
LOG.debug("{} -{}", model.getName(), event.toString());
|
||||
}
|
||||
} else {
|
||||
if(!event.optString("event").equals("pong"))
|
||||
LOG.debug("{} -{}", model.getName(), event.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String getStreamPath(JSONObject obj) {
|
||||
String streamName = "streams/clonedLiveStream";
|
||||
int height = 0;
|
||||
if(obj.has("streams")) {
|
||||
JSONArray streams = obj.getJSONArray("streams");
|
||||
for (int i = 0; i < streams.length(); i++) {
|
||||
JSONObject stream = streams.getJSONObject(i);
|
||||
int h = stream.optInt("height");
|
||||
if(h > height) {
|
||||
height = h;
|
||||
streamName = stream.getString("streamNameWithFolder");
|
||||
streamName = "free/" + stream.getString("name");
|
||||
}
|
||||
}
|
||||
}
|
||||
return streamName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
LOG.trace("relay <-- {} B{}", model.getName(), bytes.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
LOG.trace("relay closed {} {} {}", code, reason, model.getName());
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
if(!connectionClosed) {
|
||||
LOG.trace("relay failure {}", model.getName(), t);
|
||||
stop();
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void handleEvent(Event evt) {
|
||||
if(evt.getType() == Event.Type.MODEL_STATUS_CHANGED) {
|
||||
ModelStateChangedEvent me = (ModelStateChangedEvent) evt;
|
||||
if(me.getModel().equals(model) && me.getOldState() == Model.State.ONLINE) {
|
||||
LOG.debug("Model {} state changed to {} -> stopping download", me.getNewState(), model.getName());
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendToRelay(String msg) {
|
||||
LOG.trace("relay --> {} {}", model.getName(), msg);
|
||||
relay.send(msg);
|
||||
}
|
||||
|
||||
protected void getPerformerDetails(String name) throws IOException {
|
||||
String url = "https://m.livejasmin.com/en/chat-html5/" + name;
|
||||
Request req = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", "Mozilla/5.0 (iPhone; CPU OS 10_14 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/11.1.1 Mobile/14E304 Safari/605.1.15")
|
||||
.header("Accept", "application/json,*/*")
|
||||
.header("Accept-Language", "en")
|
||||
.header("Referer", "https://www.livejasmin.com")
|
||||
.header("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try (Response response = client.execute(req)) {
|
||||
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");
|
||||
JSONObject config = data.getJSONObject("config");
|
||||
JSONObject armageddonConfig = config.getJSONObject("armageddonConfig");
|
||||
JSONObject chatRoom = config.getJSONObject("chatRoom");
|
||||
sessionId = armageddonConfig.getString("sessionid");
|
||||
jsm2SessionId = armageddonConfig.getString("jsm2session");
|
||||
sb_hash = chatRoom.getString("sb_hash");
|
||||
sb_ip = chatRoom.getString("sb_ip");
|
||||
applicationId = "memberChat/jasmin" + name + sb_hash;
|
||||
relayHost = "dss-relay-" + sb_ip.replace('.', '-') + ".dditscdn.com";
|
||||
streamHost = "dss-live-" + sb_ip.replace('.', '-') + ".dditscdn.com";
|
||||
} else {
|
||||
throw new IOException("Response was not successful: " + body);
|
||||
}
|
||||
} else {
|
||||
throw new IOException(response.code() + " - " + response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startStreamSocket() throws UnsupportedEncodingException {
|
||||
String rtmpUrl = "rtmp://" + sb_ip + "/" + applicationId + "?sessionId-" + sessionId + "|clientInstanceId-" + clientInstanceId;
|
||||
String url = "https://" + streamHost + "/stream/?url=" + URLEncoder.encode(rtmpUrl, "utf-8");
|
||||
url = url += "&stream=" + URLEncoder.encode(streamPath, "utf-8") + "&cid=863621&pid=49247581854";
|
||||
LOG.trace(rtmpUrl);
|
||||
LOG.trace(url);
|
||||
|
||||
Request request = new Request.Builder().url(url).header("Origin", "https://www.livejasmin.com")
|
||||
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:63.0) Gecko/20100101 Firefox/63.0")
|
||||
.header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8").header("Accept-Language", "de,en-US;q=0.7,en;q=0.3")
|
||||
.build();
|
||||
stream = client.newWebSocket(request, new WebSocketListener() {
|
||||
FileOutputStream fos;
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
LOG.trace("stream open {}", model.getName());
|
||||
// webSocket.send("{\"event\":\"ping\"}");
|
||||
// webSocket.send("");
|
||||
response.close();
|
||||
try {
|
||||
Files.createDirectories(targetFile.getParentFile().toPath());
|
||||
fos = new FileOutputStream(targetFile);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't create video file", e);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
LOG.trace("stream <-- {} T{}", model.getName(), text);
|
||||
JSONObject event = new JSONObject(text);
|
||||
if(event.optString("eventType").equals("onRandomAccessPoint")) {
|
||||
// send ping
|
||||
sendToRelay("{\"event\":\"ping\"}");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, ByteString bytes) {
|
||||
//System.out.println("stream <-- B" + bytes.toString());
|
||||
try {
|
||||
fos.write(bytes.toByteArray());
|
||||
} catch (IOException e) {
|
||||
LOG.error("Couldn't write video chunk to file", e);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
LOG.trace("stream closed {} {} {}", code, reason, model.getName());
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
if(!connectionClosed) {
|
||||
LOG.trace("stream failure {}", model.getName(), t);
|
||||
stop();
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
connectionClosed = true;
|
||||
EventBusHolder.BUS.unregister(this);
|
||||
isAlive = false;
|
||||
if (stream != null) {
|
||||
stream.close(1000, "");
|
||||
}
|
||||
if (relay != null) {
|
||||
relay.close(1000, "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAlive() {
|
||||
return isAlive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getTarget() {
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Model getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
}
|
|
@ -59,14 +59,14 @@ public class MyFreeCams extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
public Double getTokenBalance() throws IOException {
|
||||
Request req = new Request.Builder().url(baseUrl + "/php/account.php?request=status").build();
|
||||
try(Response response = getHttpClient().execute(req)) {
|
||||
if(response.isSuccessful()) {
|
||||
String content = response.body().string();
|
||||
Elements tags = HtmlParser.getTags(content, "div.content > p > b");
|
||||
String tokens = tags.get(2).text();
|
||||
return Integer.parseInt(tokens);
|
||||
return Double.parseDouble(tokens);
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
String tipUrl = MyFreeCams.baseUrl + "/php/tip.php";
|
||||
String initUrl = tipUrl + "?request=tip&username="+getName()+"&broadcaster_id="+getUid();
|
||||
Request req = new Request.Builder().url(initUrl).build();
|
||||
|
@ -173,7 +173,7 @@ public class MyFreeCamsModel extends AbstractModel {
|
|||
RequestBody body = new FormBody.Builder()
|
||||
.add("token", token)
|
||||
.add("broadcaster_id", Integer.toString(uid))
|
||||
.add("tip_value", Integer.toString(tokens))
|
||||
.add("tip_value", Integer.toString(tokens.intValue()))
|
||||
.add("submit_tip", "1")
|
||||
.add("anonymous", "")
|
||||
.add("public", "1")
|
||||
|
|
|
@ -57,7 +57,7 @@ public class Streamate extends AbstractSite {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Integer getTokenBalance() throws IOException {
|
||||
public Double getTokenBalance() throws IOException {
|
||||
// int userId = ((StreamateHttpClient)getHttpClient()).getUserId();
|
||||
// String url = Streamate.BASE_URL + "/tools/amf.php";
|
||||
// RequestBody body = new FormBody.Builder()
|
||||
|
@ -86,7 +86,7 @@ public class Streamate extends AbstractSite {
|
|||
// throw new HttpException(response.code(), response.message());
|
||||
// }
|
||||
// }
|
||||
return 0;
|
||||
return 0d;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -132,7 +132,7 @@ public class StreamateModel extends AbstractModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void receiveTip(int tokens) throws IOException {
|
||||
public void receiveTip(Double tokens) throws IOException {
|
||||
/*
|
||||
Mt._giveGoldAjax = function(e, t) {
|
||||
var n = _t.getState(),
|
||||
|
@ -180,17 +180,17 @@ public class StreamateModel extends AbstractModel {
|
|||
|
||||
String url = "https://hybridclient.naiadsystems.com/api/v1/givegold/"; // this returns 404 at the moment. not sure if it's the wrong server, or if this is not used anymore
|
||||
RequestBody body = new FormBody.Builder()
|
||||
.add("amt", Integer.toString(tokens)) // amount
|
||||
.add("isprepopulated", "1") // ?
|
||||
.add("modelname", getName()) // model's name
|
||||
.add("nickname", nickname) // user's nickname
|
||||
.add("performernickname", getName()) // model's name
|
||||
.add("sakey", saKey) // sakey from login
|
||||
.add("session", "") // is related to gold an private shows, for normal tips keep it empty
|
||||
.add("smid", Long.toString(getId())) // model id
|
||||
.add("streamid", getStreamId()) // id of the current stream
|
||||
.add("userid", Long.toString(userId)) // user's id
|
||||
.add("username", nickname) // user's nickname
|
||||
.add("amt", Integer.toString(tokens.intValue())) // amount
|
||||
.add("isprepopulated", "1") // ?
|
||||
.add("modelname", getName()) // model's name
|
||||
.add("nickname", nickname) // user's nickname
|
||||
.add("performernickname", getName()) // model's name
|
||||
.add("sakey", saKey) // sakey from login
|
||||
.add("session", "") // is related to gold an private shows, for normal tips keep it empty
|
||||
.add("smid", Long.toString(getId())) // model id
|
||||
.add("streamid", getStreamId()) // id of the current stream
|
||||
.add("userid", Long.toString(userId)) // user's id
|
||||
.add("username", nickname) // user's nickname
|
||||
.build();
|
||||
Buffer b = new Buffer();
|
||||
body.writeTo(b);
|
||||
|
|
|
@ -6,7 +6,9 @@ import java.util.regex.Pattern;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
@ -27,7 +29,10 @@ public class StreamateWebsocketClient {
|
|||
public String getRoomId() throws InterruptedException {
|
||||
LOG.debug("Connecting to {}", url);
|
||||
Object monitor = new Object();
|
||||
client.newWebSocket(url, new WebSocketListener() {
|
||||
Request request = new Request.Builder()
|
||||
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.build();
|
||||
client.newWebSocket(request, new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
response.close();
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.16.0</version>
|
||||
<version>1.17.0</version>
|
||||
|
||||
<modules>
|
||||
<module>../common</module>
|
||||
|
@ -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>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
FROM alpine/git as open-m3u8Git
|
||||
WORKDIR /app
|
||||
RUN git clone https://github.com/0xboobface/open-m3u8.git
|
||||
|
||||
FROM gradle:4.10-jdk10 as open-m3u8Build
|
||||
WORKDIR /app/open-m3u8
|
||||
COPY --from=open-m3u8Git --chown=gradle:gradle /app /app
|
||||
RUN gradle install
|
||||
|
||||
FROM alpine/git as ctbrecGit
|
||||
WORKDIR /app
|
||||
RUN git clone https://github.com/0xboobface/ctbrec.git
|
||||
|
||||
FROM maven:3-jdk-11-slim as ctbrecBuild
|
||||
ARG ctbrec
|
||||
ARG versionM3u8
|
||||
WORKDIR /app/master
|
||||
COPY --from=ctbrecGit /app/ctbrec /app
|
||||
COPY --from=open-m3u8Build /app/open-m3u8/build/libs/ /app/common/libs/
|
||||
RUN mvn clean install:install-file -Dfile=/app/common/libs/open-m3u8-${versionM3u8}.jar -DgroupId=com.iheartradio.m3u8 -DartifactId=open-m3u8 -Dversion=${versionM3u8} -Dpackaging=jar -DgeneratePom=true
|
||||
RUN mvn clean
|
||||
RUN mvn install
|
||||
|
||||
FROM openjdk:12-alpine
|
||||
WORKDIR /app
|
||||
ARG memory
|
||||
ARG version
|
||||
ENV artifact ctbrec-server-${version}-final.jar
|
||||
ENV path /app/server/target/${artifact}
|
||||
COPY --from=ctbrecBuild ${path} ./${artifact}
|
||||
EXPOSE 8080
|
||||
CMD java ${memory} -cp ${artifact} -Dctbrec.config=/server.json ctbrec.recorder.server.HttpServer
|
||||
|
|
@ -31,5 +31,13 @@ This is the server part, which is only needed, if you want to run ctbrec in clie
|
|||
## Docker
|
||||
There is a docker image, created by Github user [1461748123](https://github.com/1461748123), which you can find on [Docker Hub](https://hub.docker.com/r/1461748123/ctbrec/)
|
||||
|
||||
You can also build your own image with the Dockerfile included.
|
||||
|
||||
To run them, execute the following command :
|
||||
``docker run -d -p 8080:8080 -v /ctb/app/config:/root/.config/ctbrec/ -v /ctb/video:/root/ctbrec 0xboobface/ctbrec``
|
||||
|
||||
You can also use the docker-compose with command :
|
||||
``docker-compose up``
|
||||
|
||||
## License
|
||||
CTB Recorder is licensed under the GPLv3. See [LICENSE.txt](https://raw.githubusercontent.com/0xboobface/ctbrec/master/LICENSE.txt).
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
version: 3
|
||||
services:
|
||||
ctbrec:
|
||||
ports:
|
||||
- '8080:8080'
|
||||
volumes:
|
||||
- 'ctbrec/config:/root/.config/ctbrec/'
|
||||
- 'ctbrec/video:/root/ctbrec'
|
||||
image: bounty1342/ctbrec
|
|
@ -8,7 +8,7 @@
|
|||
<parent>
|
||||
<groupId>ctbrec</groupId>
|
||||
<artifactId>master</artifactId>
|
||||
<version>1.16.0</version>
|
||||
<version>1.17.0</version>
|
||||
<relativePath>../master</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import ctbrec.sites.bonga.BongaCams;
|
|||
import ctbrec.sites.cam4.Cam4;
|
||||
import ctbrec.sites.camsoda.Camsoda;
|
||||
import ctbrec.sites.chaturbate.Chaturbate;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import ctbrec.sites.mfc.MyFreeCams;
|
||||
import ctbrec.sites.streamate.Streamate;
|
||||
|
||||
|
@ -78,11 +79,12 @@ public class HttpServer {
|
|||
}
|
||||
|
||||
private void createSites() {
|
||||
sites.add(new Chaturbate());
|
||||
sites.add(new MyFreeCams());
|
||||
sites.add(new Camsoda());
|
||||
sites.add(new Cam4());
|
||||
sites.add(new BongaCams());
|
||||
sites.add(new Cam4());
|
||||
sites.add(new Camsoda());
|
||||
sites.add(new Chaturbate());
|
||||
sites.add(new LiveJasmin());
|
||||
sites.add(new MyFreeCams());
|
||||
sites.add(new Streamate());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue