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,45 +55,58 @@ 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();
Request request = new Request.Builder() acquireSlot();
.url(url) try {
.header("Accept", "*/*") Request request = new Request.Builder()
.header("Accept-Language", "en-US,en;q=0.5") .url(url)
.header("Referer", getUrl()) .header("Accept", "*/*")
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("Accept-Language", "en-US,en;q=0.5")
.header("X-Requested-With", "XMLHttpRequest") .header("Referer", getUrl())
.build(); .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
try(Response response = getSite().getHttpClient().execute(request)) { .header("X-Requested-With", "XMLHttpRequest")
if(response.isSuccessful()) { .build();
String body = response.body().string(); try (Response response = getSite().getHttpClient().execute(request)) {
if(body.trim().isEmpty()) { if (response.isSuccessful()) {
return false; String body = response.body().string();
} if (body.trim().isEmpty()) {
JSONObject json = new JSONObject(body); return false;
//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;
} }
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; 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) {
loadStreamUrl(); acquireSlot();
try {
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,80 +286,92 @@ public class Flirt4FreeModel extends AbstractModel {
@Override @Override
public void receiveTip(Double tokens) throws IOException { 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 { try {
loadStreamUrl(); // if(tokens < 50 || tokens > 750000) {
} catch (InterruptedException e) { // throw new RuntimeException("Tip amount has to be between 50 and 750000");
throw new IOException("Couldn't send tip", e); // }
}
// send the tip // make sure we are logged in and all necessary model data is available
int giftId = isInteractiveShow ? 775 : 171; getSite().login();
int amount = tokens.intValue(); acquireSlot();
LOG.debug("Sending tip of {} to {}", amount, getName()); try {
String url = "https://ws.vs3.com/rooms/send-tip.php?" + loadStreamUrl();
"gift_id=" + giftId + } catch (InterruptedException e) {
"&num_credits=" + amount + throw new IOException("Couldn't send tip", e);
"&userId=" + getUserIdt() + } finally {
"&username=" + Config.getInstance().getSettings().flirt4freeUsername + releaseSlot();
"&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 { // send the tip
if(userIdt.isEmpty()) { 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() Request req = new Request.Builder()
.url(getUrl()) .url(url)
.header("Accept", "*/*") .header("Accept", "*/*")
.header("Accept-Language", "en-US,en;q=0.5") .header("Accept-Language", "en-US,en;q=0.5")
.header("Referer", getUrl()) .header("Referer", getUrl())
.header("User-Agent", Config.getInstance().getSettings().httpUserAgent) .header("User-Agent", Config.getInstance().getSettings().httpUserAgent)
.header("Referer", getUrl())
.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()) {
String body = response.body().string(); JSONObject json = new JSONObject(response.body().string());
Matcher m = Pattern.compile("idt\\s*:\\s*'(.*?)',").matcher(body); if (json.optInt("success") != 1) {
if(m.find()) { String msg = json.optString("message");
userIdt = m.group(1); if (json.has("error_message")) {
} else { msg = json.getString("error_message");
throw new IOException("userIdt not found on HTML page"); }
LOG.error("Sending tip failed: {}", msg);
LOG.debug("Response: {}", json.toString(2));
throw new IOException(msg);
} }
} else { } else {
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, 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; return userIdt;
} }
@ -361,22 +397,31 @@ public class Flirt4FreeModel extends AbstractModel {
@Override @Override
public boolean follow() throws IOException { 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 @Override
public boolean unfollow() throws IOException { public boolean unfollow() throws IOException {
try { try {
isOnline(true); isOnline(true);
return changeFavoriteStatus(false);
} catch (ExecutionException | InterruptedException e) { } 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(); getSite().login();
loadModelInfo(); acquireSlot();
try {
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());
}
} }