package ctbrec.sites.manyvids; import static ctbrec.io.HttpConstants.*; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.json.JSONArray; import org.json.JSONObject; import org.jsoup.nodes.Element; import org.jsoup.select.Elements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ctbrec.Config; import ctbrec.Model; import ctbrec.Model.State; import ctbrec.io.HtmlParser; import ctbrec.io.HttpClient; import ctbrec.io.HttpException; import ctbrec.sites.AbstractSite; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class MVLive extends AbstractSite { private static final Logger LOG = LoggerFactory.getLogger(MVLive.class); public static final String APP_HOST = "app-v1.live.manyvids.com"; public static final String WS_URL = "wss://" + APP_HOST; public static final String WS_ORIGIN = "https://live.manyvids.com"; public static final String BASE_URL = "https://www.manyvids.com/MVLive/"; private MVLiveHttpClient httpClient; private String mvtoken; @Override public String getName() { return "MV Live"; } @Override public String getBaseUrl() { return BASE_URL; } @Override public String getAffiliateLink() { return getBaseUrl() + "/Join-MV/1002294529"; } @Override public MVLiveModel createModel(String name) { MVLiveModel model = new MVLiveModel(); model.setName(name); model.setDescription(""); model.setSite(this); return model; } @Override public Double getTokenBalance() throws IOException { return 0d; } @Override public String getBuyTokensLink() { return getAffiliateLink(); } @Override public boolean login() throws IOException { return false; } @Override public HttpClient getHttpClient() { if(httpClient == null) { httpClient = new MVLiveHttpClient(); } return httpClient; } @Override public void shutdown() { if (httpClient != null) { httpClient.shutdown(); } } @Override public boolean supportsTips() { return false; } @Override public boolean supportsFollow() { return false; } @Override public boolean isSiteForModel(Model m) { return m instanceof MVLiveModel; } @Override public boolean credentialsAvailable() { return false; } @Override public void init() { // nothing special to do for manyvids } public List getModels() throws IOException { Request request = new Request.Builder() .url(getBaseUrl()) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(REFERER, MVLive.BASE_URL) .build(); try (Response response = getHttpClient().execute(request)) { if (response.isSuccessful()) { String content = response.body().string(); Elements cards = HtmlParser.getTags(content, "div[class*=-model]"); return parseModelCards(cards); } else { throw new HttpException(response.code(), response.message()); } } } private List parseModelCards(Elements cards) { List models = new ArrayList<>(); for (Element card : cards) { try { String cardHtml = card.html(); Element link = HtmlParser.getTag(cardHtml, "a"); link.setBaseUri(getBaseUrl()); String name = HtmlParser.getText(cardHtml, "h4 a"); MVLiveModel model = createModel(name); model.setUrl(link.absUrl("href")); Element thumb = HtmlParser.getTag(cardHtml, "a img.b-lazy"); thumb.setBaseUri(getBaseUrl()); model.setPreview(thumb.absUrl("data-src")); Element status = HtmlParser.getTag(cardHtml, "h4[class~=profile-pic-name]"); String cssClass = status.attr("class"); if(cssClass.contains("live")) { model.setOnlineState(Model.State.ONLINE); } else if(cssClass.contains("private")) { model.setOnlineState(Model.State.PRIVATE); } else { LOG.debug("Unknown online state {}", cssClass); model.setOnlineState(Model.State.UNKNOWN); } models.add(model); } catch(RuntimeException e) { if(e.getMessage().contains("No element selected by")) { // ignore } else { throw e; } } } return models; } @Override public boolean supportsSearch() { return true; } @Override public boolean searchRequiresLogin() { return false; } String getMvToken() throws IOException { if (mvtoken == null) { Request request = new Request.Builder() .url("https://www.manyvids.com/") .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .build(); try (Response response = getHttpClient().execute(request)) { if (response.isSuccessful()) { Element tag = HtmlParser.getTag(response.body().string(), "html"); mvtoken = tag.attr("data-mvtoken"); } else { throw new HttpException(response.code(), response.message()); } } } return mvtoken; } @Override public List search(String q) throws IOException, InterruptedException { List result = new ArrayList<>(); RequestBody body = new FormBody.Builder() .add("mvtoken", getMvToken()) .add("type", "search") .add("category", "stars") .add("search", q) .build(); Request request = new Request.Builder() .url("https://www.manyvids.com/includes/filterSearch.php") .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(ORIGIN, MVLive.BASE_URL) .header(REFERER, MVLive.BASE_URL) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .post(body) .build(); try (Response response = getHttpClient().execute(request)) { if (response.isSuccessful()) { String responseBody = response.body().string(); parseSearchResult(result, responseBody); } else { throw new HttpException(response.code(), response.message()); } } return result; } private void parseSearchResult(List result, String responseBody) { JSONObject json = new JSONObject(responseBody); if(json.has("stars")) { JSONArray stars = json.getJSONArray("stars"); for (int i = 0; i < stars.length(); i++) { JSONObject star = stars.getJSONObject(i); String name = star.getString("label"); MVLiveModel model = createModel(name); long id = star.getLong("id"); String url = "https://www.manyvids.com/MVLive/" + model.getDisplayName() + '/' + id + '/'; model.setUrl(url); model.setPreview(star.getString("img")); if (star.optString("is_live").equals("1")) { if (star.optString("is_private").equals("1")) { model.setOnlineState(State.PRIVATE); } else { model.setOnlineState(State.ONLINE); } } else { model.setOnlineState(State.OFFLINE); } result.add(model); } } } @Override public Model createModelFromUrl(String url) { try { Matcher m = Pattern.compile("https://live.manyvids.com/(?:stream/)?(.*?)(?:/.*?)?").matcher(url.trim()); if (m.matches()) { String modelName = URLDecoder.decode(m.group(1), "utf-8"); return createModel(modelName); } m = Pattern.compile("https://www.manyvids.com/MVLive/(.*?)/\\d+/?").matcher(url.trim()); if (m.matches()) { String modelName = URLDecoder.decode(m.group(1), "utf-8"); return createModel(modelName); } } catch (UnsupportedEncodingException e) { LOG.error("Couldn't decode model name from URL", e); } return super.createModelFromUrl(url); } }