Added support for new "secure" stream URLs format for Flirt4Free
This commit is contained in:
parent
1d88bca3d5
commit
b39fc69299
|
@ -15,6 +15,7 @@
|
|||
* Cam4: Fixed stream URLs search. Slightly increased chances to find good one.
|
||||
* Camsoda: Added "Voyeur" tab
|
||||
* Chaturbate: Added "Gaming" tab
|
||||
* Flirt4Free: Added support for new "secure" stream URLs format.
|
||||
* Streamate:
|
||||
- Fixed "Couldn't load model ID" error while adding models by URL or by
|
||||
nickname
|
||||
|
|
|
@ -1,17 +1,5 @@
|
|||
package ctbrec.ui.sites.flirt4free;
|
||||
|
||||
import static ctbrec.io.HttpClient.*;
|
||||
import static ctbrec.io.HttpConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.io.HtmlParser;
|
||||
|
@ -22,9 +10,20 @@ import ctbrec.ui.SiteUiFactory;
|
|||
import ctbrec.ui.tabs.PaginatedScheduledService;
|
||||
import javafx.concurrent.Task;
|
||||
import okhttp3.Request;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.select.Elements;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static ctbrec.io.HttpClient.gunzipBody;
|
||||
import static ctbrec.io.HttpConstants.*;
|
||||
|
||||
public class Flirt4FreeFavoritesUpdateService extends PaginatedScheduledService {
|
||||
private Flirt4Free flirt4free;
|
||||
private final Flirt4Free flirt4free;
|
||||
|
||||
public Flirt4FreeFavoritesUpdateService(Flirt4Free flirt4free) {
|
||||
this.flirt4free = flirt4free;
|
||||
|
@ -32,7 +31,7 @@ public class Flirt4FreeFavoritesUpdateService extends PaginatedScheduledService
|
|||
|
||||
@Override
|
||||
protected Task<List<Model>> createTask() {
|
||||
return new Task<List<Model>>() {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
public List<Model> call() throws IOException {
|
||||
return loadModelList();
|
||||
|
@ -65,7 +64,6 @@ public class Flirt4FreeFavoritesUpdateService extends PaginatedScheduledService
|
|||
model.setDisplayName(img.attr("alt"));
|
||||
model.setPreview(img.attr("src"));
|
||||
model.setDescription("");
|
||||
model.setOnline(modelHtml.contains("I'm Online"));
|
||||
try {
|
||||
model.setOnlineState(model.isOnline() ? Model.State.ONLINE : Model.State.OFFLINE);
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
@ -18,7 +18,6 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static ctbrec.io.HttpClient.gunzipBody;
|
||||
import static ctbrec.io.HttpConstants.*;
|
||||
|
@ -62,7 +61,7 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
|
|||
|
||||
private List<Model> parseResponse(String body) throws IOException {
|
||||
List<Flirt4FreeModel> models = new ArrayList<>();
|
||||
var m = Pattern.compile("window\\.__homePageData__ = (\\{.*\\})", Pattern.DOTALL).matcher(body);
|
||||
var m = Pattern.compile("window\\.__homePageData__ = (\\{.*})", Pattern.DOTALL).matcher(body);
|
||||
if (m.find()) {
|
||||
var data = new JSONObject(m.group(1));
|
||||
var modelData = data.getJSONArray("models");
|
||||
|
@ -80,7 +79,8 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
|
|||
.filter(filter)
|
||||
.skip((page - 1) * (long) MODELS_PER_PAGE)
|
||||
.limit(MODELS_PER_PAGE)
|
||||
.collect(Collectors.toList());
|
||||
.map(Model.class::cast)
|
||||
.toList();
|
||||
} else {
|
||||
throw new IOException("Pattern didn't match model JSON data");
|
||||
}
|
||||
|
@ -100,7 +100,6 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
|
|||
model.setStreamUrl(streamUrl);
|
||||
model.setPreview("https://live-screencaps.vscdns.com/" + modelId + "-desktop.jpg");
|
||||
model.setOnlineState(ctbrec.Model.State.ONLINE);
|
||||
model.setOnline(true);
|
||||
if (modelData.has("category_id")) {
|
||||
model.getCategories().add(modelData.getString("category_id"));
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ public class HttpConstants {
|
|||
public static final String CONTENT_TYPE = "Content-Type";
|
||||
public static final String COOKIE = "Cookie";
|
||||
public static final String FORM_ENCODED = "application/x-www-form-urlencoded; charset=UTF-8";
|
||||
public static final String HTTPS = "https";
|
||||
public static final String KEEP_ALIVE = "keep-alive";
|
||||
public static final String MIMETYPE_APPLICATION_JSON = "application/json";
|
||||
public static final String MIMETYPE_IMAGE_JPG = "image/jpeg";
|
||||
|
|
|
@ -8,125 +8,125 @@ import com.iheartradio.m3u8.data.StreamInfo;
|
|||
import ctbrec.AbstractModel;
|
||||
import ctbrec.Config;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.StringUtil;
|
||||
import ctbrec.io.HttpException;
|
||||
import ctbrec.recorder.download.StreamSource;
|
||||
import ctbrec.sites.ModelOfflineException;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.text.MessageFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static ctbrec.StringConstants.MODEL_ID;
|
||||
import static ctbrec.StringConstants.STATUS;
|
||||
import static ctbrec.io.HttpConstants.*;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Locale.ENGLISH;
|
||||
|
||||
@Slf4j
|
||||
public class Flirt4FreeModel extends AbstractModel {
|
||||
private static final String KEY_CONFIG = "config";
|
||||
private static final String KEY_STATUS = "status";
|
||||
|
||||
@Setter
|
||||
private String id;
|
||||
private String chatHost;
|
||||
private String chatPort;
|
||||
private String chatToken;
|
||||
private String streamHost;
|
||||
@Setter
|
||||
private String streamUrl;
|
||||
int[] resolution = new int[2];
|
||||
private final transient Object monitor = new Object();
|
||||
@Getter
|
||||
private final transient List<String> categories = new LinkedList<>();
|
||||
@Setter
|
||||
private boolean online = false;
|
||||
private boolean isInteractiveShow = false;
|
||||
private boolean isNew = false;
|
||||
private String userIdt = "";
|
||||
private String userIp = "0.0.0.0";
|
||||
|
||||
private static final Semaphore requestThrottle = new Semaphore(2, true);
|
||||
private static volatile long lastRequest = 0;
|
||||
private long lastOnlineRequest = 0;
|
||||
|
||||
private transient JSONObject modelInfo;
|
||||
private transient JSONObject stateInfo;
|
||||
private transient Instant lastInfoRequest = Instant.EPOCH;
|
||||
private transient Instant lastStateRequest = Instant.EPOCH;
|
||||
|
||||
@Override
|
||||
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
||||
long now = System.currentTimeMillis();
|
||||
long timeSinceLastCheck = now - lastOnlineRequest;
|
||||
if (ignoreCache && timeSinceLastCheck > TimeUnit.MINUTES.toMillis(1)) {
|
||||
String url = "https://ws.vs3.com/rooms/check-model-status.php?model_name=" + getName();
|
||||
acquireSlot();
|
||||
try {
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||
.header(REFERER, getUrl())
|
||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||
.build();
|
||||
try (Response response = getSite().getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
parseOnlineState(response.body().string());
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lastOnlineRequest = System.currentTimeMillis();
|
||||
releaseSlot();
|
||||
}
|
||||
if (ignoreCache) {
|
||||
JSONObject info = getStateInfo();
|
||||
parseOnlineState(info);
|
||||
}
|
||||
return online;
|
||||
return onlineState == Model.State.ONLINE;
|
||||
}
|
||||
|
||||
private void parseOnlineState(String body) {
|
||||
if (body.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
JSONObject json = new JSONObject(body);
|
||||
if (Objects.equals(json.optString("status"), "failed")) {
|
||||
if (Objects.equals(json.optString("message"), "Model is inactive")) {
|
||||
private void parseOnlineState(JSONObject json) throws IOException {
|
||||
if (json.optString(KEY_STATUS).equals("failed")) {
|
||||
if (json.optString("message").equals("Model is inactive")) {
|
||||
log.debug("Model inactive or deleted: {}", getName());
|
||||
setMarkedForLaterRecording(true);
|
||||
}
|
||||
online = false;
|
||||
onlineState = Model.State.OFFLINE;
|
||||
return;
|
||||
}
|
||||
online = Objects.equals(json.optString(STATUS), "online"); // online is true, even if the model is in private or away
|
||||
updateModelId(json);
|
||||
if (online) {
|
||||
try {
|
||||
loadModelInfo();
|
||||
} catch (Exception e) {
|
||||
online = false;
|
||||
onlineState = Model.State.OFFLINE;
|
||||
int modelId = json.optInt("model_id");
|
||||
if (modelId > 0) {
|
||||
id = String.valueOf(modelId);
|
||||
}
|
||||
if (json.optString(KEY_STATUS).equals("online") && modelId > 0) {
|
||||
getModelInfo();
|
||||
} else {
|
||||
onlineState = Model.State.OFFLINE;
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject getStateInfo() throws IOException {
|
||||
if (Objects.nonNull(stateInfo) && Duration.between(lastStateRequest, Instant.now()).getSeconds() < 5) {
|
||||
return stateInfo;
|
||||
}
|
||||
lastStateRequest = Instant.now();
|
||||
stateInfo = loadStateInfo();
|
||||
return stateInfo;
|
||||
}
|
||||
|
||||
private JSONObject loadStateInfo() throws IOException {
|
||||
String url = HTTPS + "://ws.vs3.com/rooms/check-model-status.php?model_name=" + getName();
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||
.header(REFERER, getUrl())
|
||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||
.build();
|
||||
try (Response response = getSite().getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
JSONObject json = new JSONObject(response.body().string());
|
||||
return json;
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateModelId(JSONObject json) {
|
||||
if (json.has(MODEL_ID)) {
|
||||
Object modelId = json.get(MODEL_ID);
|
||||
if (modelId instanceof Number n && n.intValue() > 0) {
|
||||
id = String.valueOf(json.get(MODEL_ID));
|
||||
}
|
||||
private JSONObject getModelInfo() throws IOException {
|
||||
if (Objects.nonNull(modelInfo) && Duration.between(lastInfoRequest, Instant.now()).getSeconds() < 5) {
|
||||
return modelInfo;
|
||||
}
|
||||
lastInfoRequest = Instant.now();
|
||||
modelInfo = loadModelInfo();
|
||||
return modelInfo;
|
||||
}
|
||||
|
||||
private void loadModelInfo() throws IOException {
|
||||
private JSONObject loadModelInfo() throws IOException {
|
||||
String url = getSite().getBaseUrl() + "/webservices/chat-room-interface.php?a=login_room&model_id=" + id;
|
||||
log.trace("Loading url {}", url);
|
||||
Request request = new Request.Builder()
|
||||
|
@ -140,25 +140,16 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
try (Response response = getSite().getHttpClient().execute(request)) {
|
||||
if (response.isSuccessful()) {
|
||||
JSONObject json = new JSONObject(response.body().string());
|
||||
if (json.optString(STATUS).equals("success")) {
|
||||
JSONObject config = json.getJSONObject("config");
|
||||
JSONObject performer = config.getJSONObject("performer");
|
||||
setUrl(getSite().getBaseUrl() + "/rooms/" + getName() + '/');
|
||||
setDisplayName(performer.optString("name", getName()));
|
||||
JSONObject room = config.getJSONObject("room");
|
||||
chatHost = room.getString("host");
|
||||
chatPort = room.getString("port_to_be");
|
||||
chatToken = json.getString("token_enc");
|
||||
String status = room.optString(STATUS);
|
||||
setOnlineState(mapStatus(status));
|
||||
online = onlineState == State.ONLINE;
|
||||
JSONObject user = config.getJSONObject("user");
|
||||
userIp = user.getString("ip");
|
||||
if (json.optString(KEY_STATUS).equals("success")) {
|
||||
JSONObject config = json.getJSONObject(KEY_CONFIG);
|
||||
setDisplayName(config.getJSONObject("performer").optString("name", getName()));
|
||||
userIp = config.getJSONObject("user").getString("ip");
|
||||
onlineState = mapStatus(config.getJSONObject("room").optString(KEY_STATUS));
|
||||
} else {
|
||||
log.trace("Loading model info failed. Assuming model {} is offline", getName());
|
||||
online = false;
|
||||
onlineState = Model.State.OFFLINE;
|
||||
}
|
||||
return json;
|
||||
} else {
|
||||
throw new HttpException(response.code(), response.message());
|
||||
}
|
||||
|
@ -174,6 +165,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||
MasterPlaylist masterPlaylist;
|
||||
try {
|
||||
|
@ -197,7 +189,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
src.setHeight((info.hasResolution()) ? info.getResolution().height : 0);
|
||||
src.setWidth((info.hasResolution()) ? info.getResolution().width : 0);
|
||||
HttpUrl masterPlaylistUrl = HttpUrl.parse(streamUrl);
|
||||
src.setMediaPlaylistUrl("https://" + masterPlaylistUrl.host() + '/' + playlist.getUri());
|
||||
src.setMediaPlaylistUrl(HTTPS + "://" + masterPlaylistUrl.host() + '/' + playlist.getUri());
|
||||
log.trace("Media playlist {}", src.getMediaPlaylistUrl());
|
||||
sources.add(src);
|
||||
}
|
||||
|
@ -231,105 +223,102 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
}
|
||||
}
|
||||
|
||||
private void loadStreamUrl() throws IOException, InterruptedException {
|
||||
loadModelInfo();
|
||||
Objects.requireNonNull(chatHost, "chatHost is null");
|
||||
String h = chatHost.replace("chat", "chat-vip");
|
||||
String url = "https://" + h + "/chat?token=" + URLEncoder.encode(chatToken, UTF_8) + "&port_to_be=" + chatPort;
|
||||
log.trace("Opening chat websocket {}", url);
|
||||
Request req = new Request.Builder()
|
||||
.url(url)
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||
.header(REFERER, getUrl())
|
||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||
.build();
|
||||
private void loadStreamUrl() throws InterruptedException {
|
||||
try {
|
||||
JSONObject info = getModelInfo();
|
||||
streamUrl = "";
|
||||
|
||||
getSite().getHttpClient().newWebSocket(req, new WebSocketListener() {
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
log.trace("Chat websocket for {} opened", getName());
|
||||
}
|
||||
String chatHost = info.getJSONObject(KEY_CONFIG).getJSONObject("room").getString("host").replace("chat", "chat-vip");
|
||||
String chatPort = info.getJSONObject(KEY_CONFIG).getJSONObject("room").getString("port");
|
||||
String chatToken = info.getString("token_enc");
|
||||
String url = HTTPS + "://" + chatHost + "/chat?token=" + URLEncoder.encode(chatToken, UTF_8) + "&port_to_be=" + chatPort;
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
log.trace("Chat wbesocket for {}: {}", getName(), text);
|
||||
JSONObject json = new JSONObject(text);
|
||||
if (json.optString("command").equals("8011")) {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
log.trace("stream info:\n{}", data.toString(2));
|
||||
streamHost = data.getString("stream_host");
|
||||
online = true;
|
||||
isInteractiveShow = data.optString("devices").equals("1");
|
||||
String roomState = data.optString("room_state");
|
||||
onlineState = mapStatus(roomState);
|
||||
online = onlineState == State.ONLINE;
|
||||
if (data.optString("room_state").equals("0") && data.optString("login_group_id").equals("14")) {
|
||||
onlineState = Model.State.GROUP;
|
||||
online = false;
|
||||
log.trace("Opening chat websocket {}", url);
|
||||
Request req = new Request.Builder()
|
||||
.url(url)
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||
.header(REFERER, getUrl())
|
||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
|
||||
.build();
|
||||
|
||||
getSite().getHttpClient().newWebSocket(req, new WebSocketListener() {
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
log.trace("Chat websocket for {}: {}", getName(), text);
|
||||
JSONObject json = new JSONObject(text);
|
||||
if (json.optString("command").equals("8011")) {
|
||||
try {
|
||||
JSONObject data = json.getJSONObject("data");
|
||||
JSONObject stream = data.getJSONObject("video_info")
|
||||
.getJSONObject("hls")
|
||||
.getJSONArray("providers")
|
||||
.getJSONObject(0);
|
||||
String streamHost = stream.optString("stream_host", "hls.vscdns.com");
|
||||
String streamName = stream.optString("stream_name", "manifest.m3u8");
|
||||
String streamKey = stream.optString("stream_key", "&");
|
||||
if (!streamKey.startsWith("&")) {
|
||||
streamUrl = MessageFormat.format(HTTPS + "://{0}/{1}?key={2}", streamHost, streamName, streamKey);
|
||||
}
|
||||
onlineState = mapStatus(data.optString("room_state"));
|
||||
resolution[0] = Integer.parseInt(json.optString("stream_width", "0"));
|
||||
resolution[1] = Integer.parseInt(json.optString("stream_height", "0"));
|
||||
} catch (Exception e) {
|
||||
log.trace("Can not get stream info from WS", e);
|
||||
} finally {
|
||||
webSocket.close(1000, "");
|
||||
}
|
||||
}
|
||||
try {
|
||||
resolution[0] = Integer.parseInt(data.getString("stream_width"));
|
||||
resolution[1] = Integer.parseInt(data.getString("stream_height"));
|
||||
} catch (Exception e) {
|
||||
log.warn("Couldn't determine stream resolution", e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
log.trace("Chat websocket for {} failed", getName(), t);
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
synchronized (monitor) {
|
||||
monitor.notifyAll();
|
||||
}
|
||||
webSocket.close(1000, "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
log.trace("Chat websocket for {} closed {} {}", getName(), code, reason);
|
||||
synchronized (monitor) {
|
||||
monitor.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (monitor) {
|
||||
monitor.wait(10_000);
|
||||
if (StringUtil.isBlank(streamUrl)) {
|
||||
if (isOnline(false)) {
|
||||
String cdn = info.getJSONObject(KEY_CONFIG).getJSONObject("env").getString("cdn").split("\\.")[0].replace(HTTPS + "://", "");
|
||||
streamUrl = MessageFormat.format(HTTPS + "://hls.vscdns.com/manifest.m3u8?key=nil&provider={0}&model_id={1}", cdn, id);
|
||||
} else {
|
||||
throw new ModelOfflineException(this);
|
||||
}
|
||||
}
|
||||
log.debug("Stream URL is {}", streamUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
log.error("Chat websocket for {} failed", getName(), t);
|
||||
synchronized (monitor) {
|
||||
monitor.notifyAll();
|
||||
}
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
log.trace("Chat websocket for {} closed {} {}", getName(), code, reason);
|
||||
synchronized (monitor) {
|
||||
monitor.notifyAll();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
synchronized (monitor) {
|
||||
monitor.wait(10_000);
|
||||
if (streamHost == null) {
|
||||
throw new RuntimeException("Couldn't determine streaming server for model " + getName());
|
||||
} else {
|
||||
url = getSite().getBaseUrl() + "/ws/chat/get-stream-urls.php?"
|
||||
+ "model_id=" + id
|
||||
+ "&video_host=" + streamHost
|
||||
+ "&t=" + System.currentTimeMillis();
|
||||
log.debug("Loading master playlist information: {}", url);
|
||||
req = new Request.Builder()
|
||||
.url(url)
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
|
||||
.header(REFERER, getUrl())
|
||||
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
|
||||
.header(REFERER, getUrl())
|
||||
.build();
|
||||
try (Response response = getSite().getHttpClient().execute(req)) {
|
||||
JSONObject json = new JSONObject(Objects.requireNonNull(response.body(), "HTTP response body is null").string());
|
||||
JSONArray hls = json.getJSONObject("data").getJSONArray("hls");
|
||||
streamUrl = "https:" + hls.getJSONObject(0).getString("url");
|
||||
log.debug("Stream URL is {}", streamUrl);
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("Interrupted while loading stream url");
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Couldn't determine stream URL for model " + getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateCacheEntries() {
|
||||
// nothing to do here
|
||||
stateInfo = null;
|
||||
modelInfo = null;
|
||||
lastInfoRequest = Instant.EPOCH;
|
||||
lastStateRequest = Instant.EPOCH;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -340,10 +329,10 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
fetchStreamUrl();
|
||||
|
||||
// send the tip
|
||||
int giftId = isInteractiveShow ? 775 : 171;
|
||||
int giftId = 171;
|
||||
int amount = tokens.intValue();
|
||||
log.debug("Sending tip of {} to {}", amount, getName());
|
||||
String url = "https://ws.vs3.com/rooms/send-tip.php?" +
|
||||
String url = HTTPS + "://ws.vs3.com/rooms/send-tip.php?" +
|
||||
"gift_id=" + giftId +
|
||||
"&num_credits=" + amount +
|
||||
"&userId=" + getUserIdt() +
|
||||
|
@ -432,7 +421,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
try {
|
||||
List<StreamSource> streamSources = getStreamSources();
|
||||
Collections.sort(streamSources);
|
||||
StreamSource best = streamSources.get(streamSources.size() - 1);
|
||||
StreamSource best = streamSources.getLast();
|
||||
resolution = new int[]{best.getHeight(), best.getHeight()};
|
||||
} catch (IOException | ParseException | PlaylistException e) {
|
||||
throw new ExecutionException("Couldn't determine stream resolution", e);
|
||||
|
@ -451,7 +440,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
return changeFavoriteStatus(true);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Couldn't change follow status for model " + getName(), e);
|
||||
throw new IOException("Couldn't follow model " + getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -462,9 +451,9 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
return changeFavoriteStatus(false);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IOException("Couldn't change follow status for model " + getName(), e);
|
||||
throw new IOException("Couldn't unfollow model " + getName(), e);
|
||||
} catch (ExecutionException e) {
|
||||
throw new IOException("Couldn't change follow status for model " + getName(), e);
|
||||
throw new IOException("Couldn't unfollow model " + getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,7 +461,7 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
getSite().login();
|
||||
acquireSlot();
|
||||
try {
|
||||
loadModelInfo();
|
||||
getModelInfo();
|
||||
} finally {
|
||||
releaseSlot();
|
||||
}
|
||||
|
@ -528,6 +517,12 @@ public class Flirt4FreeModel extends AbstractModel {
|
|||
return fixed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Instant getLastSeen() {
|
||||
Instant lastSeen = super.getLastSeen();
|
||||
return (lastSeen.equals(Instant.EPOCH)) ? getAddedTimestamp() : lastSeen;
|
||||
}
|
||||
|
||||
private void acquireSlot() throws InterruptedException {
|
||||
requestThrottle.acquire();
|
||||
long now = System.currentTimeMillis();
|
||||
|
|
Loading…
Reference in New Issue