diff --git a/CHANGELOG.md b/CHANGELOG.md index 8697d278..2b33ef93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ * Changes by @WinkRU * Added setting to restrict recording by bit rate * Added setting to use the shortest side to restrict the resolution + * Cam4: Fixed stream URLs search. Slightly increased chances to find good one. + * Camsoda: Added "Voyeur" tab * Chaturbate: Added "Gaming" tab * Streamate: - Fixed "Couldn't load model ID" error while adding models by URL or by @@ -19,7 +21,6 @@ - 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. - * Cam4: Fixed stream URLs search. Slightly increased chances to find good one. 5.2.3 ======================== diff --git a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java index 5a8c00e0..3fad516b 100644 --- a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java +++ b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaTabProvider.java @@ -33,6 +33,7 @@ public class CamsodaTabProvider extends AbstractTabProvider { tabs.add(createTab("Male", API_URL, m -> Objects.equals("m", m.getGender()))); tabs.add(createTab("Couples", API_URL, m -> Objects.equals("c", m.getGender()))); tabs.add(createTab("Trans", API_URL, m -> Objects.equals("t", m.getGender()))); + tabs.add(createTab("Voyeur", API_URL, CamsodaModel::isVoyeur)); followedTab.setRecorder(recorder); followedTab.setScene(scene); tabs.add(followedTab); diff --git a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java index 3de6b8d6..324fdd6e 100644 --- a/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java +++ b/client/src/main/java/ctbrec/ui/sites/camsoda/CamsodaUpdateService.java @@ -8,13 +8,15 @@ import ctbrec.sites.camsoda.CamsodaModel; import ctbrec.ui.SiteUiFactory; import ctbrec.ui.tabs.PaginatedScheduledService; import javafx.concurrent.Task; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; import okhttp3.Request; import org.json.JSONArray; import org.json.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -23,15 +25,16 @@ import static ctbrec.ErrorMessages.HTTP_RESPONSE_BODY_IS_NULL; import static ctbrec.Model.State.OFFLINE; import static ctbrec.Model.State.ONLINE; +@Slf4j public class CamsodaUpdateService extends PaginatedScheduledService { - private static final Logger LOG = LoggerFactory.getLogger(CamsodaUpdateService.class); - protected final String url; protected final boolean loginRequired; protected final Camsoda camsoda; - protected int modelsPerPage = 50; - + protected int modelsPerPage = 60; + private static List modelsList; + private static Instant lastListInfoRequest = Instant.EPOCH; + @Setter protected Predicate filter; public CamsodaUpdateService(String url, boolean loginRequired, Camsoda camsoda, Predicate filter) { @@ -46,7 +49,7 @@ public class CamsodaUpdateService extends PaginatedScheduledService { return new Task<>() { @Override public List call() throws IOException { - return loadOnlineModels().stream() + return getModelList().stream() .sorted((m1, m2) -> (int) (m2.getSortOrder() - m1.getSortOrder())) .filter(filter) .skip((page - 1) * (long) modelsPerPage) @@ -56,11 +59,20 @@ public class CamsodaUpdateService extends PaginatedScheduledService { }; } + private List getModelList() throws IOException { + if (Objects.nonNull(modelsList) && Duration.between(lastListInfoRequest, Instant.now()).getSeconds() < 30) { + return modelsList; + } + lastListInfoRequest = Instant.now(); + modelsList = loadOnlineModels(); + return Optional.ofNullable(modelsList).orElse(Collections.emptyList()); + } + protected List loadOnlineModels() throws IOException { if (loginRequired && StringUtil.isBlank(ctbrec.Config.getInstance().getSettings().camsodaUsername)) { return Collections.emptyList(); } else { - LOG.debug("Fetching page {}", url); + log.debug("Fetching page {}", url); if (loginRequired) { SiteUiFactory.getUi(camsoda).login(); } @@ -87,27 +99,29 @@ public class CamsodaUpdateService extends PaginatedScheduledService { if (templateObject instanceof JSONObject) { parseModelFromObject(result, templateObject, template, models); } else if (templateObject instanceof JSONArray) { - parseModelFromArray(templateObject, template, models); + parseModelFromArray(result, templateObject, template, models); } } catch (Exception e) { - LOG.warn("Couldn't parse one of the models: {}", result, e); + log.warn("Couldn't parse one of the models: {}", result, e); } } return models; } - private void parseModelFromArray(Object templateObject, JSONArray template, List models) { + private void parseModelFromArray(JSONObject result, Object templateObject, JSONArray template, List models) { var tpl = (JSONArray) templateObject; var name = tpl.getString(getTemplateIndex(template, "username")); CamsodaModel model = (CamsodaModel) camsoda.createModel(name); model.setSortOrder(tpl.getFloat(getTemplateIndex(template, "sort_value"))); model.setDescription(tpl.getString(getTemplateIndex(template, "subject_html"))); + model.setNew(result.optBoolean("new")); + model.setVoyeur(result.optBoolean("voyeur")); var preview = tpl.getString(getTemplateIndex(template, "thumb")); if (preview.startsWith("//")) { preview = "https:" + preview; } model.setPreview(preview); - LOG.trace("Preview: {}", preview); + log.trace("Preview: {}", preview); model.setOnlineStateByStatus(tpl.getString(getTemplateIndex(template, "status"))); var displayName = tpl.getString(getTemplateIndex(template, "display_name")); model.setDisplayName(displayName.replaceAll("[^a-zA-Z0-9]", "")); @@ -124,6 +138,7 @@ public class CamsodaUpdateService extends PaginatedScheduledService { model.setDescription(tpl.getString(Integer.toString(getTemplateIndex(template, "subject_html")))); model.setSortOrder(tpl.getFloat(Integer.toString(getTemplateIndex(template, "sort_value")))); model.setNew(result.optBoolean("new")); + model.setVoyeur(result.optBoolean("voyeur")); model.setGender(tpl.getString(Integer.toString(getTemplateIndex(template, "gender")))); var preview = tpl.getString(Integer.toString(getTemplateIndex(template, "thumb"))); if (preview.startsWith("//")) { @@ -160,8 +175,4 @@ public class CamsodaUpdateService extends PaginatedScheduledService { } throw new NoSuchElementException(string + " not found in template: " + template); } - - public void setFilter(Predicate filter) { - this.filter = filter; - } } diff --git a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java index 03095c37..d8790792 100644 --- a/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java +++ b/common/src/main/java/ctbrec/sites/camsoda/CamsodaModel.java @@ -7,6 +7,7 @@ import com.iheartradio.m3u8.data.PlaylistData; import com.iheartradio.m3u8.data.StreamInfo; import ctbrec.AbstractModel; import ctbrec.Config; +import ctbrec.StringUtil; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; import lombok.Getter; @@ -21,6 +22,7 @@ import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; +import java.time.Instant; import java.util.*; import java.util.concurrent.ExecutionException; @@ -40,6 +42,9 @@ public class CamsodaModel extends AbstractModel { private transient String gender; @Getter @Setter + private transient boolean isVoyeur; + @Getter + @Setter private float sortOrder = 0; private final Random random = new Random(); int[] resolution = new int[2]; @@ -166,6 +171,9 @@ public class CamsodaModel extends AbstractModel { JSONObject chat = result.getJSONObject("chat"); String status = chat.getString(STATUS); setOnlineStateByStatus(status); + if (onlineState == OFFLINE) { + setLastSeen(chat.optString("lastOnlineAt")); + } } catch (JSONException e) { throw new IOException("Couldn't parse body as JSON:\n" + body, e); } @@ -203,6 +211,16 @@ public class CamsodaModel extends AbstractModel { return onlineState; } + private void setLastSeen(String date) { + try { + if (StringUtil.isNotBlank(date)) { + setLastSeen(Instant.parse(date.replace("+0000", ".00Z"))); + } + } catch (Exception e) { + // fail silently + } + } + @Override public void invalidateCacheEntries() { streamSources = null; @@ -219,7 +237,7 @@ public class CamsodaModel extends AbstractModel { if (sources.isEmpty()) { return new int[]{0, 0}; } else { - StreamSource src = sources.get(sources.size() - 1); + StreamSource src = sources.getLast(); resolution = new int[]{src.getWidth(), src.getHeight()}; return resolution; }