package ctbrec.sites.stripchat; import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; import static ctbrec.sites.stripchat.StripchatHttpClient.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.xml.bind.JAXBException; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.PlaylistException; import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.io.HttpException; import ctbrec.recorder.download.StreamSource; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; public class StripchatModel extends AbstractModel { private static final transient Logger LOG = LoggerFactory.getLogger(StripchatModel.class); private String status = null; private int[] resolution = new int[] {0, 0}; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { if (ignoreCache || status == null) { JSONObject jsonResponse = loadModelInfo(); if (jsonResponse.has("user")) { JSONObject user = jsonResponse.getJSONObject("user"); status = user.optString("status"); mapOnlineState(status); } } return onlineState == ONLINE; } private void mapOnlineState(String status) { switch (status) { case "public": setOnlineState(ONLINE); break; case "idle": setOnlineState(AWAY); break; case "private": case "p2p": case "groupShow": case "virtualPrivate": setOnlineState(PRIVATE); break; case "off": setOnlineState(OFFLINE); break; default: LOG.debug("Unknown online state {} for model {}", status, getName()); setOnlineState(OFFLINE); break; } } private JSONObject loadModelInfo() throws IOException { String name = getName(); String url = getSite().getBaseUrl() + "/api/front/users/username/" + name; Request req = new Request.Builder() .url(url) .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(REFERER, getUrl()) .build(); try (Response response = site.getHttpClient().execute(req)) { if (response.isSuccessful()) { JSONObject jsonResponse = new JSONObject(response.body().string()); return jsonResponse; } else { throw new HttpException(response.code(), response.message()); } } } @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException { String name = getName(); String url = getSite().getBaseUrl() + "/api/front/models/username/" + name + "/cam?triggerRequest=loadCam"; Request req = new Request.Builder() .url(url) .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(X_REQUESTED_WITH, XML_HTTP_REQUEST) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(REFERER, getUrl()) .build(); try (Response response = site.getHttpClient().execute(req)) { if (response.isSuccessful()) { JSONObject jsonResponse = new JSONObject(response.body().string()); String streamName = jsonResponse.optString("streamName", jsonResponse.optString("")); JSONObject viewServers = jsonResponse.getJSONObject("viewServers"); String serverName = viewServers.optString("flashphoner-hls"); JSONObject broadcastSettings = jsonResponse.getJSONObject("broadcastSettings"); List sources = new ArrayList<>(); StreamSource best = new StreamSource(); best.height = broadcastSettings.optInt("height"); best.width = broadcastSettings.optInt("width"); best.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + streamName + '/' + streamName + ".m3u8"; sources.add(best); JSONObject presets = broadcastSettings.optJSONObject("presets"); Object defaultObject = presets.get("testing"); if (defaultObject instanceof JSONObject) { JSONObject defaults = (JSONObject) defaultObject; JSONArray heights = defaults.names(); for (int i = 0; i < heights.length(); i++) { String h = heights.getString(i); StreamSource streamSource = new StreamSource(); streamSource.height = Integer.parseInt(h.replaceAll("[^\\d]", "")); streamSource.width = streamSource.height * best.getWidth() / best.getHeight(); String source = streamName + '_' + streamSource.height + 'p'; streamSource.mediaPlaylistUrl = "https://b-" + serverName + ".stripst.com/hls/" + source + '/' + source + ".m3u8"; sources.add(streamSource); } } return sources.stream().sorted((a, b) -> a.height - b.height).collect(Collectors.toList()); } else { throw new HttpException(response.code(), response.message()); } } } @Override public void invalidateCacheEntries() { status = null; resolution = new int[] { 0, 0 }; } @Override public void receiveTip(Double tokens) throws IOException { // not implemented } @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { if (!failFast) { try { List sources = getStreamSources(); if (!sources.isEmpty()) { StreamSource best = sources.get(sources.size() - 1); resolution = new int[] { best.getWidth(), best.getHeight() }; } } catch (IOException | ParseException | PlaylistException | JAXBException e) { throw new ExecutionException(e); } } return resolution; } @Override public boolean follow() throws IOException { getSite().getHttpClient().login(); JSONObject modelInfo = loadModelInfo(); JSONObject user = modelInfo.getJSONObject("user"); long modelId = user.optLong("id"); StripchatHttpClient client = (StripchatHttpClient) getSite().getHttpClient(); String url = Stripchat.baseUri + "/api/front/users/" + client.getUserId() + "/favorites/" + modelId; JSONObject requestParams = new JSONObject(); requestParams.put("csrfToken", client.getCsrfToken()); requestParams.put("csrfTimestamp", client.getCsrfTimestamp()); requestParams.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp()); RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON); Request request = new Request.Builder() .url(url) .header(ACCEPT, "*/*") .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(ORIGIN, Stripchat.baseUri) .header(REFERER, Stripchat.baseUri + '/' + getName()) .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON) .put(body) .build(); try (Response response = client.execute(request)) { if (response.isSuccessful()) { return true; } else { throw new HttpException(response.code(), response.message()); } } } @Override public boolean unfollow() throws IOException { getSite().getHttpClient().login(); JSONObject modelInfo = loadModelInfo(); JSONObject user = modelInfo.getJSONObject("user"); long modelId = user.optLong("id"); JSONArray favoriteIds = new JSONArray(); favoriteIds.put(modelId); StripchatHttpClient client = (StripchatHttpClient) getSite().getHttpClient(); String url = Stripchat.baseUri + "/api/front/users/" + client.getUserId() + "/favorites"; JSONObject requestParams = new JSONObject(); requestParams.put("favoriteIds", favoriteIds); requestParams.put("csrfToken", client.getCsrfToken()); requestParams.put("csrfTimestamp", client.getCsrfTimestamp()); requestParams.put("csrfNotifyTimestamp", client.getCsrfNotifyTimestamp()); RequestBody body = RequestBody.Companion.create(requestParams.toString(), JSON); Request request = new Request.Builder() .url(url) .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(ORIGIN, Stripchat.baseUri) .header(REFERER, Stripchat.baseUri) .header(CONTENT_TYPE, MIMETYPE_APPLICATION_JSON) .delete(body) .build(); try (Response response = client.execute(request)) { if (response.isSuccessful()) { return true; } else { throw new HttpException(response.code(), response.message()); } } } }