diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java index 9ddfe293..82f0c8e4 100644 --- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java +++ b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvConfigUI.java @@ -1,7 +1,6 @@ package ctbrec.ui.sites.cherrytv; import ctbrec.Config; -import ctbrec.sites.cam4.Cam4; import ctbrec.sites.cherrytv.CherryTv; import ctbrec.ui.DesktopIntegration; import ctbrec.ui.settings.SettingsTab; @@ -30,7 +29,7 @@ public class CherryTvConfigUI extends AbstractConfigUI { var enabled = new CheckBox(); enabled.setSelected(!settings.disabledSites.contains(site.getName())); enabled.setOnAction(e -> { - if(enabled.isSelected()) { + if (enabled.isSelected()) { settings.disabledSites.remove(site.getName()); } else { settings.disabledSites.add(site.getName()); @@ -40,11 +39,11 @@ public class CherryTvConfigUI extends AbstractConfigUI { GridPane.setMargin(enabled, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); layout.add(enabled, 1, row++); - layout.add(new Label("Cam4 User"), 0, row); - var username = new TextField(Config.getInstance().getSettings().cam4Username); + layout.add(new Label(site.getName() + " User"), 0, row); + var username = new TextField(Config.getInstance().getSettings().cherryTvUsername); username.textProperty().addListener((ob, o, n) -> { - if(!n.equals(Config.getInstance().getSettings().cam4Username)) { - Config.getInstance().getSettings().cam4Username = username.getText(); + if (!n.equals(Config.getInstance().getSettings().cherryTvUsername)) { + Config.getInstance().getSettings().cherryTvUsername = username.getText(); site.getHttpClient().logout(); save(); } @@ -54,12 +53,12 @@ public class CherryTvConfigUI extends AbstractConfigUI { GridPane.setColumnSpan(username, 2); layout.add(username, 1, row++); - layout.add(new Label("Cam4 Password"), 0, row); + layout.add(new Label(site.getName() + " Password"), 0, row); var password = new PasswordField(); - password.setText(Config.getInstance().getSettings().cam4Password); + password.setText(Config.getInstance().getSettings().cherryTvPassword); password.textProperty().addListener((ob, o, n) -> { - if(!n.equals(Config.getInstance().getSettings().cam4Password)) { - Config.getInstance().getSettings().cam4Password = password.getText(); + if (!n.equals(Config.getInstance().getSettings().cherryTvPassword)) { + Config.getInstance().getSettings().cherryTvPassword = password.getText(); site.getHttpClient().logout(); save(); } @@ -70,7 +69,7 @@ public class CherryTvConfigUI extends AbstractConfigUI { layout.add(password, 1, row++); var createAccount = new Button("Create new Account"); - createAccount.setOnAction(e -> DesktopIntegration.open(Cam4.AFFILIATE_LINK)); + createAccount.setOnAction(e -> DesktopIntegration.open(site.getAffiliateLink())); layout.add(createAccount, 1, row++); GridPane.setColumnSpan(createAccount, 2); diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java new file mode 100644 index 00000000..f6b66200 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedTab.java @@ -0,0 +1,85 @@ + +package ctbrec.ui.sites.cherrytv; + +import ctbrec.sites.cherrytv.CherryTv; +import ctbrec.ui.tabs.FollowedTab; +import ctbrec.ui.tabs.ThumbOverviewTab; +import javafx.concurrent.WorkerStateEvent; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.layout.HBox; + +public class CherryTvFollowedTab extends ThumbOverviewTab implements FollowedTab { + private final Label status; + private ToggleGroup group; + + public CherryTvFollowedTab(String title, CherryTv site) { + super(title, new CherryTvFollowedUpdateService(site), site); + status = new Label("Logging in..."); + grid.getChildren().add(status); + } + + @Override + protected void createGui() { + super.createGui(); + group = new ToggleGroup(); + addOnlineOfflineSelector(); + } + + private void addOnlineOfflineSelector() { + var online = new RadioButton("online"); + online.setToggleGroup(group); + var offline = new RadioButton("offline"); + offline.setToggleGroup(group); + pagination.getChildren().add(online); + pagination.getChildren().add(offline); + HBox.setMargin(online, new Insets(5, 5, 5, 40)); + HBox.setMargin(offline, new Insets(5, 5, 5, 5)); + online.setSelected(true); + group.selectedToggleProperty().addListener(e -> { + ((CherryTvUpdateService) updateService).setFilter(m -> { + try { + return m.isOnline(false) == online.isSelected(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return false; + } catch (Exception ex) { + return false; + } + }); + queue.clear(); + updateService.restart(); + }); + } + + @Override + protected void onSuccess() { + grid.getChildren().remove(status); + super.onSuccess(); + } + + @Override + protected void onFail(WorkerStateEvent event) { + status.setText("Login failed"); + super.onFail(event); + } + + @Override + public void selected() { + status.setText("Logging in..."); + super.selected(); + } + + public void setScene(Scene scene) { + scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> { + if (this.isSelected() && event.getCode() == KeyCode.DELETE) { + follow(selectedThumbCells, false); + } + }); + } +} diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java new file mode 100644 index 00000000..92ce5198 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvFollowedUpdateService.java @@ -0,0 +1,53 @@ +package ctbrec.ui.sites.cherrytv; + +import ctbrec.Model; +import ctbrec.sites.cherrytv.CherryTv; +import ctbrec.sites.cherrytv.CherryTvModel; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static ctbrec.Model.State.OFFLINE; +import static ctbrec.Model.State.ONLINE; + +public class CherryTvFollowedUpdateService extends CherryTvUpdateService { + + private static final Logger LOG = LoggerFactory.getLogger(CherryTvFollowedUpdateService.class); + + public CherryTvFollowedUpdateService(CherryTv site) { + super(site.getBaseUrl() + "/graphql?operationName=FindFollowings&variables={\"cursor\":${offset},\"limit\":${limit}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"7d2cf16b113dc1d57af02685e249e28df9649ea598717dc2c877294529ae0cb3\"}}", + site,true); + } + + protected List parseModels(String body) throws IOException { + var json = new JSONObject(body); + if (json.has("errors")) { + JSONArray errors = json.getJSONArray("errors"); + JSONObject first = errors.getJSONObject(0); + throw new IOException(first.getString("message")); + } + List models = new ArrayList<>(); + try { + JSONArray followings = json.getJSONObject("data").getJSONObject("followinglist").getJSONArray("followings"); + for (int i = 0; i < followings.length(); i++) { + JSONObject following = followings.getJSONObject(i); + CherryTvModel model = site.createModel(following.optString("username")); + model.setId(following.getString("id")); + model.setPreview(following.optString("img")); + var online = following.optString("status").equalsIgnoreCase("Live"); + model.setOnline(online); + model.setOnlineState(online ? ONLINE : OFFLINE); + models.add(model); + } + } catch (JSONException e) { + LOG.error("Couldn't parse JSON, the structure might have changed", e); + } + return models; + } +} diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java index 58844da8..33c62dcf 100644 --- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java +++ b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvSiteUi.java @@ -1,9 +1,6 @@ package ctbrec.ui.sites.cherrytv; -import ctbrec.sites.cam4.Cam4; -import ctbrec.sites.cam4.Cam4HttpClient; import ctbrec.sites.cherrytv.CherryTv; -import ctbrec.ui.controls.Dialogs; import ctbrec.ui.sites.AbstractSiteUi; import ctbrec.ui.sites.ConfigUI; import ctbrec.ui.tabs.TabProvider; diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java index 09964060..6b283ffa 100644 --- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java +++ b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvTabProvider.java @@ -14,28 +14,35 @@ public class CherryTvTabProvider implements TabProvider { private final CherryTv site; private final Recorder recorder; + private final CherryTvFollowedTab followedTab; public CherryTvTabProvider(CherryTv cherryTv) { this.site = cherryTv; this.recorder = cherryTv.getRecorder(); + + followedTab = new CherryTvFollowedTab("Following", site); + followedTab.setImageAspectRatio(9.0 / 16.0); + followedTab.preserveAspectRatioProperty().set(false); + followedTab.setRecorder(recorder); } @Override public List getTabs(Scene scene) { List tabs = new ArrayList<>(); - tabs.add(createTab("Female", site.getBaseUrl() + "/graphql?operationName=findBroadcastsByPage&variables={\"slug\":\"female\",\"tag\":null,\"following\":null,\"limit\":${limit},\"cursor\":${offset}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"f1e214ca901e525301fcc6966cf081ee95ef777974ec184897c4a2cf15e9ac6f\"}}")); - tabs.add(createTab("Trans", site.getBaseUrl() + "/graphql?operationName=findBroadcastsByPage&variables={\"slug\":\"trans\",\"tag\":null,\"following\":null,\"limit\":${limit},\"cursor\":${offset}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"f1e214ca901e525301fcc6966cf081ee95ef777974ec184897c4a2cf15e9ac6f\"}}")); - tabs.add(createTab("Group Show", site.getBaseUrl() + "/graphql?operationName=findBroadcastsByPage&variables={\"slug\":\"groupshow\",\"tag\":null,\"following\":null,\"limit\":${limit},\"cursor\":${offset}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"f1e214ca901e525301fcc6966cf081ee95ef777974ec184897c4a2cf15e9ac6f\"}}")); + tabs.add(createTab("Female", site.getBaseUrl() + "/graphql?operationName=findBroadcastsByPage&variables={\"slug\":\"female\",\"tag\":null,\"following\":null,\"limit\":${limit},\"cursor\":${offset}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"f1e214ca901e525301fcc6966cf081ee95ef777974ec184897c4a2cf15e9ac6f\"}}")); + tabs.add(createTab("Trans", site.getBaseUrl() + "/graphql?operationName=findBroadcastsByPage&variables={\"slug\":\"trans\",\"tag\":null,\"following\":null,\"limit\":${limit},\"cursor\":${offset}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"f1e214ca901e525301fcc6966cf081ee95ef777974ec184897c4a2cf15e9ac6f\"}}")); + tabs.add(createTab("Group Show", site.getBaseUrl() + "/graphql?operationName=findBroadcastsByPage&variables={\"slug\":\"groupshow\",\"tag\":null,\"following\":null,\"limit\":${limit},\"cursor\":${offset}}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"f1e214ca901e525301fcc6966cf081ee95ef777974ec184897c4a2cf15e9ac6f\"}}")); + tabs.add(followedTab); return tabs; } @Override public Tab getFollowedTab() { - return null; + return followedTab; } private Tab createTab(String name, String url) { - var updateService = new CherryTvUpdateService(url, site); + var updateService = new CherryTvUpdateService(url, site, false); var tab = new ThumbOverviewTab(name, updateService, site); tab.setImageAspectRatio(9.0 / 16.0); tab.preserveAspectRatioProperty().set(false); diff --git a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java index cfe7f5ed..6f39dde8 100644 --- a/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java +++ b/client/src/main/java/ctbrec/ui/sites/cherrytv/CherryTvUpdateService.java @@ -21,6 +21,9 @@ import java.util.Locale; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static ctbrec.Model.State.OFFLINE; import static ctbrec.Model.State.ONLINE; @@ -31,12 +34,16 @@ public class CherryTvUpdateService extends PaginatedScheduledService { private static final Logger LOG = LoggerFactory.getLogger(CherryTvUpdateService.class); private static final int MODELS_PER_PAGE = 100; - private String url; - private final CherryTv site; - public CherryTvUpdateService(String url, CherryTv site) { + private final String url; + private final boolean loginRequired; + protected final CherryTv site; + private Predicate filter; + + public CherryTvUpdateService(String url, CherryTv site, boolean loginRequired) { this.site = site; this.url = url; + this.loginRequired = loginRequired; ExecutorService executor = Executors.newSingleThreadExecutor(r -> { var t = new Thread(r); @@ -52,6 +59,10 @@ public class CherryTvUpdateService extends PaginatedScheduledService { return new Task<>() { @Override public List call() throws IOException { + if (loginRequired && !site.getHttpClient().login()) { + throw new IOException("Login failed"); + } + String pageUrl = CherryTvUpdateService.this.url; pageUrl = pageUrl.replace("${limit}", String.valueOf(MODELS_PER_PAGE)); pageUrl = pageUrl.replace("${offset}", String.valueOf((page - 1) * MODELS_PER_PAGE)); @@ -64,7 +75,13 @@ public class CherryTvUpdateService extends PaginatedScheduledService { .build(); try (var response = site.getHttpClient().execute(request)) { if (response.isSuccessful()) { - return parseModels(Objects.requireNonNull(response.body()).string()); + String body = Objects.requireNonNull(response.body()).string(); + LOG.debug(body); + Stream stream = parseModels(body).stream(); + if (filter != null) { + stream = stream.filter(filter); + } + return stream.collect(Collectors.toList()); } else { throw new HttpException(response.code(), response.message()); } @@ -73,8 +90,13 @@ public class CherryTvUpdateService extends PaginatedScheduledService { }; } - private List parseModels(String body) { + protected List parseModels(String body) throws IOException { var json = new JSONObject(body); + if (json.has("errors")) { + JSONArray errors = json.getJSONArray("errors"); + JSONObject first = errors.getJSONObject(0); + throw new IOException(first.getString("message")); + } List models = new ArrayList<>(); try { JSONArray broadcasts = json.getJSONObject("data").getJSONObject("broadcasts").getJSONArray("broadcasts"); @@ -102,8 +124,7 @@ public class CherryTvUpdateService extends PaginatedScheduledService { return models; } - public void setUrl(String url) { - this.url = url; + public void setFilter(Predicate filter) { + this.filter = filter; } - } diff --git a/common/src/main/java/ctbrec/LoggingInterceptor.java b/common/src/main/java/ctbrec/LoggingInterceptor.java index b2a4457f..8a8a52dc 100644 --- a/common/src/main/java/ctbrec/LoggingInterceptor.java +++ b/common/src/main/java/ctbrec/LoggingInterceptor.java @@ -23,7 +23,7 @@ public class LoggingInterceptor implements Interceptor { } Response response = chain.proceed(request); long t2 = System.nanoTime(); - LOG.debug("OkHttp Received response for {} in {}\n{}", response.request().url(), (t2 - t1) / 1e6d, response.headers()); + LOG.debug("OkHttp Received {} response for {} in {}ms\n{}", response.code(), response.request().url(), (t2 - t1) / 1e6d, response.headers()); return response; } } diff --git a/common/src/main/java/ctbrec/Settings.java b/common/src/main/java/ctbrec/Settings.java index 9d46397d..6c3c4e3a 100644 --- a/common/src/main/java/ctbrec/Settings.java +++ b/common/src/main/java/ctbrec/Settings.java @@ -58,6 +58,8 @@ public class Settings { public String chaturbateUsername = ""; public String chaturbateBaseUrl = "https://chaturbate.com"; public int chaturbateMsBetweenRequests = 1000; + public String cherryTvPassword = ""; + public String cherryTvUsername = ""; public boolean chooseStreamQuality = false; public String colorAccent = "#FFFFFF"; public String colorBase = "#FFFFFF"; diff --git a/common/src/main/java/ctbrec/io/HttpClient.java b/common/src/main/java/ctbrec/io/HttpClient.java index 332469a8..da4b4262 100644 --- a/common/src/main/java/ctbrec/io/HttpClient.java +++ b/common/src/main/java/ctbrec/io/HttpClient.java @@ -144,8 +144,9 @@ public abstract class HttpClient { .cookieJar(cookieJar) .connectionPool(GLOBAL_HTTP_CONN_POOL) .connectTimeout(config.getSettings().httpTimeout, TimeUnit.MILLISECONDS) - .readTimeout(config.getSettings().httpTimeout, TimeUnit.MILLISECONDS); - //.addInterceptor(new LoggingInterceptor()); + .readTimeout(config.getSettings().httpTimeout, TimeUnit.MILLISECONDS) + //.addNetworkInterceptor(new LoggingInterceptor()) + ; ProxyType proxyType = config.getSettings().proxyType; if (proxyType == ProxyType.HTTP) { diff --git a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java index ff402c32..a7549921 100644 --- a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java +++ b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvHttpClient.java @@ -2,17 +2,122 @@ package ctbrec.sites.cherrytv; import ctbrec.Config; import ctbrec.io.HttpClient; +import ctbrec.io.HttpException; +import okhttp3.*; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Objects; + +import static ctbrec.io.HttpConstants.*; public class CherryTvHttpClient extends HttpClient { + private static final Logger LOG = LoggerFactory.getLogger(CherryTvHttpClient.class); + public CherryTvHttpClient(Config config) { super("cherrytv", config); } @Override public synchronized boolean login() throws IOException { - return false; + if (loggedIn) { + return true; + } + + boolean cookiesWorked = checkLoginSuccess(); + if (cookiesWorked) { + loggedIn = true; + LOG.debug("Logged in with cookies"); + return true; + } + + JSONObject body = new JSONObject() + .put("operationName", "authenticateUser") + .put("variables", new JSONObject() + .put("username", config.getSettings().cherryTvUsername) + .put("password", config.getSettings().cherryTvPassword) + ) + .put("extensions", new JSONObject() + .put("persistedQuery", new JSONObject() + .put("version", 1) + .put("sha256Hash", "9c105878022f9a7d511c12527c70f125606dc25367a4dd56aa63a6af73579087") + ) + ); + + + RequestBody requestBody = RequestBody.create(body.toString(), MediaType.parse("application/json")); + Request request = new Request.Builder() + .url(CherryTv.BASE_URL + "/graphql") + .header(REFERER, CherryTv.BASE_URL) + .header(ORIGIN, CherryTv.BASE_URL) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .post(requestBody) + .build(); + + LOG.debug("Logging in: {}\n{}", request.url(), body.toString(2)); + try (Response response = execute(request)) { + if (response.isSuccessful()) { + JSONObject resp = new JSONObject(Objects.requireNonNull(response.body()).string()); + LOG.info(resp.toString(2)); + JSONObject data = resp.getJSONObject("data"); + JSONObject login = data.getJSONObject("login"); + loggedIn = login.optBoolean("success"); + String jwt = login.optString("token"); + saveAsSessionCookie(jwt); + LOG.debug("Login successful"); + return loggedIn; + } else { + throw new HttpException(response.code(), response.message()); + } + } + } + + private void saveAsSessionCookie(String jwt) { + HttpUrl url = HttpUrl.parse(CherryTv.BASE_URL); + Objects.requireNonNull(url); + long expiresAt = Instant.now().plus(1, ChronoUnit.DAYS).getEpochSecond(); + Cookie sessionCookie = new Cookie.Builder() + .name("session") + .value(jwt) + .expiresAt(expiresAt) + .domain(Objects.requireNonNull(url.topPrivateDomain())) + .path("/") + .secure().httpOnly() + .build(); + getCookieJar().saveFromResponse(url, Collections.singletonList(sessionCookie)); + } + + private boolean checkLoginSuccess() { + String url = CherryTv.BASE_URL + "/graphql?operationName=FindFollowings&variables={\"cursor\":\"0\",\"limit\":20}&extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"7d2cf16b113dc1d57af02685e249e28df9649ea598717dc2c877294529ae0cb3\"}}"; + Request request = new Request.Builder() + .url(url) + .header(REFERER, CherryTv.BASE_URL) + .header(ORIGIN, CherryTv.BASE_URL) + .header(USER_AGENT, Config.getInstance().getSettings().httpUserAgent) + .build(); + + try (Response response = execute(request)) { + String body = Objects.requireNonNull(response.body()).string(); + LOG.debug("Login body: {}", body); + if (response.isSuccessful()) { + JSONObject json = new JSONObject(body); + if (json.has("errors")) { + LOG.error(json.toString(2)); + return false; + } else { + return json.optString("__typename").equals("FollowingList"); + } + } + return false; + } catch (Exception e) { + LOG.error("Login check failed", e); + return false; + } } } diff --git a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java index 7fcfe3c4..94e194c4 100644 --- a/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java +++ b/common/src/main/java/ctbrec/sites/cherrytv/CherryTvModel.java @@ -4,6 +4,8 @@ import com.iheartradio.m3u8.*; import com.iheartradio.m3u8.data.MasterPlaylist; import com.iheartradio.m3u8.data.Playlist; import com.iheartradio.m3u8.data.PlaylistData; +import com.squareup.moshi.JsonReader; +import com.squareup.moshi.JsonWriter; import ctbrec.AbstractModel; import ctbrec.Config; import ctbrec.NotImplementedExcetion; @@ -39,6 +41,7 @@ public class CherryTvModel extends AbstractModel { private boolean online = false; private int[] resolution; private String masterPlaylistUrl; + private String id; @Override public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { @@ -198,6 +201,8 @@ public class CherryTvModel extends AbstractModel { @Override public boolean follow() throws IOException { + // POST https://cherry.tv/graphql + // {"operationName":"follow","variables":{"userId":"1391"},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"a7a8241014074f5c02ac83863a47f7579e5f03167a7ec424ff65ad045c7fcf6f"}}} return false; } @@ -222,4 +227,23 @@ public class CherryTvModel extends AbstractModel { setOnlineState(OFFLINE); } } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + @Override + public void readSiteSpecificData(JsonReader reader) throws IOException { + reader.nextName(); + id = reader.nextString(); + } + + @Override + public void writeSiteSpecificData(JsonWriter writer) throws IOException { + writer.name("id").value(id); + } }