From 204eb99b2989cf124e3561f1700d731ec1f3c8ed Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 16 Apr 2019 19:51:57 +0200 Subject: [PATCH] Implement follow/unfollow for Flirt4Free --- .../main/java/ctbrec/ui/ThumbOverviewTab.java | 2 +- .../flirt4free/Flirt4FreeFavoritesTab.java | 25 ++++ .../Flirt4FreeFavoritesUpdateService.java | 72 ++++++++++ .../flirt4free/Flirt4FreeTabProvider.java | 6 +- .../ctbrec/sites/flirt4free/Flirt4Free.java | 7 +- .../sites/flirt4free/Flirt4FreeModel.java | 133 +++++++++++++++++- 6 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesTab.java create mode 100644 client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesUpdateService.java diff --git a/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java b/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java index 6069e6bd..f47f890c 100644 --- a/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java +++ b/client/src/main/java/ctbrec/ui/ThumbOverviewTab.java @@ -495,7 +495,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener { LOG.error("An error occured while sending tip", ex); showError("Couldn't send tip", "An error occured while sending tip:", ex); } catch (Exception ex) { - showError("Couldn't send tip", "You entered an invalid amount of tokens", null); + showError("Couldn't send tip", "You entered an invalid amount of tokens", ex); } } }); diff --git a/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesTab.java b/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesTab.java new file mode 100644 index 00000000..28cf9eeb --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesTab.java @@ -0,0 +1,25 @@ +package ctbrec.ui.sites.flirt4free; + +import ctbrec.sites.flirt4free.Flirt4Free; +import ctbrec.ui.FollowedTab; +import ctbrec.ui.ThumbOverviewTab; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; + +public class Flirt4FreeFavoritesTab extends ThumbOverviewTab implements FollowedTab { + + public Flirt4FreeFavoritesTab(Flirt4Free flirt4free) { + super("Favorites", new Flirt4FreeFavoritesUpdateService(flirt4free), flirt4free); + } + + public void setScene(Scene scene) { + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + if(this.isSelected()) { + if(event.getCode() == KeyCode.DELETE) { + follow(selectedThumbCells, false); + } + } + }); + } +} diff --git a/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesUpdateService.java b/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesUpdateService.java new file mode 100644 index 00000000..a786ab19 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeFavoritesUpdateService.java @@ -0,0 +1,72 @@ +package ctbrec.ui.sites.flirt4free; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; + +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.io.HtmlParser; +import ctbrec.io.HttpException; +import ctbrec.sites.flirt4free.Flirt4Free; +import ctbrec.sites.flirt4free.Flirt4FreeModel; +import ctbrec.ui.PaginatedScheduledService; +import ctbrec.ui.SiteUiFactory; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.Response; + +public class Flirt4FreeFavoritesUpdateService extends PaginatedScheduledService { + private Flirt4Free flirt4free; + + public Flirt4FreeFavoritesUpdateService(Flirt4Free flirt4free) { + this.flirt4free = flirt4free; + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + List models = new ArrayList<>(); + String url = flirt4free.getBaseUrl() + "/my-account/secure/favorites.php?a=models&sort=online&pg=" + page; + SiteUiFactory.getUi(flirt4free).login(); + Request request = new Request.Builder() + .url(url) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", flirt4free.getBaseUrl()) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .build(); + try(Response response = flirt4free.getHttpClient().execute(request)) { + if (response.isSuccessful()) { + String body = response.body().string(); + Elements modelContainers = HtmlParser.getTags(body, "div.model-container"); + for (Element modelContainer : modelContainers) { + String modelHtml = modelContainer.html(); + Element bioLink = HtmlParser.getTag(modelHtml, "a.common-link"); + bioLink.setBaseUri(flirt4free.getBaseUrl()); + Flirt4FreeModel model = (Flirt4FreeModel) flirt4free.createModelFromUrl(bioLink.absUrl("href")); + Element img = HtmlParser.getTag(modelHtml, "a > img[alt]"); + model.setDisplayName(img.attr("alt")); + model.setPreview(img.attr("src")); + model.setDescription(""); + model.setOnline(modelHtml.contains("I'm Online")); + try { + model.setOnlineState(model.isOnline() ? Model.State.ONLINE : Model.State.OFFLINE); + } catch (ExecutionException | InterruptedException e) {} + models.add(model); + } + return models; + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + }; + } +} diff --git a/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeTabProvider.java b/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeTabProvider.java index d057c10f..23555159 100644 --- a/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeTabProvider.java +++ b/client/src/main/java/ctbrec/ui/sites/flirt4free/Flirt4FreeTabProvider.java @@ -13,9 +13,12 @@ import javafx.util.Duration; public class Flirt4FreeTabProvider extends TabProvider { private Flirt4Free flirt4Free; + private ThumbOverviewTab followedTab; public Flirt4FreeTabProvider(Flirt4Free flirt4Free) { this.flirt4Free = flirt4Free; + followedTab = new Flirt4FreeFavoritesTab(flirt4Free); + followedTab.setRecorder(flirt4Free.getRecorder()); } @Override @@ -24,12 +27,13 @@ public class Flirt4FreeTabProvider extends TabProvider { tabs.add(createTab("Girls", flirt4Free.getBaseUrl() + "/live/girls/")); tabs.add(createTab("Boys", flirt4Free.getBaseUrl() + "/live/guys/")); tabs.add(createTab("Trans", flirt4Free.getBaseUrl() + "/live/trans/")); + tabs.add(followedTab); return tabs; } @Override public Tab getFollowedTab() { - return null; + return followedTab; } private ThumbOverviewTab createTab(String title, String url) { diff --git a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4Free.java b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4Free.java index dacba275..5273dd9a 100644 --- a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4Free.java +++ b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4Free.java @@ -9,8 +9,6 @@ import java.util.regex.Pattern; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ctbrec.Config; import ctbrec.Model; @@ -23,7 +21,6 @@ import okhttp3.Response; public class Flirt4Free extends AbstractSite { - private static final transient Logger LOG = LoggerFactory.getLogger(Flirt4Free.class); public static final String BASE_URI = "https://www.flirt4free.com"; private HttpClient httpClient; @@ -106,12 +103,12 @@ public class Flirt4Free extends AbstractSite { @Override public boolean supportsTips() { - return true; + return false; } @Override public boolean supportsFollow() { - return false; + return true; } @Override diff --git a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java index 0cb1cbe5..39a25994 100644 --- a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java +++ b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java @@ -8,6 +8,8 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.json.JSONObject; import org.slf4j.Logger; @@ -47,6 +49,9 @@ public class Flirt4FreeModel extends AbstractModel { int[] resolution = new int[2]; private Object monitor = new Object(); private boolean online = false; + private boolean isInteractiveShow = false; + private String userIdt = ""; + private String userIp = "0.0.0.0"; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { @@ -63,6 +68,7 @@ public class Flirt4FreeModel extends AbstractModel { try(Response response = getSite().getHttpClient().execute(request)) { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); + //LOG.debug("check model status: {}", json.toString(2)); online = Objects.equals(json.optString("status"), "online"); id = json.getString("model_id"); if(online) { @@ -96,6 +102,7 @@ public class Flirt4FreeModel extends AbstractModel { if(response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); if(json.optString("status").equals("success")) { + //LOG.debug("chat-room-interface {}", json.toString(2)); JSONObject config = json.getJSONObject("config"); JSONObject performer = config.getJSONObject("performer"); setName(performer.optString("name_seo", "n/a")); @@ -105,6 +112,8 @@ public class Flirt4FreeModel extends AbstractModel { chatHost = room.getString("host"); chatPort = room.getString("port_to_be"); chatToken = json.getString("token_enc"); + JSONObject user = config.getJSONObject("user"); + userIp = user.getString("ip"); } else { LOG.trace("Loading model info failed. Assuming model {} is offline", getName()); online = false; @@ -193,10 +202,20 @@ public class Flirt4FreeModel extends AbstractModel { public void onMessage(WebSocket webSocket, String text) { LOG.trace("Chat wbesocket for {}: {}", getName(), text); JSONObject json = new JSONObject(text); + //LOG.debug("WS {}", text); if (json.optString("command").equals("8011")) { JSONObject data = json.getJSONObject("data"); - streamHost = data.getString("stream_host"); + streamHost = data.getString("stream_host"); // TODO look, if the stream_host is equal to the one encoded in base64 in some of the ajax requests (parameters) online = true; + isInteractiveShow = data.optString("devices").equals("1"); + if(data.optString("room_state").equals("P")) { + onlineState = Model.State.PRIVATE; + online = false; + } + if(data.optString("room_state").equals("0") && data.optString("login_group_id").equals("14")) { + onlineState = Model.State.GROUP; + online = false; + } try { resolution[0] = Integer.parseInt(data.getString("stream_width")); resolution[1] = Integer.parseInt(data.getString("stream_height")); @@ -239,6 +258,82 @@ public class Flirt4FreeModel extends AbstractModel { @Override public void receiveTip(Double tokens) throws IOException { + // if(tokens < 50 || tokens > 750000) { + // throw new RuntimeException("Tip amount has to be between 50 and 750000"); + // } + + // make sure we are logged in and all necessary model data is available + getSite().login(); + try { + loadStreamUrl(); + } catch (InterruptedException e) { + throw new IOException("Couldn't send tip", e); + } + + // send the tip + int giftId = isInteractiveShow ? 775 : 171; + int amount = tokens.intValue(); + LOG.debug("Sending tip of {} to {}", amount, getName()); + String url = "https://ws.vs3.com/rooms/send-tip.php?" + + "gift_id=" + giftId + + "&num_credits=" + amount + + "&userId=" + getUserIdt() + + "&username=" + Config.getInstance().getSettings().flirt4freeUsername + + "&userIP=" + userIp + + "&anonymous=N&response_type=json" + + "&t=" + System.currentTimeMillis(); + LOG.debug("Trying to send tip: {}", url); + Request req = new Request.Builder() + .url(url) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", getUrl()) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .header("Referer", getUrl()) + .header("X-Requested-With", "XMLHttpRequest") + .build(); + try (Response response = getSite().getHttpClient().execute(req)) { + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + if(json.optInt("success") != 1) { + String msg = json.optString("message"); + if(json.has("error_message")) { + msg = json.getString("error_message"); + } + LOG.error("Sending tip failed: {}", msg); + LOG.debug("Response: {}", json.toString(2)); + throw new IOException(msg); + } + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + + private String getUserIdt() throws IOException { + if(userIdt.isEmpty()) { + Request req = new Request.Builder() + .url(getUrl()) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", getUrl()) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .build(); + try (Response response = getSite().getHttpClient().execute(req)) { + if(response.isSuccessful()) { + String body = response.body().string(); + Matcher m = Pattern.compile("idt\\s*:\\s*'(.*?)',").matcher(body); + if(m.find()) { + userIdt = m.group(1); + } else { + throw new IOException("userIdt not found on HTML page"); + } + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + return userIdt; } @Override @@ -262,12 +357,44 @@ public class Flirt4FreeModel extends AbstractModel { @Override public boolean follow() throws IOException { - return false; + return changeFavoriteStatus(true); } @Override public boolean unfollow() throws IOException { - return false; + try { + isOnline(true); + } catch (ExecutionException | InterruptedException e) { + throw new IOException("Couldn't rectrieve model id", e); + } + return changeFavoriteStatus(false); + } + + private boolean changeFavoriteStatus(boolean add) throws IOException { + getSite().login(); + loadModelInfo(); + String url = getSite().getBaseUrl() + "/external.php?a=" + + (add ? "add_favorite" : "delete_favorite") + + "&id=" + id + + "&name=" + getDisplayName() + + "&t=" + System.currentTimeMillis(); + LOG.debug("Sending follow/unfollow request: {}", url); + Request req = new Request.Builder() + .url(url) + .header("Accept", "*/*") + .header("Accept-Language", "en-US,en;q=0.5") + .header("Referer", getUrl()) + .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) + .build(); + try (Response response = getSite().getHttpClient().execute(req)) { + if(response.isSuccessful()) { + String body = response.body().string(); + LOG.debug("Follow/Unfollow response: {}", body); + return Objects.equals(body, "1"); + } else { + throw new HttpException(response.code(), response.message()); + } + } } public void setId(String id) {