forked from j62/ctbrec
1
0
Fork 0

First working implementation for Stripchat

This commit is contained in:
0xboobface 2020-02-23 18:47:37 +01:00
parent 9e2a2d8cf8
commit e4b9da7685
14 changed files with 1006 additions and 1 deletions

View File

@ -47,6 +47,7 @@ import ctbrec.sites.flirt4free.Flirt4Free;
import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.jasmin.LiveJasmin;
import ctbrec.sites.mfc.MyFreeCams; import ctbrec.sites.mfc.MyFreeCams;
import ctbrec.sites.streamate.Streamate; import ctbrec.sites.streamate.Streamate;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.news.NewsTab; import ctbrec.ui.news.NewsTab;
import ctbrec.ui.settings.SettingsTab; import ctbrec.ui.settings.SettingsTab;
import ctbrec.ui.tabs.DonateTabFx; import ctbrec.ui.tabs.DonateTabFx;
@ -98,6 +99,7 @@ public class CamrecApplication extends Application {
sites.add(new LiveJasmin()); sites.add(new LiveJasmin());
sites.add(new MyFreeCams()); sites.add(new MyFreeCams());
sites.add(new Streamate()); sites.add(new Streamate());
sites.add(new Stripchat());
loadConfig(); loadConfig();
registerAlertSystem(); registerAlertSystem();
registerActiveRecordingsCounter(); registerActiveRecordingsCounter();

View File

@ -10,6 +10,7 @@ import ctbrec.sites.flirt4free.Flirt4Free;
import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.jasmin.LiveJasmin;
import ctbrec.sites.mfc.MyFreeCams; import ctbrec.sites.mfc.MyFreeCams;
import ctbrec.sites.streamate.Streamate; import ctbrec.sites.streamate.Streamate;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.sites.bonga.BongaCamsSiteUi; import ctbrec.ui.sites.bonga.BongaCamsSiteUi;
import ctbrec.ui.sites.cam4.Cam4SiteUi; import ctbrec.ui.sites.cam4.Cam4SiteUi;
import ctbrec.ui.sites.camsoda.CamsodaSiteUi; import ctbrec.ui.sites.camsoda.CamsodaSiteUi;
@ -19,6 +20,7 @@ import ctbrec.ui.sites.flirt4free.Flirt4FreeSiteUi;
import ctbrec.ui.sites.jasmin.LiveJasminSiteUi; import ctbrec.ui.sites.jasmin.LiveJasminSiteUi;
import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi; import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi;
import ctbrec.ui.sites.streamate.StreamateSiteUi; import ctbrec.ui.sites.streamate.StreamateSiteUi;
import ctbrec.ui.sites.stripchat.StripchatSiteUi;
public class SiteUiFactory { public class SiteUiFactory {
@ -31,6 +33,9 @@ public class SiteUiFactory {
private static LiveJasminSiteUi jasminSiteUi; private static LiveJasminSiteUi jasminSiteUi;
private static MyFreeCamsSiteUi mfcSiteUi; private static MyFreeCamsSiteUi mfcSiteUi;
private static StreamateSiteUi streamateSiteUi; private static StreamateSiteUi streamateSiteUi;
private static StripchatSiteUi stripchatSiteUi;
private SiteUiFactory () {}
public static synchronized SiteUI getUi(Site site) { public static synchronized SiteUI getUi(Site site) {
if (site instanceof BongaCams) { if (site instanceof BongaCams) {
@ -78,6 +83,11 @@ public class SiteUiFactory {
jasminSiteUi = new LiveJasminSiteUi((LiveJasmin) site); jasminSiteUi = new LiveJasminSiteUi((LiveJasmin) site);
} }
return jasminSiteUi; return jasminSiteUi;
} else if (site instanceof Stripchat) {
if (stripchatSiteUi == null) {
stripchatSiteUi = new StripchatSiteUi((Stripchat) site);
}
return stripchatSiteUi;
} }
throw new RuntimeException("Unknown site " + site.getName()); throw new RuntimeException("Unknown site " + site.getName());
} }

View File

@ -0,0 +1,86 @@
package ctbrec.ui.sites.stripchat;
import ctbrec.Config;
import ctbrec.Settings;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.settings.SettingsTab;
import ctbrec.ui.sites.AbstractConfigUI;
import javafx.geometry.Insets;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
public class StripchatConfigUI extends AbstractConfigUI {
private Stripchat stripchat;
public StripchatConfigUI(Stripchat stripchat) {
this.stripchat = stripchat;
}
@Override
public Parent createConfigPanel() {
GridPane layout = SettingsTab.createGridLayout();
Settings settings = Config.getInstance().getSettings();
int row = 0;
Label l = new Label("Active");
layout.add(l, 0, row);
CheckBox enabled = new CheckBox();
enabled.setSelected(!settings.disabledSites.contains(stripchat.getName()));
enabled.setOnAction(e -> {
if(enabled.isSelected()) {
settings.disabledSites.remove(stripchat.getName());
} else {
settings.disabledSites.add(stripchat.getName());
}
save();
});
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
layout.add(enabled, 1, row++);
layout.add(new Label("Stripchat User"), 0, row);
TextField username = new TextField(Config.getInstance().getSettings().stripchatUsername);
username.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().stripchatUsername)) {
Config.getInstance().getSettings().stripchatUsername = username.getText();
stripchat.getHttpClient().logout();
save();
}
});
GridPane.setFillWidth(username, true);
GridPane.setHgrow(username, Priority.ALWAYS);
GridPane.setColumnSpan(username, 2);
layout.add(username, 1, row++);
layout.add(new Label("Stripchat Password"), 0, row);
PasswordField password = new PasswordField();
password.setText(Config.getInstance().getSettings().stripchatPassword);
password.textProperty().addListener((ob, o, n) -> {
if(!n.equals(Config.getInstance().getSettings().stripchatPassword)) {
Config.getInstance().getSettings().stripchatPassword = password.getText();
stripchat.getHttpClient().logout();
save();
}
});
GridPane.setFillWidth(password, true);
GridPane.setHgrow(password, Priority.ALWAYS);
GridPane.setColumnSpan(password, 2);
layout.add(password, 1, row++);
Button createAccount = new Button("Create new Account");
createAccount.setOnAction(e -> DesktopIntegration.open(stripchat.getAffiliateLink()));
layout.add(createAccount, 1, row);
GridPane.setColumnSpan(createAccount, 2);
GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
return layout;
}
}

View File

@ -0,0 +1,79 @@
package ctbrec.ui.sites.stripchat;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.tabs.FollowedTab;
import ctbrec.ui.tabs.ThumbOverviewTab;
import javafx.concurrent.WorkerStateEvent;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.RadioButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.HBox;
public class StripchatFollowedTab extends ThumbOverviewTab implements FollowedTab {
private Label status;
boolean showOnline = true;
public StripchatFollowedTab(String title, Stripchat stripchat) {
super(title, new StripchatFollowedUpdateService(stripchat), stripchat);
status = new Label("Logging in...");
grid.getChildren().add(status);
}
@Override
protected void createGui() {
super.createGui();
addOnlineOfflineSelector();
}
private void addOnlineOfflineSelector() {
ToggleGroup group = new ToggleGroup();
RadioButton online = new RadioButton("online");
online.setToggleGroup(group);
RadioButton offline = new RadioButton("offline");
offline.setToggleGroup(group);
pagination.getChildren().add(online);
pagination.getChildren().add(offline);
HBox.setMargin(online, new Insets(5, 5, 5, 40));
HBox.setMargin(offline, new Insets(5, 5, 5, 5));
online.setSelected(true);
group.selectedToggleProperty().addListener(e -> {
queue.clear();
((StripchatFollowedUpdateService)updateService).showOnline(online.isSelected());
updateService.restart();
});
}
@Override
protected void onSuccess() {
grid.getChildren().remove(status);
super.onSuccess();
}
@Override
protected void onFail(WorkerStateEvent event) {
String msg = "";
if (event.getSource().getException() != null) {
msg = ": " + event.getSource().getException().getMessage();
}
status.setText("Login failed" + msg);
super.onFail(event);
}
@Override
public void selected() {
status.setText("Logging in...");
super.selected();
}
public void setScene(Scene scene) {
scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
if (this.isSelected() && event.getCode() == KeyCode.DELETE) {
follow(selectedThumbCells, false);
}
});
}
}

View File

@ -0,0 +1,119 @@
package ctbrec.ui.sites.stripchat;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.json.JSONArray;
import org.json.JSONObject;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpException;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.sites.stripchat.StripchatHttpClient;
import ctbrec.sites.stripchat.StripchatModel;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.tabs.PaginatedScheduledService;
import javafx.concurrent.Task;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class StripchatFollowedUpdateService extends PaginatedScheduledService {
private Stripchat stripchat;
private boolean showOnline = true;
public StripchatFollowedUpdateService(Stripchat stripchat) {
this.stripchat = stripchat;
}
@Override
protected Task<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
JSONArray favoriteModelIds = loadFavoriteModelIds();
List<Model> models = loadModels(favoriteModelIds);
return models;
}
private List<Model> loadModels(JSONArray favoriteModelIds) throws IOException {
List<Model> models = new ArrayList<>();
StripchatHttpClient client = (StripchatHttpClient) stripchat.getHttpClient();
String url = stripchat.getBaseUrl() + "/api/front/users/list";
JSONObject requestParams = new JSONObject();
requestParams.put("userIds", favoriteModelIds);
requestParams.put("csrfToken", client.getCsrfToken());
requestParams.put("csrfTimestamp", client.getCsrfTimestamp());
requestParams.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp());
RequestBody body = RequestBody.create(MediaType.parse("application/json"), requestParams.toString());
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ORIGIN, Stripchat.BASE_URI)
.header(REFERER, Stripchat.BASE_URI + "/favorites")
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
.post(body)
.build();
try (Response response = stripchat.getHttpClient().execute(request)) {
if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string());
if (json.has("users")) {
JSONArray users = json.getJSONArray("users");
for (int i = 0; i < users.length(); i++) {
JSONObject user = users.getJSONObject(i);
StripchatModel model = stripchat.createModel(user.optString("username"));
model.setDescription(user.optString("description"));
model.setPreview(user.optString("previewUrlThumbBig"));
boolean online = Objects.equals(user.optString("status"), "public");
if (showOnline == online) {
models.add(model);
}
}
}
} else {
throw new HttpException(response.code(), response.message());
}
}
return models;
}
private JSONArray loadFavoriteModelIds() throws IOException {
SiteUiFactory.getUi(stripchat).login();
long userId = ((StripchatHttpClient) stripchat.getHttpClient()).getUserId();
String url = stripchat.getBaseUrl() + "/api/front/users/" + userId + "/favorites";
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ORIGIN, Stripchat.BASE_URI)
.header(REFERER, Stripchat.BASE_URI + "/favorites")
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
.build();
try (Response response = stripchat.getHttpClient().execute(request)) {
if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string());
if(json.has("userIds")) {
JSONArray userIds = json.getJSONArray("userIds");
return userIds;
} else {
return new JSONArray();
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
};
}
void showOnline(boolean online) {
this.showOnline = online;
}
}

View File

@ -0,0 +1,37 @@
package ctbrec.ui.sites.stripchat;
import java.io.IOException;
import ctbrec.sites.ConfigUI;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.sites.AbstractSiteUi;
import ctbrec.ui.tabs.TabProvider;
public class StripchatSiteUi extends AbstractSiteUi {
private StripchatTabProvider tabProvider;
private StripchatConfigUI configUi;
private Stripchat stripchat;
public StripchatSiteUi(Stripchat stripchat) {
this.stripchat = stripchat;
tabProvider = new StripchatTabProvider(stripchat);
configUi = new StripchatConfigUI(stripchat);
}
@Override
public TabProvider getTabProvider() {
return tabProvider;
}
@Override
public ConfigUI getConfigUI() {
return configUi;
}
@Override
public synchronized boolean login() throws IOException {
boolean automaticLogin = stripchat.login();
return automaticLogin;
}
}

View File

@ -0,0 +1,55 @@
package ctbrec.ui.sites.stripchat;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import ctbrec.recorder.Recorder;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.ui.tabs.TabProvider;
import ctbrec.ui.tabs.ThumbOverviewTab;
import javafx.scene.Scene;
import javafx.scene.control.Tab;
public class StripchatTabProvider extends TabProvider {
private String urlTemplate;
private Stripchat stripchat;
private Recorder recorder;
StripchatFollowedTab followedTab;
public StripchatTabProvider(Stripchat stripchat) {
this.stripchat = stripchat;
this.recorder = stripchat.getRecorder();
followedTab = new StripchatFollowedTab("Followed", stripchat);
urlTemplate = stripchat.getBaseUrl() + "/api/front/models?primaryTag={0}&sortBy=viewersRating&withMixedTags=true&parentTag=";
}
@Override
public List<Tab> getTabs(Scene scene) {
List<Tab> tabs = new ArrayList<>();
tabs.add(createTab("Girls", MessageFormat.format(urlTemplate, "girls")));
tabs.add(createTab("Couples", MessageFormat.format(urlTemplate, "couples")));
tabs.add(createTab("Boys", MessageFormat.format(urlTemplate, "men")));
tabs.add(createTab("Trans", MessageFormat.format(urlTemplate, "trans")));
followedTab.setRecorder(recorder);
followedTab.setScene(scene);
tabs.add(followedTab);
return tabs;
}
@Override
public Tab getFollowedTab() {
return followedTab;
}
private Tab createTab(String title, String url) {
StripchatUpdateService updateService = new StripchatUpdateService(url, false, stripchat);
ThumbOverviewTab tab = new ThumbOverviewTab(title, updateService, stripchat);
tab.setRecorder(recorder);
return tab;
}
}

View File

@ -0,0 +1,97 @@
package ctbrec.ui.sites.stripchat;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.StringUtil;
import ctbrec.io.HttpException;
import ctbrec.sites.stripchat.Stripchat;
import ctbrec.sites.stripchat.StripchatModel;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.tabs.PaginatedScheduledService;
import javafx.concurrent.Task;
import okhttp3.Request;
import okhttp3.Response;
public class StripchatUpdateService extends PaginatedScheduledService {
private static final Logger LOG = LoggerFactory.getLogger(StripchatUpdateService.class);
private String url;
private boolean loginRequired;
private Stripchat stripchat;
int modelsPerPage = 60;
public StripchatUpdateService(String url, boolean loginRequired, Stripchat stripchat) {
this.url = url;
this.loginRequired = loginRequired;
this.stripchat = stripchat;
}
@Override
protected Task<List<Model>> createTask() {
return new Task<List<Model>>() {
@Override
public List<Model> call() throws IOException {
List<Model> models = new ArrayList<>();
if(loginRequired && StringUtil.isBlank(ctbrec.Config.getInstance().getSettings().username)) {
return Collections.emptyList();
} else {
int offset = (getPage() - 1) * modelsPerPage;
int limit = offset + modelsPerPage;
String paginatedUrl = url + "&offset=" + offset + "&limit=" + limit;
LOG.debug("Fetching page {}", paginatedUrl);
if(loginRequired) {
SiteUiFactory.getUi(stripchat).login();
}
Request request = new Request.Builder()
.url(paginatedUrl)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.build();
try (Response response = stripchat.getHttpClient().execute(request)) {
if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string());
if(json.has("models")) {
JSONArray jsonModels = json.getJSONArray("models");
for (int i = 0; i < jsonModels.length(); i++) {
JSONObject jsonModel = jsonModels.getJSONObject(i);
try {
StripchatModel model = stripchat.createModel(jsonModel.getString("username"));
model.setDescription("");
model.setPreview(jsonModel.optString("snapshotUrl"));
model.setDisplayName(model.getName());
models.add(model);
} catch (Exception e) {
LOG.warn("Couldn't parse one of the models: {}", jsonModel, e);
}
}
return models;
} else {
LOG.debug("Response was not successful: {}", json);
return Collections.emptyList();
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
}
};
}
}

View File

@ -376,7 +376,7 @@ public class ThumbCell extends StackPane {
throw new HttpException(resp.code(), resp.message()); throw new HttpException(resp.code(), resp.message());
} }
} catch (IOException e) { } catch (IOException e) {
LOG.error("Error loading image", e); LOG.warn("Error loading thumbnail: {}", e.getLocalizedMessage());
} }
}); });
} }

View File

@ -112,6 +112,8 @@ public class Settings {
public String startTab = "Settings"; public String startTab = "Settings";
public String streamatePassword = ""; public String streamatePassword = "";
public String streamateUsername = ""; public String streamateUsername = "";
public String stripchatUsername = "";
public String stripchatPassword = "";
public boolean transportLayerSecurity = true; public boolean transportLayerSecurity = true;
public int thumbWidth = 180; public int thumbWidth = 180;
public boolean updateThumbnails = true; public boolean updateThumbnails = true;

View File

@ -5,6 +5,7 @@ public class HttpConstants {
public static final String ACCEPT = "Accept"; public static final String ACCEPT = "Accept";
public static final String ACCEPT_LANGUAGE = "Accept-Language"; public static final String ACCEPT_LANGUAGE = "Accept-Language";
public static final String CONNECTION = "Connection"; public static final String CONNECTION = "Connection";
public static final String CONTENT_TYPE = "Content-Type";
public static final String KEEP_ALIVE = "keep-alive"; public static final String KEEP_ALIVE = "keep-alive";
public static final String MIMETYPE_APPLICATION_JSON = "application/json"; public static final String MIMETYPE_APPLICATION_JSON = "application/json";
public static final String ORIGIN = "Origin"; public static final String ORIGIN = "Origin";

View File

@ -0,0 +1,183 @@
package ctbrec.sites.stripchat;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import ctbrec.sites.AbstractSite;
import okhttp3.Request;
import okhttp3.Response;
public class Stripchat extends AbstractSite {
private static final Logger LOG = LoggerFactory.getLogger(Stripchat.class);
public static final String BASE_URI = "https://stripchat.com";
private HttpClient httpClient;
@Override
public String getName() {
return "Stripchat";
}
@Override
public String getBaseUrl() {
return BASE_URI;
}
@Override
public String getAffiliateLink() {
return "https://go.strpjmp.com?creativeId=1&campaignId=1&sourceId=app&path=%2F&userId=cd0bfa6152f1e2b55f8e8218de2f32c7e6c41d9846a390a2113ed4a5edfc95d8";
}
@Override
public String getBuyTokensLink() {
return getAffiliateLink();
}
@Override
public StripchatModel createModel(String name) {
StripchatModel model = new StripchatModel();
model.setName(name);
model.setUrl(getBaseUrl() + "/" + name);
model.setSite(this);
return model;
}
@Override
public Double getTokenBalance() throws IOException {
if (!credentialsAvailable()) {
throw new IOException("Account settings not available");
}
String username = Config.getInstance().getSettings().camsodaUsername;
String url = BASE_URI + "/api/v1/user/" + username;
Request request = new Request.Builder().url(url).build();
try(Response response = getHttpClient().execute(request)) {
if(response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string());
if(json.has("user")) {
JSONObject user = json.getJSONObject("user");
if(user.has("tokens")) {
return (double) user.getInt("tokens");
}
}
} else {
throw new HttpException(response.code(), response.message());
}
}
throw new RuntimeException("Tokens not found in response");
}
@Override
public synchronized boolean login() throws IOException {
return credentialsAvailable() && getHttpClient().login();
}
@Override
public HttpClient getHttpClient() {
if(httpClient == null) {
httpClient = new StripchatHttpClient();
}
return httpClient;
}
@Override
public void init() throws IOException {
// noop
}
@Override
public void shutdown() {
if(httpClient != null) {
httpClient.shutdown();
}
}
@Override
public boolean supportsTips() {
return false;
}
@Override
public boolean supportsFollow() {
return true;
}
@Override
public boolean supportsSearch() {
return false;
}
@Override
public List<Model> search(String q) throws IOException, InterruptedException {
String url = BASE_URI + "/api/v1/browse/autocomplete?s=" + URLEncoder.encode(q, "utf-8");
Request req = new Request.Builder()
.url(url)
.addHeader(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.build();
try(Response response = getHttpClient().execute(req)) {
if(response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string());
if(json.optBoolean("status")) {
List<Model> models = new ArrayList<>();
JSONArray results = json.getJSONArray("results");
for (int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i);
StripchatModel model = createModel(result.getString("username"));
String thumb = result.getString("thumb");
if(thumb != null) {
model.setPreview("https:" + thumb);
}
if(result.has("display_name")) {
model.setDisplayName(result.getString("display_name"));
}
models.add(model);
}
return models;
} else {
LOG.warn("Search result: {}", json.toString(2));
return Collections.emptyList();
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
@Override
public boolean isSiteForModel(Model m) {
return m instanceof StripchatModel;
}
@Override
public boolean credentialsAvailable() {
String username = Config.getInstance().getSettings().stripchatUsername;
return username != null && !username.trim().isEmpty();
}
@Override
public Model createModelFromUrl(String url) {
Matcher m = Pattern.compile("https?://(?:.*?\\.)?stripchat.com/([^/]*?)/?").matcher(url);
if (m.matches()) {
String modelName = m.group(1);
return createModel(modelName);
} else {
return super.createModelFromUrl(url);
}
}
}

View File

@ -0,0 +1,129 @@
package ctbrec.sites.stripchat;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.io.HttpClient;
import ctbrec.io.HttpException;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class StripchatHttpClient extends HttpClient {
private static final Logger LOG = LoggerFactory.getLogger(StripchatHttpClient.class);
private long userId;
private String csrfToken;
private String csrfTimestamp;
private String csrfNotifyTimestamp;
public StripchatHttpClient() {
super("stripchat");
}
@Override
public boolean login() throws IOException {
if(loggedIn) {
return true;
}
// persisted cookies might let us log in
if(checkLoginSuccess()) {
loggedIn = true;
LOG.debug("Logged in with cookies");
return true;
}
if (csrfToken == null) {
loadCsrfToken();
}
String url = Stripchat.BASE_URI + "/api/front/auth/login";
JSONObject requestParams = new JSONObject();
requestParams.put("loginOrEmail", Config.getInstance().getSettings().stripchatUsername);
requestParams.put("password", Config.getInstance().getSettings().stripchatPassword);
requestParams.put("remember", true);
requestParams.put("csrfToken", csrfToken);
requestParams.put("csrfTimestamp", csrfTimestamp);
requestParams.put("csrfNotifyTimestamp", csrfNotifyTimestamp);
RequestBody body = RequestBody.create(MediaType.parse("application/json"), requestParams.toString());
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ORIGIN, Stripchat.BASE_URI)
.header(REFERER, Stripchat.BASE_URI)
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
.post(body)
.build();
try (Response response = execute(request)) {
if (response.isSuccessful()) {
JSONObject resp = new JSONObject(response.body().string());
if(resp.has("user")) {
JSONObject user = resp.getJSONObject("user");
userId = user.optLong("id");
return true;
} else {
return false;
}
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private void loadCsrfToken() throws IOException {
String url = Stripchat.BASE_URI + "/api/front/config";
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ORIGIN, Stripchat.BASE_URI)
.header(REFERER, Stripchat.BASE_URI)
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
.build();
try (Response response = execute(request)) {
if (response.isSuccessful()) {
JSONObject resp = new JSONObject(response.body().string());
JSONObject data = resp.getJSONObject("data");
csrfToken = data.optString("csrfToken");
csrfTimestamp = data.optString("csrfTimestamp");
csrfNotifyTimestamp = data.optString("csrfNotifyTimestamp");
} else {
throw new HttpException(response.code(), response.message());
}
}
}
/**
* check, if the login worked
* @throws IOException
*/
public boolean checkLoginSuccess() {
return userId > 0;
}
public long getUserId() {
return userId;
}
public String getCsrfNotifyTimestamp() {
return csrfNotifyTimestamp;
}
public String getCsrfTimestamp() {
return csrfTimestamp;
}
public String getCsrfToken() {
return csrfToken;
}
}

View File

@ -0,0 +1,205 @@
package ctbrec.sites.stripchat;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.xml.bind.JAXBException;
import org.json.JSONArray;
import org.json.JSONObject;
import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.PlaylistException;
import ctbrec.AbstractModel;
import ctbrec.Config;
import ctbrec.io.HttpException;
import ctbrec.recorder.download.StreamSource;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class StripchatModel extends AbstractModel {
private String status = null;
private int[] resolution = new int[] {0, 0};
@Override
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException {
if (ignoreCache || status == null) {
JSONObject jsonResponse = loadModelInfo();
if (jsonResponse.has("user")) {
JSONObject user = jsonResponse.getJSONObject("user");
status = user.optString("status");
}
}
return Objects.equals(status, "public");
}
private JSONObject loadModelInfo() throws IOException {
String url = getSite().getBaseUrl() + "/api/front/users/username/" + URLEncoder.encode(getName(), StandardCharsets.UTF_8.name());
Request req = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(REFERER, getUrl())
.build();
try (Response response = site.getHttpClient().execute(req)) {
if (response.isSuccessful()) {
JSONObject jsonResponse = new JSONObject(response.body().string());
return jsonResponse;
} else {
throw new HttpException(response.code(), response.message());
}
}
}
@Override
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
String name = URLEncoder.encode(getName(), StandardCharsets.UTF_8.name());
String url = getSite().getBaseUrl() + "/api/front/models/username/" + name + "/cam?triggerRequest=loadCam";
Request req = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(REFERER, getUrl())
.build();
try (Response response = site.getHttpClient().execute(req)) {
if (response.isSuccessful()) {
JSONObject jsonResponse = new JSONObject(response.body().string());
String streamName = jsonResponse.optString("streamName");
JSONObject viewServers = jsonResponse.getJSONObject("viewServers");
String serverName = viewServers.optString("flashphoner-hls");
JSONObject broadcastSettings = jsonResponse.getJSONObject("broadcastSettings");
List<StreamSource> sources = new ArrayList<>();
StreamSource best = new StreamSource();
best.height = broadcastSettings.optInt("height");
best.width = broadcastSettings.optInt("width");
best.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + streamName + "/" + streamName + ".m3u8";
sources.add(best);
Object resolutionObject = broadcastSettings.get("resolutions");
if (resolutionObject instanceof JSONObject) {
JSONObject resolutions = (JSONObject) resolutionObject;
JSONArray heights = resolutions.names();
for (int i = 0; i < heights.length(); i++) {
String h = heights.getString(i);
StreamSource streamSource = new StreamSource();
streamSource.height = Integer.parseInt(h.replace("p", ""));
streamSource.width = streamSource.height * best.getWidth() / best.getHeight();
String source = streamName + "-" + streamSource.height + "p";
streamSource.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + source + "/" + source + ".m3u8";
sources.add(streamSource);
}
}
return sources.stream().sorted((a, b) -> a.height - b.height).collect(Collectors.toList());
} else {
throw new HttpException(response.code(), response.message());
}
}
}
@Override
public void invalidateCacheEntries() {
status = null;
resolution = new int[] { 0, 0 };
}
@Override
public void receiveTip(Double tokens) throws IOException {
// not implemented
}
@Override
public int[] getStreamResolution(boolean failFast) throws ExecutionException {
if (!failFast) {
try {
List<StreamSource> sources = getStreamSources();
if (!sources.isEmpty()) {
StreamSource best = sources.get(sources.size() - 1);
resolution = new int[] { best.getWidth(), best.getHeight() };
}
} catch (IOException | ParseException | PlaylistException | JAXBException e) {
throw new ExecutionException(e);
}
}
return resolution;
}
@Override
public boolean follow() throws IOException {
JSONObject modelInfo = loadModelInfo();
JSONObject user = modelInfo.getJSONObject("user");
long modelId = user.optLong("id");
StripchatHttpClient client = (StripchatHttpClient) getSite().getHttpClient();
String url = Stripchat.BASE_URI + "/api/front/users/" + client.getUserId() + "/favorites/" + modelId;
JSONObject requestParams = new JSONObject();
requestParams.put("csrfToken", client.getCsrfToken());
requestParams.put("csrfTimestamp", client.getCsrfTimestamp());
requestParams.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp());
RequestBody body = RequestBody.create(MediaType.parse("application/json"), requestParams.toString());
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ORIGIN, Stripchat.BASE_URI)
.header(REFERER, Stripchat.BASE_URI)
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
.put(body)
.build();
try (Response response = client.execute(request)) {
if (response.isSuccessful()) {
return true;
} else {
throw new HttpException(response.code(), response.message());
}
}
}
@Override
public boolean unfollow() throws IOException {
JSONObject modelInfo = loadModelInfo();
JSONObject user = modelInfo.getJSONObject("user");
long modelId = user.optLong("id");
JSONArray favoriteIds = new JSONArray();
favoriteIds.put(modelId);
StripchatHttpClient client = (StripchatHttpClient) getSite().getHttpClient();
String url = Stripchat.BASE_URI + "/api/front/users/" + client.getUserId() + "/favorites";
JSONObject requestParams = new JSONObject();
requestParams.put("favoriteIds", favoriteIds);
requestParams.put("csrfToken", client.getCsrfToken());
requestParams.put("csrfTimestamp", client.getCsrfTimestamp());
requestParams.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp());
RequestBody body = RequestBody.create(MediaType.parse("application/json"), requestParams.toString());
Request request = new Request.Builder()
.url(url)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ORIGIN, Stripchat.BASE_URI)
.header(REFERER, Stripchat.BASE_URI)
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
.delete(body)
.build();
try (Response response = client.execute(request)) {
if (response.isSuccessful()) {
return true;
} else {
throw new HttpException(response.code(), response.message());
}
}
}
}