Abstract more stuff in the site packages

This commit is contained in:
0xboobface 2018-10-21 19:06:01 +02:00
parent 362d90b29b
commit 387661cfdf
32 changed files with 764 additions and 688 deletions

View File

@ -30,5 +30,6 @@ public interface Model {
public void invalidateCacheEntries();
public void receiveTip(int tokens) throws IOException;
public int[] getStreamResolution(boolean failFast) throws ExecutionException;
public boolean follow() throws IOException;
public boolean unfollow() throws IOException;
}

View File

@ -28,7 +28,6 @@ public class Settings {
public boolean determineResolution = false;
public boolean requireAuthentication = false;
public boolean chooseStreamQuality = false;
public boolean recordFollowed = false;
public byte[] key = null;
public ProxyType proxyType = ProxyType.DIRECT;
public String proxyHost;

View File

@ -1,44 +1,24 @@
package ctbrec.io;
import java.io.IOException;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Settings.ProxyType;
import ctbrec.ui.CamrecApplication;
import ctbrec.ui.CookieJarImpl;
import ctbrec.ui.HtmlParser;
import okhttp3.ConnectionPool;
import okhttp3.Cookie;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class HttpClient {
private static final transient Logger LOG = LoggerFactory.getLogger(HttpClient.class);
private static HttpClient instance = new HttpClient();
public abstract class HttpClient {
protected OkHttpClient client;
protected CookieJarImpl cookieJar = new CookieJarImpl();
protected boolean loggedIn = false;
protected int loginTries = 0;
private OkHttpClient client;
private CookieJarImpl cookieJar = new CookieJarImpl();
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();
protected HttpClient() {
reconfigure();
}
private void loadProxySettings() {
@ -89,24 +69,11 @@ public class HttpClient {
}
}
public static HttpClient getInstance() {
return instance;
}
public Response execute(Request request) throws IOException {
Response resp = execute(request, false);
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 {
if(requiresLogin && !loggedIn) {
boolean loginSuccessful = login();
@ -115,64 +82,20 @@ public class HttpClient {
}
}
Response resp = client.newCall(req).execute();
extractCsrfToken(req);
return resp;
}
public 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 abstract boolean login() throws IOException;
public void reconfigure() {
instance = new HttpClient();
}
public String getToken() throws IOException {
if(token == null) {
login();
}
return token;
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();
}
public void shutdown() {

View File

@ -1,8 +1,6 @@
package ctbrec.recorder;
import static ctbrec.Recording.STATUS.FINISHED;
import static ctbrec.Recording.STATUS.GENERATING_PLAYLIST;
import static ctbrec.Recording.STATUS.RECORDING;
import static ctbrec.Recording.STATUS.*;
import java.io.File;
import java.io.IOException;
@ -30,32 +28,27 @@ import com.iheartradio.m3u8.PlaylistException;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.io.HttpClient;
import ctbrec.recorder.PlaylistGenerator.InvalidPlaylistException;
import ctbrec.recorder.download.Download;
import ctbrec.recorder.download.HlsDownload;
import ctbrec.recorder.download.MergedHlsDownload;
import ctbrec.sites.chaturbate.ChaturbateModelParser;
import okhttp3.Request;
import okhttp3.Response;
import ctbrec.recorder.server.RecorderHttpClient;
public class LocalRecorder implements Recorder {
private static final transient Logger LOG = LoggerFactory.getLogger(LocalRecorder.class);
private static final boolean IGNORE_CACHE = true;
private List<Model> followedModels = Collections.synchronizedList(new ArrayList<>());
private List<Model> models = Collections.synchronizedList(new ArrayList<>());
private Map<Model, Download> recordingProcesses = Collections.synchronizedMap(new HashMap<>());
private Map<File, PlaylistGenerator> playlistGenerators = new HashMap<>();
private Config config;
private ProcessMonitor processMonitor;
private OnlineMonitor onlineMonitor;
private FollowedMonitor followedMonitor;
private PlaylistGeneratorTrigger playlistGenTrigger;
private HttpClient client = HttpClient.getInstance();
private volatile boolean recording = true;
private List<File> deleteInProgress = Collections.synchronizedList(new ArrayList<>());
private RecorderHttpClient client = new RecorderHttpClient();
public LocalRecorder(Config config) {
this.config = config;
@ -74,11 +67,6 @@ public class LocalRecorder implements Recorder {
playlistGenTrigger.start();
}
if (config.getSettings().recordFollowed) {
followedMonitor = new FollowedMonitor();
followedMonitor.start();
}
LOG.debug("Recorder initialized");
LOG.info("Models to record: {}", models);
LOG.info("Saving recordings in {}", config.getSettings().recordingsDir);
@ -88,9 +76,6 @@ public class LocalRecorder implements Recorder {
public void startRecording(Model model) {
if (!models.contains(model)) {
LOG.info("Model {} added", model);
if (followedModels.contains(model)) {
followedModels.remove(model);
}
models.add(model);
config.getSettings().models.add(model);
}
@ -98,9 +83,8 @@ public class LocalRecorder implements Recorder {
@Override
public void stopRecording(Model model) throws IOException {
if (models.contains(model) || followedModels.contains(model)) {
if (models.contains(model)) {
models.remove(model);
followedModels.remove(model);
config.getSettings().models.remove(model);
if (recordingProcesses.containsKey(model)) {
stopRecordingProcess(model);
@ -118,7 +102,7 @@ public class LocalRecorder implements Recorder {
return;
}
if (!models.contains(model) && !followedModels.contains(model)) {
if (!models.contains(model)) {
LOG.info("Model {} has been removed. Restarting of recording cancelled.", model);
return;
}
@ -151,15 +135,12 @@ public class LocalRecorder implements Recorder {
@Override
public boolean isRecording(Model model) {
return models.contains(model) || followedModels.contains(model);
return models.contains(model);
}
@Override
public List<Model> getModelsRecording() {
List<Model> union = new ArrayList<>();
union.addAll(models);
union.addAll(followedModels);
return Collections.unmodifiableList(union);
return Collections.unmodifiableList(models);
}
@Override
@ -170,11 +151,9 @@ public class LocalRecorder implements Recorder {
onlineMonitor.running = false;
processMonitor.running = false;
playlistGenTrigger.running = false;
if (followedMonitor != null) {
followedMonitor.running = false;
}
LOG.debug("Stopping all recording processes");
stopRecordingProcesses();
client.shutdown();
}
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) {
Thread t = new Thread() {
@Override

View File

@ -1,9 +1,12 @@
package ctbrec.recorder.server;
import static javax.servlet.http.HttpServletResponse.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -33,20 +36,20 @@ public class HlsServlet extends AbstractCtbrecServlet {
File recordingsDir = new File(config.getSettings().recordingsDir);
File requestedFile = new File(recordingsDir, request);
// try {
// boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI());
// if (!isRequestAuthenticated) {
// resp.setStatus(SC_UNAUTHORIZED);
// String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
// resp.getWriter().write(response);
// return;
// }
// } catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
// resp.setStatus(SC_UNAUTHORIZED);
// String response = "{\"status\": \"error\", \"msg\": \"Authentication failed\"}";
// resp.getWriter().write(response);
// return;
// }
try {
boolean isRequestAuthenticated = checkAuthentication(req, req.getRequestURI());
if (!isRequestAuthenticated) {
resp.setStatus(SC_UNAUTHORIZED);
String response = "{\"status\": \"error\", \"msg\": \"HMAC does not match\"}";
resp.getWriter().write(response);
return;
}
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
resp.setStatus(SC_UNAUTHORIZED);
String response = "{\"status\": \"error\", \"msg\": \"Authentication failed\"}";
resp.getWriter().write(response);
return;
}
if (requestedFile.getCanonicalPath().startsWith(config.getSettings().recordingsDir)) {
if (requestedFile.getName().equals("playlist.m3u8")) {

View File

@ -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;
}
}

View File

@ -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.ui.TabProvider;
@ -10,4 +14,9 @@ public interface Site {
public void setRecorder(Recorder recorder);
public TabProvider getTabProvider();
public Model createModel(String name);
public Integer getTokenBalance() throws IOException;
public String getBuyTokensLink();
public void login() throws IOException;
public HttpClient getHttpClient();
public void shutdown();
}

View File

@ -1,13 +1,49 @@
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.Site;
import ctbrec.Settings;
import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.ui.HtmlParser;
import ctbrec.ui.TabProvider;
import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
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 ChaturbateHttpClient httpClient = new ChaturbateHttpClient();
@Override
public String getName() {
@ -36,9 +72,214 @@ public class Chaturbate implements Site {
@Override
public Model createModel(String name) {
ChaturbateModel m = new ChaturbateModel();
ChaturbateModel m = new ChaturbateModel(this);
m.setName(name);
m.setUrl(getBaseUrl() + '/' + name + '/');
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();
}
}
}

View File

@ -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;
}
}

View File

@ -1,35 +1,23 @@
package ctbrec.sites.chaturbate;
import java.io.EOFException;
import static ctbrec.sites.chaturbate.Chaturbate.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import 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.AbstractModel;
import ctbrec.io.HttpClient;
import ctbrec.recorder.download.StreamSource;
import okhttp3.FormBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
@ -37,6 +25,11 @@ import okhttp3.Response;
public class ChaturbateModel extends AbstractModel {
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateModel.class);
private Chaturbate site;
ChaturbateModel(Chaturbate site) {
this.site = site;
}
@Override
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 {
StreamInfo info;
if(ignoreCache) {
info = Chaturbate.INSTANCE.loadStreamInfo(getName());
info = site.loadStreamInfo(getName());
LOG.trace("Model {} room status: {}", getName(), info.room_status);
} else {
info = Chaturbate.INSTANCE.getStreamInfo(getName());
info = site.getStreamInfo(getName());
}
return Objects.equals("public", info.room_status);
}
@Override
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
int[] resolution = Chaturbate.INSTANCE.streamResolutionCache.getIfPresent(getName());
int[] resolution = site.streamResolutionCache.getIfPresent(getName());
if(resolution != null) {
return Chaturbate.INSTANCE.getResolution(getName());
return site.getResolution(getName());
} else {
if(failFast) {
return new int[2];
} else {
return Chaturbate.INSTANCE.getResolution(getName());
return site.getResolution(getName());
}
}
}
@ -75,8 +68,8 @@ public class ChaturbateModel extends AbstractModel {
*/
@Override
public void invalidateCacheEntries() {
Chaturbate.INSTANCE.streamInfoCache.invalidate(getName());
Chaturbate.INSTANCE.streamResolutionCache.invalidate(getName());
site.streamInfoCache.invalidate(getName());
site.streamResolutionCache.invalidate(getName());
}
public String getOnlineState() throws IOException, ExecutionException {
@ -85,20 +78,20 @@ public class ChaturbateModel extends AbstractModel {
@Override
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";
}
public StreamInfo getStreamInfo() throws IOException, ExecutionException {
return Chaturbate.INSTANCE.getStreamInfo(getName());
return site.getStreamInfo(getName());
}
public MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException, ExecutionException {
return Chaturbate.INSTANCE.getMasterPlaylist(getName());
return site.getMasterPlaylist(getName());
}
@Override
public void receiveTip(int tokens) throws IOException {
Chaturbate.INSTANCE.sendTip(getName(), tokens);
site.sendTip(getName(), tokens);
}
@Override
@ -123,167 +116,52 @@ public class ChaturbateModel extends AbstractModel {
return sources;
}
private static class Chaturbate {
private static final transient Logger LOG = LoggerFactory.getLogger(Chaturbate.class);
@Override
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();
private 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);
}
});
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;
String url = null;
if(follow) {
url = BASE_URI + "/follow/follow/" + getName() + "/";
} else {
url = BASE_URI + "/follow/unfollow/" + getName() + "/";
}
public void sendTip(String name, int tokens) throws IOException {
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
RequestBody body = new FormBody.Builder()
.add("csrfmiddlewaretoken", client.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 = client.execute(req, true)) {
if(!response.isSuccessful()) {
throw new IOException(response.code() + " " + response.message());
}
}
}
}
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();
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", getUrl())
.header("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:59.0) Gecko/20100101 Firefox/59.0")
.header("X-CSRFToken", ((ChaturbateHttpClient)site.getHttpClient()).getToken())
.header("X-Requested-With", "XMLHttpRequest")
.build();
resp = site.getHttpClient().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);
return true;
}
} else {
resp.close();
throw new IOException("HTTP status " + resp.code() + " " + resp.message());
}
}
}

View File

@ -1,7 +1,5 @@
package ctbrec.sites.chaturbate;
import static ctbrec.ui.CamrecApplication.BASE_URI;
import java.util.ArrayList;
import java.util.List;
@ -16,16 +14,15 @@ import ctbrec.ui.HtmlParser;
public class ChaturbateModelParser {
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<>();
Elements cells = HtmlParser.getTags(html, "ul.list > li");
for (Element cell : cells) {
String cellHtml = cell.html();
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.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"));
Elements tags = HtmlParser.getTags(cellHtml, "div.details ul.subject li a");
if(tags != null) {

View File

@ -29,7 +29,7 @@ public class ChaturbateTabProvider extends TabProvider {
tabs.add(createTab("Male", BASE_URI + "/male-cams/"));
tabs.add(createTab("Couples", BASE_URI + "/couple-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.setScene(scene);
tabs.add(followedTab);
@ -37,8 +37,8 @@ public class ChaturbateTabProvider extends TabProvider {
}
private Tab createTab(String title, String url) {
ChaturbateUpdateService updateService = new ChaturbateUpdateService(url, false);
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService);
ChaturbateUpdateService updateService = new ChaturbateUpdateService(url, false, chaturbate);
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, chaturbate);
tab.setRecorder(recorder);
return tab;
}

View File

@ -10,7 +10,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.ui.PaginatedScheduledService;
import javafx.concurrent.Task;
import okhttp3.Request;
@ -21,10 +20,12 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
private static final transient Logger LOG = LoggerFactory.getLogger(ChaturbateUpdateService.class);
private String url;
private boolean loginRequired;
private Chaturbate chaturbate;
public ChaturbateUpdateService(String url, boolean loginRequired) {
public ChaturbateUpdateService(String url, boolean loginRequired, Chaturbate chaturbate) {
this.url = url;
this.loginRequired = loginRequired;
this.chaturbate = chaturbate;
ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
@ -46,9 +47,9 @@ public class ChaturbateUpdateService extends PaginatedScheduledService {
String url = ChaturbateUpdateService.this.url + "?page="+page+"&keywords=&_=" + System.currentTimeMillis();
LOG.debug("Fetching page {}", url);
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()) {
List<Model> models = ChaturbateModelParser.parseModels(response.body().string());
List<Model> models = ChaturbateModelParser.parseModels(chaturbate, response.body().string());
response.close();
return models;
} else {

View File

@ -37,7 +37,7 @@ public class FriendsUpdateService extends PaginatedScheduledService {
.url(url)
.header("Referer", myFreeCams.getBaseUrl())
.build();
Response resp = MyFreeCams.httpClient.newCall(req).execute();
Response resp = myFreeCams.getHttpClient().execute(req, true);
if(resp.isSuccessful()) {
String json = resp.body().string().substring(4);
JSONObject object = new JSONObject(json);

View File

@ -1,35 +1,18 @@
package ctbrec.sites.mfc;
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.ui.CookieJarImpl;
import ctbrec.sites.Site;
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 {
private static final transient Logger LOG = LoggerFactory.getLogger(MyFreeCams.class);
public static final String BASE_URI = "https://www.myfreecams.com";
private Recorder recorder;
private MyFreeCamsClient client;
public static OkHttpClient httpClient = new OkHttpClient.Builder()
.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();
private MyFreeCamsHttpClient httpClient = new MyFreeCamsHttpClient();
public MyFreeCams() throws IOException {
client = MyFreeCamsClient.getInstance();
@ -39,25 +22,9 @@ public class MyFreeCams implements Site {
login();
}
@Override
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
@ -67,7 +34,7 @@ public class MyFreeCams implements Site {
@Override
public String getBaseUrl() {
return "https://www.myfreecams.com";
return BASE_URI;
}
@Override
@ -87,9 +54,29 @@ public class MyFreeCams implements Site {
@Override
public MyFreeCamsModel createModel(String name) {
MyFreeCamsModel model = new MyFreeCamsModel();
MyFreeCamsModel model = new MyFreeCamsModel(this);
model.setName(name);
model.setUrl("https://profiles.myfreecams.com/" + name);
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();
}
}

View File

@ -62,7 +62,7 @@ public class MyFreeCamsClient {
public void start() throws IOException {
running = true;
ServerConfig serverConfig = new ServerConfig(MyFreeCams.httpClient);
ServerConfig serverConfig = new ServerConfig(mfc.getHttpClient());
List<String> websocketServers = new ArrayList<String>(serverConfig.wsServers.keySet());
String server = websocketServers.get((int) (Math.random()*websocketServers.size()));
String wsUrl = "ws://" + server + ".myfreecams.com:8080/fcsl";
@ -89,7 +89,7 @@ public class MyFreeCamsClient {
}
private WebSocket createWebSocket(Request req) {
WebSocket ws = MyFreeCams.httpClient.newWebSocket(req, new WebSocketListener() {
WebSocket ws = mfc.getHttpClient().newWebSocket(req, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
@ -111,7 +111,7 @@ public class MyFreeCamsClient {
super.onClosed(webSocket, code, reason);
LOG.trace("close: {} {}", code, reason);
running = false;
MyFreeCams.httpClient.dispatcher().executorService().shutdownNow();
mfc.getHttpClient().shutdown();
}
private StringBuilder msgBuffer = new StringBuilder();
@ -190,7 +190,7 @@ public class MyFreeCamsClient {
String url = base + "?respkey="+respkey+"&opts="+opts+"&serv="+serv+"&type="+type;
Request req = new Request.Builder().url(url).build();
LOG.debug("Requesting EXTDATA {}", url);
Response resp = MyFreeCams.httpClient.newCall(req).execute();
Response resp = mfc.getHttpClient().execute(req);
if(resp.isSuccessful()) {
parseExtDataSessionStates(resp.body().string());
@ -354,4 +354,14 @@ public class MyFreeCamsClient {
public void execute(Runnable 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("#####################");
}
}
}
}

View File

@ -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);
}
}

View File

@ -32,6 +32,11 @@ public class MyFreeCamsModel extends AbstractModel {
private double camScore;
private State state;
private int resolution[];
private MyFreeCams site;
MyFreeCamsModel(MyFreeCams site) {
this.site = site;
}
@Override
public boolean isOnline() throws IOException, ExecutionException, InterruptedException {
@ -78,7 +83,7 @@ public class MyFreeCamsModel extends AbstractModel {
}
LOG.debug("Loading master playlist {}", hlsUrl);
Request req = new Request.Builder().url(hlsUrl).build();
Response response = MyFreeCams.httpClient.newCall(req).execute();
Response response = site.getHttpClient().execute(req);
try {
InputStream inputStream = response.body().byteStream();
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8);
@ -97,7 +102,7 @@ public class MyFreeCamsModel extends AbstractModel {
@Override
public void receiveTip(int tokens) throws IOException {
throw new RuntimeException("Not implemented");
throw new RuntimeException("Not implemented for MFC");
}
@Override
@ -155,8 +160,29 @@ public class MyFreeCamsModel extends AbstractModel {
// stream url
Integer camserv = state.getU().getCamserv();
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";
setStreamUrl(hlsUrl);
}
}
@Override
public boolean follow() {
return false;
}
@Override
public boolean unfollow() {
return false;
}
}

View File

@ -26,18 +26,17 @@ public class MyFreeCamsTabProvider extends TabProvider {
List<Tab> tabs = new ArrayList<>();
PaginatedScheduledService updateService = new OnlineCamsUpdateService();
ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService);
ThumbOverviewTab online = new ThumbOverviewTab("Online", updateService, myFreeCams);
online.setRecorder(recorder);
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
tabs.add(online);
updateService = new FriendsUpdateService(myFreeCams);
ThumbOverviewTab friends = new ThumbOverviewTab("Friends", updateService);
ThumbOverviewTab friends = new ThumbOverviewTab("Friends", updateService, myFreeCams);
friends.setRecorder(recorder);
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(10)));
tabs.add(friends);
return tabs;
}
}

View File

@ -1,12 +1,13 @@
package ctbrec.sites.mfc;
import ctbrec.sites.Site;
import ctbrec.ui.PaginatedScheduledService;
import ctbrec.ui.ThumbOverviewTab;
public class OnlineModelsTab extends ThumbOverviewTab {
public OnlineModelsTab(String title, PaginatedScheduledService updateService) {
super(title, updateService);
public OnlineModelsTab(String title, PaginatedScheduledService updateService, Site site) {
super(title, updateService, site);
}
}

View File

@ -9,7 +9,7 @@ import java.util.Map;
import org.json.JSONArray;
import org.json.JSONObject;
import okhttp3.OkHttpClient;
import ctbrec.io.HttpClient;
import okhttp3.Request;
import okhttp3.Response;
@ -23,9 +23,9 @@ public class ServerConfig {
Map<String, String> wzobsServers;
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();
Response resp = client.newCall(req).execute();
Response resp = client.execute(req);
String json = resp.body().string();
JSONObject serverConfig = new JSONObject(json);

View File

@ -7,7 +7,6 @@ import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
@ -20,26 +19,31 @@ import com.squareup.moshi.Moshi;
import com.squareup.moshi.Types;
import ctbrec.Config;
import ctbrec.Site;
import ctbrec.Version;
import ctbrec.io.HttpClient;
import ctbrec.recorder.LocalRecorder;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.RemoteRecorder;
import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCams;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
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.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.image.Image;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import okhttp3.Request;
import okhttp3.Response;
@ -47,12 +51,9 @@ import okhttp3.Response;
public class CamrecApplication extends Application {
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 Recorder recorder;
private HttpClient client;
static HostServices hostServices;
private SettingsTab settingsTab;
private TabPane tabPane = new TabPane();
@ -65,12 +66,13 @@ public class CamrecApplication extends Application {
loadConfig();
bus = new AsyncEventBus(Executors.newSingleThreadExecutor());
hostServices = getHostServices();
client = HttpClient.getInstance();
createRecorder();
//site = new Chaturbate();
// site = new Chaturbate();
site = new MyFreeCams();
site.setRecorder(recorder);
// TODO move this to Chaturbate class doInitialLogin();
if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
site.login();
}
createGui(primaryStage);
checkForUpdates();
}
@ -132,7 +134,7 @@ public class CamrecApplication extends Application {
public void run() {
settingsTab.saveConfig();
recorder.shutdown();
client.shutdown();
site.shutdown();
try {
Config.getInstance().save();
LOG.info("Shutdown complete. Goodbye!");
@ -152,103 +154,43 @@ public class CamrecApplication extends Application {
}.start();
});
// TODO think about a solution, which works for all sites
// String username = Config.getInstance().getSettings().username;
// if(username != null && !username.trim().isEmpty()) {
// double fontSize = tabPane.getTabMaxHeight() / 2 - 1;
// Button buyTokens = new Button("Buy Tokens");
// buyTokens.setFont(Font.font(fontSize));
// buyTokens.setOnAction((e) -> DesktopIntergation.open(AFFILIATE_LINK));
// buyTokens.setMaxHeight(tabPane.getTabMaxHeight());
// TokenLabel tokenBalance = new TokenLabel();
// tokenPanel = new HBox(5, tokenBalance, buyTokens);
// //tokenPanel.setBackground(new Background(new BackgroundFill(Color.GREEN, CornerRadii.EMPTY, new Insets(0))));
// tokenPanel.setAlignment(Pos.BASELINE_RIGHT);
// tokenPanel.setMaxHeight(tabPane.getTabMaxHeight());
// tokenPanel.setMaxWidth(200);
// tokenBalance.setFont(Font.font(fontSize));
// HBox.setMargin(tokenBalance, new Insets(0, 5, 0, 0));
// HBox.setMargin(buyTokens, new Insets(0, 5, 0, 0));
// for (Node node : tabPane.getChildrenUnmodifiable()) {
// if(node.getStyleClass().contains("tab-header-area")) {
// Parent header = (Parent) node;
// for (Node nd : header.getChildrenUnmodifiable()) {
// if(nd.getStyleClass().contains("tab-header-background")) {
// StackPane pane = (StackPane) nd;
// 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);
String username = Config.getInstance().getSettings().username;
if(username != null && !username.trim().isEmpty()) {
double fontSize = tabPane.getTabMaxHeight() / 2 - 1;
Button buyTokens = new Button("Buy Tokens");
buyTokens.setFont(Font.font(fontSize));
buyTokens.setOnAction((e) -> DesktopIntergation.open(site.getBuyTokensLink()));
buyTokens.setMaxHeight(tabPane.getTabMaxHeight());
TokenLabel tokenBalance = new TokenLabel(site);
tokenPanel = new HBox(5, tokenBalance, buyTokens);
tokenPanel.setAlignment(Pos.BASELINE_RIGHT);
tokenPanel.setMaxHeight(tabPane.getTabMaxHeight());
tokenPanel.setMaxWidth(200);
tokenBalance.setFont(Font.font(fontSize));
HBox.setMargin(tokenBalance, new Insets(0, 5, 0, 0));
HBox.setMargin(buyTokens, new Insets(0, 5, 0, 0));
for (Node node : tabPane.getChildrenUnmodifiable()) {
if(node.getStyleClass().contains("tab-header-area")) {
Parent header = (Parent) node;
for (Node nd : header.getChildrenUnmodifiable()) {
if(nd.getStyleClass().contains("tab-header-background")) {
StackPane pane = (StackPane) nd;
StackPane.setAlignment(tokenPanel, Pos.CENTER_RIGHT);
pane.getChildren().add(tokenPanel);
}
}
};
}.start();
}
}
tokenBalance.loadBalance();
}
}
private void createRecorder() {
if(config.getSettings().localRecording) {
if (config.getSettings().localRecording) {
recorder = new LocalRecorder(config);
} else {
recorder = new RemoteRecorder(config, client);
recorder = new RemoteRecorder(config, site.getHttpClient());
}
}
@ -275,8 +217,8 @@ public class CamrecApplication extends Application {
try {
String url = "https://api.github.com/repos/0xboobface/ctbrec/releases";
Request request = new Request.Builder().url(url).build();
Response response = client.execute(request);
if(response.isSuccessful()) {
Response response = site.getHttpClient().execute(request);
if (response.isSuccessful()) {
Moshi moshi = new Moshi.Builder().build();
Type type = Types.newParameterizedType(List.class, Release.class);
JsonAdapter<List<Release>> adapter = moshi.adapter(type);
@ -284,7 +226,7 @@ public class CamrecApplication extends Application {
Release latest = releases.get(0);
Version latestVersion = latest.getVersion();
Version ctbrecVersion = getVersion();
if(latestVersion.compareTo(ctbrecVersion) > 0) {
if (latestVersion.compareTo(ctbrecVersion) > 0) {
LOG.debug("Update available {} < {}", ctbrecVersion, latestVersion);
Platform.runLater(() -> tabPane.getTabs().add(new UpdateTab(latest)));
} else {
@ -303,10 +245,10 @@ public class CamrecApplication extends Application {
}
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");
} else {
try(InputStream is = getClass().getClassLoader().getResourceAsStream("version")) {
try (InputStream is = getClass().getClassLoader().getResourceAsStream("version")) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
String versionString = reader.readLine();
Version version = Version.of(versionString);

View File

@ -2,6 +2,7 @@ package ctbrec.ui;
import ctbrec.sites.chaturbate.Chaturbate;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
@ -44,7 +45,7 @@ public class DonateTabFx extends Tab {
ImageView tokenImage = new ImageView(getClass().getResource("/html/token.png").toString());
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);
tokenBox.setAlignment(Pos.TOP_CENTER);
Label tokenDesc = new Label("If you buy tokens by using this button,\n"

View File

@ -1,5 +1,6 @@
package ctbrec.ui;
import ctbrec.sites.chaturbate.Chaturbate;
import ctbrec.sites.chaturbate.ChaturbateUpdateService;
import javafx.concurrent.WorkerStateEvent;
import javafx.geometry.Insets;
@ -16,8 +17,8 @@ public class FollowedTab extends ThumbOverviewTab {
private String onlineUrl;
private String offlineUrl;
public FollowedTab(String title, String url) {
super(title, new ChaturbateUpdateService(url, true));
public FollowedTab(String title, String url, Chaturbate chaturbate) {
super(title, new ChaturbateUpdateService(url, true, chaturbate), chaturbate);
onlineUrl = url;
offlineUrl = url + "offline/";

View File

@ -126,4 +126,14 @@ public class JavaFxModel extends AbstractModel {
public int[] getStreamResolution(boolean b) throws ExecutionException {
return delegate.getStreamResolution(b);
}
@Override
public boolean follow() throws IOException {
return delegate.follow();
}
@Override
public boolean unfollow() throws IOException {
return delegate.unfollow();
}
}

View File

@ -19,9 +19,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Model;
import ctbrec.Site;
import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@ -266,7 +265,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return;
}
HttpClient client = HttpClient.getInstance();
Function<Model, Void> onSuccess = (m) -> {
try {
recorder.switchStreamSource(m);
@ -281,7 +279,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
showStreamSwitchErrorDialog(t);
return null;
};
StreamSourceSelectionDialog.show(fxModel.getDelegate(), client, onSuccess, onFail);
StreamSourceSelectionDialog.show(fxModel.getDelegate(), site.getHttpClient(), onSuccess, onFail);
}
private void showStreamSwitchErrorDialog(Throwable throwable) {

View File

@ -1,7 +1,6 @@
package ctbrec.ui;
import static javafx.scene.control.ButtonType.NO;
import static javafx.scene.control.ButtonType.YES;
import static javafx.scene.control.ButtonType.*;
import java.io.File;
import java.io.FileNotFoundException;
@ -32,10 +31,9 @@ import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import ctbrec.Recording.STATUS;
import ctbrec.Site;
import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.download.MergedHlsDownload;
import ctbrec.sites.Site;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
@ -306,7 +304,7 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
@Override
public void run() {
try {
MergedHlsDownload download = new MergedHlsDownload(HttpClient.getInstance());
MergedHlsDownload download = new MergedHlsDownload(site.getHttpClient());
download.start(url.toString(), target, (progress) -> {
Platform.runLater(() -> {
if (progress == 100) {

View File

@ -13,6 +13,7 @@ import com.sun.javafx.collections.ObservableListWrapper;
import ctbrec.Config;
import ctbrec.Hmac;
import ctbrec.sites.chaturbate.Chaturbate;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
@ -57,7 +58,6 @@ public class SettingsTab extends Tab implements TabSelectionListener {
private CheckBox loadResolution;
private CheckBox secureCommunication = new CheckBox();
private CheckBox chooseStreamQuality = new CheckBox();
private CheckBox autoRecordFollowed = new CheckBox();
private CheckBox multiplePlayers = new CheckBox();
private PasswordField password;
private RadioButton recordLocal;
@ -188,25 +188,9 @@ public class SettingsTab extends Tab implements TabSelectionListener {
layout.add(password, 1, 1);
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);
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(password, new Insets(0, 0, 0, CHECKBOX_MARGIN));
GridPane.setMargin(createAccount, new Insets(0, 0, 0, CHECKBOX_MARGIN));

View File

@ -45,9 +45,6 @@ import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.util.Duration;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class ThumbCell extends StackPane {
@ -347,91 +344,55 @@ public class ThumbCell extends StackPane {
}
private void _startStopAction(Model model, boolean start) {
new Thread() {
@Override
public void run() {
try {
if(start) {
recorder.startRecording(model);
setRecording(true);
} else {
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);
new Thread(() -> {
try {
if(start) {
recorder.startRecording(model);
setRecording(true);
} else {
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);
}
}.start();
}).start();
}
void follow(boolean follow) {
setCursor(Cursor.WAIT);
new Thread() {
@Override
public void run() {
try {
Request req = new Request.Builder().url(model.getUrl()).build();
Response resp = HttpClient.getInstance().execute(req);
resp.close();
String url = null;
if(follow) {
url = CamrecApplication.BASE_URI + "/follow/follow/" + model.getName() + "/";
} else {
url = CamrecApplication.BASE_URI + "/follow/unfollow/" + model.getName() + "/";
new Thread(() -> {
try {
if(follow) {
model.follow();
} else {
boolean unfollowed = model.unfollow();
if(unfollowed) {
Platform.runLater(() -> thumbCellList.remove(ThumbCell.this));
}
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() {

View File

@ -29,6 +29,8 @@ import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCamsClient;
import javafx.collections.ObservableList;
import javafx.concurrent.Worker.State;
import javafx.concurrent.WorkerStateEvent;
@ -71,19 +73,20 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
ReentrantLock gridLock = new ReentrantLock();
ScrollPane scrollPane = new ScrollPane();
boolean loginRequired;
HttpClient client = HttpClient.getInstance();
HBox pagination;
TextField pageInput = new TextField(Integer.toString(1));
Button pagePrev = new Button("");
Button pageNext = new Button("");
private volatile boolean updatesSuspended = false;
ContextMenu popup;
Site site;
private ComboBox<Integer> thumbWidth;
public ThumbOverviewTab(String title, PaginatedScheduledService updateService) {
public ThumbOverviewTab(String title, PaginatedScheduledService updateService, Site site) {
super(title);
this.updateService = updateService;
this.site = site;
setClosable(false);
createGui();
initializeUpdateService();
@ -253,7 +256,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
}
if(!found) {
ThumbCell newCell = createThumbCell(this, model, recorder, client);
ThumbCell newCell = createThumbCell(this, model, recorder, site.getHttpClient());
newCell.setIndex(index);
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 newCell = new ThumbCell(this, model, recorder, client);
ThumbCell newCell = new ThumbCell(this, model, recorder, site.getHttpClient());
newCell.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
suspendUpdates(true);
popup = createContextMenu(newCell);
@ -328,7 +331,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
MenuItem sendTip = new MenuItem("Send Tip");
sendTip.setOnAction((e) -> {
TipDialog tipDialog = new TipDialog(cell.getModel());
TipDialog tipDialog = new TipDialog(site, cell.getModel());
tipDialog.showAndWait();
String tipText = tipDialog.getResult();
if(tipText != null) {
@ -340,7 +343,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
event.put("event", "tokens.sent");
event.put("amount", tokens);
CamrecApplication.bus.post(event);
} catch (IOException e1) {
} catch (Exception e1) {
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR);
alert.setTitle("Error");
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.setAutoHide(true);
contextMenu.setHideOnEscape(true);
contextMenu.setAutoFix(true);
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;
}

View File

@ -1,30 +1,27 @@
package ctbrec.ui;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.sites.Site;
import ctbrec.sites.chaturbate.Chaturbate;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextInputDialog;
import okhttp3.Request;
import okhttp3.Response;
public class TipDialog extends TextInputDialog {
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");
loadTokenBalance();
setHeaderText("Loading token balance…");
@ -38,27 +35,7 @@ public class TipDialog extends TextInputDialog {
@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);
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());
}
return site.getTokenBalance();
} else {
return 1_000_000;
}
@ -78,7 +55,7 @@ public class TipDialog extends TextInputDialog {
buyTokens.showAndWait();
TipDialog.this.close();
if(buyTokens.getResult() == ButtonType.YES) {
DesktopIntergation.open(CamrecApplication.AFFILIATE_LINK);
DesktopIntergation.open(Chaturbate.AFFILIATE_LINK);
}
} else {
getEditor().setDisable(false);

View File

@ -2,17 +2,26 @@ package ctbrec.ui;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.eventbus.Subscribe;
import ctbrec.sites.Site;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.Label;
public class TokenLabel extends Label {
private static final transient Logger LOG = LoggerFactory.getLogger(TokenLabel.class);
private int tokens = -1;
private Site site;
public TokenLabel() {
public TokenLabel(Site site) {
this.site = site;
setText("Tokens: loading…");
CamrecApplication.bus.register(new Object() {
@Subscribe
@ -42,4 +51,29 @@ public class TokenLabel extends Label {
private void updateText() {
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();
}
}