jafea7-ctbrec-v5.3.0-based/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java

295 lines
12 KiB
Java

package ctbrec.sites.flirt4free;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
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.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;
public class Flirt4FreeModel extends AbstractModel {
private static final transient Logger LOG = LoggerFactory.getLogger(Flirt4FreeModel.class);
private String id;
private String chatHost;
private String chatPort;
private String chatToken;
private String streamHost;
private String streamUrl;
int[] resolution = new int[2];
private Object monitor = new Object();
private boolean online = false;
@Override
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
if(ignoreCache) {
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", "en-US,en;q=0.5")
.header("Referer", getUrl())
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest")
.build();
try(Response response = getSite().getHttpClient().execute(request)) {
if(response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string());
online = Objects.equals(json.optString("status"), "online");
if(online) {
try {
loadStreamUrl();
} catch(Exception e) {
online = false;
onlineState = Model.State.OFFLINE;
}
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
return online;
}
private void 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()
.url(url)
.header("Accept", "*/*")
.header("Accept-Language", "en-US,en;q=0.5")
.header("Referer", getUrl())
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest")
.build();
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");
setName(performer.optString("name_seo", "n/a"));
setDisplayName(performer.optString("name", "n/a"));
setUrl(getSite().getBaseUrl() + "/rooms/" + getName() + '/');
JSONObject room = config.getJSONObject("room");
chatHost = room.getString("host");
chatPort = room.getString("port_to_be");
chatToken = json.getString("token_enc");
} else {
LOG.trace("Loading model info failed. Assuming model {} is offline", getName());
online = false;
onlineState = Model.State.OFFLINE;
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
@Override
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
return getStreamSources(true);
}
private List<StreamSource> getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException {
MasterPlaylist masterPlaylist = null;
try {
if(withWebsocket) {
loadStreamUrl();
}
masterPlaylist = getMasterPlaylist();
} catch (InterruptedException e) {
throw new ExecutionException(e);
}
List<StreamSource> sources = new ArrayList<>();
for (PlaylistData playlist : masterPlaylist.getPlaylists()) {
if (playlist.hasStreamInfo()) {
StreamSource src = new StreamSource();
src.bandwidth = playlist.getStreamInfo().getBandwidth();
src.height = playlist.getStreamInfo().getResolution().height;
src.mediaPlaylistUrl = "https://manifest.vscdns.com/" + playlist.getUri();
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
sources.add(src);
}
}
return sources;
}
public MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException, InterruptedException {
LOG.trace("Loading master playlist {}", streamUrl);
Request req = new Request.Builder()
.url(streamUrl)
.header("Accept", "*/*")
.header("Accept-Language", "en-US,en;q=0.5")
.header("Referer", getUrl())
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest")
.build();
try (Response response = getSite().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();
return master;
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private void loadStreamUrl() throws IOException, InterruptedException {
loadModelInfo();
Objects.requireNonNull(chatHost, "chatHost is null");
String h = chatHost.replaceAll("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", "en-US,en;q=0.5")
.header("Referer", getUrl())
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest")
.build();
getSite().getHttpClient().newWebSocket(req, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
LOG.trace("Chat websocket for {} opened", getName());
}
@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");
streamHost = data.getString("stream_host");
online = true;
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);
}
webSocket.close(1000, "");
}
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
LOG.error("Chat websocket for {} failed", getName(), t);
synchronized (monitor) {
monitor.notify();
}
}
@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
LOG.trace("Chat websocket for {} closed {} {}", getName(), code, reason);
synchronized (monitor) {
monitor.notify();
}
}
});
synchronized (monitor) {
monitor.wait(10_000);
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=level3&secure=true&host=" + streamHost + "&model_id=" + id;
}
}
}
@Override
public void invalidateCacheEntries() {
}
@Override
public void receiveTip(Double tokens) throws IOException {
}
@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);
}
}
return resolution;
}
}
@Override
public boolean follow() throws IOException {
return false;
}
@Override
public boolean unfollow() throws IOException {
return false;
}
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 {
writer.name("id").value(id);
}
public void setStreamUrl(String streamUrl) {
this.streamUrl = streamUrl;
}
public void setOnline(boolean b) {
online = b;
}
}