From 35c8378d88ab847964c1655475f41f22b017bdae Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 11 May 2019 15:03:15 +0200 Subject: [PATCH] Add mechanism to restrict the number of requests Flirt4Free is finnicky with the amount of requests you can do. So we use a mechanism to only allow 2 requests at a time and a cooldown of 500 ms between requests. --- .../sites/flirt4free/Flirt4FreeModel.java | 260 +++++++++++------- 1 file changed, 161 insertions(+), 99 deletions(-) diff --git a/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java b/common/src/main/java/ctbrec/sites/flirt4free/Flirt4FreeModel.java index 6f7befb3..5d56fa24 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.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -53,45 +55,58 @@ public class Flirt4FreeModel extends AbstractModel { private String userIdt = ""; private String userIp = "0.0.0.0"; + private static Semaphore requestThrottle = new Semaphore(2, true); + private static volatile long lastRequest = 0; + private long lastOnlineRequest = 0; + + @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { - if(ignoreCache) { + long now = System.currentTimeMillis(); + long timeSinceLastCheck = now - lastOnlineRequest; + if (ignoreCache && timeSinceLastCheck > TimeUnit.MINUTES.toMillis(1)) { String url = "https://ws.vs3.com/rooms/check-model-status.php?model_name=" + getName(); - Request request = 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("X-Requested-With", "XMLHttpRequest") - .build(); - try(Response response = getSite().getHttpClient().execute(request)) { - if(response.isSuccessful()) { - String body = response.body().string(); - if(body.trim().isEmpty()) { - return false; - } - JSONObject json = new JSONObject(body); - //LOG.debug("check model status: ", json.toString(2)); - online = Objects.equals(json.optString("status"), "online"); - id = json.getString("model_id"); - if(online) { - try { - loadStreamUrl(); - } catch(Exception e) { - online = false; - onlineState = Model.State.OFFLINE; + acquireSlot(); + try { + Request request = 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("X-Requested-With", "XMLHttpRequest") + .build(); + try (Response response = getSite().getHttpClient().execute(request)) { + if (response.isSuccessful()) { + String body = response.body().string(); + if (body.trim().isEmpty()) { + return false; } + JSONObject json = new JSONObject(body); + // LOG.debug("check model status: {}", json.toString(2)); + online = Objects.equals(json.optString("status"), "online"); + id = String.valueOf(json.get("model_id")); + if (online) { + try { + loadStreamUrl(); + } catch (Exception e) { + online = false; + onlineState = Model.State.OFFLINE; + } + } + } else { + throw new HttpException(response.code(), response.message()); } - } else { - throw new HttpException(response.code(), response.message()); } + } finally { + lastOnlineRequest = System.currentTimeMillis(); + releaseSlot(); } } return online; } - private void loadModelInfo() throws IOException { + private void loadModelInfo() throws IOException, InterruptedException { String url = getSite().getBaseUrl() + "/webservices/chat-room-interface.php?a=login_room&model_id=" + id; LOG.trace("Loading url {}", url); Request request = new Request.Builder() @@ -102,11 +117,11 @@ public class Flirt4FreeModel extends AbstractModel { .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("X-Requested-With", "XMLHttpRequest") .build(); - try(Response response = getSite().getHttpClient().execute(request)) { - if(response.isSuccessful()) { + try (Response response = getSite().getHttpClient().execute(request)) { + if (response.isSuccessful()) { JSONObject json = new JSONObject(response.body().string()); - if(json.optString("status").equals("success")) { - //LOG.debug("chat-room-interface {}", json.toString(2)); + 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")); @@ -137,8 +152,13 @@ public class Flirt4FreeModel extends AbstractModel { private List getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException { MasterPlaylist masterPlaylist = null; try { - if(withWebsocket) { - loadStreamUrl(); + if (withWebsocket) { + acquireSlot(); + try { + loadStreamUrl(); + } finally { + releaseSlot(); + } } masterPlaylist = getMasterPlaylist(); } catch (InterruptedException e) { @@ -168,8 +188,9 @@ public class Flirt4FreeModel extends AbstractModel { .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("X-Requested-With", "XMLHttpRequest") .build(); + acquireSlot(); try (Response response = getSite().getHttpClient().execute(req)) { - if(response.isSuccessful()) { + if (response.isSuccessful()) { InputStream inputStream = response.body().byteStream(); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); @@ -178,6 +199,8 @@ public class Flirt4FreeModel extends AbstractModel { } else { throw new HttpException(response.code(), response.message()); } + } finally { + releaseSlot(); } } @@ -246,6 +269,7 @@ public class Flirt4FreeModel extends AbstractModel { } } }); + synchronized (monitor) { monitor.wait(10_000); if (streamHost == null) { @@ -262,80 +286,92 @@ 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); - } + // if(tokens < 50 || tokens > 750000) { + // throw new RuntimeException("Tip amount has to be between 50 and 750000"); + // } - // 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()); + // make sure we are logged in and all necessary model data is available + getSite().login(); + acquireSlot(); + try { + loadStreamUrl(); + } catch (InterruptedException e) { + throw new IOException("Couldn't send tip", e); + } finally { + releaseSlot(); } - } - } - private String getUserIdt() throws IOException { - if(userIdt.isEmpty()) { + // 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(getUrl()) + .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()) { - 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"); + 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()); } } + } catch (InterruptedException e) { + throw new IOException("Couldn't acquire request slot", e); + } + } + + private String getUserIdt() throws IOException, InterruptedException { + if (userIdt.isEmpty()) { + acquireSlot(); + try { + 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()); + } + } + } finally { + releaseSlot(); + } } return userIdt; } @@ -361,22 +397,31 @@ public class Flirt4FreeModel extends AbstractModel { @Override public boolean follow() throws IOException { - return changeFavoriteStatus(true); + try { + return changeFavoriteStatus(true); + } catch (InterruptedException e) { + throw new IOException("Couldn't change follow status for model " + getName(), e); + } } @Override public boolean unfollow() throws IOException { try { isOnline(true); + return changeFavoriteStatus(false); } catch (ExecutionException | InterruptedException e) { - throw new IOException("Couldn't rectrieve model id", e); + throw new IOException("Couldn't change follow status for model " + getName(), e); } - return changeFavoriteStatus(false); } - private boolean changeFavoriteStatus(boolean add) throws IOException { + private boolean changeFavoriteStatus(boolean add) throws IOException, InterruptedException { getSite().login(); - loadModelInfo(); + acquireSlot(); + try { + loadModelInfo(); + } finally { + releaseSlot(); + } String url = getSite().getBaseUrl() + "/external.php?a=" + (add ? "add_favorite" : "delete_favorite") + "&id=" + id + @@ -391,7 +436,7 @@ public class Flirt4FreeModel extends AbstractModel { .header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .build(); try (Response response = getSite().getHttpClient().execute(req)) { - if(response.isSuccessful()) { + if (response.isSuccessful()) { String body = response.body().string(); LOG.debug("Follow/Unfollow response: {}", body); return Objects.equals(body, "1"); @@ -423,4 +468,21 @@ public class Flirt4FreeModel extends AbstractModel { public void setOnline(boolean b) { online = b; } + + private void acquireSlot() throws InterruptedException { + LOG.debug("Acquire: {}", requestThrottle.availablePermits()); + requestThrottle.acquire(); + long now = System.currentTimeMillis(); + long millisSinceLastRequest = now - lastRequest; + if(millisSinceLastRequest < 500) { + LOG.debug("Sleeping: {}", (500-millisSinceLastRequest)); + Thread.sleep(500 - millisSinceLastRequest); + } + } + + private void releaseSlot() { + lastRequest = System.currentTimeMillis(); + requestThrottle.release(); + LOG.debug("Release: {}", requestThrottle.availablePermits()); + } }