package ctbrec.sites.amateurtv; import com.iheartradio.m3u8.*; import com.iheartradio.m3u8.data.MediaPlaylist; import com.iheartradio.m3u8.data.Playlist; import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.io.HttpClient; import ctbrec.io.HttpException; import ctbrec.recorder.download.Download; import ctbrec.recorder.download.StreamSource; import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.xml.bind.JAXBException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.ExecutionException; import static ctbrec.Model.State.*; import static ctbrec.io.HttpConstants.*; public class AmateurTvModel extends AbstractModel { private static final Logger LOG = LoggerFactory.getLogger(AmateurTvModel.class); private boolean online = false; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { if (ignoreCache) { JSONObject json = getModelInfo(); online = json.optString("status").equalsIgnoreCase("online"); onlineState = online ? ONLINE : OFFLINE; } return online; } @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { if (failFast && onlineState != UNKNOWN) { return onlineState; } else { try { onlineState = isOnline(true) ? ONLINE : OFFLINE; } catch (InterruptedException e) { Thread.currentThread().interrupt(); onlineState = OFFLINE; } catch (IOException | ExecutionException e) { onlineState = OFFLINE; } return onlineState; } } @Override public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException, JAXBException { List streamSources = new ArrayList<>(); String streamUrl = getStreamUrl(); Request req = new Request.Builder().url(streamUrl).build(); try (Response response = site.getHttpClient().execute(req)) { if (response.isSuccessful()) { InputStream inputStream = Objects.requireNonNull(response.body()).byteStream(); PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); MediaPlaylist media = playlist.getMediaPlaylist(); String baseUrl = streamUrl.substring(0, streamUrl.lastIndexOf('/') + 1); String vodUri = baseUrl + media.getTracks().get(0).getUri(); StreamSource streamsource = new StreamSource(); streamsource.mediaPlaylistUrl = vodUri; streamsource.width = 0; streamsource.height = 0; streamSources.add(streamsource); } else { throw new HttpException(response.code(), response.message()); } } return streamSources; } private String getStreamUrl() throws IOException { JSONObject json = getModelInfo(); JSONObject videoTech = json.getJSONObject("videoTechnologies"); return videoTech.getString("fmp4-hls"); } @Override public void invalidateCacheEntries() { // nothing to do } @Override public void receiveTip(Double tokens) throws IOException { // not supported } @Override public int[] getStreamResolution(boolean failFast) throws ExecutionException { try { return new int[]{getStreamSources().get(0).width, getStreamSources().get(0).height}; } catch (Exception e) { throw new ExecutionException(e); } } @Override public boolean follow() throws IOException { String url = getSite().getBaseUrl() + "/v3/user/follow"; return followUnfollow(url); } @Override public boolean unfollow() throws IOException { String url = getSite().getBaseUrl() + "/v3/user/unfollow"; return followUnfollow(url); } private boolean followUnfollow(String url) throws IOException { if (!getSite().login()) { throw new IOException("Not logged in"); } LOG.debug("Calling {}", url); RequestBody body = new FormBody.Builder() .add("username", getName()) .build(); Request req = new Request.Builder() .url(url) .method("POST", body) .header(ACCEPT, "*/*") .header(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage()) .header(REFERER, getUrl()) .header(ORIGIN, getSite().getBaseUrl()) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .build(); try (Response resp = site.getHttpClient().execute(req)) { if (resp.isSuccessful()) { String msg = Objects.requireNonNull(resp.body()).string(); JSONObject json = new JSONObject(msg); if (Objects.equals(json.getString("result"), "OK")) { LOG.debug("Follow/Unfollow -> {}", msg); return true; } else { LOG.debug(msg); throw new IOException("Response was " + msg); } } else { throw new HttpException(resp.code(), resp.message()); } } } private JSONObject getModelInfo() throws IOException { String url = AmateurTv.baseUrl + "/v3/readmodel/show/" + getName() + "/es"; Request req = new Request.Builder().url(url) .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(ACCEPT, MIMETYPE_APPLICATION_JSON) .header(ACCEPT_LANGUAGE, "en") .header(REFERER, getSite().getBaseUrl() + '/' + getName()) .build(); try (Response resp = site.getHttpClient().execute(req)) { JSONObject json = new JSONObject(HttpClient.bodyToJsonObject(resp)); return json; } } @Override public Download createDownload() { return new AmateurTvDownload(getSite().getHttpClient()); } }