From c376f30c562066de13a6f32ca80aa565c58bc43b Mon Sep 17 00:00:00 2001 From: 0xb00bface <0xboobface@gmail.com> Date: Sun, 5 Nov 2023 15:21:24 +0100 Subject: [PATCH] Add online/offline switch on followed tab for Stripchat --- .../sites/stripchat/StripchatFollowedTab.java | 33 +++- .../StripchatFollowedUpdateService.java | 166 ++++++++---------- .../sites/stripchat/StripchatHttpClient.java | 72 +++----- 3 files changed, 137 insertions(+), 134 deletions(-) diff --git a/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedTab.java b/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedTab.java index 2009422d..a56006bf 100644 --- a/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedTab.java +++ b/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedTab.java @@ -4,14 +4,17 @@ 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); @@ -19,6 +22,34 @@ public class StripchatFollowedTab extends ThumbOverviewTab implements FollowedTa grid.getChildren().add(status); } + @Override + protected void createGui() { + super.createGui(); + addOnlineOfflineSelector(); + } + + private void addOnlineOfflineSelector() { + var group = new ToggleGroup(); + var online = new RadioButton("online"); + online.setToggleGroup(group); + var 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 -> { + if (online.isSelected()) { + ((StripchatFollowedUpdateService) updateService).setOnline(true); + } else { + ((StripchatFollowedUpdateService) updateService).setOnline(false); + } + queue.clear(); + updateService.restart(); + }); + } + @Override protected void onSuccess() { grid.getChildren().remove(status); diff --git a/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedUpdateService.java b/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedUpdateService.java index 26acf930..70ae12aa 100644 --- a/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedUpdateService.java +++ b/client/src/main/java/ctbrec/ui/sites/stripchat/StripchatFollowedUpdateService.java @@ -8,27 +8,30 @@ import ctbrec.sites.stripchat.StripchatHttpClient; import ctbrec.sites.stripchat.StripchatModel; import ctbrec.ui.SiteUiFactory; import javafx.concurrent.Task; -import okhttp3.HttpUrl; +import lombok.extern.slf4j.Slf4j; import okhttp3.Request; +import okhttp3.Response; import org.json.JSONArray; import org.json.JSONObject; import java.io.IOException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; -import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; +@Slf4j public class StripchatFollowedUpdateService extends AbstractStripchatUpdateService { - private static final int PAGE_SIZE = 48; - private static final String FAVORITES = "/favorites"; + + private static final Random RNG = new Random(); + private static final int MODELS_PER_PAGE = 60; private final Stripchat stripchat; + private final boolean loginRequired; + private String url; public StripchatFollowedUpdateService(Stripchat stripchat) { this.stripchat = stripchat; + this.loginRequired = true; + this.url = stripchat.getBaseUrl() + "/api/front/models/favorites?sortBy=lastAdded"; } @Override @@ -36,97 +39,80 @@ public class StripchatFollowedUpdateService extends AbstractStripchatUpdateServi return new Task<>() { @Override public List call() throws IOException { - return loadModels(); - } - }; - } - - private List loadModels() throws IOException { - int startIndex = (getPage() - 1) * PAGE_SIZE; - JSONArray favoriteModelIds = loadFavoriteModelIds(); - List modelIdsToLoad = new ArrayList<>(PAGE_SIZE); - List models; - if (startIndex < favoriteModelIds.length()) { - int modelsOnPage = Math.min(PAGE_SIZE, favoriteModelIds.length() - startIndex - 1); - for (var i = 0; i < modelsOnPage; i++) { - modelIdsToLoad.add(favoriteModelIds.getInt(startIndex + i)); - } - models = loadModels(modelIdsToLoad); - } else { - models = Collections.emptyList(); - } - return models; - } - - private List loadModels(List modelIdsToLoad) throws IOException { - List models = new ArrayList<>(); - var urlBuilder = HttpUrl.parse(stripchat.getBaseUrl() + "/api/front/models/list").newBuilder(); - for (var i = 0; i < modelIdsToLoad.size(); i++) { - urlBuilder.addQueryParameter("modelIds[" + i + "]", modelIdsToLoad.get(i).toString()); - } - var request = new Request.Builder() - .url(urlBuilder.build()) - .header(ACCEPT, "*/*") - .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) - .header(REFERER, Stripchat.baseUri + FAVORITES) - .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON) - .build(); - try (var response = stripchat.getHttpClient().execute(request)) { - if (response.isSuccessful()) { - var json = new JSONObject(response.body().string()); - if (json.has("models")) { - var users = json.getJSONArray("models"); - for (var i = 0; i < users.length(); i++) { - var user = users.getJSONObject(i); - StripchatModel model = stripchat.createModel(user.optString("username")); - model.setDescription(user.optString("description")); - model.setPreview(getPreviewUrl(user)); - model.setOnlineState(mapStatus(user.optString("status"))); - model.setLastSeen(Instant.now()); - models.add(model); + if (loginRequired && !stripchat.credentialsAvailable()) { + return Collections.emptyList(); + } else { + int offset = (getPage() - 1) * MODELS_PER_PAGE; + int limit = MODELS_PER_PAGE; + String paginatedUrl = url + "&offset=" + offset + "&limit=" + limit + "&uniq=" + getUniq(); + log.debug("Fetching page {}", paginatedUrl); + if (loginRequired) { + SiteUiFactory.getUi(stripchat).login(); + } + String jwtToken = ((StripchatHttpClient) stripchat.getHttpClient()).getJwtToken(); + 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) + .header("Authorization", Optional.ofNullable(jwtToken).orElse("")) + .build(); + try (Response response = stripchat.getHttpClient().execute(request)) { + if (response.isSuccessful()) { + List models = new ArrayList<>(); + JSONObject json = new JSONObject(response.body().string()); + if (json.has("totalCount") && offset >= json.getInt("totalCount")) { + return Collections.emptyList(); + } + if (json.has("models")) { + JSONArray jsonModels = json.getJSONArray("models"); + models.addAll(parseModels(jsonModels)); + } else { + log.debug("Response was not successful: {}", json); + return Collections.emptyList(); + } + return models; + } else { + throw new HttpException(response.code(), response.message()); + } } } - } else { - throw new HttpException(response.code(), response.message()); + } + }; + } + + private List parseModels(JSONArray jsonModels) { + List models = new ArrayList<>(); + for (var i = 0; i < jsonModels.length(); i++) { + var jsonModel = jsonModels.getJSONObject(i); + try { + StripchatModel model = stripchat.createModel(jsonModel.getString("username")); + model.setPreview(getPreviewUrl(jsonModel)); + model.setDisplayName(model.getName()); + models.add(model); + } catch (Exception e) { + log.warn("Couldn't parse one of the models: {}", jsonModel, e); } } return models; } - private JSONArray loadFavoriteModelIds() throws IOException { - SiteUiFactory.getUi(stripchat).login(); - stripchat.getHttpClient().login(); - long userId = ((StripchatHttpClient) stripchat.getHttpClient()).getUserId(); - String url = stripchat.getBaseUrl() + "/api/front/users/" + userId + FAVORITES; - var request = new Request.Builder() - .url(url) - .header(ACCEPT, MIMETYPE_APPLICATION_JSON) - .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) - .header(ORIGIN, Stripchat.baseUri) - .header(REFERER, Stripchat.baseUri + FAVORITES) - .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON) - .build(); - try (var response = stripchat.getHttpClient().execute(request)) { - if (response.isSuccessful()) { - var json = new JSONObject(response.body().string()); - if (json.has("modelIds")) { - var userIds = json.getJSONArray("modelIds"); - return userIds; - } else { - return new JSONArray(); - } - } else { - throw new HttpException(response.code(), response.message()); - } + public void setOnline(boolean online) { + if (online) { + url = stripchat.getBaseUrl() + "/api/front/models/favorites?sortBy=lastAdded"; + } else { + url = stripchat.getBaseUrl() + "/api/front/models/favorites/offline?sortBy=lastAdded"; } } - protected ctbrec.Model.State mapStatus(String status) { - return switch (status) { - case "public" -> ONLINE; - case "idle" -> AWAY; - case "off" -> OFFLINE; - default -> UNKNOWN; - }; + private String getUniq() { + String dict = "0123456789abcdefghijklmnopqarstvwxyz"; + char[] text = new char[16]; + for (int i = 0; i < 16; i++) { + text[i] = dict.charAt(RNG.nextInt(dict.length())); + } + return new String(text); } + } diff --git a/common/src/main/java/ctbrec/sites/stripchat/StripchatHttpClient.java b/common/src/main/java/ctbrec/sites/stripchat/StripchatHttpClient.java index e7651911..e15b3ed9 100644 --- a/common/src/main/java/ctbrec/sites/stripchat/StripchatHttpClient.java +++ b/common/src/main/java/ctbrec/sites/stripchat/StripchatHttpClient.java @@ -1,31 +1,37 @@ package ctbrec.sites.stripchat; -import static ctbrec.io.HttpConstants.*; - -import java.io.IOException; - -import org.json.JSONException; -import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ctbrec.Config; +import ctbrec.StringUtil; import ctbrec.io.HttpClient; import ctbrec.io.HttpException; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import org.json.JSONException; +import org.json.JSONObject; +import java.io.IOException; + +import static ctbrec.io.HttpConstants.*; + +@Slf4j public class StripchatHttpClient extends HttpClient { - private static final Logger LOG = LoggerFactory.getLogger(StripchatHttpClient.class); public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private long userId; + + @Getter private String csrfToken; + @Getter private String csrfTimestamp; + @Getter private String csrfNotifyTimestamp; + @Getter + private String jwtToken; public StripchatHttpClient(Config config) { super("stripchat", config); @@ -43,7 +49,7 @@ public class StripchatHttpClient extends HttpClient { // persisted cookies might let us log in if (checkLoginSuccess()) { loggedIn = true; - LOG.debug("Logged in with cookies"); + log.debug("Logged in with cookies"); if (csrfToken == null) { loadCsrfToken(); } @@ -75,7 +81,7 @@ public class StripchatHttpClient extends HttpClient { try (Response response = execute(request)) { if (response.isSuccessful()) { JSONObject resp = new JSONObject(response.body().string()); - if(resp.has("user")) { + if (resp.has("user")) { JSONObject user = resp.getJSONObject("user"); userId = user.optLong("id"); return true; @@ -83,14 +89,14 @@ public class StripchatHttpClient extends HttpClient { return false; } } else { - LOG.info("Auto-Login failed: {} {} {}", response.code(), response.message(), url); + log.info("Auto-Login failed: {} {} {}", response.code(), response.message(), url); return false; } } } private void loadCsrfToken() throws IOException { - String url = Stripchat.baseUri + "/api/front/v2/config/data?requestPath=%2F&timezoneOffset=0"; + String url = Stripchat.baseUri + "/api/front/v2/config?requestPath=%2F&timezoneOffset=0"; Request request = new Request.Builder() .url(url) .header(ACCEPT, MIMETYPE_APPLICATION_JSON) @@ -106,6 +112,8 @@ public class StripchatHttpClient extends HttpClient { csrfToken = data.optString("csrfToken"); csrfTimestamp = data.optString("csrfTimestamp"); csrfNotifyTimestamp = data.optString("csrfNotifyTimestamp"); + JSONObject config = resp.getJSONObject("config"); + jwtToken = config.optString("jwtToken"); } else { throw new HttpException(response.code(), response.message()); } @@ -113,28 +121,18 @@ public class StripchatHttpClient extends HttpClient { } /** - * check, if the login worked + * check, if the login worked + * * @throws IOException */ public boolean checkLoginSuccess() throws IOException { - userId = getUserId(); - String url = Stripchat.baseUri + "/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, Stripchat.baseUri) - .header(REFERER, Stripchat.baseUri + "/favorites") - .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON) - .build(); - try (Response response = execute(request)) { - if (response.isSuccessful()) { - return true; - } + try { + loadCsrfToken(); } catch (Exception e) { - LOG.info("Login check returned unsuccessful: {}", e.getLocalizedMessage()); + log.info("Login check returned unsuccessful: {}", e.getLocalizedMessage()); + return false; } - return false; + return StringUtil.isNotBlank(jwtToken); } public long getUserId() throws JSONException, IOException { @@ -160,16 +158,4 @@ public class StripchatHttpClient extends HttpClient { } return userId; } - - public String getCsrfNotifyTimestamp() { - return csrfNotifyTimestamp; - } - - public String getCsrfTimestamp() { - return csrfTimestamp; - } - - public String getCsrfToken() { - return csrfToken; - } }