diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCams.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCams.java index d762cbe9..bcdf18cf 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCams.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCams.java @@ -5,6 +5,7 @@ import static ctbrec.io.HttpConstants.*; import java.io.IOException; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -71,7 +72,7 @@ public class MyFreeCams extends AbstractSite { .build(); try(Response response = getHttpClient().execute(req)) { if(response.isSuccessful()) { - String content = response.body().string(); + String content = Objects.requireNonNull(response.body(), "HTTP response is null").string(); Elements tags = HtmlParser.getTags(content, "div.content > p > b"); String tokens = tags.get(2).text(); return Double.parseDouble(tokens); diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java index 59673e8c..ecc78958 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsClient.java @@ -58,12 +58,12 @@ public class MyFreeCamsClient { private MyFreeCams mfc; private WebSocket ws; private Thread keepAlive; - private Moshi moshi; + private final Moshi moshi; private volatile boolean running = false; - private Cache sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build(); - private Cache models = CacheBuilder.newBuilder().maximumSize(4000).build(); - private Lock lock = new ReentrantLock(); + private final Cache sessionStates = CacheBuilder.newBuilder().maximumSize(4000).build(); + private final Cache models = CacheBuilder.newBuilder().maximumSize(4000).build(); + private final Lock lock = new ReentrantLock(); private ServerConfig serverConfig; @SuppressWarnings("unused") private String tkx; @@ -74,9 +74,10 @@ public class MyFreeCamsClient { private long heartBeat; private volatile boolean connecting = false; private static int messageId = 31415; // starting with 31415 just for fun - private Map> responseHandlers = new HashMap<>(); + private final Map> responseHandlers = new HashMap<>(); + private final Random rng = new Random(); - private Queue receivedTextHistory = new LinkedList<>(); + private final Queue receivedTextHistory = new LinkedList<>(); private MyFreeCamsClient() { moshi = new Moshi.Builder().build(); @@ -104,7 +105,7 @@ public class MyFreeCamsClient { } } - String server = websocketServers.get(new Random().nextInt(websocketServers.size() - 1)); + String server = websocketServers.get(rng.nextInt(websocketServers.size() - 1)); String wsUrl = "wss://" + server + ".myfreecams.com/fcsl"; LOG.debug("Connecting to random websocket server {}", wsUrl); @@ -636,7 +637,7 @@ public class MyFreeCamsClient { public String getStreamUrl(SessionState state) { int userChannel = 100000000 + state.getUid(); - String phase = Optional.ofNullable(state).map(SessionState::getU).map(User::getPhase).orElse("z"); + String phase = Optional.of(state).map(SessionState::getU).map(User::getPhase).orElse("z"); String phasePrefix = phase.equals("z") ? "" : '_' + phase; String server = "video" + getCamServ(state).replaceAll("^\\D+", ""); String nonce = Double.toString(Math.random()); @@ -645,21 +646,21 @@ public class MyFreeCamsClient { } private String getCamServ(SessionState state) { - Integer camserv = Optional.ofNullable(state.getU()).map(User::getCamserv).orElse(-1); - String camservString = camserv.toString(); + int camserv = Optional.ofNullable(state.getU()).map(User::getCamserv).orElse(-1); + String camservString = Integer.toString(camserv); if (serverConfig.isOnWzObsVideoServer(state)) { - camservString = serverConfig.wzobsServers.get(camserv.toString()); + camservString = serverConfig.wzobsServers.get(camservString); } else if (serverConfig.isOnObsServer(state)) { - camservString = serverConfig.ngVideoServers.get(camserv.toString()); + camservString = serverConfig.ngVideoServers.get(camservString); } else if (serverConfig.isOnHtml5VideoServer(state)) { - camservString = serverConfig.h5Servers.get(camserv.toString()); + camservString = serverConfig.h5Servers.get(camservString); } else if (camserv > 500) { if (camserv >= 3000) { camserv -= 1000; } else { camserv -= 500; } - camservString = camserv.toString(); + camservString = Integer.toString(camserv); } return camservString; } @@ -674,9 +675,12 @@ public class MyFreeCamsClient { } public SessionState getSessionState(ctbrec.Model model) { - for (SessionState state : sessionStates.asMap().values()) { - if (Objects.equals(state.getNm(), model.getName())) { - return state; + if (model instanceof MyFreeCamsModel) { + MyFreeCamsModel mfcModel = (MyFreeCamsModel) model; + for (SessionState state : sessionStates.asMap().values()) { + if (mfcModel.getUid() > 0 && state.getUid() != null && state.getUid() > 0 && mfcModel.getUid() == state.getUid()) { + return state; + } } } return null; @@ -723,6 +727,37 @@ public class MyFreeCamsClient { return result; } + /** + * Tries to look up the sessionId of a model + */ + public String getSessionId(String modelName) throws InterruptedException { + if (ws == null) { + return ""; + } + + LOG.trace("Sending USERNAMELOOKUP for {}", modelName); + int msgId = messageId++; + Object monitor = new Object(); + + List resultHolder = new ArrayList<>(); + responseHandlers.put(msgId, msg -> { + LOG.trace("Search result: {}", msg); + if (StringUtil.isNotBlank(msg.getMessage()) && !Objects.equals(msg.getMessage(), modelName)) { + JSONObject json = new JSONObject(msg.getMessage()); + resultHolder.add(Integer.toString(json.optInt("sid"))); + } + synchronized (monitor) { + monitor.notifyAll(); + } + }); + ws.send("10 " + sessionId + " 0 " + msgId + " 0 " + modelName + "\n"); + synchronized (monitor) { + monitor.wait(); + } + + return resultHolder.isEmpty() ? "" : resultHolder.get(0); + } + public Collection getSessionStates() { return Collections.unmodifiableCollection(sessionStates.asMap().values()); } diff --git a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java index 58d482b4..40d29070 100644 --- a/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java +++ b/common/src/main/java/ctbrec/sites/mfc/MyFreeCamsModel.java @@ -1,31 +1,9 @@ package ctbrec.sites.mfc; -import static ctbrec.io.HttpConstants.*; -import static java.util.Optional.*; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ExecutionException; - -import javax.xml.bind.JAXBException; - -import org.jsoup.nodes.Element; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.iheartradio.m3u8.ParseException; import com.iheartradio.m3u8.PlaylistException; import com.squareup.moshi.JsonReader; import com.squareup.moshi.JsonWriter; - import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.io.HtmlParser; @@ -38,6 +16,19 @@ import okhttp3.FormBody; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import org.jsoup.nodes.Element; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.bind.JAXBException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.*; +import java.util.concurrent.ExecutionException; + +import static ctbrec.io.HttpConstants.*; +import static java.util.Optional.ofNullable; public class MyFreeCamsModel extends AbstractModel { @@ -67,15 +58,38 @@ public class MyFreeCamsModel extends AbstractModel { @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { - return isOnline(); + if (ignoreCache) { + String sessionId = MyFreeCamsClient.getInstance().getSessionId(getName()); + boolean online = !(sessionId.isEmpty() || sessionId.equals("0")); + SessionState sessionState = MyFreeCamsClient.getInstance().getSessionState(this); + if (online) { + if (sessionState == null) { + LOG.warn("MFC model {} [{}] seems to be online but a SessionState could not be found", getName(), getUid()); + } + } else { + state = ctbrec.sites.mfc.State.OFFLINE; + } + return online; + } else { + return isOnline(); + } } @Override public State getOnlineState(boolean failFast) throws IOException, ExecutionException { - if(state == null) { + if (state == null) { return State.UNKNOWN; } + if (!failFast) { + MyFreeCamsClient.getInstance().update(this); + try { + isOnline(true); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + switch(state) { case ONLINE: case RECORDING: @@ -106,7 +120,7 @@ public class MyFreeCamsModel extends AbstractModel { } private String updateStreamUrl() { - if(streamUrl == null) { + if (streamUrl == null) { MyFreeCams mfc = (MyFreeCams) getSite(); mfc.getClient().update(this); } @@ -129,9 +143,9 @@ public class MyFreeCamsModel extends AbstractModel { .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) .header(CONNECTION, KEEP_ALIVE) .build(); - try(Response resp = site.getHttpClient().execute(req)) { - if(resp.isSuccessful()) { - String page = resp.body().string(); + try (Response resp = site.getHttpClient().execute(req)) { + if (resp.isSuccessful()) { + String page = Objects.requireNonNull(resp.body(), "HTTP response is null").string(); Element hiddenInput = HtmlParser.getTag(page, "input[name=token]"); String token = hiddenInput.attr("value"); if (!Objects.equals(System.getenv("CTBREC_DEV"), "1")) {