Add first implementation for SecretFriends
This commit is contained in:
parent
165eac94b9
commit
6ad7f47479
|
@ -22,6 +22,7 @@ import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -179,6 +180,7 @@ public class CamrecApplication extends Application {
|
||||||
sites.add(new LiveJasmin());
|
sites.add(new LiveJasmin());
|
||||||
sites.add(new MVLive());
|
sites.add(new MVLive());
|
||||||
sites.add(new MyFreeCams());
|
sites.add(new MyFreeCams());
|
||||||
|
sites.add(new SecretFriends());
|
||||||
sites.add(new Showup());
|
sites.add(new Showup());
|
||||||
sites.add(new Streamate());
|
sites.add(new Streamate());
|
||||||
sites.add(new Stripchat());
|
sites.add(new Stripchat());
|
||||||
|
|
|
@ -11,6 +11,7 @@ import ctbrec.sites.flirt4free.Flirt4Free;
|
||||||
import ctbrec.sites.jasmin.LiveJasmin;
|
import ctbrec.sites.jasmin.LiveJasmin;
|
||||||
import ctbrec.sites.manyvids.MVLive;
|
import ctbrec.sites.manyvids.MVLive;
|
||||||
import ctbrec.sites.mfc.MyFreeCams;
|
import ctbrec.sites.mfc.MyFreeCams;
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
import ctbrec.sites.showup.Showup;
|
import ctbrec.sites.showup.Showup;
|
||||||
import ctbrec.sites.streamate.Streamate;
|
import ctbrec.sites.streamate.Streamate;
|
||||||
import ctbrec.sites.stripchat.Stripchat;
|
import ctbrec.sites.stripchat.Stripchat;
|
||||||
|
@ -25,6 +26,7 @@ import ctbrec.ui.sites.flirt4free.Flirt4FreeSiteUi;
|
||||||
import ctbrec.ui.sites.jasmin.LiveJasminSiteUi;
|
import ctbrec.ui.sites.jasmin.LiveJasminSiteUi;
|
||||||
import ctbrec.ui.sites.manyvids.MVLiveSiteUi;
|
import ctbrec.ui.sites.manyvids.MVLiveSiteUi;
|
||||||
import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi;
|
import ctbrec.ui.sites.myfreecams.MyFreeCamsSiteUi;
|
||||||
|
import ctbrec.ui.sites.secretfriends.SecretFriendsSiteUi;
|
||||||
import ctbrec.ui.sites.showup.ShowupSiteUi;
|
import ctbrec.ui.sites.showup.ShowupSiteUi;
|
||||||
import ctbrec.ui.sites.streamate.StreamateSiteUi;
|
import ctbrec.ui.sites.streamate.StreamateSiteUi;
|
||||||
import ctbrec.ui.sites.stripchat.StripchatSiteUi;
|
import ctbrec.ui.sites.stripchat.StripchatSiteUi;
|
||||||
|
@ -42,6 +44,7 @@ public class SiteUiFactory {
|
||||||
private static LiveJasminSiteUi jasminSiteUi;
|
private static LiveJasminSiteUi jasminSiteUi;
|
||||||
private static MVLiveSiteUi mvLiveSiteUi;
|
private static MVLiveSiteUi mvLiveSiteUi;
|
||||||
private static MyFreeCamsSiteUi mfcSiteUi;
|
private static MyFreeCamsSiteUi mfcSiteUi;
|
||||||
|
private static SecretFriendsSiteUi secretFriendsSiteUi;
|
||||||
private static ShowupSiteUi showupSiteUi;
|
private static ShowupSiteUi showupSiteUi;
|
||||||
private static StreamateSiteUi streamateSiteUi;
|
private static StreamateSiteUi streamateSiteUi;
|
||||||
private static StripchatSiteUi stripchatSiteUi;
|
private static StripchatSiteUi stripchatSiteUi;
|
||||||
|
@ -95,6 +98,11 @@ public class SiteUiFactory {
|
||||||
mfcSiteUi = new MyFreeCamsSiteUi((MyFreeCams) site);
|
mfcSiteUi = new MyFreeCamsSiteUi((MyFreeCams) site);
|
||||||
}
|
}
|
||||||
return mfcSiteUi;
|
return mfcSiteUi;
|
||||||
|
} else if (site instanceof SecretFriends) {
|
||||||
|
if (secretFriendsSiteUi == null) {
|
||||||
|
secretFriendsSiteUi = new SecretFriendsSiteUi((SecretFriends) site);
|
||||||
|
}
|
||||||
|
return secretFriendsSiteUi;
|
||||||
} else if (site instanceof Showup) {
|
} else if (site instanceof Showup) {
|
||||||
if (showupSiteUi == null) {
|
if (showupSiteUi == null) {
|
||||||
showupSiteUi = new ShowupSiteUi((Showup) site);
|
showupSiteUi = new ShowupSiteUi((Showup) site);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
package ctbrec.ui.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
|
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.layout.GridPane;
|
||||||
|
|
||||||
|
public class SecretFriendsConfigUI extends AbstractConfigUI {
|
||||||
|
private final SecretFriends site;
|
||||||
|
|
||||||
|
public SecretFriendsConfigUI(SecretFriends secretFriends) {
|
||||||
|
this.site = secretFriends;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Parent createConfigPanel() {
|
||||||
|
GridPane layout = SettingsTab.createGridLayout();
|
||||||
|
var settings = Config.getInstance().getSettings();
|
||||||
|
|
||||||
|
var row = 0;
|
||||||
|
var l = new Label("Active");
|
||||||
|
layout.add(l, 0, row);
|
||||||
|
var enabled = new CheckBox();
|
||||||
|
enabled.setSelected(!settings.disabledSites.contains(site.getName()));
|
||||||
|
enabled.setOnAction(e -> {
|
||||||
|
if(enabled.isSelected()) {
|
||||||
|
settings.disabledSites.remove(site.getName());
|
||||||
|
} else {
|
||||||
|
settings.disabledSites.add(site.getName());
|
||||||
|
}
|
||||||
|
save();
|
||||||
|
});
|
||||||
|
GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
|
||||||
|
layout.add(enabled, 1, row++);
|
||||||
|
|
||||||
|
// layout.add(new Label(site.getName() + " User"), 0, row);
|
||||||
|
// var 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();
|
||||||
|
// site.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(site.getName() + " Password"), 0, row);
|
||||||
|
// var 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();
|
||||||
|
// site.getHttpClient().logout();
|
||||||
|
// save();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// GridPane.setFillWidth(password, true);
|
||||||
|
// GridPane.setHgrow(password, Priority.ALWAYS);
|
||||||
|
// GridPane.setColumnSpan(password, 2);
|
||||||
|
// layout.add(password, 1, row++);
|
||||||
|
|
||||||
|
// var createAccount = new Button("Create new Account");
|
||||||
|
// createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink()));
|
||||||
|
// layout.add(createAccount, 1, row++);
|
||||||
|
// GridPane.setColumnSpan(createAccount, 2);
|
||||||
|
|
||||||
|
var deleteCookies = new Button("Delete Cookies");
|
||||||
|
deleteCookies.setOnAction(e -> site.getHttpClient().clearCookies());
|
||||||
|
layout.add(deleteCookies, 1, row);
|
||||||
|
GridPane.setColumnSpan(deleteCookies, 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));
|
||||||
|
GridPane.setMargin(deleteCookies, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN));
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package ctbrec.ui.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
|
import ctbrec.ui.sites.AbstractSiteUi;
|
||||||
|
import ctbrec.ui.sites.ConfigUI;
|
||||||
|
import ctbrec.ui.tabs.TabProvider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class SecretFriendsSiteUi extends AbstractSiteUi {
|
||||||
|
|
||||||
|
private SecretFriendsTabProvider tabProvider;
|
||||||
|
private SecretFriendsConfigUI configUi;
|
||||||
|
private final SecretFriends site;
|
||||||
|
|
||||||
|
public SecretFriendsSiteUi(SecretFriends site) {
|
||||||
|
this.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TabProvider getTabProvider() {
|
||||||
|
if (tabProvider == null) {
|
||||||
|
tabProvider = new SecretFriendsTabProvider(site);
|
||||||
|
}
|
||||||
|
return tabProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConfigUI getConfigUI() {
|
||||||
|
if (configUi == null) {
|
||||||
|
configUi = new SecretFriendsConfigUI(site);
|
||||||
|
}
|
||||||
|
return configUi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean login() throws IOException {
|
||||||
|
return site.login();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package ctbrec.ui.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
|
import ctbrec.ui.tabs.TabProvider;
|
||||||
|
import ctbrec.ui.tabs.ThumbOverviewTab;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SecretFriendsTabProvider implements TabProvider {
|
||||||
|
|
||||||
|
private final SecretFriends site;
|
||||||
|
private final Recorder recorder;
|
||||||
|
|
||||||
|
// StripchatFollowedTab followedTab;
|
||||||
|
|
||||||
|
public SecretFriendsTabProvider(SecretFriends site) {
|
||||||
|
this.site = site;
|
||||||
|
this.recorder = site.getRecorder();
|
||||||
|
//followedTab = new StripchatFollowedTab("Followed", site);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Tab> getTabs(Scene scene) {
|
||||||
|
List<Tab> tabs = new ArrayList<>();
|
||||||
|
tabs.add(createTab("Girls", SecretFriends.BASE_URI + "/users"));
|
||||||
|
tabs.add(createTab("New", SecretFriends.BASE_URI + "/newgirls"));
|
||||||
|
tabs.add(createTab("Couples", SecretFriends.BASE_URI + "/site/couple"));
|
||||||
|
//tabs.add(createTab("Vibrating Toy", SecretFriends.BASE_URI + "/tag/view?slug=vibrating-toy"));
|
||||||
|
// tabs.add(createTab("Girls HD", site.getBaseUrl() + "/api/front/models?primaryTag=girls&filterGroupTags=%5B%5B%22autoTagHd%22%5D%5D&parentTag=autoTagHd"));
|
||||||
|
// tabs.add(createTab("New Girls", site.getBaseUrl() +"/api/front/models?primaryTag=girls&filterGroupTags=%5B%5B%22autoTagNew%22%5D%5D&parentTag=autoTagNew"));
|
||||||
|
// 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);
|
||||||
|
// followedTab.setImageAspectRatio(9.0 / 16.0);
|
||||||
|
// tabs.add(followedTab);
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Tab getFollowedTab() {
|
||||||
|
// return followedTab;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tab createTab(String title, String url) {
|
||||||
|
var updateService = new SecretFriendsUpdateService(url, false, site);
|
||||||
|
var tab = new ThumbOverviewTab(title, updateService, site);
|
||||||
|
tab.setRecorder(recorder);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package ctbrec.ui.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.HtmlParser;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriends;
|
||||||
|
import ctbrec.sites.secretfriends.SecretFriendsModelParser;
|
||||||
|
import ctbrec.ui.SiteUiFactory;
|
||||||
|
import ctbrec.ui.tabs.PaginatedScheduledService;
|
||||||
|
import javafx.concurrent.Task;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
|
||||||
|
public class SecretFriendsUpdateService extends PaginatedScheduledService {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SecretFriendsUpdateService.class);
|
||||||
|
|
||||||
|
private final String url;
|
||||||
|
private final boolean loginRequired;
|
||||||
|
private final SecretFriends site;
|
||||||
|
|
||||||
|
public SecretFriendsUpdateService(String url, boolean loginRequired, SecretFriends site) {
|
||||||
|
this.url = url;
|
||||||
|
this.loginRequired = loginRequired;
|
||||||
|
this.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Task<List<Model>> createTask() {
|
||||||
|
return new Task<>() {
|
||||||
|
@Override
|
||||||
|
public List<Model> call() throws IOException {
|
||||||
|
if (loginRequired && !site.credentialsAvailable()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
String paginatedUrl = url + (url.indexOf('?') >= 0 ? '&' : '?') + "Friend_page=" + page;
|
||||||
|
LOG.debug("Fetching page {}", paginatedUrl);
|
||||||
|
if (loginRequired) {
|
||||||
|
SiteUiFactory.getUi(site).login();
|
||||||
|
}
|
||||||
|
var 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 (var response = site.getHttpClient().execute(request)) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
return parseModels(Objects.requireNonNull(response.body(), "HTTP response body is null").string());
|
||||||
|
} else {
|
||||||
|
throw new HttpException(response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Model> parseModels(String body) {
|
||||||
|
List<Model> models = new ArrayList<>();
|
||||||
|
Elements modelDivs = HtmlParser.getTags(body, "div[class~=model-wrapper]");
|
||||||
|
LOG.debug("Found {} models", modelDivs.size());
|
||||||
|
for (Element div : modelDivs) {
|
||||||
|
try {
|
||||||
|
models.add(SecretFriendsModelParser.parse(site, div));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Couldn't parse one of the models: {}", div.html(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package ctbrec.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.io.HtmlParser;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.sites.AbstractSite;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static ctbrec.io.HttpConstants.USER_AGENT;
|
||||||
|
|
||||||
|
public class SecretFriends extends AbstractSite {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SecretFriends.class);
|
||||||
|
public static final String BASE_URI = "https://www.secretfriends.com";
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws IOException {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "SecretFriends";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBaseUrl() {
|
||||||
|
return BASE_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAffiliateLink() {
|
||||||
|
return BASE_URI;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getBuyTokensLink() {
|
||||||
|
return getAffiliateLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecretFriendsModel createModel(String name) {
|
||||||
|
SecretFriendsModel model = new SecretFriendsModel();
|
||||||
|
model.setName(name);
|
||||||
|
model.setUrl(getBaseUrl() + "/friends/" + name);
|
||||||
|
model.setSite(this);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getTokenBalance() throws IOException {
|
||||||
|
return 0d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean login() throws IOException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClient getHttpClient() {
|
||||||
|
if (httpClient == null) {
|
||||||
|
httpClient = new SecretFriendsHttpClient(getConfig());
|
||||||
|
}
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
if (httpClient != null) {
|
||||||
|
httpClient.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsTips() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFollow() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsSearch() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Model> search(String q) throws IOException, InterruptedException {
|
||||||
|
String url = BASE_URI + "/user?SearchForm[keyword]=" + URLEncoder.encode(q, "utf-8");
|
||||||
|
Request req = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header(USER_AGENT, getConfig().getSettings().httpUserAgent)
|
||||||
|
.build();
|
||||||
|
try (Response response = getHttpClient().execute(req)) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
String body = Objects.requireNonNull(response.body(), "HTTP response body is null").string();
|
||||||
|
List<Model> models = new ArrayList<>();
|
||||||
|
Elements modelDivs = HtmlParser.getTags(body, "div[class~=model-wrapper]");
|
||||||
|
LOG.debug("Found {} models", modelDivs.size());
|
||||||
|
for (Element div : modelDivs) {
|
||||||
|
try {
|
||||||
|
models.add(SecretFriendsModelParser.parse(this, div));
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Couldn't parse one of the models: {}", div.html(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return models;
|
||||||
|
} else {
|
||||||
|
throw new HttpException(response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSiteForModel(Model m) {
|
||||||
|
return m instanceof SecretFriendsModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean credentialsAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Model createModelFromUrl(String url) {
|
||||||
|
Matcher m = Pattern.compile("https?://(?:www\\.)?secretfriends.com/friends/([^/]*?)/?").matcher(url);
|
||||||
|
if (m.matches()) {
|
||||||
|
String modelName = m.group(1);
|
||||||
|
return createModel(modelName);
|
||||||
|
} else {
|
||||||
|
return super.createModelFromUrl(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,174 @@
|
||||||
|
package ctbrec.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.io.HttpClient;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
|
||||||
|
public class SecretFriendsHttpClient extends HttpClient {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(SecretFriendsHttpClient.class);
|
||||||
|
public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
|
||||||
|
|
||||||
|
private long userId;
|
||||||
|
private String csrfToken;
|
||||||
|
private String csrfTimestamp;
|
||||||
|
private String csrfNotifyTimestamp;
|
||||||
|
|
||||||
|
public SecretFriendsHttpClient(Config config) {
|
||||||
|
super("secretfirends", config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean login() throws IOException {
|
||||||
|
if (loggedIn) {
|
||||||
|
if (csrfToken == null) {
|
||||||
|
loadCsrfToken();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// persisted cookies might let us log in
|
||||||
|
if (checkLoginSuccess()) {
|
||||||
|
loggedIn = true;
|
||||||
|
LOG.debug("Logged in with cookies");
|
||||||
|
if (csrfToken == null) {
|
||||||
|
loadCsrfToken();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (csrfToken == null) {
|
||||||
|
loadCsrfToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
String url = SecretFriends.BASE_URI + "/api/front/auth/login";
|
||||||
|
JSONObject requestParams = new JSONObject();
|
||||||
|
requestParams.put("loginOrEmail", config.getSettings().stripchatUsername);
|
||||||
|
requestParams.put("password", config.getSettings().stripchatPassword);
|
||||||
|
requestParams.put("remember", true);
|
||||||
|
requestParams.put("csrfToken", csrfToken);
|
||||||
|
requestParams.put("csrfTimestamp", csrfTimestamp);
|
||||||
|
requestParams.put("csrfNotifyTimestamp", csrfNotifyTimestamp);
|
||||||
|
RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON);
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.header(USER_AGENT, config.getSettings().httpUserAgent)
|
||||||
|
.header(ORIGIN, SecretFriends.BASE_URI)
|
||||||
|
.header(REFERER, SecretFriends.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 {
|
||||||
|
LOG.info("Auto-Login failed: {} {} {}", response.code(), response.message(), url);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCsrfToken() throws IOException {
|
||||||
|
String url = SecretFriends.BASE_URI + "/api/front/v2/config/data?requestPath=%2F&timezoneOffset=0";
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.header(USER_AGENT, config.getSettings().httpUserAgent)
|
||||||
|
.header(ORIGIN, SecretFriends.BASE_URI)
|
||||||
|
.header(REFERER, SecretFriends.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() throws IOException {
|
||||||
|
userId = getUserId();
|
||||||
|
String url = SecretFriends.BASE_URI + "/api/front/users/" + userId + "/favorites";
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.header(USER_AGENT, config.getSettings().httpUserAgent)
|
||||||
|
.header(ORIGIN, SecretFriends.BASE_URI)
|
||||||
|
.header(REFERER, SecretFriends.BASE_URI + "/favorites")
|
||||||
|
.header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.build();
|
||||||
|
try (Response response = execute(request)) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.info("Login check returned unsuccessful: {}", e.getLocalizedMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getUserId() throws JSONException, IOException {
|
||||||
|
if (userId == 0) {
|
||||||
|
String url = SecretFriends.BASE_URI + "/api/front/users/username/" + config.getSettings().stripchatUsername;
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
|
||||||
|
.header(USER_AGENT, config.getSettings().httpUserAgent)
|
||||||
|
.header(ORIGIN, SecretFriends.BASE_URI)
|
||||||
|
.header(REFERER, SecretFriends.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 user = resp.getJSONObject("user");
|
||||||
|
userId = user.optLong("id");
|
||||||
|
} else {
|
||||||
|
throw new HttpException(url, response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCsrfNotifyTimestamp() {
|
||||||
|
return csrfNotifyTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCsrfTimestamp() {
|
||||||
|
return csrfTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCsrfToken() {
|
||||||
|
return csrfToken;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
package ctbrec.sites.secretfriends;
|
||||||
|
|
||||||
|
import com.iheartradio.m3u8.ParseException;
|
||||||
|
import com.iheartradio.m3u8.PlaylistException;
|
||||||
|
import ctbrec.AbstractModel;
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.io.HtmlParser;
|
||||||
|
import ctbrec.io.HttpException;
|
||||||
|
import ctbrec.recorder.download.StreamSource;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
|
import static ctbrec.Model.State.ONLINE;
|
||||||
|
import static ctbrec.io.HttpConstants.*;
|
||||||
|
|
||||||
|
public class SecretFriendsModel 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) {
|
||||||
|
String url = SecretFriends.BASE_URI + "/friend/bio/" + getName();
|
||||||
|
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()) {
|
||||||
|
String body = Objects.requireNonNull(response.body(), "HTTP response body is null").string();
|
||||||
|
Element wrapper = HtmlParser.getTag(body, "div[class~=model-wrapper]");
|
||||||
|
SecretFriendsModel parsedModel = SecretFriendsModelParser.parse((SecretFriends) getSite(), wrapper);
|
||||||
|
setName(parsedModel.getName());
|
||||||
|
setUrl(parsedModel.getUrl());
|
||||||
|
setPreview(parsedModel.getPreview());
|
||||||
|
setOnlineState(parsedModel.getOnlineState(true));
|
||||||
|
} else {
|
||||||
|
throw new HttpException(response.code(), response.message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return onlineState == ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<StreamSource> getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException {
|
||||||
|
String name = getName();
|
||||||
|
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()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
} 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 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean unfollow() throws IOException {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package ctbrec.sites.secretfriends;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class SecretFriendsModelParser {
|
||||||
|
private SecretFriendsModelParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SecretFriendsModel parse(SecretFriends site, Element modelWrapper) {
|
||||||
|
String name = parseName(modelWrapper);
|
||||||
|
SecretFriendsModel model = site.createModel(name);
|
||||||
|
model.setPreview(parsePreview(modelWrapper));
|
||||||
|
model.setOnlineState(extractOnlineState(modelWrapper));
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parsePreview(Element div) {
|
||||||
|
Element wrapper = div.selectFirst("div.placeholder-wrapper");
|
||||||
|
if (wrapper == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String style = wrapper.attr("style");
|
||||||
|
Pattern p = Pattern.compile("background-image: url\\('(.*?)'\\)");
|
||||||
|
Matcher m = p.matcher(style);
|
||||||
|
if (m.find()) {
|
||||||
|
return m.group(1);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String parseName(Element div) {
|
||||||
|
Element bioLink = Objects.requireNonNull(div.selectFirst("a[href*=/friend]"), "a[href*=/friend] not found");
|
||||||
|
bioLink.setBaseUri(SecretFriends.BASE_URI);
|
||||||
|
String href = bioLink.attr("href");
|
||||||
|
String name = href.substring(href.lastIndexOf('/') + 1);
|
||||||
|
if (name.indexOf('?') >= 0) {
|
||||||
|
name = name.substring(0, name.indexOf('?'));
|
||||||
|
}
|
||||||
|
if (name.indexOf('#') >= 0) {
|
||||||
|
name = name.substring(0, name.indexOf('#'));
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Model.State extractOnlineState(Element div) {
|
||||||
|
Element modelTag = Objects.requireNonNull(div.selectFirst("div[class~=model-tag]"), "div.model-tag not found");
|
||||||
|
Set<String> cssClasses = modelTag.classNames();
|
||||||
|
for (String cssClass : cssClasses) {
|
||||||
|
switch (cssClass) {
|
||||||
|
case "model-online":
|
||||||
|
return Model.State.ONLINE;
|
||||||
|
case "model-private":
|
||||||
|
case "model-show":
|
||||||
|
case "model-vip":
|
||||||
|
return Model.State.PRIVATE;
|
||||||
|
case "model-offline":
|
||||||
|
return Model.State.OFFLINE;
|
||||||
|
default:
|
||||||
|
// keep going
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Model.State.OFFLINE;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue