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:
parent
7a36f49896
commit
dc12e12dc0
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue