Added Online / Offline switch on "Favorites" tab for Streamray

This commit is contained in:
0xb00bface 2023-12-31 14:01:12 +01:00
parent 67ef95cc8c
commit f45991effc
6 changed files with 336 additions and 41 deletions

View File

@ -19,13 +19,15 @@
* Streamate:
- Fixed "Couldn't load model ID" error while adding models by URL or by
nickname
- Online/Offline switch on all tabs. Up to 10 000 offline models in each
- Online / Offline switch on all tabs. Up to 10 000 offline models in each
category. How do you like it, Elon Musk?
- Added "New Girls" tab and adjusted others. All same models on less tabs.
- Added "New Girls" tab and adjusted others. All same models on less tabs
* Stripchat:
- Added "Private" tab.
- Added "Private" tab
- CTBRec can record your Spy/Private/Ticket shows (login required)
*
* Streamray:
- Added models tags
- Added Online / Offline switch on "Favorites" tab
5.2.3
========================

View File

@ -2,17 +2,95 @@ package ctbrec.ui.sites.streamray;
import ctbrec.sites.streamray.Streamray;
import ctbrec.ui.tabs.PaginatedScheduledService;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.json.JSONArray;
abstract class AbstractStreamrayUpdateService extends PaginatedScheduledService {
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
protected int modelsPerPage = 48;
import static java.nio.charset.StandardCharsets.UTF_8;
@RequiredArgsConstructor
public abstract class AbstractStreamrayUpdateService extends PaginatedScheduledService {
@Getter
@Setter
private static JSONArray mapping;
protected static final int MODELS_PER_PAGE = 48;
protected static final String API_URL = "https://beta-api.cams.com/won/compressed/";
protected final Streamray site;
AbstractStreamrayUpdateService(Streamray site) {
this.site = site;
protected String getPreviewURL(String name) {
String lname = name.toLowerCase();
String url = MessageFormat.format("https://images4.streamray.com/images/streamray/won/jpg/{0}/{1}/{2}_640.jpg", lname.substring(0, 1), lname.substring(lname.length() - 1), lname);
try {
return MessageFormat.format("https://dynimages.securedataimages.com/unsigned/rs:fill:640::0/g:no/plain/{0}@jpg", URLEncoder.encode(url, UTF_8));
} catch (Exception ex) {
return url;
}
}
boolean isLoggedIn() {
return site.isLoggedIn();
protected List<String> createTags(JSONArray m) {
List<String> tags = new ArrayList<>();
int idx1 = mappingIndex("gender");
switch (m.optString(idx1)) {
case "M" -> tags.add("male");
case "F" -> tags.add("female");
case "TS" -> tags.add("trans");
default -> {
// don't add anything
}
}
int idx2 = mappingIndex("ethnicity");
switch (m.optString(idx2)) {
case "02" -> tags.add("asian");
case "03" -> tags.add("ebony");
case "04" -> tags.add("white");
case "05" -> tags.add("indian");
case "06" -> tags.add("latina");
case "07" -> tags.add("middle-eastern");
default -> {
// don't add anything
}
}
int idx3 = mappingIndex("hair_color");
switch (m.optString(idx3)) {
case "01" -> tags.add("black-hair");
case "02" -> tags.add("blonde");
case "03" -> tags.add("brunette");
case "06" -> tags.add("redhead");
default -> {
// don't add anything
}
}
int idx4 = mappingIndex("chat_type");
switch (m.optString(idx4)) {
case "0" -> tags.add("offline");
case "1" -> tags.add("public");
case "2" -> tags.add("nude-show");
case "3" -> tags.add("private");
case "4" -> tags.add("exclusive");
case "6" -> tags.add("ticket-show");
case "7" -> tags.add("voyeur");
case "10" -> tags.add("party");
case "13" -> tags.add("group");
case "14" -> tags.add("c2c");
default -> {
// don't add anything
}
}
return tags;
}
protected int mappingIndex(String s) {
for (var i = 0; i < mapping.length(); i++) {
if (Objects.equals(s, mapping.get(i))) return i;
}
return -1;
}
}

View File

@ -1,18 +1,36 @@
package ctbrec.ui.sites.streamray;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.StringUtil;
import ctbrec.io.HttpException;
import ctbrec.sites.streamray.Streamray;
import ctbrec.sites.streamray.StreamrayHttpClient;
import ctbrec.sites.streamray.StreamrayModel;
import javafx.concurrent.Task;
import lombok.Getter;
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.util.Collections;
import java.util.List;
import java.time.Duration;
import java.time.Instant;
import java.util.*;
import static ctbrec.io.HttpConstants.*;
@Slf4j
public class StreamrayFavoritesService extends AbstractStreamrayUpdateService {
private List<StreamrayModel> modelsList;
private Instant lastListInfoRequest = Instant.EPOCH;
@Getter
private boolean loggedIn = false;
private boolean showOnline = true;
public StreamrayFavoritesService(Streamray site) {
super(site);
}
@ -22,20 +40,120 @@ public class StreamrayFavoritesService extends AbstractStreamrayUpdateService {
return new Task<>() {
@Override
public List<Model> call() throws IOException {
return getModelList().stream()
.skip((page - 1) * (long) modelsPerPage)
.limit(modelsPerPage)
.map(Model.class::cast)
.toList();
if (showOnline) {
return getModelList().stream()
.skip((page - 1) * (long) MODELS_PER_PAGE)
.limit(MODELS_PER_PAGE)
.map(Model.class::cast)
.toList();
} else {
return loadOfflineModelList().stream()
.map(Model.class::cast)
.toList();
}
}
};
}
private List<StreamrayModel> getModelList() throws IOException {
List<StreamrayModel> models = site.loadModelList(true).stream().filter(StreamrayModel::isFavorite).toList();
if (models == null) {
models = Collections.emptyList();
if (Duration.between(lastListInfoRequest, Instant.now()).getSeconds() < 30) {
return Objects.nonNull(modelsList) ? modelsList : loadModelList(API_URL);
}
return models;
modelsList = loadModelList(API_URL);
return modelsList;
}
private List<StreamrayModel> loadOfflineModelList() throws IOException {
String url = "https://beta-api.cams.com/favorites/member_favorites/?gender=female";
int offset = (getPage() - 1) * MODELS_PER_PAGE;
String paginatedUrl = url + "&offset=" + offset + "&limit=" + MODELS_PER_PAGE;
return loadModelList(paginatedUrl);
}
private List<StreamrayModel> loadModelList(String url) throws IOException {
log.debug("Fetching page {}", url);
lastListInfoRequest = Instant.now();
StreamrayHttpClient client = (StreamrayHttpClient) site.getHttpClient();
String token;
if (site.login()) {
loggedIn = true;
token = client.getUserToken();
} else {
loggedIn = false;
return Collections.emptyList();
}
Request req = new Request.Builder()
.url(url)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(REFERER, site.getBaseUrl() + "/")
.header(ORIGIN, site.getBaseUrl())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.header(AUTHORIZATION, "Bearer " + token)
.build();
try (Response response = client.execute(req)) {
if (response.isSuccessful()) {
List<StreamrayModel> models = new ArrayList<>();
JSONObject json = new JSONObject(response.body().string());
if (showOnline) {
if (json.has("models")) {
JSONArray modelNodes = json.getJSONArray("models");
AbstractStreamrayUpdateService.setMapping(json.getJSONArray("mapping"));
parseModels(modelNodes, models);
}
} else {
if (json.has("results")) {
JSONArray modelNodes = json.getJSONArray("results");
parseOfflineModels(modelNodes, models);
}
}
return models;
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private void parseModels(JSONArray jsonModels, List<StreamrayModel> models) {
int nameIdx = mappingIndex("stream_name");
int favIdx = mappingIndex("is_favorite");
for (int i = 0; i < jsonModels.length(); i++) {
JSONArray m = jsonModels.getJSONArray(i);
String name = m.optString(nameIdx);
boolean favorite = m.optBoolean(favIdx);
if (favorite) {
StreamrayModel model = site.createModel(name);
String preview = getPreviewURL(name);
model.setPreview(preview);
model.setTags(createTags(m));
StringBuilder description = new StringBuilder();
for (String tag : model.getTags()) {
description.append("#").append(tag).append(" ");
}
model.setDescription(description.toString());
models.add(model);
}
}
}
private void parseOfflineModels(JSONArray jsonModels, List<StreamrayModel> models) {
for (int i = 0; i < jsonModels.length(); i++) {
JSONObject m = jsonModels.getJSONObject(i);
String name = m.optString("stream_name");
if (StringUtil.isBlank(name) || m.optBoolean("is_online")) {
continue;
}
StreamrayModel model = site.createModel(name);
String preview = getPreviewURL(name);
model.setPreview(preview);
model.setDisplayName(m.getString("screen_name"));
model.setOnlineState(Model.State.OFFLINE);
models.add(model);
}
}
public void setOnline(boolean online) {
showOnline = online;
}
}

View File

@ -9,8 +9,11 @@ import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
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 StreamrayFavoritesTab extends ThumbOverviewTab implements FollowedTab {
private final Label status;
@ -29,12 +32,38 @@ public class StreamrayFavoritesTab extends ThumbOverviewTab implements FollowedT
loginButton.setOnAction(e -> {
try {
new StreamrayElectronLoginDialog(site.getHttpClient().getCookieJar());
queue.clear();
updateService.restart();
} catch (Exception ex) {
// fail silently
}
});
}
@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 -> {
((StreamrayFavoritesService) updateService).setOnline(online.isSelected());
queue.clear();
updateService.restart();
});
}
protected void addLoginButton() {
grid.getChildren().clear();
grid.setAlignment(Pos.CENTER);
@ -45,7 +74,7 @@ public class StreamrayFavoritesTab extends ThumbOverviewTab implements FollowedT
protected void onSuccess() {
grid.getChildren().removeAll(status, loginButton);
grid.setAlignment(Pos.TOP_LEFT);
if (!((AbstractStreamrayUpdateService) updateService).isLoggedIn()) {
if (!streamrayFavoritesService.isLoggedIn()) {
addLoginButton();
} else {
super.onSuccess();

View File

@ -1,22 +1,37 @@
package ctbrec.ui.sites.streamray;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.io.HttpException;
import ctbrec.sites.streamray.Streamray;
import ctbrec.sites.streamray.StreamrayHttpClient;
import ctbrec.sites.streamray.StreamrayModel;
import javafx.concurrent.Task;
import lombok.Setter;
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.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Predicate;
import static ctbrec.io.HttpConstants.*;
@Slf4j
public class StreamrayUpdateService extends AbstractStreamrayUpdateService {
private List<StreamrayModel> models;
private List<StreamrayModel> modelsList;
private Instant lastListInfoRequest = Instant.EPOCH;
@Setter
@ -32,21 +47,78 @@ public class StreamrayUpdateService extends AbstractStreamrayUpdateService {
return new Task<>() {
@Override
public List<Model> call() throws IOException {
return getModels().stream()
return getModelList().stream()
.filter(filter)
.skip((page - 1) * (long) modelsPerPage)
.limit(modelsPerPage)
.skip((page - 1) * (long) MODELS_PER_PAGE)
.limit(MODELS_PER_PAGE)
.map(Model.class::cast)
.toList();
}
};
}
private List<StreamrayModel> getModels() throws IOException {
if (models == null || Duration.between(lastListInfoRequest, Instant.now()).getSeconds() >= 10) {
models = site.loadModelList();
lastListInfoRequest = Instant.now();
private List<StreamrayModel> getModelList() throws IOException {
if (Duration.between(lastListInfoRequest, Instant.now()).getSeconds() < 30) {
return Objects.nonNull(modelsList) ? modelsList : loadModelList();
}
modelsList = loadModelList();
return modelsList;
}
private List<StreamrayModel> loadModelList() throws IOException {
log.debug("Fetching page {}", API_URL);
lastListInfoRequest = Instant.now();
StreamrayHttpClient client = (StreamrayHttpClient) site.getHttpClient();
Request req = new Request.Builder()
.url(API_URL)
.header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent)
.header(ACCEPT, MIMETYPE_APPLICATION_JSON)
.header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage())
.header(REFERER, site.getBaseUrl() + "/")
.header(ORIGIN, site.getBaseUrl())
.header(X_REQUESTED_WITH, XML_HTTP_REQUEST)
.build();
try (Response response = client.execute(req)) {
if (response.isSuccessful()) {
List<StreamrayModel> models = new ArrayList<>();
String content = response.body().string();
JSONObject json = new JSONObject(content);
JSONArray modelNodes = json.getJSONArray("models");
AbstractStreamrayUpdateService.setMapping(json.getJSONArray("mapping"));
parseModels(modelNodes, models);
return models;
} else {
throw new HttpException(response.code(), response.message());
}
}
}
private void parseModels(JSONArray jsonModels, List<StreamrayModel> models) {
int nameIdx = mappingIndex("stream_name");
int dateIdx = mappingIndex("create_date");
int genIdx = mappingIndex("gender");
for (var i = 0; i < jsonModels.length(); i++) {
var m = jsonModels.getJSONArray(i);
String name = m.optString(nameIdx);
String gender = m.optString(genIdx);
String reg = m.optString(dateIdx);
StreamrayModel model = site.createModel(name);
try {
LocalDate regDate = LocalDate.parse(reg, DateTimeFormatter.BASIC_ISO_DATE);
model.setRegDate(regDate);
} catch (DateTimeParseException e) {
model.setRegDate(LocalDate.EPOCH);
}
String preview = getPreviewURL(name);
model.setPreview(preview);
model.setGender(gender);
model.setTags(createTags(m));
StringBuilder description = new StringBuilder();
for (String tag : model.getTags()) {
description.append("#").append(tag).append(" ");
}
model.setDescription(description.toString());
models.add(model);
}
return models;
}
}

View File

@ -1,19 +1,15 @@
package ctbrec.ui.tabs;
import java.util.List;
import ctbrec.Model;
import javafx.concurrent.ScheduledService;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public abstract class PaginatedScheduledService extends ScheduledService<List<Model>> {
protected int page = 1;
public int getPage() {
return page;
}
public void setPage(int page) {
this.page = page;
}
}