forked from j62/ctbrec
First addition for Cam4
This commit is contained in:
parent
8287d04396
commit
34f443c6a9
|
@ -20,6 +20,7 @@ import ctbrec.Config;
|
||||||
import ctbrec.recorder.LocalRecorder;
|
import ctbrec.recorder.LocalRecorder;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.cam4.Cam4;
|
||||||
import ctbrec.sites.chaturbate.Chaturbate;
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
import ctbrec.sites.mfc.MyFreeCams;
|
import ctbrec.sites.mfc.MyFreeCams;
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ public class HttpServer {
|
||||||
private void createSites() {
|
private void createSites() {
|
||||||
sites.add(new Chaturbate());
|
sites.add(new Chaturbate());
|
||||||
sites.add(new MyFreeCams());
|
sites.add(new MyFreeCams());
|
||||||
|
sites.add(new Cam4());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addShutdownHook() {
|
private void addShutdownHook() {
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
package ctbrec.sites.cam4;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.AbstractSite;
|
||||||
|
import ctbrec.ui.TabProvider;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
|
||||||
|
public class Cam4 extends AbstractSite {
|
||||||
|
|
||||||
|
public static final String BASE_URI = "https://www.cam4.com";
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
private Recorder recorder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Cam4";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return BASE_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAffiliateLink() {
|
||||||
|
return getBaseUrl() + "/?referrerId=1514a80d87b5effb456cca02f6743aa1";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRecorder(Recorder recorder) {
|
||||||
|
this.recorder = recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TabProvider getTabProvider() {
|
||||||
|
return new Cam4TabProvider(this, recorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Model createModel(String name) {
|
||||||
|
Cam4Model m = new Cam4Model();
|
||||||
|
m.setSite(this);
|
||||||
|
m.setName(name);
|
||||||
|
m.setUrl(getBaseUrl() + '/' + name + '/');
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getTokenBalance() throws IOException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBuyTokensLink() {
|
||||||
|
return getAffiliateLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void login() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClient getHttpClient() {
|
||||||
|
if(httpClient == null) {
|
||||||
|
httpClient = new HttpClient() {
|
||||||
|
@Override
|
||||||
|
public boolean login() throws IOException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
getHttpClient().shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws IOException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsTips() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFollow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSiteForModel(Model m) {
|
||||||
|
return m instanceof Cam4Model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Node getConfigurationGui() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean credentialsAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
package ctbrec.sites.cam4;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
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.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 ctbrec.AbstractModel;
|
||||||
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class Cam4Model extends AbstractModel {
|
||||||
|
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4Model.class);
|
||||||
|
private Cam4 site;
|
||||||
|
private String playlistUrl;
|
||||||
|
private String onlineState = "offline";
|
||||||
|
private int[] resolution = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
|
||||||
|
return isOnline(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
||||||
|
if(ignoreCache || onlineState == null) {
|
||||||
|
loadModelDetails();
|
||||||
|
}
|
||||||
|
return Objects.equals("NORMAL", onlineState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadModelDetails() throws IOException {
|
||||||
|
String url = "https://www.cam4.de.com/getBroadcasting?usernames=" + getName();
|
||||||
|
LOG.debug("Loading model details {}", url);
|
||||||
|
Request req = new Request.Builder().url(url).build();
|
||||||
|
Response response = site.getHttpClient().execute(req);
|
||||||
|
if(response.isSuccessful()) {
|
||||||
|
JSONArray json = new JSONArray(response.body().string());
|
||||||
|
JSONObject details = json.getJSONObject(0);
|
||||||
|
onlineState = details.getString("showType");
|
||||||
|
playlistUrl = details.getString("hlsPreviewUrl");
|
||||||
|
if(details.has("resolution")) {
|
||||||
|
String res = details.getString("resolution");
|
||||||
|
String[] tokens = res.split(":");
|
||||||
|
resolution = new int[] {Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IOException io = new IOException(response.code() + " " + response.message());
|
||||||
|
response.close();
|
||||||
|
throw io;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getOnlineState(boolean failFast) throws IOException, ExecutionException {
|
||||||
|
return onlineState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPlaylistUrl() throws IOException {
|
||||||
|
if(playlistUrl == null) {
|
||||||
|
loadModelDetails();
|
||||||
|
}
|
||||||
|
return playlistUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException {
|
||||||
|
MasterPlaylist masterPlaylist = getMasterPlaylist();
|
||||||
|
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;
|
||||||
|
String masterUrl = getPlaylistUrl();
|
||||||
|
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
|
||||||
|
String segmentUri = baseUrl + playlist.getUri();
|
||||||
|
src.mediaPlaylistUrl = segmentUri;
|
||||||
|
LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
|
||||||
|
sources.add(src);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
|
||||||
|
LOG.trace("Loading master playlist {}", getPlaylistUrl());
|
||||||
|
Request req = new Request.Builder().url(getPlaylistUrl()).build();
|
||||||
|
Response response = site.getHttpClient().execute(req);
|
||||||
|
try {
|
||||||
|
InputStream inputStream = response.body().byteStream();
|
||||||
|
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
|
||||||
|
Playlist playlist = parser.parse();
|
||||||
|
MasterPlaylist master = playlist.getMasterPlaylist();
|
||||||
|
return master;
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCacheEntries() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receiveTip(int tokens) throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
||||||
|
if(resolution == null) {
|
||||||
|
if(failFast) {
|
||||||
|
return new int[2];
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
loadModelDetails();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ExecutionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resolution;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean follow() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unfollow() throws IOException {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSite(Site site) {
|
||||||
|
if(site instanceof Cam4) {
|
||||||
|
this.site = (Cam4) site;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Site has to be an instance of Cam4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Site getSite() {
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlaylistUrl(String playlistUrl) {
|
||||||
|
this.playlistUrl = playlistUrl;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package ctbrec.sites.cam4;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.ui.TabProvider;
|
||||||
|
import ctbrec.ui.ThumbOverviewTab;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
|
||||||
|
public class Cam4TabProvider extends TabProvider {
|
||||||
|
|
||||||
|
private Cam4 cam4;
|
||||||
|
private Recorder recorder;
|
||||||
|
|
||||||
|
public Cam4TabProvider(Cam4 cam4, Recorder recorder) {
|
||||||
|
this.cam4 = cam4;
|
||||||
|
this.recorder = recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Tab> getTabs(Scene scene) {
|
||||||
|
List<Tab> tabs = new ArrayList<>();
|
||||||
|
String url = cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS";
|
||||||
|
Cam4UpdateService female = new Cam4UpdateService(url, false, cam4);
|
||||||
|
ThumbOverviewTab tab = new ThumbOverviewTab("Female", female, cam4);
|
||||||
|
tab.setRecorder(recorder);
|
||||||
|
tabs.add(tab);
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package ctbrec.sites.cam4;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.ui.HtmlParser;
|
||||||
|
import ctbrec.ui.PaginatedScheduledService;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class Cam4UpdateService extends PaginatedScheduledService {
|
||||||
|
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(Cam4UpdateService.class);
|
||||||
|
private String url;
|
||||||
|
private Cam4 site;
|
||||||
|
private boolean loginRequired;
|
||||||
|
|
||||||
|
public Cam4UpdateService(String url, boolean loginRequired, Cam4 site) {
|
||||||
|
this.site = site;
|
||||||
|
this.url = url;
|
||||||
|
this.loginRequired = loginRequired;
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable r) {
|
||||||
|
Thread t = new Thread(r);
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.setName("ThumbOverviewTab UpdateService");
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setExecutor(executor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<List<Model>> createTask() {
|
||||||
|
return new Task<List<Model>>() {
|
||||||
|
@Override
|
||||||
|
public List<Model> call() throws IOException {
|
||||||
|
if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().username)) { // FIXME change to cam4 username
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
String url = Cam4UpdateService.this.url + "&page=" + page;
|
||||||
|
LOG.debug("Fetching page {}", url);
|
||||||
|
Request request = new Request.Builder().url(url).build();
|
||||||
|
Response response = site.getHttpClient().execute(request, loginRequired);
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
JSONObject json = new JSONObject(response.body().string());
|
||||||
|
String html = json.getString("html");
|
||||||
|
Elements profilesBoxes = HtmlParser.getTags(html, "div[class~=profileDataBox]");
|
||||||
|
List<Model> models = new ArrayList<>(profilesBoxes.size());
|
||||||
|
for (Element profileBox : profilesBoxes) {
|
||||||
|
String boxHtml = profileBox.html();
|
||||||
|
Element profileLink = HtmlParser.getTag(boxHtml, "a.profile-preview");
|
||||||
|
String path = profileLink.attr("href");
|
||||||
|
String slug = path.substring(1);
|
||||||
|
Cam4Model model = (Cam4Model) site.createModel(slug);
|
||||||
|
String playlistUrl = profileLink.attr("data-hls-preview-url");
|
||||||
|
model.setPlaylistUrl(playlistUrl);
|
||||||
|
model.setPreview(HtmlParser.getTag(boxHtml, "a img").attr("data-src"));
|
||||||
|
model.setDescription(parseDesription(boxHtml));
|
||||||
|
//model.setOnlineState(parseOnlineState(boxHtml));
|
||||||
|
models.add(model);
|
||||||
|
}
|
||||||
|
response.close();
|
||||||
|
return models;
|
||||||
|
} else {
|
||||||
|
int code = response.code();
|
||||||
|
response.close();
|
||||||
|
throw new IOException("HTTP status " + code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseDesription(String boxHtml) {
|
||||||
|
try {
|
||||||
|
return HtmlParser.getText(boxHtml, "div[class~=statusMsg2]");
|
||||||
|
} catch(Exception e) {
|
||||||
|
LOG.trace("Couldn't parse description for room");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import ctbrec.recorder.LocalRecorder;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.recorder.RemoteRecorder;
|
import ctbrec.recorder.RemoteRecorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.cam4.Cam4;
|
||||||
import ctbrec.sites.chaturbate.Chaturbate;
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
import ctbrec.sites.mfc.MyFreeCams;
|
import ctbrec.sites.mfc.MyFreeCams;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
@ -60,6 +61,7 @@ public class CamrecApplication extends Application {
|
||||||
public void start(Stage primaryStage) throws Exception {
|
public void start(Stage primaryStage) throws Exception {
|
||||||
sites.add(new Chaturbate());
|
sites.add(new Chaturbate());
|
||||||
sites.add(new MyFreeCams());
|
sites.add(new MyFreeCams());
|
||||||
|
sites.add(new Cam4());
|
||||||
loadConfig();
|
loadConfig();
|
||||||
createHttpClient();
|
createHttpClient();
|
||||||
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
|
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
|
||||||
|
|
Loading…
Reference in New Issue