Abstract more stuff in the site packages
This commit is contained in:
parent
362d90b29b
commit
387661cfdf
|
@ -30,5 +30,6 @@ public interface Model {
|
||||||
public void invalidateCacheEntries();
|
public void invalidateCacheEntries();
|
||||||
public void receiveTip(int tokens) throws IOException;
|
public void receiveTip(int tokens) throws IOException;
|
||||||
public int[] getStreamResolution(boolean failFast) throws ExecutionException;
|
public int[] getStreamResolution(boolean failFast) throws ExecutionException;
|
||||||
|
public boolean follow() throws IOException;
|
||||||
|
public boolean unfollow() throws IOException;
|
||||||
}
|
}
|
|
@ -28,7 +28,6 @@ public class Settings {
|
||||||
public boolean determineResolution = false;
|
public boolean determineResolution = false;
|
||||||
public boolean requireAuthentication = false;
|
public boolean requireAuthentication = false;
|
||||||
public boolean chooseStreamQuality = false;
|
public boolean chooseStreamQuality = false;
|
||||||
public boolean recordFollowed = false;
|
|
||||||
public byte[] key = null;
|
public byte[] key = null;
|
||||||
public ProxyType proxyType = ProxyType.DIRECT;
|
public ProxyType proxyType = ProxyType.DIRECT;
|
||||||
public String proxyHost;
|
public String proxyHost;
|
||||||
|
|
|
@ -1,44 +1,24 @@
|
||||||
package ctbrec.io;
|
package ctbrec.io;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Settings.ProxyType;
|
import ctbrec.Settings.ProxyType;
|
||||||
import ctbrec.ui.CamrecApplication;
|
|
||||||
import ctbrec.ui.CookieJarImpl;
|
import ctbrec.ui.CookieJarImpl;
|
||||||
import ctbrec.ui.HtmlParser;
|
|
||||||
import okhttp3.ConnectionPool;
|
import okhttp3.ConnectionPool;
|
||||||
import okhttp3.Cookie;
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class HttpClient {
|
public abstract class HttpClient {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(HttpClient.class);
|
protected OkHttpClient client;
|
||||||
private static HttpClient instance = new HttpClient();
|
protected CookieJarImpl cookieJar = new CookieJarImpl();
|
||||||
|
protected boolean loggedIn = false;
|
||||||
|
protected int loginTries = 0;
|
||||||
|
|
||||||
private OkHttpClient client;
|
protected HttpClient() {
|
||||||
private CookieJarImpl cookieJar = new CookieJarImpl();
|
reconfigure();
|
||||||
private boolean loggedIn = false;
|
|
||||||
private int loginTries = 0;
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
private HttpClient() {
|
|
||||||
loadProxySettings();
|
|
||||||
client = new OkHttpClient.Builder()
|
|
||||||
.cookieJar(cookieJar)
|
|
||||||
.connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
.readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
.connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES))
|
|
||||||
//.addInterceptor(new LoggingInterceptor())
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadProxySettings() {
|
private void loadProxySettings() {
|
||||||
|
@ -89,24 +69,11 @@ public class HttpClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HttpClient getInstance() {
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response execute(Request request) throws IOException {
|
public Response execute(Request request) throws IOException {
|
||||||
Response resp = execute(request, false);
|
Response resp = execute(request, false);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void extractCsrfToken(Request request) {
|
|
||||||
try {
|
|
||||||
Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken");
|
|
||||||
token = csrfToken.value();
|
|
||||||
} catch(NoSuchElementException e) {
|
|
||||||
LOG.trace("CSRF token not found in cookies");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Response execute(Request req, boolean requiresLogin) throws IOException {
|
public Response execute(Request req, boolean requiresLogin) throws IOException {
|
||||||
if(requiresLogin && !loggedIn) {
|
if(requiresLogin && !loggedIn) {
|
||||||
boolean loginSuccessful = login();
|
boolean loginSuccessful = login();
|
||||||
|
@ -115,64 +82,20 @@ public class HttpClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Response resp = client.newCall(req).execute();
|
Response resp = client.newCall(req).execute();
|
||||||
extractCsrfToken(req);
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean login() throws IOException {
|
public abstract boolean login() throws IOException;
|
||||||
try {
|
|
||||||
Request login = new Request.Builder()
|
|
||||||
.url(CamrecApplication.BASE_URI + "/auth/login/")
|
|
||||||
.build();
|
|
||||||
Response response = client.newCall(login).execute();
|
|
||||||
String content = response.body().string();
|
|
||||||
token = HtmlParser.getTag(content, "input[name=csrfmiddlewaretoken]").attr("value");
|
|
||||||
LOG.debug("csrf token is {}", token);
|
|
||||||
|
|
||||||
RequestBody body = new FormBody.Builder()
|
|
||||||
.add("username", Config.getInstance().getSettings().username)
|
|
||||||
.add("password", Config.getInstance().getSettings().password)
|
|
||||||
.add("next", "")
|
|
||||||
.add("csrfmiddlewaretoken", token)
|
|
||||||
.build();
|
|
||||||
login = new Request.Builder()
|
|
||||||
.url(CamrecApplication.BASE_URI + "/auth/login/")
|
|
||||||
.header("Referer", CamrecApplication.BASE_URI + "/auth/login/")
|
|
||||||
.post(body)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
response = client.newCall(login).execute();
|
|
||||||
if(response.isSuccessful()) {
|
|
||||||
content = response.body().string();
|
|
||||||
if(content.contains("Login, Chaturbate login")) {
|
|
||||||
loggedIn = false;
|
|
||||||
} else {
|
|
||||||
loggedIn = true;
|
|
||||||
extractCsrfToken(login);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(loginTries++ < 3) {
|
|
||||||
login();
|
|
||||||
} else {
|
|
||||||
throw new IOException("Login failed: " + response.code() + " " + response.message());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response.close();
|
|
||||||
} finally {
|
|
||||||
loginTries = 0;
|
|
||||||
}
|
|
||||||
return loggedIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void reconfigure() {
|
public void reconfigure() {
|
||||||
instance = new HttpClient();
|
loadProxySettings();
|
||||||
}
|
client = new OkHttpClient.Builder()
|
||||||
|
.cookieJar(cookieJar)
|
||||||
public String getToken() throws IOException {
|
.connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
|
||||||
if(token == null) {
|
.readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
|
||||||
login();
|
.connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES))
|
||||||
}
|
//.addInterceptor(new LoggingInterceptor())
|
||||||
return token;
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package ctbrec.recorder;
|
package ctbrec.recorder;
|
||||||
|
|
||||||
import static ctbrec.Recording.STATUS.FINISHED;
|
import static ctbrec.Recording.STATUS.*;
|
||||||
import static ctbrec.Recording.STATUS.GENERATING_PLAYLIST;
|
|
||||||
import static ctbrec.Recording.STATUS.RECORDING;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -30,32 +28,27 @@ import com.iheartradio.m3u8.PlaylistException;
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
|
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
|
||||||
import ctbrec.recorder.download.Download;
|
import ctbrec.recorder.download.Download;
|
||||||
import ctbrec.recorder.download.HlsDownload;
|
import ctbrec.recorder.download.HlsDownload;
|
||||||
import ctbrec.recorder.download.MergedHlsDownload;
|
import ctbrec.recorder.download.MergedHlsDownload;
|
||||||
import ctbrec.sites.chaturbate.ChaturbateModelParser;
|
import ctbrec.recorder.server.RecorderHttpClient;
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class LocalRecorder implements Recorder {
|
public class LocalRecorder implements Recorder {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(LocalRecorder.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(LocalRecorder.class);
|
||||||
|
|
||||||
private static final boolean IGNORE_CACHE = true;
|
private static final boolean IGNORE_CACHE = true;
|
||||||
private List<Model> followedModels = Collections.synchronizedList(new ArrayList<>());
|
|
||||||
private List<Model> models = Collections.synchronizedList(new ArrayList<>());
|
private List<Model> models = Collections.synchronizedList(new ArrayList<>());
|
||||||
private Map<Model, Download> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
|
private Map<Model, Download> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
|
||||||
private Map<File, PlaylistGenerator> playlistGenerators = new HashMap<>();
|
private Map<File, PlaylistGenerator> playlistGenerators = new HashMap<>();
|
||||||
private Config config;
|
private Config config;
|
||||||
private ProcessMonitor processMonitor;
|
private ProcessMonitor processMonitor;
|
||||||
private OnlineMonitor onlineMonitor;
|
private OnlineMonitor onlineMonitor;
|
||||||
private FollowedMonitor followedMonitor;
|
|
||||||
private PlaylistGeneratorTrigger playlistGenTrigger;
|
private PlaylistGeneratorTrigger playlistGenTrigger;
|
||||||
private HttpClient client = HttpClient.getInstance();
|
|
||||||
private volatile boolean recording = true;
|
private volatile boolean recording = true;
|
||||||
private List<File> deleteInProgress = Collections.synchronizedList(new ArrayList<>());
|
private List<File> deleteInProgress = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
private RecorderHttpClient client = new RecorderHttpClient();
|
||||||
|
|
||||||
public LocalRecorder(Config config) {
|
public LocalRecorder(Config config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
@ -74,11 +67,6 @@ public class LocalRecorder implements Recorder {
|
||||||
playlistGenTrigger.start();
|
playlistGenTrigger.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.getSettings().recordFollowed) {
|
|
||||||
followedMonitor = new FollowedMonitor();
|
|
||||||
followedMonitor.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug("Recorder initialized");
|
LOG.debug("Recorder initialized");
|
||||||
LOG.info("Models to record: {}", models);
|
LOG.info("Models to record: {}", models);
|
||||||
LOG.info("Saving recordings in {}", config.getSettings().recordingsDir);
|
LOG.info("Saving recordings in {}", config.getSettings().recordingsDir);
|
||||||
|
@ -88,9 +76,6 @@ public class LocalRecorder implements Recorder {
|
||||||
public void startRecording(Model model) {
|
public void startRecording(Model model) {
|
||||||
if (!models.contains(model)) {
|
if (!models.contains(model)) {
|
||||||
LOG.info("Model {} added", model);
|
LOG.info("Model {} added", model);
|
||||||
if (followedModels.contains(model)) {
|
|
||||||
followedModels.remove(model);
|
|
||||||
}
|
|
||||||
models.add(model);
|
models.add(model);
|
||||||
config.getSettings().models.add(model);
|
config.getSettings().models.add(model);
|
||||||
}
|
}
|
||||||
|
@ -98,9 +83,8 @@ public class LocalRecorder implements Recorder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopRecording(Model model) throws IOException {
|
public void stopRecording(Model model) throws IOException {
|
||||||
if (models.contains(model) || followedModels.contains(model)) {
|
if (models.contains(model)) {
|
||||||
models.remove(model);
|
models.remove(model);
|
||||||
followedModels.remove(model);
|
|
||||||
config.getSettings().models.remove(model);
|
config.getSettings().models.remove(model);
|
||||||
if (recordingProcesses.containsKey(model)) {
|
if (recordingProcesses.containsKey(model)) {
|
||||||
stopRecordingProcess(model);
|
stopRecordingProcess(model);
|
||||||
|
@ -118,7 +102,7 @@ public class LocalRecorder implements Recorder {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!models.contains(model) && !followedModels.contains(model)) {
|
if (!models.contains(model)) {
|
||||||
LOG.info("Model {} has been removed. Restarting of recording cancelled.", model);
|
LOG.info("Model {} has been removed. Restarting of recording cancelled.", model);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -151,15 +135,12 @@ public class LocalRecorder implements Recorder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRecording(Model model) {
|
public boolean isRecording(Model model) {
|
||||||
return models.contains(model) || followedModels.contains(model);
|
return models.contains(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Model> getModelsRecording() {
|
public List<Model> getModelsRecording() {
|
||||||
List<Model> union = new ArrayList<>();
|
return Collections.unmodifiableList(models);
|
||||||
union.addAll(models);
|
|
||||||
union.addAll(followedModels);
|
|
||||||
return Collections.unmodifiableList(union);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -170,11 +151,9 @@ public class LocalRecorder implements Recorder {
|
||||||
onlineMonitor.running = false;
|
onlineMonitor.running = false;
|
||||||
processMonitor.running = false;
|
processMonitor.running = false;
|
||||||
playlistGenTrigger.running = false;
|
playlistGenTrigger.running = false;
|
||||||
if (followedMonitor != null) {
|
|
||||||
followedMonitor.running = false;
|
|
||||||
}
|
|
||||||
LOG.debug("Stopping all recording processes");
|
LOG.debug("Stopping all recording processes");
|
||||||
stopRecordingProcesses();
|
stopRecordingProcesses();
|
||||||
|
client.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopRecordingProcesses() {
|
private void stopRecordingProcesses() {
|
||||||
|
@ -251,54 +230,6 @@ public class LocalRecorder implements Recorder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class FollowedMonitor extends Thread {
|
|
||||||
private volatile boolean running = false;
|
|
||||||
|
|
||||||
public FollowedMonitor() {
|
|
||||||
setName("FollowedMonitor");
|
|
||||||
setDaemon(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
running = true;
|
|
||||||
while (running) {
|
|
||||||
try {
|
|
||||||
String url = "https://chaturbate.com/followed-cams/?page=1&keywords=&_=" + System.currentTimeMillis();
|
|
||||||
LOG.debug("Fetching page {}", url);
|
|
||||||
Request request = new Request.Builder().url(url).build();
|
|
||||||
Response response = client.execute(request, true);
|
|
||||||
if (response.isSuccessful()) {
|
|
||||||
List<Model> followed = ChaturbateModelParser.parseModels(response.body().string());
|
|
||||||
response.close();
|
|
||||||
followedModels.clear();
|
|
||||||
for (Model model : followed) {
|
|
||||||
if (!followedModels.contains(model) && !models.contains(model)) {
|
|
||||||
LOG.info("Model {} added", model);
|
|
||||||
followedModels.add(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onlineMonitor.interrupt();
|
|
||||||
} else {
|
|
||||||
int code = response.code();
|
|
||||||
response.close();
|
|
||||||
LOG.error("Couldn't retrieve followed models. HTTP status {}", code);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
LOG.error("Couldn't retrieve followed models.", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (running)
|
|
||||||
Thread.sleep(10000);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
LOG.error("Couldn't sleep", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LOG.debug(getName() + " terminated");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishRecording(File directory) {
|
private void finishRecording(File directory) {
|
||||||
Thread t = new Thread() {
|
Thread t = new Thread() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package ctbrec.recorder.server;
|
package ctbrec.recorder.server;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -33,20 +36,20 @@ public class HlsServlet extends AbstractCtbrecServlet {
|
||||||
File recordingsDir = new File(config.getSettings().recordingsDir);
|
File recordingsDir = new File(config.getSettings().recordingsDir);
|
||||||
File requestedFile = new File(recordingsDir, request);
|
File requestedFile = new File(recordingsDir, request);
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI());
|
boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI());
|
||||||
// if (!isRequestAuthenticated) {
|
if (!isRequestAuthenticated) {
|
||||||
// resp.setStatus(SC_UNAUTHORIZED);
|
resp.setStatus(SC_UNAUTHORIZED);
|
||||||
// String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
|
String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
|
||||||
// resp.getWriter().write(response);
|
resp.getWriter().write(response);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
// } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||||
// resp.setStatus(SC_UNAUTHORIZED);
|
resp.setStatus(SC_UNAUTHORIZED);
|
||||||
// String response = "{\"status\": \"error\", \"msg\": \"Authentication failed\"}";
|
String response = "{\"status\": \"error\", \"msg\": \"Authentication failed\"}";
|
||||||
// resp.getWriter().write(response);
|
resp.getWriter().write(response);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
if (requestedFile.getCanonicalPath().startsWith(config.getSettings().recordingsDir)) {
|
if (requestedFile.getCanonicalPath().startsWith(config.getSettings().recordingsDir)) {
|
||||||
if (requestedFile.getName().equals("playlist.m3u8")) {
|
if (requestedFile.getName().equals("playlist.m3u8")) {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package ctbrec.recorder.server;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
|
||||||
|
public class RecorderHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean login() throws IOException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
package ctbrec;
|
package ctbrec.sites;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.TabProvider;
|
import ctbrec.ui.TabProvider;
|
||||||
|
|
||||||
|
@ -10,4 +14,9 @@ public interface Site {
|
||||||
public void setRecorder(Recorder recorder);
|
public void setRecorder(Recorder recorder);
|
||||||
public TabProvider getTabProvider();
|
public TabProvider getTabProvider();
|
||||||
public Model createModel(String name);
|
public Model createModel(String name);
|
||||||
|
public Integer getTokenBalance() throws IOException;
|
||||||
|
public String getBuyTokensLink();
|
||||||
|
public void login() throws IOException;
|
||||||
|
public HttpClient getHttpClient();
|
||||||
|
public void shutdown();
|
||||||
}
|
}
|
|
@ -1,13 +1,49 @@
|
||||||
package ctbrec.sites.chaturbate;
|
package ctbrec.sites.chaturbate;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
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 com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Site;
|
import ctbrec.Settings;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.ui.HtmlParser;
|
||||||
import ctbrec.ui.TabProvider;
|
import ctbrec.ui.TabProvider;
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class Chaturbate implements Site {
|
public class Chaturbate implements Site {
|
||||||
|
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class);
|
||||||
|
public static final String BASE_URI = "https://chaturbate.com";
|
||||||
|
public static final String AFFILIATE_LINK = BASE_URI + "/in/?track=default&tour=LQps&campaign=55vTi&room=0xb00bface";
|
||||||
private Recorder recorder;
|
private Recorder recorder;
|
||||||
|
private ChaturbateHttpClient httpClient = new ChaturbateHttpClient();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -36,9 +72,214 @@ public class Chaturbate implements Site {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Model createModel(String name) {
|
public Model createModel(String name) {
|
||||||
ChaturbateModel m = new ChaturbateModel();
|
ChaturbateModel m = new ChaturbateModel(this);
|
||||||
m.setName(name);
|
m.setName(name);
|
||||||
m.setUrl(getBaseUrl() + '/' + name + '/');
|
m.setUrl(getBaseUrl() + '/' + name + '/');
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getTokenBalance() throws IOException {
|
||||||
|
String username = Config.getInstance().getSettings().username;
|
||||||
|
if (username == null || username.trim().isEmpty()) {
|
||||||
|
throw new IOException("Not logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = "https://chaturbate.com/p/" + username + "/";
|
||||||
|
Request req = new Request.Builder().url(url).build();
|
||||||
|
Response resp = httpClient.execute(req, true);
|
||||||
|
if (resp.isSuccessful()) {
|
||||||
|
String profilePage = resp.body().string();
|
||||||
|
String tokenText = HtmlParser.getText(profilePage, "span.tokencount");
|
||||||
|
int tokens = Integer.parseInt(tokenText);
|
||||||
|
return tokens;
|
||||||
|
} else {
|
||||||
|
throw new IOException("HTTP response: " + resp.code() + " - " + resp.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBuyTokensLink() {
|
||||||
|
return AFFILIATE_LINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void login() {
|
||||||
|
Settings settings = Config.getInstance().getSettings();
|
||||||
|
if (settings.username != null && !settings.username.isEmpty()) {
|
||||||
|
new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
httpClient.login();
|
||||||
|
} catch (IOException e1) {
|
||||||
|
LOG.warn("Initial login failed", e1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClient getHttpClient() {
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
httpClient.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
// #######################
|
||||||
|
private long lastRequest = System.currentTimeMillis();
|
||||||
|
|
||||||
|
LoadingCache<String, StreamInfo> streamInfoCache = CacheBuilder.newBuilder()
|
||||||
|
.initialCapacity(10_000)
|
||||||
|
.maximumSize(10_000)
|
||||||
|
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<String, StreamInfo> () {
|
||||||
|
@Override
|
||||||
|
public StreamInfo load(String model) throws Exception {
|
||||||
|
return loadStreamInfo(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
LoadingCache<String, int[]> streamResolutionCache = CacheBuilder.newBuilder()
|
||||||
|
.initialCapacity(10_000)
|
||||||
|
.maximumSize(10_000)
|
||||||
|
.expireAfterWrite(5, TimeUnit.MINUTES)
|
||||||
|
.build(new CacheLoader<String, int[]> () {
|
||||||
|
@Override
|
||||||
|
public int[] load(String model) throws Exception {
|
||||||
|
return loadResolution(model);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public void sendTip(String name, int tokens) throws IOException {
|
||||||
|
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
|
RequestBody body = new FormBody.Builder()
|
||||||
|
.add("csrfmiddlewaretoken", httpClient.getToken())
|
||||||
|
.add("tip_amount", Integer.toString(tokens))
|
||||||
|
.add("tip_room_type", "public")
|
||||||
|
.build();
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url("https://chaturbate.com/tipping/send_tip/"+name+"/")
|
||||||
|
.post(body)
|
||||||
|
.addHeader("Referer", "https://chaturbate.com/"+name+"/")
|
||||||
|
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.build();
|
||||||
|
try(Response response = httpClient.execute(req, true)) {
|
||||||
|
if(!response.isSuccessful()) {
|
||||||
|
throw new IOException(response.code() + " " + response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamInfo getStreamInfo(String modelName) throws IOException, ExecutionException {
|
||||||
|
return streamInfoCache.get(modelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamInfo loadStreamInfo(String modelName) throws IOException, InterruptedException {
|
||||||
|
throttleRequests();
|
||||||
|
RequestBody body = new FormBody.Builder()
|
||||||
|
.add("room_slug", modelName)
|
||||||
|
.add("bandwidth", "high")
|
||||||
|
.build();
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url("https://chaturbate.com/get_edge_hls_url_ajax/")
|
||||||
|
.post(body)
|
||||||
|
.addHeader("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.build();
|
||||||
|
Response response = httpClient.execute(req);
|
||||||
|
try {
|
||||||
|
if(response.isSuccessful()) {
|
||||||
|
String content = response.body().string();
|
||||||
|
LOG.trace("Raw stream info: {}", content);
|
||||||
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
|
JsonAdapter<StreamInfo> adapter = moshi.adapter(StreamInfo.class);
|
||||||
|
StreamInfo streamInfo = adapter.fromJson(content);
|
||||||
|
streamInfoCache.put(modelName, streamInfo);
|
||||||
|
return streamInfo;
|
||||||
|
} else {
|
||||||
|
int code = response.code();
|
||||||
|
String message = response.message();
|
||||||
|
throw new IOException("Server responded with " + code + " - " + message + " headers: [" + response.headers() + "]");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] getResolution(String modelName) throws ExecutionException {
|
||||||
|
return streamResolutionCache.get(modelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] loadResolution(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException, InterruptedException {
|
||||||
|
int[] res = new int[2];
|
||||||
|
StreamInfo streamInfo = getStreamInfo(modelName);
|
||||||
|
if(!streamInfo.url.startsWith("http")) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
EOFException ex = null;
|
||||||
|
for(int i=0; i<2; i++) {
|
||||||
|
try {
|
||||||
|
MasterPlaylist master = getMasterPlaylist(modelName);
|
||||||
|
for (PlaylistData playlistData : master.getPlaylists()) {
|
||||||
|
if(playlistData.hasStreamInfo() && playlistData.getStreamInfo().hasResolution()) {
|
||||||
|
int h = playlistData.getStreamInfo().getResolution().height;
|
||||||
|
int w = playlistData.getStreamInfo().getResolution().width;
|
||||||
|
if(w > res[1]) {
|
||||||
|
res[0] = w;
|
||||||
|
res[1] = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ex = null;
|
||||||
|
break; // this attempt worked, exit loop
|
||||||
|
} catch(EOFException e) {
|
||||||
|
// the cause might be, that the playlist url in streaminfo is outdated,
|
||||||
|
// so let's remove it from cache and retry in the next iteration
|
||||||
|
streamInfoCache.invalidate(modelName);
|
||||||
|
ex = e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ex != null) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamResolutionCache.put(modelName, res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void throttleRequests() throws InterruptedException {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
long diff = now - lastRequest;
|
||||||
|
if(diff < 500) {
|
||||||
|
Thread.sleep(diff);
|
||||||
|
}
|
||||||
|
lastRequest = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MasterPlaylist getMasterPlaylist(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException {
|
||||||
|
StreamInfo streamInfo = getStreamInfo(modelName);
|
||||||
|
return getMasterPlaylist(streamInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MasterPlaylist getMasterPlaylist(StreamInfo streamInfo) throws IOException, ParseException, PlaylistException {
|
||||||
|
LOG.trace("Loading master playlist {}", streamInfo.url);
|
||||||
|
Request req = new Request.Builder().url(streamInfo.url).build();
|
||||||
|
Response response = httpClient.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
package ctbrec.sites.chaturbate;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.ui.HtmlParser;
|
||||||
|
import okhttp3.Cookie;
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
public class ChaturbateHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateHttpClient.class);
|
||||||
|
protected String token;
|
||||||
|
|
||||||
|
private void extractCsrfToken(Request request) {
|
||||||
|
try {
|
||||||
|
Cookie csrfToken = cookieJar.getCookie(request.url(), "csrftoken");
|
||||||
|
token = csrfToken.value();
|
||||||
|
} catch(NoSuchElementException e) {
|
||||||
|
LOG.trace("CSRF token not found in cookies");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getToken() throws IOException {
|
||||||
|
if(token == null) {
|
||||||
|
login();
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean login() throws IOException {
|
||||||
|
try {
|
||||||
|
Request login = new Request.Builder()
|
||||||
|
.url(Chaturbate.BASE_URI + "/auth/login/")
|
||||||
|
.build();
|
||||||
|
Response response = client.newCall(login).execute();
|
||||||
|
String content = response.body().string();
|
||||||
|
token = HtmlParser.getTag(content, "input[name=csrfmiddlewaretoken]").attr("value");
|
||||||
|
LOG.debug("csrf token is {}", token);
|
||||||
|
|
||||||
|
RequestBody body = new FormBody.Builder()
|
||||||
|
.add("username", Config.getInstance().getSettings().username)
|
||||||
|
.add("password", Config.getInstance().getSettings().password)
|
||||||
|
.add("next", "")
|
||||||
|
.add("csrfmiddlewaretoken", token)
|
||||||
|
.build();
|
||||||
|
login = new Request.Builder()
|
||||||
|
.url(Chaturbate.BASE_URI + "/auth/login/")
|
||||||
|
.header("Referer", Chaturbate.BASE_URI + "/auth/login/")
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
response = client.newCall(login).execute();
|
||||||
|
if(response.isSuccessful()) {
|
||||||
|
content = response.body().string();
|
||||||
|
if(content.contains("Login, Chaturbate login")) {
|
||||||
|
loggedIn = false;
|
||||||
|
} else {
|
||||||
|
loggedIn = true;
|
||||||
|
extractCsrfToken(login);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(loginTries++ < 3) {
|
||||||
|
login();
|
||||||
|
} else {
|
||||||
|
throw new IOException("Login failed: " + response.code() + " " + response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
response.close();
|
||||||
|
} finally {
|
||||||
|
loginTries = 0;
|
||||||
|
}
|
||||||
|
return loggedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response execute(Request req, boolean requiresLogin) throws IOException {
|
||||||
|
Response resp = super.execute(req, requiresLogin);
|
||||||
|
extractCsrfToken(req);
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,35 +1,23 @@
|
||||||
package ctbrec.sites.chaturbate;
|
package ctbrec.sites.chaturbate;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import static ctbrec.sites.chaturbate.Chaturbate.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
import com.iheartradio.m3u8.Encoding;
|
|
||||||
import com.iheartradio.m3u8.Format;
|
|
||||||
import com.iheartradio.m3u8.ParseException;
|
import com.iheartradio.m3u8.ParseException;
|
||||||
import com.iheartradio.m3u8.PlaylistException;
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
import com.iheartradio.m3u8.PlaylistParser;
|
|
||||||
import com.iheartradio.m3u8.data.MasterPlaylist;
|
import com.iheartradio.m3u8.data.MasterPlaylist;
|
||||||
import com.iheartradio.m3u8.data.Playlist;
|
|
||||||
import com.iheartradio.m3u8.data.PlaylistData;
|
import com.iheartradio.m3u8.data.PlaylistData;
|
||||||
import com.squareup.moshi.JsonAdapter;
|
|
||||||
import com.squareup.moshi.Moshi;
|
|
||||||
|
|
||||||
import ctbrec.AbstractModel;
|
import ctbrec.AbstractModel;
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import okhttp3.FormBody;
|
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
@ -37,6 +25,11 @@ import okhttp3.Response;
|
||||||
public class ChaturbateModel extends AbstractModel {
|
public class ChaturbateModel extends AbstractModel {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModel.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModel.class);
|
||||||
|
private Chaturbate site;
|
||||||
|
|
||||||
|
ChaturbateModel(Chaturbate site) {
|
||||||
|
this.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
|
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
|
||||||
|
@ -47,24 +40,24 @@ public class ChaturbateModel extends AbstractModel {
|
||||||
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
|
||||||
StreamInfo info;
|
StreamInfo info;
|
||||||
if(ignoreCache) {
|
if(ignoreCache) {
|
||||||
info = Chaturbate.INSTANCE.loadStreamInfo(getName());
|
info = site.loadStreamInfo(getName());
|
||||||
LOG.trace("Model {} room status: {}", getName(), info.room_status);
|
LOG.trace("Model {} room status: {}", getName(), info.room_status);
|
||||||
} else {
|
} else {
|
||||||
info = Chaturbate.INSTANCE.getStreamInfo(getName());
|
info = site.getStreamInfo(getName());
|
||||||
}
|
}
|
||||||
return Objects.equals("public", info.room_status);
|
return Objects.equals("public", info.room_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
|
||||||
int[] resolution = Chaturbate.INSTANCE.streamResolutionCache.getIfPresent(getName());
|
int[] resolution = site.streamResolutionCache.getIfPresent(getName());
|
||||||
if(resolution != null) {
|
if(resolution != null) {
|
||||||
return Chaturbate.INSTANCE.getResolution(getName());
|
return site.getResolution(getName());
|
||||||
} else {
|
} else {
|
||||||
if(failFast) {
|
if(failFast) {
|
||||||
return new int[2];
|
return new int[2];
|
||||||
} else {
|
} else {
|
||||||
return Chaturbate.INSTANCE.getResolution(getName());
|
return site.getResolution(getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,8 +68,8 @@ public class ChaturbateModel extends AbstractModel {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void invalidateCacheEntries() {
|
public void invalidateCacheEntries() {
|
||||||
Chaturbate.INSTANCE.streamInfoCache.invalidate(getName());
|
site.streamInfoCache.invalidate(getName());
|
||||||
Chaturbate.INSTANCE.streamResolutionCache.invalidate(getName());
|
site.streamResolutionCache.invalidate(getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOnlineState() throws IOException, ExecutionException {
|
public String getOnlineState() throws IOException, ExecutionException {
|
||||||
|
@ -85,20 +78,20 @@ public class ChaturbateModel extends AbstractModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getOnlineState(boolean failFast) throws IOException, ExecutionException {
|
public String getOnlineState(boolean failFast) throws IOException, ExecutionException {
|
||||||
StreamInfo info = Chaturbate.INSTANCE.streamInfoCache.getIfPresent(getName());
|
StreamInfo info = site.streamInfoCache.getIfPresent(getName());
|
||||||
return info != null ? info.room_status : "n/a";
|
return info != null ? info.room_status : "n/a";
|
||||||
}
|
}
|
||||||
|
|
||||||
public StreamInfo getStreamInfo() throws IOException, ExecutionException {
|
public StreamInfo getStreamInfo() throws IOException, ExecutionException {
|
||||||
return Chaturbate.INSTANCE.getStreamInfo(getName());
|
return site.getStreamInfo(getName());
|
||||||
}
|
}
|
||||||
public MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException, ExecutionException {
|
public MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException, ExecutionException {
|
||||||
return Chaturbate.INSTANCE.getMasterPlaylist(getName());
|
return site.getMasterPlaylist(getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveTip(int tokens) throws IOException {
|
public void receiveTip(int tokens) throws IOException {
|
||||||
Chaturbate.INSTANCE.sendTip(getName(), tokens);
|
site.sendTip(getName(), tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,167 +116,52 @@ public class ChaturbateModel extends AbstractModel {
|
||||||
return sources;
|
return sources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class Chaturbate {
|
@Override
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class);
|
public boolean follow() throws IOException {
|
||||||
|
return follow(true);
|
||||||
|
}
|
||||||
|
|
||||||
public static final Chaturbate INSTANCE = new Chaturbate(HttpClient.getInstance());
|
@Override
|
||||||
|
public boolean unfollow() throws IOException {
|
||||||
|
return follow(false);
|
||||||
|
}
|
||||||
|
|
||||||
private HttpClient client;
|
private boolean follow(boolean follow) throws IOException {
|
||||||
|
Request req = new Request.Builder().url(getUrl()).build();
|
||||||
|
Response resp = site.getHttpClient().execute(req);
|
||||||
|
resp.close();
|
||||||
|
|
||||||
private static long lastRequest = System.currentTimeMillis();
|
String url = null;
|
||||||
|
if(follow) {
|
||||||
private LoadingCache<String, StreamInfo> streamInfoCache = CacheBuilder.newBuilder()
|
url = BASE_URI + "/follow/follow/" + getName() + "/";
|
||||||
.initialCapacity(10_000)
|
} else {
|
||||||
.maximumSize(10_000)
|
url = BASE_URI + "/follow/unfollow/" + getName() + "/";
|
||||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
|
||||||
.build(new CacheLoader<String, StreamInfo> () {
|
|
||||||
@Override
|
|
||||||
public StreamInfo load(String model) throws Exception {
|
|
||||||
return loadStreamInfo(model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
private LoadingCache<String, int[]> streamResolutionCache = CacheBuilder.newBuilder()
|
|
||||||
.initialCapacity(10_000)
|
|
||||||
.maximumSize(10_000)
|
|
||||||
.expireAfterWrite(5, TimeUnit.MINUTES)
|
|
||||||
.build(new CacheLoader<String, int[]> () {
|
|
||||||
@Override
|
|
||||||
public int[] load(String model) throws Exception {
|
|
||||||
return loadResolution(model);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
public Chaturbate(HttpClient client) {
|
|
||||||
this.client = client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendTip(String name, int tokens) throws IOException {
|
RequestBody body = RequestBody.create(null, new byte[0]);
|
||||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
req = new Request.Builder()
|
||||||
RequestBody body = new FormBody.Builder()
|
.url(url)
|
||||||
.add("csrfmiddlewaretoken", client.getToken())
|
.method("POST", body)
|
||||||
.add("tip_amount", Integer.toString(tokens))
|
.header("Accept", "*/*")
|
||||||
.add("tip_room_type", "public")
|
.header("Accept-Language", "en-US,en;q=0.5")
|
||||||
.build();
|
.header("Referer", getUrl())
|
||||||
Request req = new Request.Builder()
|
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0")
|
||||||
.url("https://chaturbate.com/tipping/send_tip/"+name+"/")
|
.header("X-CSRFToken", ((ChaturbateHttpClient)site.getHttpClient()).getToken())
|
||||||
.post(body)
|
.header("X-Requested-With", "XMLHttpRequest")
|
||||||
.addHeader("Referer", "https://chaturbate.com/"+name+"/")
|
.build();
|
||||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
resp = site.getHttpClient().execute(req, true);
|
||||||
.build();
|
if(resp.isSuccessful()) {
|
||||||
try(Response response = client.execute(req, true)) {
|
String msg = resp.body().string();
|
||||||
if(!response.isSuccessful()) {
|
if(!msg.equalsIgnoreCase("ok")) {
|
||||||
throw new IOException(response.code() + " " + response.message());
|
LOG.debug(msg);
|
||||||
}
|
throw new IOException("Response was " + msg.substring(0, Math.min(msg.length(), 500)));
|
||||||
}
|
} else {
|
||||||
}
|
LOG.debug("Follow/Unfollow -> {}", msg);
|
||||||
}
|
return true;
|
||||||
|
|
||||||
private StreamInfo getStreamInfo(String modelName) throws IOException, ExecutionException {
|
|
||||||
return streamInfoCache.get(modelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private StreamInfo loadStreamInfo(String modelName) throws IOException, InterruptedException {
|
|
||||||
throttleRequests();
|
|
||||||
RequestBody body = new FormBody.Builder()
|
|
||||||
.add("room_slug", modelName)
|
|
||||||
.add("bandwidth", "high")
|
|
||||||
.build();
|
|
||||||
Request req = new Request.Builder()
|
|
||||||
.url("https://chaturbate.com/get_edge_hls_url_ajax/")
|
|
||||||
.post(body)
|
|
||||||
.addHeader("X-Requested-With", "XMLHttpRequest")
|
|
||||||
.build();
|
|
||||||
Response response = client.execute(req);
|
|
||||||
try {
|
|
||||||
if(response.isSuccessful()) {
|
|
||||||
String content = response.body().string();
|
|
||||||
LOG.trace("Raw stream info: {}", content);
|
|
||||||
Moshi moshi = new Moshi.Builder().build();
|
|
||||||
JsonAdapter<StreamInfo> adapter = moshi.adapter(StreamInfo.class);
|
|
||||||
StreamInfo streamInfo = adapter.fromJson(content);
|
|
||||||
streamInfoCache.put(modelName, streamInfo);
|
|
||||||
return streamInfo;
|
|
||||||
} else {
|
|
||||||
int code = response.code();
|
|
||||||
String message = response.message();
|
|
||||||
throw new IOException("Server responded with " + code + " - " + message + " headers: [" + response.headers() + "]");
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
response.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int[] getResolution(String modelName) throws ExecutionException {
|
|
||||||
return streamResolutionCache.get(modelName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int[] loadResolution(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException, InterruptedException {
|
|
||||||
int[] res = new int[2];
|
|
||||||
StreamInfo streamInfo = getStreamInfo(modelName);
|
|
||||||
if(!streamInfo.url.startsWith("http")) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
EOFException ex = null;
|
|
||||||
for(int i=0; i<2; i++) {
|
|
||||||
try {
|
|
||||||
MasterPlaylist master = getMasterPlaylist(modelName);
|
|
||||||
for (PlaylistData playlistData : master.getPlaylists()) {
|
|
||||||
if(playlistData.hasStreamInfo() && playlistData.getStreamInfo().hasResolution()) {
|
|
||||||
int h = playlistData.getStreamInfo().getResolution().height;
|
|
||||||
int w = playlistData.getStreamInfo().getResolution().width;
|
|
||||||
if(w > res[1]) {
|
|
||||||
res[0] = w;
|
|
||||||
res[1] = h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ex = null;
|
|
||||||
break; // this attempt worked, exit loop
|
|
||||||
} catch(EOFException e) {
|
|
||||||
// the cause might be, that the playlist url in streaminfo is outdated,
|
|
||||||
// so let's remove it from cache and retry in the next iteration
|
|
||||||
streamInfoCache.invalidate(modelName);
|
|
||||||
ex = e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ex != null) {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
|
|
||||||
streamResolutionCache.put(modelName, res);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void throttleRequests() throws InterruptedException {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
long diff = now - lastRequest;
|
|
||||||
if(diff < 500) {
|
|
||||||
Thread.sleep(diff);
|
|
||||||
}
|
|
||||||
lastRequest = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MasterPlaylist getMasterPlaylist(String modelName) throws IOException, ParseException, PlaylistException, ExecutionException {
|
|
||||||
StreamInfo streamInfo = getStreamInfo(modelName);
|
|
||||||
return getMasterPlaylist(streamInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MasterPlaylist getMasterPlaylist(StreamInfo streamInfo) throws IOException, ParseException, PlaylistException {
|
|
||||||
LOG.trace("Loading master playlist {}", streamInfo.url);
|
|
||||||
Request req = new Request.Builder().url(streamInfo.url).build();
|
|
||||||
Response response = client.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();
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
resp.close();
|
||||||
|
throw new IOException("HTTP status " + resp.code() + " " + resp.message());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package ctbrec.sites.chaturbate;
|
package ctbrec.sites.chaturbate;
|
||||||
|
|
||||||
import static ctbrec.ui.CamrecApplication.BASE_URI;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -16,16 +14,15 @@ import ctbrec.ui.HtmlParser;
|
||||||
public class ChaturbateModelParser {
|
public class ChaturbateModelParser {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModelParser.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModelParser.class);
|
||||||
|
|
||||||
public static List<Model> parseModels(String html) {
|
public static List<Model> parseModels(Chaturbate chaturbate, String html) {
|
||||||
List<Model> models = new ArrayList<>();
|
List<Model> models = new ArrayList<>();
|
||||||
Elements cells = HtmlParser.getTags(html, "ul.list > li");
|
Elements cells = HtmlParser.getTags(html, "ul.list > li");
|
||||||
for (Element cell : cells) {
|
for (Element cell : cells) {
|
||||||
String cellHtml = cell.html();
|
String cellHtml = cell.html();
|
||||||
try {
|
try {
|
||||||
Model model = new ChaturbateModel();
|
Model model = chaturbate.createModel(HtmlParser.getText(cellHtml, "div.title > a").trim());
|
||||||
model.setName(HtmlParser.getText(cellHtml, "div.title > a").trim());
|
model.setName(HtmlParser.getText(cellHtml, "div.title > a").trim());
|
||||||
model.setPreview(HtmlParser.getTag(cellHtml, "a img").attr("src"));
|
model.setPreview(HtmlParser.getTag(cellHtml, "a img").attr("src"));
|
||||||
model.setUrl(BASE_URI + HtmlParser.getTag(cellHtml, "a").attr("href"));
|
|
||||||
model.setDescription(HtmlParser.getText(cellHtml, "div.details ul.subject"));
|
model.setDescription(HtmlParser.getText(cellHtml, "div.details ul.subject"));
|
||||||
Elements tags = HtmlParser.getTags(cellHtml, "div.details ul.subject li a");
|
Elements tags = HtmlParser.getTags(cellHtml, "div.details ul.subject li a");
|
||||||
if(tags != null) {
|
if(tags != null) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ public class ChaturbateTabProvider extends TabProvider {
|
||||||
tabs.add(createTab("Male", BASE_URI + "/male-cams/"));
|
tabs.add(createTab("Male", BASE_URI + "/male-cams/"));
|
||||||
tabs.add(createTab("Couples", BASE_URI + "/couple-cams/"));
|
tabs.add(createTab("Couples", BASE_URI + "/couple-cams/"));
|
||||||
tabs.add(createTab("Trans", BASE_URI + "/trans-cams/"));
|
tabs.add(createTab("Trans", BASE_URI + "/trans-cams/"));
|
||||||
FollowedTab followedTab = new FollowedTab("Followed", BASE_URI + "/followed-cams/");
|
FollowedTab followedTab = new FollowedTab("Followed", BASE_URI + "/followed-cams/", chaturbate);
|
||||||
followedTab.setRecorder(recorder);
|
followedTab.setRecorder(recorder);
|
||||||
followedTab.setScene(scene);
|
followedTab.setScene(scene);
|
||||||
tabs.add(followedTab);
|
tabs.add(followedTab);
|
||||||
|
@ -37,8 +37,8 @@ public class ChaturbateTabProvider extends TabProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tab createTab(String title, String url) {
|
private Tab createTab(String title, String url) {
|
||||||
ChaturbateUpdateService updateService = new ChaturbateUpdateService(url, false);
|
ChaturbateUpdateService updateService = new ChaturbateUpdateService(url, false, chaturbate);
|
||||||
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService);
|
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, chaturbate);
|
||||||
tab.setRecorder(recorder);
|
tab.setRecorder(recorder);
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.ui.PaginatedScheduledService;
|
import ctbrec.ui.PaginatedScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
@ -21,10 +20,12 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateUpdateService.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateUpdateService.class);
|
||||||
private String url;
|
private String url;
|
||||||
private boolean loginRequired;
|
private boolean loginRequired;
|
||||||
|
private Chaturbate chaturbate;
|
||||||
|
|
||||||
public ChaturbateUpdateService(String url, boolean loginRequired) {
|
public ChaturbateUpdateService(String url, boolean loginRequired, Chaturbate chaturbate) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.loginRequired = loginRequired;
|
this.loginRequired = loginRequired;
|
||||||
|
this.chaturbate = chaturbate;
|
||||||
|
|
||||||
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -46,9 +47,9 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
|
||||||
String url = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
|
String url = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
|
||||||
LOG.debug("Fetching page {}", url);
|
LOG.debug("Fetching page {}", url);
|
||||||
Request request = new Request.Builder().url(url).build();
|
Request request = new Request.Builder().url(url).build();
|
||||||
Response response = HttpClient.getInstance().execute(request, loginRequired);
|
Response response = chaturbate.getHttpClient().execute(request, loginRequired);
|
||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
List<Model> models = ChaturbateModelParser.parseModels(response.body().string());
|
List<Model> models = ChaturbateModelParser.parseModels(chaturbate, response.body().string());
|
||||||
response.close();
|
response.close();
|
||||||
return models;
|
return models;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class FriendsUpdateService extends PaginatedScheduledService {
|
||||||
.url(url)
|
.url(url)
|
||||||
.header("Referer", myFreeCams.getBaseUrl())
|
.header("Referer", myFreeCams.getBaseUrl())
|
||||||
.build();
|
.build();
|
||||||
Response resp = MyFreeCams.httpClient.newCall(req).execute();
|
Response resp = myFreeCams.getHttpClient().execute(req, true);
|
||||||
if(resp.isSuccessful()) {
|
if(resp.isSuccessful()) {
|
||||||
String json = resp.body().string().substring(4);
|
String json = resp.body().string().substring(4);
|
||||||
JSONObject object = new JSONObject(json);
|
JSONObject object = new JSONObject(json);
|
||||||
|
|
|
@ -1,35 +1,18 @@
|
||||||
package ctbrec.sites.mfc;
|
package ctbrec.sites.mfc;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.Site;
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.CookieJarImpl;
|
import ctbrec.sites.Site;
|
||||||
import ctbrec.ui.TabProvider;
|
import ctbrec.ui.TabProvider;
|
||||||
import okhttp3.ConnectionPool;
|
|
||||||
import okhttp3.FormBody;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class MyFreeCams implements Site {
|
public class MyFreeCams implements Site {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCams.class);
|
public static final String BASE_URI = "https://www.myfreecams.com";
|
||||||
|
|
||||||
private Recorder recorder;
|
private Recorder recorder;
|
||||||
private MyFreeCamsClient client;
|
private MyFreeCamsClient client;
|
||||||
public static OkHttpClient httpClient = new OkHttpClient.Builder()
|
private MyFreeCamsHttpClient httpClient = new MyFreeCamsHttpClient();
|
||||||
.connectTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
.readTimeout(Config.getInstance().getSettings().httpTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
.connectionPool(new ConnectionPool(50, 10, TimeUnit.MINUTES))
|
|
||||||
.cookieJar(new CookieJarImpl())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public MyFreeCams() throws IOException {
|
public MyFreeCams() throws IOException {
|
||||||
client = MyFreeCamsClient.getInstance();
|
client = MyFreeCamsClient.getInstance();
|
||||||
|
@ -39,25 +22,9 @@ public class MyFreeCams implements Site {
|
||||||
login();
|
login();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void login() throws IOException {
|
public void login() throws IOException {
|
||||||
RequestBody body = new FormBody.Builder()
|
|
||||||
.add("username", "affenhubert")
|
|
||||||
.add("password", "hampel81")
|
|
||||||
.add("tz", "2")
|
|
||||||
.add("ss", "1920x1080")
|
|
||||||
.add("submit_login", "97")
|
|
||||||
.build();
|
|
||||||
Request req = new Request.Builder()
|
|
||||||
.url(getBaseUrl() + "/php/login.php")
|
|
||||||
.header("Referer", getBaseUrl())
|
|
||||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
|
||||||
.post(body)
|
|
||||||
.build();
|
|
||||||
Response resp = httpClient.newCall(req).execute();
|
|
||||||
if(!resp.isSuccessful()) {
|
|
||||||
LOG.error("Login failed {} {}", resp.code(), resp.message());
|
|
||||||
}
|
|
||||||
resp.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -67,7 +34,7 @@ public class MyFreeCams implements Site {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return "https://www.myfreecams.com";
|
return BASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -87,9 +54,29 @@ public class MyFreeCams implements Site {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MyFreeCamsModel createModel(String name) {
|
public MyFreeCamsModel createModel(String name) {
|
||||||
MyFreeCamsModel model = new MyFreeCamsModel();
|
MyFreeCamsModel model = new MyFreeCamsModel(this);
|
||||||
model.setName(name);
|
model.setName(name);
|
||||||
model.setUrl("https://profiles.myfreecams.com/" + name);
|
model.setUrl("https://profiles.myfreecams.com/" + name);
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getTokenBalance() throws IOException {
|
||||||
|
throw new RuntimeException("Not implemented for MFC");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBuyTokensLink() {
|
||||||
|
return "https://www.myfreecams.com/php/purchase.php?request=tokens";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MyFreeCamsHttpClient getHttpClient() {
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
httpClient.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class MyFreeCamsClient {
|
||||||
|
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
running = true;
|
running = true;
|
||||||
ServerConfig serverConfig = new ServerConfig(MyFreeCams.httpClient);
|
ServerConfig serverConfig = new ServerConfig(mfc.getHttpClient());
|
||||||
List<String> websocketServers = new ArrayList<String>(serverConfig.wsServers.keySet());
|
List<String> websocketServers = new ArrayList<String>(serverConfig.wsServers.keySet());
|
||||||
String server = websocketServers.get((int) (Math.random()*websocketServers.size()));
|
String server = websocketServers.get((int) (Math.random()*websocketServers.size()));
|
||||||
String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
|
String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
|
||||||
|
@ -89,7 +89,7 @@ public class MyFreeCamsClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebSocket createWebSocket(Request req) {
|
private WebSocket createWebSocket(Request req) {
|
||||||
WebSocket ws = MyFreeCams.httpClient.newWebSocket(req, new WebSocketListener() {
|
WebSocket ws = mfc.getHttpClient().newWebSocket(req, new WebSocketListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onOpen(WebSocket webSocket, Response response) {
|
public void onOpen(WebSocket webSocket, Response response) {
|
||||||
super.onOpen(webSocket, response);
|
super.onOpen(webSocket, response);
|
||||||
|
@ -111,7 +111,7 @@ public class MyFreeCamsClient {
|
||||||
super.onClosed(webSocket, code, reason);
|
super.onClosed(webSocket, code, reason);
|
||||||
LOG.trace("close: {} {}", code, reason);
|
LOG.trace("close: {} {}", code, reason);
|
||||||
running = false;
|
running = false;
|
||||||
MyFreeCams.httpClient.dispatcher().executorService().shutdownNow();
|
mfc.getHttpClient().shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder msgBuffer = new StringBuilder();
|
private StringBuilder msgBuffer = new StringBuilder();
|
||||||
|
@ -190,7 +190,7 @@ public class MyFreeCamsClient {
|
||||||
String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type;
|
String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type;
|
||||||
Request req = new Request.Builder().url(url).build();
|
Request req = new Request.Builder().url(url).build();
|
||||||
LOG.debug("Requesting EXTDATA {}", url);
|
LOG.debug("Requesting EXTDATA {}", url);
|
||||||
Response resp = MyFreeCams.httpClient.newCall(req).execute();
|
Response resp = mfc.getHttpClient().execute(req);
|
||||||
|
|
||||||
if(resp.isSuccessful()) {
|
if(resp.isSuccessful()) {
|
||||||
parseExtDataSessionStates(resp.body().string());
|
parseExtDataSessionStates(resp.body().string());
|
||||||
|
@ -354,4 +354,14 @@ public class MyFreeCamsClient {
|
||||||
public void execute(Runnable r) {
|
public void execute(Runnable r) {
|
||||||
executor.execute(r);
|
executor.execute(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void getSessionState(ctbrec.Model model) {
|
||||||
|
for (SessionState state : sessionStates.values()) {
|
||||||
|
if(Objects.equals(state.getNm(), model.getName())) {
|
||||||
|
JsonAdapter<SessionState> adapter = moshi.adapter(SessionState.class).indent(" ");
|
||||||
|
System.out.println(adapter.toJson(state));
|
||||||
|
System.out.println("#####################");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
package ctbrec.sites.mfc;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
import okhttp3.FormBody;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import okhttp3.WebSocket;
|
||||||
|
import okhttp3.WebSocketListener;
|
||||||
|
|
||||||
|
public class MyFreeCamsHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCamsHttpClient.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean login() throws IOException {
|
||||||
|
String username = Config.getInstance().getSettings().username;
|
||||||
|
String password = Config.getInstance().getSettings().password;
|
||||||
|
RequestBody body = new FormBody.Builder()
|
||||||
|
.add("username", username)
|
||||||
|
.add("password", password)
|
||||||
|
.add("tz", "2")
|
||||||
|
.add("ss", "1920x1080")
|
||||||
|
.add("submit_login", "97")
|
||||||
|
.build();
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url(MyFreeCams.BASE_URI + "/php/login.php")
|
||||||
|
.header("Referer", MyFreeCams.BASE_URI)
|
||||||
|
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
.post(body)
|
||||||
|
.build();
|
||||||
|
Response resp = execute(req);
|
||||||
|
if(resp.isSuccessful()) {
|
||||||
|
resp.close();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
resp.close();
|
||||||
|
LOG.error("Login failed {} {}", resp.code(), resp.message());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public WebSocket newWebSocket(Request req, WebSocketListener webSocketListener) {
|
||||||
|
return client.newWebSocket(req, webSocketListener);
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,11 @@ public class MyFreeCamsModel extends AbstractModel {
|
||||||
private double camScore;
|
private double camScore;
|
||||||
private State state;
|
private State state;
|
||||||
private int resolution[];
|
private int resolution[];
|
||||||
|
private MyFreeCams site;
|
||||||
|
|
||||||
|
MyFreeCamsModel(MyFreeCams site) {
|
||||||
|
this.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
|
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
|
||||||
|
@ -78,7 +83,7 @@ public class MyFreeCamsModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
LOG.debug("Loading master playlist {}", hlsUrl);
|
LOG.debug("Loading master playlist {}", hlsUrl);
|
||||||
Request req = new Request.Builder().url(hlsUrl).build();
|
Request req = new Request.Builder().url(hlsUrl).build();
|
||||||
Response response = MyFreeCams.httpClient.newCall(req).execute();
|
Response response = site.getHttpClient().execute(req);
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = response.body().byteStream();
|
InputStream inputStream = response.body().byteStream();
|
||||||
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
|
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
|
||||||
|
@ -97,7 +102,7 @@ public class MyFreeCamsModel extends AbstractModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveTip(int tokens) throws IOException {
|
public void receiveTip(int tokens) throws IOException {
|
||||||
throw new RuntimeException("Not implemented");
|
throw new RuntimeException("Not implemented for MFC");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -155,8 +160,29 @@ public class MyFreeCamsModel extends AbstractModel {
|
||||||
// stream url
|
// stream url
|
||||||
Integer camserv = state.getU().getCamserv();
|
Integer camserv = state.getU().getCamserv();
|
||||||
if(camserv != null) {
|
if(camserv != null) {
|
||||||
|
if(state.getM() != null) {
|
||||||
|
if(state.getM().getFlags() != null) {
|
||||||
|
int flags = state.getM().getFlags();
|
||||||
|
int hd = flags >> 18 & 0x1;
|
||||||
|
if(hd == 1) {
|
||||||
|
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_a_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
|
||||||
|
setStreamUrl(hlsUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
|
String hlsUrl = "http://video" + (camserv - 500) + ".myfreecams.com:1935/NxServer/ngrp:mfc_" + (100000000 + state.getUid()) + ".f4v_mobile/playlist.m3u8";
|
||||||
setStreamUrl(hlsUrl);
|
setStreamUrl(hlsUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean follow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unfollow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,18 +26,17 @@ public class MyFreeCamsTabProvider extends TabProvider {
|
||||||
List<Tab> tabs = new ArrayList<>();
|
List<Tab> tabs = new ArrayList<>();
|
||||||
|
|
||||||
PaginatedScheduledService updateService = new OnlineCamsUpdateService();
|
PaginatedScheduledService updateService = new OnlineCamsUpdateService();
|
||||||
ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService);
|
ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService, myFreeCams);
|
||||||
online.setRecorder(recorder);
|
online.setRecorder(recorder);
|
||||||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
|
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
|
||||||
tabs.add(online);
|
tabs.add(online);
|
||||||
|
|
||||||
updateService = new FriendsUpdateService(myFreeCams);
|
updateService = new FriendsUpdateService(myFreeCams);
|
||||||
ThumbOverviewTab friends = new ThumbOverviewTab("Friends", updateService);
|
ThumbOverviewTab friends = new ThumbOverviewTab("Friends", updateService, myFreeCams);
|
||||||
friends.setRecorder(recorder);
|
friends.setRecorder(recorder);
|
||||||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
|
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
|
||||||
tabs.add(friends);
|
tabs.add(friends);
|
||||||
|
|
||||||
|
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package ctbrec.sites.mfc;
|
package ctbrec.sites.mfc;
|
||||||
|
|
||||||
|
import ctbrec.sites.Site;
|
||||||
import ctbrec.ui.PaginatedScheduledService;
|
import ctbrec.ui.PaginatedScheduledService;
|
||||||
import ctbrec.ui.ThumbOverviewTab;
|
import ctbrec.ui.ThumbOverviewTab;
|
||||||
|
|
||||||
public class OnlineModelsTab extends ThumbOverviewTab {
|
public class OnlineModelsTab extends ThumbOverviewTab {
|
||||||
|
|
||||||
public OnlineModelsTab(String title, PaginatedScheduledService updateService) {
|
public OnlineModelsTab(String title, PaginatedScheduledService updateService, Site site) {
|
||||||
super(title, updateService);
|
super(title, updateService, site);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import java.util.Map;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
@ -23,9 +23,9 @@ public class ServerConfig {
|
||||||
Map<String, String> wzobsServers;
|
Map<String, String> wzobsServers;
|
||||||
Map<String, String> ngVideo;
|
Map<String, String> ngVideo;
|
||||||
|
|
||||||
public ServerConfig(OkHttpClient client) throws IOException {
|
public ServerConfig(HttpClient client) throws IOException {
|
||||||
Request req = new Request.Builder().url("http://www.myfreecams.com/_js/serverconfig.js").build();
|
Request req = new Request.Builder().url("http://www.myfreecams.com/_js/serverconfig.js").build();
|
||||||
Response resp = client.newCall(req).execute();
|
Response resp = client.execute(req);
|
||||||
String json = resp.body().string();
|
String json = resp.body().string();
|
||||||
|
|
||||||
JSONObject serverConfig = new JSONObject(json);
|
JSONObject serverConfig = new JSONObject(json);
|
||||||
|
|
|
@ -7,7 +7,6 @@ import java.io.InputStreamReader;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -20,26 +19,31 @@ import com.squareup.moshi.Moshi;
|
||||||
import com.squareup.moshi.Types;
|
import com.squareup.moshi.Types;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Site;
|
|
||||||
import ctbrec.Version;
|
import ctbrec.Version;
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.recorder.LocalRecorder;
|
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.mfc.MyFreeCams;
|
import ctbrec.sites.mfc.MyFreeCams;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.HostServices;
|
import javafx.application.HostServices;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.concurrent.Task;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Parent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TabPane;
|
import javafx.scene.control.TabPane;
|
||||||
import javafx.scene.control.TabPane.TabClosingPolicy;
|
import javafx.scene.control.TabPane.TabClosingPolicy;
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.StackPane;
|
||||||
|
import javafx.scene.text.Font;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
@ -47,12 +51,9 @@ import okhttp3.Response;
|
||||||
public class CamrecApplication extends Application {
|
public class CamrecApplication extends Application {
|
||||||
|
|
||||||
static final transient Logger LOG = LoggerFactory.getLogger(CamrecApplication.class);
|
static final transient Logger LOG = LoggerFactory.getLogger(CamrecApplication.class);
|
||||||
public static final String BASE_URI = "https://chaturbate.com";
|
|
||||||
public static final String AFFILIATE_LINK = BASE_URI + "/in/?track=default&tour=LQps&campaign=55vTi&room=0xb00bface";
|
|
||||||
|
|
||||||
private Config config;
|
private Config config;
|
||||||
private Recorder recorder;
|
private Recorder recorder;
|
||||||
private HttpClient client;
|
|
||||||
static HostServices hostServices;
|
static HostServices hostServices;
|
||||||
private SettingsTab settingsTab;
|
private SettingsTab settingsTab;
|
||||||
private TabPane tabPane = new TabPane();
|
private TabPane tabPane = new TabPane();
|
||||||
|
@ -65,12 +66,13 @@ public class CamrecApplication extends Application {
|
||||||
loadConfig();
|
loadConfig();
|
||||||
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
|
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
|
||||||
hostServices = getHostServices();
|
hostServices = getHostServices();
|
||||||
client = HttpClient.getInstance();
|
|
||||||
createRecorder();
|
createRecorder();
|
||||||
//site = new Chaturbate();
|
// site = new Chaturbate();
|
||||||
site = new MyFreeCams();
|
site = new MyFreeCams();
|
||||||
site.setRecorder(recorder);
|
site.setRecorder(recorder);
|
||||||
// TODO move this to Chaturbate class doInitialLogin();
|
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
|
site.login();
|
||||||
|
}
|
||||||
createGui(primaryStage);
|
createGui(primaryStage);
|
||||||
checkForUpdates();
|
checkForUpdates();
|
||||||
}
|
}
|
||||||
|
@ -132,7 +134,7 @@ public class CamrecApplication extends Application {
|
||||||
public void run() {
|
public void run() {
|
||||||
settingsTab.saveConfig();
|
settingsTab.saveConfig();
|
||||||
recorder.shutdown();
|
recorder.shutdown();
|
||||||
client.shutdown();
|
site.shutdown();
|
||||||
try {
|
try {
|
||||||
Config.getInstance().save();
|
Config.getInstance().save();
|
||||||
LOG.info("Shutdown complete. Goodbye!");
|
LOG.info("Shutdown complete. Goodbye!");
|
||||||
|
@ -152,103 +154,43 @@ public class CamrecApplication extends Application {
|
||||||
}.start();
|
}.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO think about a solution, which works for all sites
|
String username = Config.getInstance().getSettings().username;
|
||||||
// String username = Config.getInstance().getSettings().username;
|
if(username != null && !username.trim().isEmpty()) {
|
||||||
// if(username != null && !username.trim().isEmpty()) {
|
double fontSize = tabPane.getTabMaxHeight() / 2 - 1;
|
||||||
// double fontSize = tabPane.getTabMaxHeight() / 2 - 1;
|
Button buyTokens = new Button("Buy Tokens");
|
||||||
// Button buyTokens = new Button("Buy Tokens");
|
buyTokens.setFont(Font.font(fontSize));
|
||||||
// buyTokens.setFont(Font.font(fontSize));
|
buyTokens.setOnAction((e) -> DesktopIntergation.open(site.getBuyTokensLink()));
|
||||||
// buyTokens.setOnAction((e) -> DesktopIntergation.open(AFFILIATE_LINK));
|
buyTokens.setMaxHeight(tabPane.getTabMaxHeight());
|
||||||
// buyTokens.setMaxHeight(tabPane.getTabMaxHeight());
|
TokenLabel tokenBalance = new TokenLabel(site);
|
||||||
// TokenLabel tokenBalance = new TokenLabel();
|
tokenPanel = new HBox(5, tokenBalance, buyTokens);
|
||||||
// tokenPanel = new HBox(5, tokenBalance, buyTokens);
|
tokenPanel.setAlignment(Pos.BASELINE_RIGHT);
|
||||||
// //tokenPanel.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, new Insets(0))));
|
tokenPanel.setMaxHeight(tabPane.getTabMaxHeight());
|
||||||
// tokenPanel.setAlignment(Pos.BASELINE_RIGHT);
|
tokenPanel.setMaxWidth(200);
|
||||||
// tokenPanel.setMaxHeight(tabPane.getTabMaxHeight());
|
tokenBalance.setFont(Font.font(fontSize));
|
||||||
// tokenPanel.setMaxWidth(200);
|
HBox.setMargin(tokenBalance, new Insets(0, 5, 0, 0));
|
||||||
// tokenBalance.setFont(Font.font(fontSize));
|
HBox.setMargin(buyTokens, new Insets(0, 5, 0, 0));
|
||||||
// HBox.setMargin(tokenBalance, new Insets(0, 5, 0, 0));
|
for (Node node : tabPane.getChildrenUnmodifiable()) {
|
||||||
// HBox.setMargin(buyTokens, new Insets(0, 5, 0, 0));
|
if(node.getStyleClass().contains("tab-header-area")) {
|
||||||
// for (Node node : tabPane.getChildrenUnmodifiable()) {
|
Parent header = (Parent) node;
|
||||||
// if(node.getStyleClass().contains("tab-header-area")) {
|
for (Node nd : header.getChildrenUnmodifiable()) {
|
||||||
// Parent header = (Parent) node;
|
if(nd.getStyleClass().contains("tab-header-background")) {
|
||||||
// for (Node nd : header.getChildrenUnmodifiable()) {
|
StackPane pane = (StackPane) nd;
|
||||||
// if(nd.getStyleClass().contains("tab-header-background")) {
|
StackPane.setAlignment(tokenPanel, Pos.CENTER_RIGHT);
|
||||||
// StackPane pane = (StackPane) nd;
|
pane.getChildren().add(tokenPanel);
|
||||||
// StackPane.setAlignment(tokenPanel, Pos.CENTER_RIGHT);
|
|
||||||
// pane.getChildren().add(tokenPanel);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// loadTokenBalance(tokenBalance);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadTokenBalance(TokenLabel label) {
|
|
||||||
Task<Integer> task = new Task<Integer>() {
|
|
||||||
@Override
|
|
||||||
protected Integer call() throws Exception {
|
|
||||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
|
||||||
String username = Config.getInstance().getSettings().username;
|
|
||||||
if (username == null || username.trim().isEmpty()) {
|
|
||||||
throw new IOException("Not logged in");
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = "https://chaturbate.com/p/" + username + "/";
|
|
||||||
HttpClient client = HttpClient.getInstance();
|
|
||||||
Request req = new Request.Builder().url(url).build();
|
|
||||||
Response resp = client.execute(req, true);
|
|
||||||
if (resp.isSuccessful()) {
|
|
||||||
String profilePage = resp.body().string();
|
|
||||||
String tokenText = HtmlParser.getText(profilePage, "span.tokencount");
|
|
||||||
int tokens = Integer.parseInt(tokenText);
|
|
||||||
return tokens;
|
|
||||||
} else {
|
|
||||||
throw new IOException("HTTP response: " + resp.code() + " - " + resp.message());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return 1_000_000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void done() {
|
|
||||||
try {
|
|
||||||
int tokens = get();
|
|
||||||
label.update(tokens);
|
|
||||||
} catch (InterruptedException | ExecutionException e) {
|
|
||||||
LOG.error("Couldn't retrieve account balance", e);
|
|
||||||
Platform.runLater(() -> label.setText("Tokens: error"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
new Thread(task).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doInitialLogin() {
|
|
||||||
if(config.getSettings().username != null && !config.getSettings().username.isEmpty()) {
|
|
||||||
new Thread() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if(!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
|
||||||
try {
|
|
||||||
client.login();
|
|
||||||
} catch (IOException e1) {
|
|
||||||
LOG.warn("Initial login failed" , e1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}.start();
|
}
|
||||||
|
}
|
||||||
|
tokenBalance.loadBalance();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createRecorder() {
|
private void createRecorder() {
|
||||||
if(config.getSettings().localRecording) {
|
if (config.getSettings().localRecording) {
|
||||||
recorder = new LocalRecorder(config);
|
recorder = new LocalRecorder(config);
|
||||||
} else {
|
} else {
|
||||||
recorder = new RemoteRecorder(config, client);
|
recorder = new RemoteRecorder(config, site.getHttpClient());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,8 +217,8 @@ public class CamrecApplication extends Application {
|
||||||
try {
|
try {
|
||||||
String url = "https://api.github.com/repos/0xboobface/ctbrec/releases";
|
String url = "https://api.github.com/repos/0xboobface/ctbrec/releases";
|
||||||
Request request = new Request.Builder().url(url).build();
|
Request request = new Request.Builder().url(url).build();
|
||||||
Response response = client.execute(request);
|
Response response = site.getHttpClient().execute(request);
|
||||||
if(response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
Moshi moshi = new Moshi.Builder().build();
|
Moshi moshi = new Moshi.Builder().build();
|
||||||
Type type = Types.newParameterizedType(List.class, Release.class);
|
Type type = Types.newParameterizedType(List.class, Release.class);
|
||||||
JsonAdapter<List<Release>> adapter = moshi.adapter(type);
|
JsonAdapter<List<Release>> adapter = moshi.adapter(type);
|
||||||
|
@ -284,7 +226,7 @@ public class CamrecApplication extends Application {
|
||||||
Release latest = releases.get(0);
|
Release latest = releases.get(0);
|
||||||
Version latestVersion = latest.getVersion();
|
Version latestVersion = latest.getVersion();
|
||||||
Version ctbrecVersion = getVersion();
|
Version ctbrecVersion = getVersion();
|
||||||
if(latestVersion.compareTo(ctbrecVersion) > 0) {
|
if (latestVersion.compareTo(ctbrecVersion) > 0) {
|
||||||
LOG.debug("Update available {} < {}", ctbrecVersion, latestVersion);
|
LOG.debug("Update available {} < {}", ctbrecVersion, latestVersion);
|
||||||
Platform.runLater(() -> tabPane.getTabs().add(new UpdateTab(latest)));
|
Platform.runLater(() -> tabPane.getTabs().add(new UpdateTab(latest)));
|
||||||
} else {
|
} else {
|
||||||
|
@ -303,10 +245,10 @@ public class CamrecApplication extends Application {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Version getVersion() throws IOException {
|
private Version getVersion() throws IOException {
|
||||||
if(Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
return Version.of("0.0.0-DEV");
|
return Version.of("0.0.0-DEV");
|
||||||
} else {
|
} else {
|
||||||
try(InputStream is = getClass().getClassLoader().getResourceAsStream("version")) {
|
try (InputStream is = getClass().getClassLoader().getResourceAsStream("version")) {
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||||
String versionString = reader.readLine();
|
String versionString = reader.readLine();
|
||||||
Version version = Version.of(versionString);
|
Version version = Version.of(versionString);
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ctbrec.ui;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
|
@ -44,7 +45,7 @@ public class DonateTabFx extends Tab {
|
||||||
|
|
||||||
ImageView tokenImage = new ImageView(getClass().getResource("/html/token.png").toString());
|
ImageView tokenImage = new ImageView(getClass().getResource("/html/token.png").toString());
|
||||||
Button tokenButton = new Button("Buy tokens");
|
Button tokenButton = new Button("Buy tokens");
|
||||||
tokenButton.setOnAction((e) -> { DesktopIntergation.open(CamrecApplication.AFFILIATE_LINK); });
|
tokenButton.setOnAction((e) -> { DesktopIntergation.open(Chaturbate.AFFILIATE_LINK); });
|
||||||
VBox tokenBox = new VBox(5);
|
VBox tokenBox = new VBox(5);
|
||||||
tokenBox.setAlignment(Pos.TOP_CENTER);
|
tokenBox.setAlignment(Pos.TOP_CENTER);
|
||||||
Label tokenDesc = new Label("If you buy tokens by using this button,\n"
|
Label tokenDesc = new Label("If you buy tokens by using this button,\n"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
import ctbrec.sites.chaturbate.ChaturbateUpdateService;
|
import ctbrec.sites.chaturbate.ChaturbateUpdateService;
|
||||||
import javafx.concurrent.WorkerStateEvent;
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -16,8 +17,8 @@ public class FollowedTab extends ThumbOverviewTab {
|
||||||
private String onlineUrl;
|
private String onlineUrl;
|
||||||
private String offlineUrl;
|
private String offlineUrl;
|
||||||
|
|
||||||
public FollowedTab(String title, String url) {
|
public FollowedTab(String title, String url, Chaturbate chaturbate) {
|
||||||
super(title, new ChaturbateUpdateService(url, true));
|
super(title, new ChaturbateUpdateService(url, true, chaturbate), chaturbate);
|
||||||
onlineUrl = url;
|
onlineUrl = url;
|
||||||
offlineUrl = url + "offline/";
|
offlineUrl = url + "offline/";
|
||||||
|
|
||||||
|
|
|
@ -126,4 +126,14 @@ public class JavaFxModel extends AbstractModel {
|
||||||
public int[] getStreamResolution(boolean b) throws ExecutionException {
|
public int[] getStreamResolution(boolean b) throws ExecutionException {
|
||||||
return delegate.getStreamResolution(b);
|
return delegate.getStreamResolution(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean follow() throws IOException {
|
||||||
|
return delegate.follow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unfollow() throws IOException {
|
||||||
|
return delegate.unfollow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,8 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Site;
|
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
|
@ -266,7 +265,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpClient client = HttpClient.getInstance();
|
|
||||||
Function<Model, Void> onSuccess = (m) -> {
|
Function<Model, Void> onSuccess = (m) -> {
|
||||||
try {
|
try {
|
||||||
recorder.switchStreamSource(m);
|
recorder.switchStreamSource(m);
|
||||||
|
@ -281,7 +279,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
showStreamSwitchErrorDialog(t);
|
showStreamSwitchErrorDialog(t);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
StreamSourceSelectionDialog.show(fxModel.getDelegate(), client, onSuccess, onFail);
|
StreamSourceSelectionDialog.show(fxModel.getDelegate(), site.getHttpClient(), onSuccess, onFail);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showStreamSwitchErrorDialog(Throwable throwable) {
|
private void showStreamSwitchErrorDialog(Throwable throwable) {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
import static javafx.scene.control.ButtonType.NO;
|
import static javafx.scene.control.ButtonType.*;
|
||||||
import static javafx.scene.control.ButtonType.YES;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
@ -32,10 +31,9 @@ import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.Recording.STATUS;
|
import ctbrec.Recording.STATUS;
|
||||||
import ctbrec.Site;
|
|
||||||
import ctbrec.io.HttpClient;
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.recorder.download.MergedHlsDownload;
|
import ctbrec.recorder.download.MergedHlsDownload;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
@ -306,7 +304,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
MergedHlsDownload download = new MergedHlsDownload(HttpClient.getInstance());
|
MergedHlsDownload download = new MergedHlsDownload(site.getHttpClient());
|
||||||
download.start(url.toString(), target, (progress) -> {
|
download.start(url.toString(), target, (progress) -> {
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
if (progress == 100) {
|
if (progress == 100) {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.sun.javafx.collections.ObservableListWrapper;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Hmac;
|
import ctbrec.Hmac;
|
||||||
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
@ -57,7 +58,6 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private CheckBox loadResolution;
|
private CheckBox loadResolution;
|
||||||
private CheckBox secureCommunication = new CheckBox();
|
private CheckBox secureCommunication = new CheckBox();
|
||||||
private CheckBox chooseStreamQuality = new CheckBox();
|
private CheckBox chooseStreamQuality = new CheckBox();
|
||||||
private CheckBox autoRecordFollowed = new CheckBox();
|
|
||||||
private CheckBox multiplePlayers = new CheckBox();
|
private CheckBox multiplePlayers = new CheckBox();
|
||||||
private PasswordField password;
|
private PasswordField password;
|
||||||
private RadioButton recordLocal;
|
private RadioButton recordLocal;
|
||||||
|
@ -188,25 +188,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
layout.add(password, 1, 1);
|
layout.add(password, 1, 1);
|
||||||
|
|
||||||
Button createAccount = new Button("Create new Account");
|
Button createAccount = new Button("Create new Account");
|
||||||
createAccount.setOnAction((e) -> DesktopIntergation.open(CamrecApplication.AFFILIATE_LINK));
|
createAccount.setOnAction((e) -> DesktopIntergation.open(Chaturbate.AFFILIATE_LINK));
|
||||||
layout.add(createAccount, 1, 2);
|
layout.add(createAccount, 1, 2);
|
||||||
GridPane.setColumnSpan(createAccount, 2);
|
GridPane.setColumnSpan(createAccount, 2);
|
||||||
|
|
||||||
l = new Label("Record all followed models");
|
|
||||||
layout.add(l, 0, 3);
|
|
||||||
autoRecordFollowed = new CheckBox();
|
|
||||||
autoRecordFollowed.setSelected(Config.getInstance().getSettings().recordFollowed);
|
|
||||||
autoRecordFollowed.setOnAction((e) -> {
|
|
||||||
Config.getInstance().getSettings().recordFollowed = autoRecordFollowed.isSelected();
|
|
||||||
showRestartRequired();
|
|
||||||
});
|
|
||||||
layout.add(autoRecordFollowed, 1, 3);
|
|
||||||
Label warning = new Label("Don't do this, if you follow many models. You have been warned ;) !");
|
|
||||||
warning.setTextFill(Color.RED);
|
|
||||||
layout.add(warning, 2, 3);
|
|
||||||
GridPane.setMargin(l, new Insets(3, 0, 0, 0));
|
|
||||||
GridPane.setMargin(warning, new Insets(3, 0, 0, 0));
|
|
||||||
GridPane.setMargin(autoRecordFollowed, new Insets(3, 0, 0, CHECKBOX_MARGIN));
|
|
||||||
GridPane.setMargin(username, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
GridPane.setMargin(username, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||||
GridPane.setMargin(password, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
GridPane.setMargin(password, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||||
GridPane.setMargin(createAccount, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
GridPane.setMargin(createAccount, new Insets(0, 0, 0, CHECKBOX_MARGIN));
|
||||||
|
|
|
@ -45,9 +45,6 @@ import javafx.scene.text.Font;
|
||||||
import javafx.scene.text.Text;
|
import javafx.scene.text.Text;
|
||||||
import javafx.scene.text.TextAlignment;
|
import javafx.scene.text.TextAlignment;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class ThumbCell extends StackPane {
|
public class ThumbCell extends StackPane {
|
||||||
|
|
||||||
|
@ -347,91 +344,55 @@ public class ThumbCell extends StackPane {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void _startStopAction(Model model, boolean start) {
|
private void _startStopAction(Model model, boolean start) {
|
||||||
new Thread() {
|
new Thread(() -> {
|
||||||
@Override
|
try {
|
||||||
public void run() {
|
if(start) {
|
||||||
try {
|
recorder.startRecording(model);
|
||||||
if(start) {
|
setRecording(true);
|
||||||
recorder.startRecording(model);
|
} else {
|
||||||
setRecording(true);
|
recorder.stopRecording(model);
|
||||||
} else {
|
setRecording(false);
|
||||||
recorder.stopRecording(model);
|
|
||||||
setRecording(false);
|
|
||||||
}
|
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Couldn't start/stop recording", e1);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
|
||||||
alert.setTitle("Error");
|
|
||||||
alert.setHeaderText("Couldn't start/stop recording");
|
|
||||||
alert.setContentText("I/O error while starting/stopping the recording: " + e1.getLocalizedMessage());
|
|
||||||
alert.showAndWait();
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setCursor(Cursor.DEFAULT);
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e1) {
|
||||||
|
LOG.error("Couldn't start/stop recording", e1);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||||
|
alert.setTitle("Error");
|
||||||
|
alert.setHeaderText("Couldn't start/stop recording");
|
||||||
|
alert.setContentText("I/O error while starting/stopping the recording: " + e1.getLocalizedMessage());
|
||||||
|
alert.showAndWait();
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setCursor(Cursor.DEFAULT);
|
||||||
}
|
}
|
||||||
}.start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void follow(boolean follow) {
|
void follow(boolean follow) {
|
||||||
setCursor(Cursor.WAIT);
|
setCursor(Cursor.WAIT);
|
||||||
new Thread() {
|
new Thread(() -> {
|
||||||
@Override
|
try {
|
||||||
public void run() {
|
if(follow) {
|
||||||
try {
|
model.follow();
|
||||||
Request req = new Request.Builder().url(model.getUrl()).build();
|
} else {
|
||||||
Response resp = HttpClient.getInstance().execute(req);
|
boolean unfollowed = model.unfollow();
|
||||||
resp.close();
|
if(unfollowed) {
|
||||||
|
Platform.runLater(() -> thumbCellList.remove(ThumbCell.this));
|
||||||
String url = null;
|
|
||||||
if(follow) {
|
|
||||||
url = CamrecApplication.BASE_URI + "/follow/follow/" + model.getName() + "/";
|
|
||||||
} else {
|
|
||||||
url = CamrecApplication.BASE_URI + "/follow/unfollow/" + model.getName() + "/";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBody body = RequestBody.create(null, new byte[0]);
|
|
||||||
req = new Request.Builder()
|
|
||||||
.url(url)
|
|
||||||
.method("POST", body)
|
|
||||||
.header("Accept", "*/*")
|
|
||||||
.header("Accept-Language", "en-US,en;q=0.5")
|
|
||||||
.header("Referer", model.getUrl())
|
|
||||||
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0")
|
|
||||||
.header("X-CSRFToken", HttpClient.getInstance().getToken())
|
|
||||||
.header("X-Requested-With", "XMLHttpRequest")
|
|
||||||
.build();
|
|
||||||
resp = HttpClient.getInstance().execute(req, true);
|
|
||||||
if(resp.isSuccessful()) {
|
|
||||||
String msg = resp.body().string();
|
|
||||||
if(!msg.equalsIgnoreCase("ok")) {
|
|
||||||
LOG.debug(msg);
|
|
||||||
throw new IOException("Response was " + msg.substring(0, Math.min(msg.length(), 500)));
|
|
||||||
} else {
|
|
||||||
LOG.debug("Follow/Unfollow -> {}", msg);
|
|
||||||
if(!follow) {
|
|
||||||
Platform.runLater(() -> thumbCellList.remove(ThumbCell.this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resp.close();
|
|
||||||
throw new IOException("HTTP status " + resp.code() + " " + resp.message());
|
|
||||||
}
|
|
||||||
} catch (Exception e1) {
|
|
||||||
LOG.error("Couldn't follow/unfollow model {}", model.getName(), e1);
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
|
||||||
alert.setTitle("Error");
|
|
||||||
alert.setHeaderText("Couldn't follow/unfollow model");
|
|
||||||
alert.setContentText("I/O error while following/unfollowing model " + model.getName() + ": " + e1.getLocalizedMessage());
|
|
||||||
alert.showAndWait();
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setCursor(Cursor.DEFAULT);
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e1) {
|
||||||
|
LOG.error("Couldn't follow/unfollow model {}", model.getName(), e1);
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||||
|
alert.setTitle("Error");
|
||||||
|
alert.setHeaderText("Couldn't follow/unfollow model");
|
||||||
|
alert.setContentText("I/O error while following/unfollowing model " + model.getName() + ": " + e1.getLocalizedMessage());
|
||||||
|
alert.showAndWait();
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setCursor(Cursor.DEFAULT);
|
||||||
}
|
}
|
||||||
}.start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Model getModel() {
|
public Model getModel() {
|
||||||
|
|
|
@ -29,6 +29,8 @@ import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.io.HttpClient;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.mfc.MyFreeCamsClient;
|
||||||
import javafx.collections.ObservableList;
|
import javafx.collections.ObservableList;
|
||||||
import javafx.concurrent.Worker.State;
|
import javafx.concurrent.Worker.State;
|
||||||
import javafx.concurrent.WorkerStateEvent;
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
|
@ -71,19 +73,20 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
ReentrantLock gridLock = new ReentrantLock();
|
ReentrantLock gridLock = new ReentrantLock();
|
||||||
ScrollPane scrollPane = new ScrollPane();
|
ScrollPane scrollPane = new ScrollPane();
|
||||||
boolean loginRequired;
|
boolean loginRequired;
|
||||||
HttpClient client = HttpClient.getInstance();
|
|
||||||
HBox pagination;
|
HBox pagination;
|
||||||
TextField pageInput = new TextField(Integer.toString(1));
|
TextField pageInput = new TextField(Integer.toString(1));
|
||||||
Button pagePrev = new Button("◀");
|
Button pagePrev = new Button("◀");
|
||||||
Button pageNext = new Button("▶");
|
Button pageNext = new Button("▶");
|
||||||
private volatile boolean updatesSuspended = false;
|
private volatile boolean updatesSuspended = false;
|
||||||
ContextMenu popup;
|
ContextMenu popup;
|
||||||
|
Site site;
|
||||||
|
|
||||||
private ComboBox<Integer> thumbWidth;
|
private ComboBox<Integer> thumbWidth;
|
||||||
|
|
||||||
public ThumbOverviewTab(String title, PaginatedScheduledService updateService) {
|
public ThumbOverviewTab(String title, PaginatedScheduledService updateService, Site site) {
|
||||||
super(title);
|
super(title);
|
||||||
this.updateService = updateService;
|
this.updateService = updateService;
|
||||||
|
this.site = site;
|
||||||
setClosable(false);
|
setClosable(false);
|
||||||
createGui();
|
createGui();
|
||||||
initializeUpdateService();
|
initializeUpdateService();
|
||||||
|
@ -253,7 +256,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!found) {
|
if(!found) {
|
||||||
ThumbCell newCell = createThumbCell(this, model, recorder, client);
|
ThumbCell newCell = createThumbCell(this, model, recorder, site.getHttpClient());
|
||||||
newCell.setIndex(index);
|
newCell.setIndex(index);
|
||||||
positionChangedOrNew.add(newCell);
|
positionChangedOrNew.add(newCell);
|
||||||
}
|
}
|
||||||
|
@ -277,7 +280,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder2, HttpClient client2) {
|
ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder2, HttpClient client2) {
|
||||||
ThumbCell newCell = new ThumbCell(this, model, recorder, client);
|
ThumbCell newCell = new ThumbCell(this, model, recorder, site.getHttpClient());
|
||||||
newCell.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
newCell.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||||
suspendUpdates(true);
|
suspendUpdates(true);
|
||||||
popup = createContextMenu(newCell);
|
popup = createContextMenu(newCell);
|
||||||
|
@ -328,7 +331,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
|
|
||||||
MenuItem sendTip = new MenuItem("Send Tip");
|
MenuItem sendTip = new MenuItem("Send Tip");
|
||||||
sendTip.setOnAction((e) -> {
|
sendTip.setOnAction((e) -> {
|
||||||
TipDialog tipDialog = new TipDialog(cell.getModel());
|
TipDialog tipDialog = new TipDialog(site, cell.getModel());
|
||||||
tipDialog.showAndWait();
|
tipDialog.showAndWait();
|
||||||
String tipText = tipDialog.getResult();
|
String tipText = tipDialog.getResult();
|
||||||
if(tipText != null) {
|
if(tipText != null) {
|
||||||
|
@ -340,7 +343,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
event.put("event", "tokens.sent");
|
event.put("event", "tokens.sent");
|
||||||
event.put("amount", tokens);
|
event.put("amount", tokens);
|
||||||
CamrecApplication.bus.post(event);
|
CamrecApplication.bus.post(event);
|
||||||
} catch (IOException e1) {
|
} catch (Exception e1) {
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
|
||||||
alert.setTitle("Error");
|
alert.setTitle("Error");
|
||||||
alert.setHeaderText("Couldn't send tip");
|
alert.setHeaderText("Couldn't send tip");
|
||||||
|
@ -374,12 +377,17 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MenuItem debug = new MenuItem("debug");
|
||||||
|
debug.setOnAction((e) -> {
|
||||||
|
MyFreeCamsClient.getInstance().getSessionState(cell.getModel());
|
||||||
|
});
|
||||||
|
|
||||||
ContextMenu contextMenu = new ContextMenu();
|
ContextMenu contextMenu = new ContextMenu();
|
||||||
contextMenu.setAutoHide(true);
|
contextMenu.setAutoHide(true);
|
||||||
contextMenu.setHideOnEscape(true);
|
contextMenu.setHideOnEscape(true);
|
||||||
contextMenu.setAutoFix(true);
|
contextMenu.setAutoFix(true);
|
||||||
MenuItem followOrUnFollow = this instanceof FollowedTab ? unfollow : follow;
|
MenuItem followOrUnFollow = this instanceof FollowedTab ? unfollow : follow;
|
||||||
contextMenu.getItems().addAll(openInPlayer, startStop , followOrUnFollow, copyUrl, sendTip);
|
contextMenu.getItems().addAll(openInPlayer, startStop , followOrUnFollow, copyUrl, sendTip, debug);
|
||||||
return contextMenu;
|
return contextMenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,27 @@
|
||||||
package ctbrec.ui;
|
package ctbrec.ui;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.io.HttpClient;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.sites.chaturbate.Chaturbate;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.scene.control.Alert;
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.ButtonType;
|
import javafx.scene.control.ButtonType;
|
||||||
import javafx.scene.control.TextInputDialog;
|
import javafx.scene.control.TextInputDialog;
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.Response;
|
|
||||||
|
|
||||||
public class TipDialog extends TextInputDialog {
|
public class TipDialog extends TextInputDialog {
|
||||||
|
|
||||||
private static final transient Logger LOG = LoggerFactory.getLogger(TipDialog.class);
|
private static final transient Logger LOG = LoggerFactory.getLogger(TipDialog.class);
|
||||||
|
private Site site;
|
||||||
|
|
||||||
public TipDialog(Model model) {
|
public TipDialog(Site site, Model model) {
|
||||||
|
this.site = site;
|
||||||
setTitle("Send Tip");
|
setTitle("Send Tip");
|
||||||
loadTokenBalance();
|
loadTokenBalance();
|
||||||
setHeaderText("Loading token balance…");
|
setHeaderText("Loading token balance…");
|
||||||
|
@ -38,27 +35,7 @@ public class TipDialog extends TextInputDialog {
|
||||||
@Override
|
@Override
|
||||||
protected Integer call() throws Exception {
|
protected Integer call() throws Exception {
|
||||||
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
String username = Config.getInstance().getSettings().username;
|
return site.getTokenBalance();
|
||||||
if (username == null || username.trim().isEmpty()) {
|
|
||||||
throw new IOException("Not logged in");
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = "https://chaturbate.com/p/" + username + "/";
|
|
||||||
HttpClient client = HttpClient.getInstance();
|
|
||||||
Request req = new Request.Builder().url(url).build();
|
|
||||||
Response resp = client.execute(req, true);
|
|
||||||
if (resp.isSuccessful()) {
|
|
||||||
String profilePage = resp.body().string();
|
|
||||||
String tokenText = HtmlParser.getText(profilePage, "span.tokencount");
|
|
||||||
int tokens = Integer.parseInt(tokenText);
|
|
||||||
Map<String, Object> event = new HashMap<>();
|
|
||||||
event.put("event", "tokens");
|
|
||||||
event.put("amount", tokens);
|
|
||||||
CamrecApplication.bus.post(event);
|
|
||||||
return tokens;
|
|
||||||
} else {
|
|
||||||
throw new IOException("HTTP response: " + resp.code() + " - " + resp.message());
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return 1_000_000;
|
return 1_000_000;
|
||||||
}
|
}
|
||||||
|
@ -78,7 +55,7 @@ public class TipDialog extends TextInputDialog {
|
||||||
buyTokens.showAndWait();
|
buyTokens.showAndWait();
|
||||||
TipDialog.this.close();
|
TipDialog.this.close();
|
||||||
if(buyTokens.getResult() == ButtonType.YES) {
|
if(buyTokens.getResult() == ButtonType.YES) {
|
||||||
DesktopIntergation.open(CamrecApplication.AFFILIATE_LINK);
|
DesktopIntergation.open(Chaturbate.AFFILIATE_LINK);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
getEditor().setDisable(false);
|
getEditor().setDisable(false);
|
||||||
|
|
|
@ -2,17 +2,26 @@ package ctbrec.ui;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.common.eventbus.Subscribe;
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
|
||||||
|
import ctbrec.sites.Site;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
|
||||||
public class TokenLabel extends Label {
|
public class TokenLabel extends Label {
|
||||||
|
|
||||||
|
private static final transient Logger LOG = LoggerFactory.getLogger(TokenLabel.class);
|
||||||
private int tokens = -1;
|
private int tokens = -1;
|
||||||
|
private Site site;
|
||||||
|
|
||||||
public TokenLabel() {
|
public TokenLabel(Site site) {
|
||||||
|
this.site = site;
|
||||||
setText("Tokens: loading…");
|
setText("Tokens: loading…");
|
||||||
CamrecApplication.bus.register(new Object() {
|
CamrecApplication.bus.register(new Object() {
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
@ -42,4 +51,29 @@ public class TokenLabel extends Label {
|
||||||
private void updateText() {
|
private void updateText() {
|
||||||
Platform.runLater(() -> setText("Tokens: " + tokens));
|
Platform.runLater(() -> setText("Tokens: " + tokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadBalance() {
|
||||||
|
Task<Integer> task = new Task<Integer>() {
|
||||||
|
@Override
|
||||||
|
protected Integer call() throws Exception {
|
||||||
|
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
|
||||||
|
return site.getTokenBalance();
|
||||||
|
} else {
|
||||||
|
return 1_000_000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void done() {
|
||||||
|
try {
|
||||||
|
int tokens = get();
|
||||||
|
update(tokens);
|
||||||
|
} catch (InterruptedException | ExecutionException e) {
|
||||||
|
LOG.error("Couldn't retrieve account balance", e);
|
||||||
|
Platform.runLater(() -> setText("Tokens: error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new Thread(task).start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue