Fix Flirt4Free recordings

This commit is contained in:
0xb00bface 2022-07-02 16:43:54 +02:00
parent 5cf45a87e3
commit 062a450e69
3 changed files with 102 additions and 76 deletions

View File

@ -14,8 +14,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
@ -74,13 +72,13 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
try {
Flirt4FreeModel model = parseModel(modelJson);
models.add(model);
} catch(Exception e) {
} catch (Exception e) {
LOG.warn("Couldn't parse model {}", modelJson);
}
}
return models.stream()
.filter(filter)
.skip((page - 1) * (long)MODELS_PER_PAGE)
.skip((page - 1) * (long) MODELS_PER_PAGE)
.limit(MODELS_PER_PAGE)
.collect(Collectors.toList());
} else {
@ -112,6 +110,14 @@ public class Flirt4FreeUpdateService extends PaginatedScheduledService {
if (modelData.has("category_id_3")) {
model.getCategories().add(modelData.getString("category_id_3"));
}
if (modelData.has("video_width") && modelData.has("video_aspect_ratio")) {
String[] aspectRatioParts = modelData.getString("video_aspect_ratio").split(":");
double aspectWidth = Integer.parseInt(aspectRatioParts[0]);
double aspectHeight = Integer.parseInt(aspectRatioParts[1]);
int width = Integer.parseInt(modelData.getString("video_width"));
int height = (int) Math.ceil(width * aspectHeight / aspectWidth);
model.setStreamResolution(new int[]{width, height});
}
return model;
}
}

View File

@ -0,0 +1,10 @@
package ctbrec;
public class StringConstants {
public static final String MODEL_ID = "model_id";
public static final String STATUS = "status";
private StringConstants() {
}
}

View File

@ -1,6 +1,21 @@
package ctbrec.sites.flirt4free;
import static ctbrec.io.HttpConstants.*;
import com.iheartradio.m3u8.*;
import com.iheartradio.m3u8.data.MasterPlaylist;
import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.PlaylistData;
import com.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import ctbrec.AbstractModel;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpException;
import ctbrec.recorder.download.StreamSource;
import okhttp3.*;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
@ -12,35 +27,15 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.squareup.moshi.JsonReader;
import com.squareup.moshi.JsonWriter;
import ctbrec.AbstractModel;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpException;
import ctbrec.recorder.download.StreamSource;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
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;
public class Flirt4FreeModel extends AbstractModel {
private static final transient Logger LOG = LoggerFactory.getLogger(Flirt4FreeModel.class);
private static final Logger LOG = LoggerFactory.getLogger(Flirt4FreeModel.class);
private String id;
private String chatHost;
private String chatPort;
@ -72,7 +67,7 @@ public class Flirt4FreeModel extends AbstractModel {
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, "*/*")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
.header(REFERER, getUrl())
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
@ -97,7 +92,7 @@ public class Flirt4FreeModel extends AbstractModel {
return;
}
JSONObject json = new JSONObject(body);
online = Objects.equals(json.optString("status"), "online"); // online is true, even if the model is in private or away
online = Objects.equals(json.optString(STATUS), "online"); // online is true, even if the model is in private or away
updateModelId(json);
if (online) {
try {
@ -110,12 +105,11 @@ public class Flirt4FreeModel extends AbstractModel {
}
private void updateModelId(JSONObject json) {
if (json.has("model_id")) {
Object modelId = json.get("model_id");
if (modelId instanceof Number) {
Number n = (Number) modelId;
if (json.has(MODEL_ID)) {
Object modelId = json.get(MODEL_ID);
if (modelId instanceof Number n) {
if (n.intValue() > 0) {
id = String.valueOf(json.get("model_id"));
id = String.valueOf(json.get(MODEL_ID));
}
}
}
@ -127,7 +121,7 @@ public class Flirt4FreeModel extends AbstractModel {
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, "*/*")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
.header(REFERER, getUrl())
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
@ -135,7 +129,7 @@ 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")) {
if (json.optString(STATUS).equals("success")) {
JSONObject config = json.getJSONObject("config");
JSONObject performer = config.getJSONObject("performer");
setUrl(getSite().getBaseUrl() + "/rooms/" + getName() + '/');
@ -144,7 +138,7 @@ public class Flirt4FreeModel extends AbstractModel {
chatHost = room.getString("host");
chatPort = room.getString("port_to_be");
chatToken = json.getString("token_enc");
String status = room.optString("status");
String status = room.optString(STATUS);
setOnlineState(mapStatus(status));
online = onlineState == State.ONLINE;
JSONObject user = config.getJSONObject("user");
@ -161,16 +155,12 @@ public class Flirt4FreeModel extends AbstractModel {
}
private State mapStatus(String status) {
switch (status) {
case "P":
case "F":
return State.PRIVATE;
case "A":
return State.AWAY;
case "O":
default:
return State.ONLINE;
}
return switch (status) {
case "P", "F" -> State.PRIVATE;
case "A" -> State.AWAY;
case "O" -> State.ONLINE;
default -> State.UNKNOWN;
};
}
@Override
@ -179,7 +169,7 @@ public class Flirt4FreeModel extends AbstractModel {
}
private List<StreamSource> getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException {
MasterPlaylist masterPlaylist = null;
MasterPlaylist masterPlaylist;
try {
if (withWebsocket) {
acquireSlot();
@ -200,7 +190,8 @@ public class Flirt4FreeModel extends AbstractModel {
StreamSource src = new StreamSource();
src.bandwidth = playlist.getStreamInfo().getBandwidth();
src.height = playlist.getStreamInfo().getResolution().height;
src.mediaPlaylistUrl = "https://manifest.vscdns.com/" + playlist.getUri();
HttpUrl masterPlaylistUrl = HttpUrl.parse(streamUrl);
src.mediaPlaylistUrl = "https://" + masterPlaylistUrl.host() + '/' + playlist.getUri();
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
sources.add(src);
}
@ -213,7 +204,7 @@ public class Flirt4FreeModel extends AbstractModel {
Request req = new Request.Builder()
.url(streamUrl)
.header(ACCEPT, "*/*")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
.header(REFERER, getUrl())
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
@ -238,12 +229,12 @@ public class Flirt4FreeModel extends AbstractModel {
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;
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, "en-US,en;q=0.5")
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
.header(REFERER, getUrl())
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
@ -268,14 +259,14 @@ public class Flirt4FreeModel extends AbstractModel {
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")) {
if (data.optString("room_state").equals("0") && data.optString("login_group_id").equals("14")) {
onlineState = Model.State.GROUP;
online = false;
}
try {
resolution[0] = Integer.parseInt(data.getString("stream_width"));
resolution[1] = Integer.parseInt(data.getString("stream_height"));
} catch(Exception e) {
} catch (Exception e) {
LOG.warn("Couldn't determine stream resolution", e);
}
webSocket.close(1000, "");
@ -288,7 +279,9 @@ public class Flirt4FreeModel extends AbstractModel {
synchronized (monitor) {
monitor.notifyAll();
}
response.close();
if (response != null) {
response.close();
}
}
@Override
@ -305,8 +298,25 @@ public class Flirt4FreeModel extends AbstractModel {
if (streamHost == null) {
throw new RuntimeException("Couldn't determine streaming server for model " + getName());
} else {
streamUrl = "https://manifest.vscdns.com/manifest.m3u8.m3u8?key=nil&provider=highwinds&secure=true&host=" + streamHost + "&model_id=" + id;
LOG.debug("Stream URL is {}", streamUrl);
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);
}
}
}
}
@ -339,7 +349,7 @@ public class Flirt4FreeModel extends AbstractModel {
Request req = new Request.Builder()
.url(url)
.header(ACCEPT, "*/*")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_LANGUAGE, ENGLISH.getLanguage())
.header(REFERER, getUrl())
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(REFERER, getUrl())
@ -412,21 +422,21 @@ public class Flirt4FreeModel extends AbstractModel {
@Override
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
if(failFast) {
return resolution;
} else {
if(streamUrl != null) {
try {
List<StreamSource> streamSources = getStreamSources(false);
Collections.sort(streamSources);
StreamSource best = streamSources.get(streamSources.size()-1);
resolution = new int[] {best.width, best.height};
} catch (IOException | ParseException | PlaylistException e) {
throw new ExecutionException("Couldn't determine stream resolution", e);
}
if (!failFast && streamUrl != null && resolution[0] == 0) {
try {
List<StreamSource> streamSources = getStreamSources(true);
Collections.sort(streamSources);
StreamSource best = streamSources.get(streamSources.size() - 1);
resolution = new int[]{best.width, best.height};
} catch (IOException | ParseException | PlaylistException e) {
throw new ExecutionException("Couldn't determine stream resolution", e);
}
return resolution;
}
return resolution;
}
public void setStreamResolution(int[] res) {
this.resolution = res;
}
@Override
@ -521,7 +531,7 @@ public class Flirt4FreeModel extends AbstractModel {
requestThrottle.acquire();
long now = System.currentTimeMillis();
long millisSinceLastRequest = now - lastRequest;
if(millisSinceLastRequest < 500) {
if (millisSinceLastRequest < 500) {
Thread.sleep(500 - millisSinceLastRequest);
}
}