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.
This commit is contained in:
0xboobface 2019-05-11 15:03:15 +02:00
parent 6c9bff56fc
commit 35c8378d88
1 changed files with 161 additions and 99 deletions

View File

@ -8,6 +8,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -53,10 +55,19 @@ public class Flirt4FreeModel extends AbstractModel {
private String userIdt = ""; private String userIdt = "";
private String userIp = "0.0.0.0"; 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 @Override
public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { 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(); String url = "https://ws.vs3.com/rooms/check-model-status.php?model_name=" + getName();
acquireSlot();
try {
Request request = new Request.Builder() Request request = new Request.Builder()
.url(url) .url(url)
.header("Accept", "*/*") .header("Accept", "*/*")
@ -65,20 +76,20 @@ public class Flirt4FreeModel extends AbstractModel {
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest") .header("X-Requested-With", "XMLHttpRequest")
.build(); .build();
try(Response response = getSite().getHttpClient().execute(request)) { try (Response response = getSite().getHttpClient().execute(request)) {
if(response.isSuccessful()) { if (response.isSuccessful()) {
String body = response.body().string(); String body = response.body().string();
if(body.trim().isEmpty()) { if (body.trim().isEmpty()) {
return false; return false;
} }
JSONObject json = new JSONObject(body); JSONObject json = new JSONObject(body);
//LOG.debug("check model status: ", json.toString(2)); // LOG.debug("check model status: {}", json.toString(2));
online = Objects.equals(json.optString("status"), "online"); online = Objects.equals(json.optString("status"), "online");
id = json.getString("model_id"); id = String.valueOf(json.get("model_id"));
if(online) { if (online) {
try { try {
loadStreamUrl(); loadStreamUrl();
} catch(Exception e) { } catch (Exception e) {
online = false; online = false;
onlineState = Model.State.OFFLINE; onlineState = Model.State.OFFLINE;
} }
@ -87,11 +98,15 @@ public class Flirt4FreeModel extends AbstractModel {
throw new HttpException(response.code(), response.message()); throw new HttpException(response.code(), response.message());
} }
} }
} finally {
lastOnlineRequest = System.currentTimeMillis();
releaseSlot();
}
} }
return online; 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; String url = getSite().getBaseUrl() + "/webservices/chat-room-interface.php?a=login_room&model_id=" + id;
LOG.trace("Loading url {}", url); LOG.trace("Loading url {}", url);
Request request = new Request.Builder() Request request = new Request.Builder()
@ -102,11 +117,11 @@ public class Flirt4FreeModel extends AbstractModel {
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest") .header("X-Requested-With", "XMLHttpRequest")
.build(); .build();
try(Response response = getSite().getHttpClient().execute(request)) { try (Response response = getSite().getHttpClient().execute(request)) {
if(response.isSuccessful()) { if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(response.body().string());
if(json.optString("status").equals("success")) { if (json.optString("status").equals("success")) {
//LOG.debug("chat-room-interface {}", json.toString(2)); // LOG.debug("chat-room-interface {}", json.toString(2));
JSONObject config = json.getJSONObject("config"); JSONObject config = json.getJSONObject("config");
JSONObject performer = config.getJSONObject("performer"); JSONObject performer = config.getJSONObject("performer");
setName(performer.optString("name_seo", "n/a")); setName(performer.optString("name_seo", "n/a"));
@ -137,8 +152,13 @@ public class Flirt4FreeModel extends AbstractModel {
private List<StreamSource> getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException { private List<StreamSource> getStreamSources(boolean withWebsocket) throws IOException, ExecutionException, ParseException, PlaylistException {
MasterPlaylist masterPlaylist = null; MasterPlaylist masterPlaylist = null;
try { try {
if(withWebsocket) { if (withWebsocket) {
acquireSlot();
try {
loadStreamUrl(); loadStreamUrl();
} finally {
releaseSlot();
}
} }
masterPlaylist = getMasterPlaylist(); masterPlaylist = getMasterPlaylist();
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -168,8 +188,9 @@ public class Flirt4FreeModel extends AbstractModel {
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("X-Requested-With", "XMLHttpRequest") .header("X-Requested-With", "XMLHttpRequest")
.build(); .build();
acquireSlot();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if(response.isSuccessful()) { if (response.isSuccessful()) {
InputStream inputStream = response.body().byteStream(); InputStream inputStream = response.body().byteStream();
PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT);
Playlist playlist = parser.parse(); Playlist playlist = parser.parse();
@ -178,6 +199,8 @@ public class Flirt4FreeModel extends AbstractModel {
} else { } else {
throw new HttpException(response.code(), response.message()); throw new HttpException(response.code(), response.message());
} }
} finally {
releaseSlot();
} }
} }
@ -246,6 +269,7 @@ public class Flirt4FreeModel extends AbstractModel {
} }
} }
}); });
synchronized (monitor) { synchronized (monitor) {
monitor.wait(10_000); monitor.wait(10_000);
if (streamHost == null) { if (streamHost == null) {
@ -262,16 +286,20 @@ public class Flirt4FreeModel extends AbstractModel {
@Override @Override
public void receiveTip(Double tokens) throws IOException { public void receiveTip(Double tokens) throws IOException {
try {
// if(tokens < 50 || tokens > 750000) { // if(tokens < 50 || tokens > 750000) {
// throw new RuntimeException("Tip amount has to be between 50 and 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 // make sure we are logged in and all necessary model data is available
getSite().login(); getSite().login();
acquireSlot();
try { try {
loadStreamUrl(); loadStreamUrl();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new IOException("Couldn't send tip", e); throw new IOException("Couldn't send tip", e);
} finally {
releaseSlot();
} }
// send the tip // send the tip
@ -297,11 +325,11 @@ public class Flirt4FreeModel extends AbstractModel {
.header("X-Requested-With", "XMLHttpRequest") .header("X-Requested-With", "XMLHttpRequest")
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if(response.isSuccessful()) { if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(response.body().string());
if(json.optInt("success") != 1) { if (json.optInt("success") != 1) {
String msg = json.optString("message"); String msg = json.optString("message");
if(json.has("error_message")) { if (json.has("error_message")) {
msg = json.getString("error_message"); msg = json.getString("error_message");
} }
LOG.error("Sending tip failed: {}", msg); LOG.error("Sending tip failed: {}", msg);
@ -312,10 +340,15 @@ public class Flirt4FreeModel extends AbstractModel {
throw new HttpException(response.code(), response.message()); throw new HttpException(response.code(), response.message());
} }
} }
} catch (InterruptedException e) {
throw new IOException("Couldn't acquire request slot", e);
}
} }
private String getUserIdt() throws IOException { private String getUserIdt() throws IOException, InterruptedException {
if(userIdt.isEmpty()) { if (userIdt.isEmpty()) {
acquireSlot();
try {
Request req = new Request.Builder() Request req = new Request.Builder()
.url(getUrl()) .url(getUrl())
.header("Accept", "*/*") .header("Accept", "*/*")
@ -324,10 +357,10 @@ public class Flirt4FreeModel extends AbstractModel {
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if(response.isSuccessful()) { if (response.isSuccessful()) {
String body = response.body().string(); String body = response.body().string();
Matcher m = Pattern.compile("idt\\s*:\\s*'(.*?)',").matcher(body); Matcher m = Pattern.compile("idt\\s*:\\s*'(.*?)',").matcher(body);
if(m.find()) { if (m.find()) {
userIdt = m.group(1); userIdt = m.group(1);
} else { } else {
throw new IOException("userIdt not found on HTML page"); throw new IOException("userIdt not found on HTML page");
@ -336,6 +369,9 @@ public class Flirt4FreeModel extends AbstractModel {
throw new HttpException(response.code(), response.message()); throw new HttpException(response.code(), response.message());
} }
} }
} finally {
releaseSlot();
}
} }
return userIdt; return userIdt;
} }
@ -361,22 +397,31 @@ public class Flirt4FreeModel extends AbstractModel {
@Override @Override
public boolean follow() throws IOException { public boolean follow() throws IOException {
try {
return changeFavoriteStatus(true); return changeFavoriteStatus(true);
} catch (InterruptedException e) {
throw new IOException("Couldn't change follow status for model " + getName(), e);
}
} }
@Override @Override
public boolean unfollow() throws IOException { public boolean unfollow() throws IOException {
try { try {
isOnline(true); isOnline(true);
} catch (ExecutionException | InterruptedException e) {
throw new IOException("Couldn't rectrieve model id", e);
}
return changeFavoriteStatus(false); return changeFavoriteStatus(false);
} catch (ExecutionException | InterruptedException e) {
throw new IOException("Couldn't change follow status for model " + getName(), e);
}
} }
private boolean changeFavoriteStatus(boolean add) throws IOException { private boolean changeFavoriteStatus(boolean add) throws IOException, InterruptedException {
getSite().login(); getSite().login();
acquireSlot();
try {
loadModelInfo(); loadModelInfo();
} finally {
releaseSlot();
}
String url = getSite().getBaseUrl() + "/external.php?a=" + String url = getSite().getBaseUrl() + "/external.php?a=" +
(add ? "add_favorite" : "delete_favorite") + (add ? "add_favorite" : "delete_favorite") +
"&id=" + id + "&id=" + id +
@ -391,7 +436,7 @@ public class Flirt4FreeModel extends AbstractModel {
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.build(); .build();
try (Response response = getSite().getHttpClient().execute(req)) { try (Response response = getSite().getHttpClient().execute(req)) {
if(response.isSuccessful()) { if (response.isSuccessful()) {
String body = response.body().string(); String body = response.body().string();
LOG.debug("Follow/Unfollow response: {}", body); LOG.debug("Follow/Unfollow response: {}", body);
return Objects.equals(body, "1"); return Objects.equals(body, "1");
@ -423,4 +468,21 @@ public class Flirt4FreeModel extends AbstractModel {
public void setOnline(boolean b) { public void setOnline(boolean b) {
online = 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());
}
} }