Add HTTP header factory to Cam4Model

The edge URL requires a Referer header as @gohufrapoc@mastodon.cloud found out. Otherwise you get a HTTP 403 error.
We also set a few other standard headers.
This commit is contained in:
0xb00bface 2021-01-09 13:50:51 +01:00
parent 7a36f49896
commit dc12e12dc0
2 changed files with 65 additions and 66 deletions

View File

@ -1,17 +1,5 @@
package ctbrec.sites.cam4; package ctbrec.sites.cam4;
import static ctbrec.io.HttpConstants.*;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONObject;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.StringUtil; import ctbrec.StringUtil;
@ -20,6 +8,18 @@ import ctbrec.io.HttpException;
import ctbrec.sites.AbstractSite; import ctbrec.sites.AbstractSite;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import org.json.JSONArray;
import org.json.JSONObject;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static ctbrec.io.HttpClient.bodyToJsonObject;
import static ctbrec.io.HttpConstants.USER_AGENT;
public class Cam4 extends AbstractSite { public class Cam4 extends AbstractSite {
@ -130,7 +130,7 @@ public class Cam4 extends AbstractSite {
.build(); .build();
try(Response response = getHttpClient().execute(req)) { try(Response response = getHttpClient().execute(req)) {
if(response.isSuccessful()) { if(response.isSuccessful()) {
String body = response.body().string(); String body = bodyToJsonObject(response);
JSONArray results = new JSONArray(body); JSONArray results = new JSONArray(body);
for (int i = 0; i < results.length(); i++) { for (int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i); JSONObject result = results.getJSONObject(i);

View File

@ -1,46 +1,39 @@
package ctbrec.sites.cam4; package ctbrec.sites.cam4;
import static ctbrec.Model.State.*; import com.iheartradio.m3u8.*;
import static ctbrec.io.HttpConstants.*;
import static java.util.regex.Pattern.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.iheartradio.m3u8.Encoding;
import com.iheartradio.m3u8.Format;
import com.iheartradio.m3u8.ParseException;
import com.iheartradio.m3u8.ParsingMode;
import com.iheartradio.m3u8.PlaylistException;
import com.iheartradio.m3u8.PlaylistParser;
import com.iheartradio.m3u8.data.MasterPlaylist; import com.iheartradio.m3u8.data.MasterPlaylist;
import com.iheartradio.m3u8.data.Playlist; import com.iheartradio.m3u8.data.Playlist;
import com.iheartradio.m3u8.data.PlaylistData; import com.iheartradio.m3u8.data.PlaylistData;
import com.iheartradio.m3u8.data.StreamInfo; import com.iheartradio.m3u8.data.StreamInfo;
import ctbrec.AbstractModel; import ctbrec.AbstractModel;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.StringUtil; import ctbrec.StringUtil;
import ctbrec.io.HtmlParser; import ctbrec.io.HtmlParser;
import ctbrec.io.HttpException; import ctbrec.io.HttpException;
import ctbrec.recorder.download.HttpHeaderFactory;
import ctbrec.recorder.download.HttpHeaderFactoryImpl;
import ctbrec.recorder.download.StreamSource; import ctbrec.recorder.download.StreamSource;
import okhttp3.FormBody; import okhttp3.FormBody;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import org.json.JSONObject;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static ctbrec.Model.State.*;
import static ctbrec.io.HttpClient.bodyToJsonObject;
import static ctbrec.io.HttpConstants.*;
import static java.util.regex.Pattern.DOTALL;
import static java.util.regex.Pattern.MULTILINE;
public class Cam4Model extends AbstractModel { public class Cam4Model extends AbstractModel {
@ -103,18 +96,14 @@ public class Cam4Model extends AbstractModel {
@Override @Override
public State getOnlineState(boolean failFast) throws IOException, ExecutionException { public State getOnlineState(boolean failFast) throws IOException, ExecutionException {
if(failFast) { if (!failFast && onlineState == UNKNOWN) {
return onlineState; try {
} else { loadModelDetails();
if(onlineState == UNKNOWN) { } catch (Exception e) {
try { LOG.warn("Couldn't load model details {}", e.getMessage());
loadModelDetails();
} catch (Exception e) {
LOG.warn("Couldn't load model details {}", e.getMessage());
}
} }
return onlineState;
} }
return onlineState;
} }
private String getPlaylistUrl() throws IOException { private String getPlaylistUrl() throws IOException {
@ -146,8 +135,8 @@ public class Cam4Model extends AbstractModel {
.build(); // @formatter:on .build(); // @formatter:on
try (Response response = site.getHttpClient().execute(req)) { try (Response response = site.getHttpClient().execute(req)) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
JSONObject json = new JSONObject(response.body().string()); JSONObject json = new JSONObject(bodyToJsonObject(response));
LOG.trace(json.toString(2)); if (LOG.isTraceEnabled()) LOG.trace(json.toString(2));
if (json.has("canUseCDN")) { if (json.has("canUseCDN")) {
if (json.getBoolean("canUseCDN")) { if (json.getBoolean("canUseCDN")) {
playlistUrl = json.getString("cdnURL"); playlistUrl = json.getString("cdnURL");
@ -191,8 +180,7 @@ public class Cam4Model extends AbstractModel {
src.height = Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0); src.height = Optional.ofNullable(playlist.getStreamInfo()).map(StreamInfo::getResolution).map(res -> res.height).orElse(0);
String masterUrl = getPlaylistUrl(); String masterUrl = getPlaylistUrl();
String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1);
String segmentUri = baseUrl + playlist.getUri(); src.mediaPlaylistUrl = baseUrl + playlist.getUri();
src.mediaPlaylistUrl = segmentUri;
LOG.trace("Media playlist {}", src.mediaPlaylistUrl); LOG.trace("Media playlist {}", src.mediaPlaylistUrl);
sources.add(src); sources.add(src);
} }
@ -201,19 +189,18 @@ public class Cam4Model extends AbstractModel {
} }
private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException { private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException {
String playlistUrl = getPlaylistUrl(); String masterPlaylistUrl = getPlaylistUrl();
LOG.trace("Loading master playlist [{}]", playlistUrl); LOG.trace("Loading master playlist [{}]", masterPlaylistUrl);
Request req = new Request.Builder().url(playlistUrl).build(); Request req = new Request.Builder().url(masterPlaylistUrl).build();
try (Response response = site.getHttpClient().execute(req)) { try (Response response = site.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();
MasterPlaylist master = playlist.getMasterPlaylist(); return playlist.getMasterPlaylist();
return master;
} else { } else {
throw new HttpException(response.code(), "Couldn't download HLS playlist " + playlistUrl); throw new HttpException(response.code(), "Couldn't download HLS playlist " + masterPlaylistUrl);
} }
} }
} }
@ -321,12 +308,6 @@ public class Cam4Model extends AbstractModel {
this.playlistUrl = playlistUrl; this.playlistUrl = playlistUrl;
} }
public class ModelDetailsEmptyException extends Exception {
public ModelDetailsEmptyException(String msg) {
super(msg);
}
}
@Override @Override
public void setUrl(String url) { public void setUrl(String url) {
String normalizedUrl = url.toLowerCase(); String normalizedUrl = url.toLowerCase();
@ -335,4 +316,22 @@ public class Cam4Model extends AbstractModel {
} }
super.setUrl(normalizedUrl); super.setUrl(normalizedUrl);
} }
@Override
public HttpHeaderFactory getHttpHeaderFactory() {
HttpHeaderFactoryImpl fac = new HttpHeaderFactoryImpl();
Map<String, String> headers = new HashMap<>();
headers.put(ACCEPT, "*/*");
headers.put(ACCEPT_LANGUAGE, Locale.ENGLISH.getLanguage());
headers.put(CONNECTION, KEEP_ALIVE);
if (getSite() != null) {
headers.put(ORIGIN, getSite().getBaseUrl());
headers.put(REFERER, getSite().getBaseUrl());
}
headers.put(USER_AGENT, Config.getInstance().getSettings().httpUserAgent);
fac.setMasterPlaylistHeaders(headers);
fac.setSegmentPlaylistHeaders(headers);
fac.setSegmentHeaders(headers);
return fac;
}
} }