forked from j62/ctbrec
Add websocket download
This is the first version with working downloads for SD and HD. These dowloads only work, if you are logged in. So at the moment you have to set the session ID in the settings to make this work. The session ID can be copied from a valid session in a browser.
This commit is contained in:
parent
4f3fd8a677
commit
2425a9dc60
|
@ -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);
|
||||
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(sessionId, 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,170 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpCookie;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.OS;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.sites.jasmin.LiveJasmin;
|
||||
import javafx.concurrent.Worker.State;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class LiveJasminLoginDialog {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminLoginDialog.class);
|
||||
public static final String URL = "https://m.livejasmin.com/en/list"; // #login-modal
|
||||
private List<HttpCookie> cookies = null;
|
||||
private String url;
|
||||
private Region veil;
|
||||
private ProgressIndicator p;
|
||||
private LiveJasmin liveJasmin;
|
||||
|
||||
public LiveJasminLoginDialog(LiveJasmin liveJasmin) throws IOException {
|
||||
this.liveJasmin = liveJasmin;
|
||||
Stage stage = new Stage();
|
||||
stage.setTitle("LiveJasmin Login");
|
||||
InputStream icon = getClass().getResourceAsStream("/icon.png");
|
||||
stage.getIcons().add(new Image(icon));
|
||||
CookieManager cookieManager = new CookieManager();
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
WebView webView = createWebView(stage);
|
||||
|
||||
veil = new Region();
|
||||
veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)");
|
||||
p = new ProgressIndicator();
|
||||
p.setMaxSize(140, 140);
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.getChildren().addAll(webView, veil, p);
|
||||
|
||||
stage.setScene(new Scene(stackPane, 360, 480));
|
||||
stage.showAndWait();
|
||||
cookies = cookieManager.getCookieStore().getCookies();
|
||||
}
|
||||
|
||||
private WebView createWebView(Stage stage) throws IOException {
|
||||
|
||||
|
||||
WebView browser = new WebView();
|
||||
WebEngine webEngine = browser.getEngine();
|
||||
webEngine.setJavaScriptEnabled(true);
|
||||
//webEngine.setUserAgent("Mozilla/5.0 (Android 9.0; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0");
|
||||
webEngine.setUserAgent("Mozilla/5.0 (Mobile; rv:30.0) Gecko/20100101 Firefox/30.0");
|
||||
webEngine.locationProperty().addListener((obs, oldV, newV) -> {
|
||||
try {
|
||||
URL _url = new URL(newV);
|
||||
if (Objects.equals(_url.getPath(), "/")) {
|
||||
stage.close();
|
||||
}
|
||||
} catch (MalformedURLException e) {
|
||||
LOG.error("Couldn't parse new url {}", newV, e);
|
||||
}
|
||||
url = newV.toString();
|
||||
});
|
||||
webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> {
|
||||
if (newState == State.SUCCEEDED) {
|
||||
veil.setVisible(false);
|
||||
p.setVisible(false);
|
||||
// try {
|
||||
// //webEngine.executeScript("$('#eighteen-plus-modal').hide();");
|
||||
// //webEngine.executeScript("$('body').html('"+loginForm+"');");
|
||||
// //webEngine.executeScript("$('#listpage').append('"+loginForm+"');");
|
||||
// // webEngine.executeScript("$('#main-menu-button').click();");
|
||||
// // webEngine.executeScript("$('#login-menu').click();");
|
||||
// String username = Config.getInstance().getSettings().livejasminUsername;
|
||||
// if (username != null && !username.trim().isEmpty()) {
|
||||
// webEngine.executeScript("$('#username').attr('value','" + username + "')");
|
||||
// }
|
||||
// String password = Config.getInstance().getSettings().livejasminPassword;
|
||||
// if (password != null && !password.trim().isEmpty()) {
|
||||
// webEngine.executeScript("$('#password').attr('value','" + password + "')");
|
||||
// }
|
||||
// } catch(Exception e) {
|
||||
// LOG.warn("Couldn't auto fill username and password for LiveJasmin", e);
|
||||
// }
|
||||
} else if (newState == State.CANCELLED || newState == State.FAILED) {
|
||||
veil.setVisible(false);
|
||||
p.setVisible(false);
|
||||
}
|
||||
});
|
||||
webEngine.setUserDataDirectory(new File(OS.getConfigDir(), "webengine"));
|
||||
webEngine.load(URL);
|
||||
return browser;
|
||||
}
|
||||
|
||||
private String getLoginForm() throws IOException {
|
||||
callBaseUrl(); // to get cookies
|
||||
String url = "https://m.livejasmin.com/en/auth/window/get-login-window?isAjax=1";
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.addHeader("User-Agent", "Mozilla/5.0 (Android 9.0; Mobile; rv:63.0) Gecko/63.0 Firefox/63.0")
|
||||
.addHeader("Accept", "application/json, text/javascript, */*")
|
||||
.addHeader("Accept-Language", "en")
|
||||
.addHeader("Referer", LiveJasmin.BASE_URL)
|
||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
.build();
|
||||
try(Response response = liveJasmin.getHttpClient().execute(request)) {
|
||||
if(response.isSuccessful()) {
|
||||
String body = response.body().string();
|
||||
JSONObject json = new JSONObject(body);
|
||||
System.out.println(json.toString(2));
|
||||
if(json.optBoolean("success")) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
return data.getString("content");
|
||||
} else {
|
||||
throw new IOException("Request was not successful: " + body);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void callBaseUrl() throws IOException {
|
||||
String url = liveJasmin.getBaseUrl();
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
|
||||
.build();
|
||||
try(Response response = liveJasmin.getHttpClient().execute(request)) {
|
||||
if(response.isSuccessful()) {
|
||||
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<HttpCookie> getCookies() {
|
||||
for (HttpCookie httpCookie : cookies) {
|
||||
LOG.debug("Cookie: {}", httpCookie);
|
||||
}
|
||||
return cookies;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,39 @@
|
|||
package ctbrec.ui.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpCookie;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
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 okhttp3.Cookie;
|
||||
import okhttp3.CookieJar;
|
||||
import okhttp3.HttpUrl;
|
||||
|
||||
public class LiveJasminSiteUi implements SiteUI {
|
||||
|
||||
private static final transient Logger LOG = LoggerFactory.getLogger(LiveJasminSiteUi.class);
|
||||
private LiveJasmin liveJasmin;
|
||||
private LiveJasminTabProvider tabProvider;
|
||||
private LiveJasminConfigUi configUi;
|
||||
|
||||
public LiveJasminSiteUi(LiveJasmin liveJasmin) {
|
||||
this.liveJasmin = liveJasmin;
|
||||
tabProvider = new LiveJasminTabProvider(liveJasmin);
|
||||
configUi = new LiveJasminConfigUi(liveJasmin);
|
||||
try {
|
||||
login();
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,12 +43,80 @@ public class LiveJasminSiteUi implements SiteUI {
|
|||
|
||||
@Override
|
||||
public ConfigUI getConfigUI() {
|
||||
return null;
|
||||
return configUi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean login() throws IOException {
|
||||
return liveJasmin.login();
|
||||
public synchronized boolean login() throws IOException {
|
||||
boolean automaticLogin = liveJasmin.login();
|
||||
return automaticLogin;
|
||||
// if(automaticLogin) {
|
||||
// return true;
|
||||
// } else {
|
||||
// BlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
|
||||
//
|
||||
// Runnable showDialog = () -> {
|
||||
// // login with javafx WebView
|
||||
// LiveJasminLoginDialog loginDialog;
|
||||
// try {
|
||||
// loginDialog = new LiveJasminLoginDialog(liveJasmin);
|
||||
// // transfer cookies from WebView to OkHttp cookie jar
|
||||
// transferCookies(loginDialog);
|
||||
// } catch (IOException e1) {
|
||||
// LOG.error("Couldn't load login dialog", e1);
|
||||
// }
|
||||
//
|
||||
// try {
|
||||
// queue.put(true);
|
||||
// } catch (InterruptedException e) {
|
||||
// LOG.error("Error while signaling termination", e);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// if(Platform.isFxApplicationThread()) {
|
||||
// showDialog.run();
|
||||
// } else {
|
||||
// Platform.runLater(showDialog);
|
||||
// try {
|
||||
// queue.take();
|
||||
// } catch (InterruptedException e) {
|
||||
// LOG.error("Error while waiting for login dialog to close", e);
|
||||
// throw new IOException(e);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
|
||||
// boolean loggedIn = httpClient.checkLoginSuccess();
|
||||
// if(loggedIn) {
|
||||
// LOG.info("Logged in.");
|
||||
// } else {
|
||||
// LOG.info("Login failed");
|
||||
// }
|
||||
// return loggedIn;
|
||||
// }
|
||||
}
|
||||
|
||||
private void transferCookies(LiveJasminLoginDialog loginDialog) {
|
||||
LiveJasminHttpClient httpClient = (LiveJasminHttpClient)liveJasmin.getHttpClient();
|
||||
CookieJar cookieJar = httpClient.getCookieJar();
|
||||
|
||||
String[] urls = {
|
||||
"https://www.livejasmin.com",
|
||||
"http://www.livejasmin.com",
|
||||
"https://m.livejasmin.com",
|
||||
"http://m.livejasmin.com",
|
||||
"https://livejasmin.com",
|
||||
"http://livejasmin.com"
|
||||
};
|
||||
|
||||
for (String u : urls) {
|
||||
HttpUrl url = HttpUrl.parse(u);
|
||||
List<Cookie> cookies = new ArrayList<>();
|
||||
for (HttpCookie webViewCookie : loginDialog.getCookies()) {
|
||||
Cookie cookie = Cookie.parse(url, webViewCookie.toString());
|
||||
cookies.add(cookie);
|
||||
}
|
||||
cookieJar.saveFromResponse(url, cookies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,11 +65,12 @@ public class LiveJasminUpdateService extends PaginatedScheduledService {
|
|||
model.setId(m.getString("id"));
|
||||
model.setPreview(m.getString("profilePictureUrl"));
|
||||
model.setOnline(true);
|
||||
model.setOnlineState(ctbrec.Model.State.ONLINE);
|
||||
models.add(model);
|
||||
}
|
||||
} else {
|
||||
LOG.error("Request failed:\n{}", body);
|
||||
throw new IOException("Response was not successfull");
|
||||
throw new IOException("Response was not successful");
|
||||
}
|
||||
return models;
|
||||
} else {
|
||||
|
|
|
@ -62,6 +62,9 @@ public class Settings {
|
|||
public String camsodaPassword = "";
|
||||
public String cam4Username = "";
|
||||
public String cam4Password = "";
|
||||
public String livejasminUsername = "";
|
||||
public String livejasminPassword = "";
|
||||
public String livejasminSession = "";
|
||||
public String streamateUsername = "";
|
||||
public String streamatePassword = "";
|
||||
public String lastDownloadDir = "";
|
||||
|
|
|
@ -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
|
||||
|
@ -461,7 +462,7 @@ 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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -36,18 +39,19 @@ 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;
|
||||
}
|
||||
|
||||
SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
|
||||
protected SegmentPlaylist getNextSegments(String segments) throws IOException, ParseException, PlaylistException {
|
||||
URL segmentsUrl = new URL(segments);
|
||||
Request request = new Request.Builder().url(segmentsUrl).addHeader("connection", "keep-alive").build();
|
||||
try(Response response = client.execute(request)) {
|
||||
|
@ -85,7 +89,7 @@ public abstract class AbstractHlsDownload implements Download {
|
|||
}
|
||||
|
||||
|
||||
String getSegmentPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||
protected String getSegmentPlaylistUrl(Model model) throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||
LOG.debug("{} stream idx: {}", model.getName(), model.getStreamUrlIndex());
|
||||
List<StreamSource> streamSources = model.getStreamSources();
|
||||
Collections.sort(streamSources);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ public class LiveJasmin extends AbstractSite {
|
|||
model.setName(name);
|
||||
model.setDescription("");
|
||||
model.setSite(this);
|
||||
model.setUrl(getBaseUrl() + "/en/chat/" + name);
|
||||
return model;
|
||||
}
|
||||
|
||||
|
@ -47,7 +48,7 @@ public class LiveJasmin extends AbstractSite {
|
|||
|
||||
@Override
|
||||
public boolean login() throws IOException {
|
||||
return false;
|
||||
return getHttpClient().login();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,18 +1,172 @@
|
|||
package ctbrec.sites.jasmin;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
|
||||
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 boolean login() throws IOException {
|
||||
public synchronized boolean login() throws IOException {
|
||||
if (loggedIn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// set session cookie, if session id is available
|
||||
if(!Config.getInstance().getSettings().livejasminSession.isEmpty()) {
|
||||
Cookie captchaCookie = new Cookie.Builder()
|
||||
.domain("livejasmin.com")
|
||||
.name("session")
|
||||
.value(Config.getInstance().getSettings().livejasminSession)
|
||||
.build();
|
||||
getCookieJar().saveFromResponse(HttpUrl.parse("https://livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
getCookieJar().saveFromResponse(HttpUrl.parse("https://www.livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
getCookieJar().saveFromResponse(HttpUrl.parse("https://m.livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
}
|
||||
|
||||
|
||||
// loadMainPage(); // to get initial cookies
|
||||
// Cookie captchaCookie = new Cookie.Builder()
|
||||
// .domain("livejasmin.com")
|
||||
// .name("captchaRequired")
|
||||
// .value("0")
|
||||
// .build();
|
||||
// getCookieJar().saveFromResponse(HttpUrl.parse("https://livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
// getCookieJar().saveFromResponse(HttpUrl.parse("https://www.livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
// getCookieJar().saveFromResponse(HttpUrl.parse("https://m.livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
// Map<String, String> formParams = getLoginFormParameters();
|
||||
// getCookieJar().saveFromResponse(HttpUrl.parse("https://livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
// getCookieJar().saveFromResponse(HttpUrl.parse("https://m.livejasmin.com"), Collections.singletonList(captchaCookie));
|
||||
// String action = formParams.get("action");
|
||||
// formParams.remove("action");
|
||||
// Builder formBuilder = new FormBody.Builder();
|
||||
// for (Entry<String, String> param : formParams.entrySet()) {
|
||||
// formBuilder.add(param.getKey(), param.getValue());
|
||||
// }
|
||||
// formBuilder.add("username", Config.getInstance().getSettings().livejasminUsername);
|
||||
// formBuilder.add("password", Config.getInstance().getSettings().livejasminPassword);
|
||||
// FormBody form = formBuilder.build();
|
||||
// Buffer b = new Buffer();
|
||||
// form.writeTo(b);
|
||||
// LOG.debug("Form: {}", b.readUtf8());
|
||||
// Map<String, List<Cookie>> cookies = getCookieJar().getCookies();
|
||||
// for (Entry<String, List<Cookie>> domain : cookies.entrySet()) {
|
||||
// LOG.debug("{}", domain.getKey());
|
||||
// List<Cookie> cks = domain.getValue();
|
||||
// for (Cookie cookie : cks) {
|
||||
// LOG.debug(" {}", cookie);
|
||||
// }
|
||||
// }
|
||||
// Request request = new Request.Builder()
|
||||
// .url(LiveJasmin.BASE_URL + action)
|
||||
// .header("User-Agent", USER_AGENT)
|
||||
// .header("Accept", "*/*")
|
||||
// .header("Accept-Language", "en")
|
||||
// .header("Referer", LiveJasmin.BASE_URL + "/en/girls/")
|
||||
// .header("X-Requested-With", "XMLHttpRequest")
|
||||
// .post(form)
|
||||
// .build();
|
||||
// try(Response response = execute(request)) {
|
||||
// System.out.println("login " + response.code() + " - " + response.message());
|
||||
// System.out.println("login " + response.body().string());
|
||||
// }
|
||||
|
||||
boolean cookiesWorked = checkLoginSuccess();
|
||||
if (cookiesWorked) {
|
||||
loggedIn = true;
|
||||
LOG.debug("Logged in with cookies");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// private void loadMainPage() throws IOException {
|
||||
// Request request = new Request.Builder()
|
||||
// .url(LiveJasmin.BASE_URL)
|
||||
// .header("User-Agent", USER_AGENT)
|
||||
// .build();
|
||||
// try(Response response = execute(request)) {
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private Map<String, String> getLoginFormParameters() throws IOException {
|
||||
// long ts = System.currentTimeMillis();
|
||||
// String url = LiveJasmin.BASE_URL + "/en/auth/overlay/get-login-block?_dc="+ts;
|
||||
// Request request = new Request.Builder()
|
||||
// .url(url)
|
||||
// .addHeader("User-Agent", USER_AGENT)
|
||||
// .addHeader("Accept", "application/json, text/javascript, */*")
|
||||
// .addHeader("Accept-Language", "en")
|
||||
// .addHeader("Referer", LiveJasmin.BASE_URL)
|
||||
// .addHeader("X-Requested-With", "XMLHttpRequest")
|
||||
// .build();
|
||||
// try(Response response = execute(request)) {
|
||||
// if(response.isSuccessful()) {
|
||||
// String body = response.body().string();
|
||||
// JSONObject json = new JSONObject(body);
|
||||
// if(json.optBoolean("success")) {
|
||||
// JSONObject data = json.getJSONObject("data");
|
||||
// String content = data.getString("content");
|
||||
// Map<String, String> params = new HashMap<>();
|
||||
// Element form = HtmlParser.getTag(content, "form");
|
||||
// params.put("action", form.attr("action"));
|
||||
// Elements hiddenInputs = HtmlParser.getTags(content, "input[type=hidden]");
|
||||
// for (Element input : hiddenInputs) {
|
||||
// String name = input.attr("name");
|
||||
// String value = input.attr("value");
|
||||
// params.put(name, value);
|
||||
// }
|
||||
// params.put("keepmeloggedin", "1");
|
||||
// params.put("captcha", "");
|
||||
// params.remove("captcha_needed");
|
||||
// return params;
|
||||
// } else {
|
||||
// throw new IOException("Request was not successful: " + body);
|
||||
// }
|
||||
// } else {
|
||||
// throw new HttpException(response.code(), response.message());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
public boolean checkLoginSuccess() throws IOException {
|
||||
OkHttpClient temp = client.newBuilder()
|
||||
.followRedirects(false)
|
||||
.followSslRedirects(false)
|
||||
.build();
|
||||
String url = LiveJasmin.BASE_URL + "/en/free/favourite/get-favourite-list";
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ 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;
|
||||
|
||||
|
@ -26,6 +27,8 @@ import com.squareup.moshi.JsonWriter;
|
|||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.recorder.download.Download;
|
||||
import ctbrec.recorder.download.HlsDownload;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
@ -35,20 +38,72 @@ 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) {
|
||||
try {
|
||||
getMasterPlaylistUrl();
|
||||
online = true;
|
||||
} catch (Exception e) {
|
||||
online = false;
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private 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, getUrl());
|
||||
return State.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||
String masterUrl = getMasterPlaylistUrl();
|
||||
|
@ -86,6 +141,7 @@ public class LiveJasminModel extends AbstractModel {
|
|||
}
|
||||
|
||||
private String getMasterPlaylistUrl() throws IOException {
|
||||
loadModelInfo();
|
||||
String url = site.getBaseUrl() + "/en/stream/hls/free/" + getName();
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
|
@ -99,14 +155,12 @@ public class LiveJasminModel extends AbstractModel {
|
|||
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 hlsStream = data.getJSONObject("hls_stream");
|
||||
return hlsStream.getString("url");
|
||||
} else {
|
||||
LOG.error("Request failed:\n{}", body);
|
||||
throw new IOException("Response was not successfull");
|
||||
throw new IOException("Response was not successful: " + url + "\n" + body);
|
||||
}
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
|
@ -124,7 +178,19 @@ public class LiveJasminModel extends AbstractModel {
|
|||
|
||||
@Override
|
||||
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
||||
return new int[2];
|
||||
if(resolution == null) {
|
||||
if(failFast) {
|
||||
return new int[2];
|
||||
}
|
||||
try {
|
||||
loadModelInfo();
|
||||
} catch (IOException e) {
|
||||
throw new ExecutionException(e);
|
||||
}
|
||||
return resolution;
|
||||
} else {
|
||||
return resolution;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -154,12 +220,11 @@ public class LiveJasminModel extends AbstractModel {
|
|||
@Override
|
||||
public void writeSiteSpecificData(JsonWriter writer) throws IOException {
|
||||
if(id == null) {
|
||||
// TODO make sure the id is set
|
||||
// try {
|
||||
// loadModelInfo();
|
||||
// } catch (IOException e) {
|
||||
// LOG.error("Couldn't load model ID for {}. This can cause problems with saving / loading the model", getName());
|
||||
// }
|
||||
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);
|
||||
}
|
||||
|
@ -167,4 +232,17 @@ public class LiveJasminModel extends AbstractModel {
|
|||
public void setOnline(boolean online) {
|
||||
this.online = online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Download createDownload() {
|
||||
if(Config.getInstance().getSettings().livejasminSession.isEmpty()) {
|
||||
if(Config.isServerMode()) {
|
||||
return new HlsDownload(getSite().getHttpClient());
|
||||
} else {
|
||||
return new LiveJasminMergedHlsDownload(getSite().getHttpClient());
|
||||
}
|
||||
} else {
|
||||
return new LiveJasminWebSocketDownload(getSite().getHttpClient());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
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 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 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);
|
||||
|
||||
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")) {
|
||||
// TODO
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
if(!connectionClosed) {
|
||||
LOG.trace("relay failure {}", model.getName(), t);
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
if(!connectionClosed) {
|
||||
LOG.trace("stream failure {}", model.getName(), t);
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
connectionClosed = true;
|
||||
stream.close(1000, "");
|
||||
relay.close(1000, "");
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue