From 8287d043960a009a4926f717754c02746a8073c3 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 27 Oct 2018 22:30:17 +0200 Subject: [PATCH 01/21] Set playlist parsing mode to lenient --- .../java/ctbrec/recorder/download/AbstractHlsDownload.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java index 89a1600b..787ca938 100644 --- a/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java +++ b/src/main/java/ctbrec/recorder/download/AbstractHlsDownload.java @@ -13,6 +13,7 @@ import java.util.concurrent.Executors; 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.MediaPlaylist; @@ -41,7 +42,7 @@ public abstract class AbstractHlsDownload implements Download { Response response = client.execute(request); try { InputStream inputStream = response.body().byteStream(); - PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); + PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8, ParsingMode.LENIENT); Playlist playlist = parser.parse(); if(playlist.hasMediaPlaylist()) { MediaPlaylist mediaPlaylist = playlist.getMediaPlaylist(); From 34f443c6a995bbdbbc073ca697a95d7c38941434 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 27 Oct 2018 22:32:07 +0200 Subject: [PATCH 02/21] First addition for Cam4 --- .../ctbrec/recorder/server/HttpServer.java | 2 + src/main/java/ctbrec/sites/cam4/Cam4.java | 114 +++++++++++ .../java/ctbrec/sites/cam4/Cam4Model.java | 177 ++++++++++++++++++ .../ctbrec/sites/cam4/Cam4TabProvider.java | 33 ++++ .../ctbrec/sites/cam4/Cam4UpdateService.java | 105 +++++++++++ .../java/ctbrec/ui/CamrecApplication.java | 2 + 6 files changed, 433 insertions(+) create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4Model.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java diff --git a/src/main/java/ctbrec/recorder/server/HttpServer.java b/src/main/java/ctbrec/recorder/server/HttpServer.java index ccf47cde..69aceead 100644 --- a/src/main/java/ctbrec/recorder/server/HttpServer.java +++ b/src/main/java/ctbrec/recorder/server/HttpServer.java @@ -20,6 +20,7 @@ import ctbrec.Config; import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.Recorder; import ctbrec.sites.Site; +import ctbrec.sites.cam4.Cam4; import ctbrec.sites.chaturbate.Chaturbate; import ctbrec.sites.mfc.MyFreeCams; @@ -60,6 +61,7 @@ public class HttpServer { private void createSites() { sites.add(new Chaturbate()); sites.add(new MyFreeCams()); + sites.add(new Cam4()); } private void addShutdownHook() { diff --git a/src/main/java/ctbrec/sites/cam4/Cam4.java b/src/main/java/ctbrec/sites/cam4/Cam4.java new file mode 100644 index 00000000..9198aa87 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4.java @@ -0,0 +1,114 @@ +package ctbrec.sites.cam4; + +import java.io.IOException; + +import ctbrec.Model; +import ctbrec.io.HttpClient; +import ctbrec.recorder.Recorder; +import ctbrec.sites.AbstractSite; +import ctbrec.ui.TabProvider; +import javafx.scene.Node; + +public class Cam4 extends AbstractSite { + + public static final String BASE_URI = "https://www.cam4.com"; + + private HttpClient httpClient; + private Recorder recorder; + + @Override + public String getName() { + return "Cam4"; + } + + @Override + public String getBaseUrl() { + return BASE_URI; + } + + @Override + public String getAffiliateLink() { + return getBaseUrl() + "/?referrerId=1514a80d87b5effb456cca02f6743aa1"; + } + + @Override + public void setRecorder(Recorder recorder) { + this.recorder = recorder; + } + + @Override + public TabProvider getTabProvider() { + return new Cam4TabProvider(this, recorder); + } + + @Override + public Model createModel(String name) { + Cam4Model m = new Cam4Model(); + m.setSite(this); + m.setName(name); + m.setUrl(getBaseUrl() + '/' + name + '/'); + return m; + } + + @Override + public Integer getTokenBalance() throws IOException { + return 0; + } + + @Override + public String getBuyTokensLink() { + return getAffiliateLink(); + } + + @Override + public void login() throws IOException { + } + + @Override + public HttpClient getHttpClient() { + if(httpClient == null) { + httpClient = new HttpClient() { + @Override + public boolean login() throws IOException { + return false; + } + }; + } + return httpClient; + } + + @Override + public void shutdown() { + getHttpClient().shutdown(); + } + + @Override + public void init() throws IOException { + } + + @Override + public boolean supportsTips() { + return false; + } + + @Override + public boolean supportsFollow() { + return false; + } + + @Override + public boolean isSiteForModel(Model m) { + return m instanceof Cam4Model; + } + + @Override + public Node getConfigurationGui() { + return null; + } + + @Override + public boolean credentialsAvailable() { + return false; + } + +} diff --git a/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/src/main/java/ctbrec/sites/cam4/Cam4Model.java new file mode 100644 index 00000000..35b96268 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4Model.java @@ -0,0 +1,177 @@ +package ctbrec.sites.cam4; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ExecutionException; + +import org.json.JSONArray; +import org.json.JSONObject; +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.PlaylistException; +import com.iheartradio.m3u8.PlaylistParser; +import com.iheartradio.m3u8.data.MasterPlaylist; +import com.iheartradio.m3u8.data.Playlist; +import com.iheartradio.m3u8.data.PlaylistData; + +import ctbrec.AbstractModel; +import ctbrec.recorder.download.StreamSource; +import ctbrec.sites.Site; +import okhttp3.Request; +import okhttp3.Response; + +public class Cam4Model extends AbstractModel { + + private static final transient Logger LOG = LoggerFactory.getLogger(Cam4Model.class); + private Cam4 site; + private String playlistUrl; + private String onlineState = "offline"; + private int[] resolution = null; + + @Override + public boolean isOnline() throws IOException, ExecutionException, InterruptedException { + return isOnline(false); + } + + @Override + public boolean isOnline(boolean ignoreCache) throws IOException, ExecutionException, InterruptedException { + if(ignoreCache || onlineState == null) { + loadModelDetails(); + } + return Objects.equals("NORMAL", onlineState); + } + + private void loadModelDetails() throws IOException { + String url = "https://www.cam4.de.com/getBroadcasting?usernames=" + getName(); + LOG.debug("Loading model details {}", url); + Request req = new Request.Builder().url(url).build(); + Response response = site.getHttpClient().execute(req); + if(response.isSuccessful()) { + JSONArray json = new JSONArray(response.body().string()); + JSONObject details = json.getJSONObject(0); + onlineState = details.getString("showType"); + playlistUrl = details.getString("hlsPreviewUrl"); + if(details.has("resolution")) { + String res = details.getString("resolution"); + String[] tokens = res.split(":"); + resolution = new int[] {Integer.parseInt(tokens[0]), Integer.parseInt(tokens[1])}; + } + } else { + IOException io = new IOException(response.code() + " " + response.message()); + response.close(); + throw io; + } + } + + @Override + public String getOnlineState(boolean failFast) throws IOException, ExecutionException { + return onlineState; + } + + private String getPlaylistUrl() throws IOException { + if(playlistUrl == null) { + loadModelDetails(); + } + return playlistUrl; + } + + @Override + public List getStreamSources() throws IOException, ExecutionException, ParseException, PlaylistException { + MasterPlaylist masterPlaylist = getMasterPlaylist(); + List sources = new ArrayList<>(); + for (PlaylistData playlist : masterPlaylist.getPlaylists()) { + if (playlist.hasStreamInfo()) { + StreamSource src = new StreamSource(); + src.bandwidth = playlist.getStreamInfo().getBandwidth(); + src.height = playlist.getStreamInfo().getResolution().height; + String masterUrl = getPlaylistUrl(); + String baseUrl = masterUrl.substring(0, masterUrl.lastIndexOf('/') + 1); + String segmentUri = baseUrl + playlist.getUri(); + src.mediaPlaylistUrl = segmentUri; + LOG.trace("Media playlist {}", src.mediaPlaylistUrl); + sources.add(src); + } + } + return sources; + } + + private MasterPlaylist getMasterPlaylist() throws IOException, ParseException, PlaylistException { + LOG.trace("Loading master playlist {}", getPlaylistUrl()); + Request req = new Request.Builder().url(getPlaylistUrl()).build(); + Response response = site.getHttpClient().execute(req); + try { + InputStream inputStream = response.body().byteStream(); + PlaylistParser parser = new PlaylistParser(inputStream, Format.EXT_M3U, Encoding.UTF_8); + Playlist playlist = parser.parse(); + MasterPlaylist master = playlist.getMasterPlaylist(); + return master; + } finally { + response.close(); + } + } + + @Override + public void invalidateCacheEntries() { + // TODO Auto-generated method stub + + } + + @Override + public void receiveTip(int tokens) throws IOException { + // TODO Auto-generated method stub + + } + + @Override + public int[] getStreamResolution(boolean failFast) throws ExecutionException { + if(resolution == null) { + if(failFast) { + return new int[2]; + } else { + try { + loadModelDetails(); + } catch (IOException e) { + throw new ExecutionException(e); + } + } + } + return resolution; + } + + @Override + public boolean follow() throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean unfollow() throws IOException { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setSite(Site site) { + if(site instanceof Cam4) { + this.site = (Cam4) site; + } else { + throw new IllegalArgumentException("Site has to be an instance of Cam4"); + } + } + + @Override + public Site getSite() { + return site; + } + + public void setPlaylistUrl(String playlistUrl) { + this.playlistUrl = playlistUrl; + } +} diff --git a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java new file mode 100644 index 00000000..a07f97e7 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java @@ -0,0 +1,33 @@ +package ctbrec.sites.cam4; + +import java.util.ArrayList; +import java.util.List; + +import ctbrec.recorder.Recorder; +import ctbrec.ui.TabProvider; +import ctbrec.ui.ThumbOverviewTab; +import javafx.scene.Scene; +import javafx.scene.control.Tab; + +public class Cam4TabProvider extends TabProvider { + + private Cam4 cam4; + private Recorder recorder; + + public Cam4TabProvider(Cam4 cam4, Recorder recorder) { + this.cam4 = cam4; + this.recorder = recorder; + } + + @Override + public List getTabs(Scene scene) { + List tabs = new ArrayList<>(); + String url = cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS"; + Cam4UpdateService female = new Cam4UpdateService(url, false, cam4); + ThumbOverviewTab tab = new ThumbOverviewTab("Female", female, cam4); + tab.setRecorder(recorder); + tabs.add(tab); + return tabs; + } + +} diff --git a/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java b/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java new file mode 100644 index 00000000..26dd5787 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java @@ -0,0 +1,105 @@ +package ctbrec.sites.cam4; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import org.eclipse.jetty.util.StringUtil; +import org.json.JSONObject; +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.ui.HtmlParser; +import ctbrec.ui.PaginatedScheduledService; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.Response; + +public class Cam4UpdateService extends PaginatedScheduledService { + + private static final transient Logger LOG = LoggerFactory.getLogger(Cam4UpdateService.class); + private String url; + private Cam4 site; + private boolean loginRequired; + + public Cam4UpdateService(String url, boolean loginRequired, Cam4 site) { + this.site = site; + this.url = url; + this.loginRequired = loginRequired; + + ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("ThumbOverviewTab UpdateService"); + return t; + } + }); + setExecutor(executor); + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().username)) { // FIXME change to cam4 username + return Collections.emptyList(); + } else { + String url = Cam4UpdateService.this.url + "&page=" + page; + LOG.debug("Fetching page {}", url); + Request request = new Request.Builder().url(url).build(); + Response response = site.getHttpClient().execute(request, loginRequired); + if (response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + String html = json.getString("html"); + Elements profilesBoxes = HtmlParser.getTags(html, "div[class~=profileDataBox]"); + List models = new ArrayList<>(profilesBoxes.size()); + for (Element profileBox : profilesBoxes) { + String boxHtml = profileBox.html(); + Element profileLink = HtmlParser.getTag(boxHtml, "a.profile-preview"); + String path = profileLink.attr("href"); + String slug = path.substring(1); + Cam4Model model = (Cam4Model) site.createModel(slug); + String playlistUrl = profileLink.attr("data-hls-preview-url"); + model.setPlaylistUrl(playlistUrl); + model.setPreview(HtmlParser.getTag(boxHtml, "a img").attr("data-src")); + model.setDescription(parseDesription(boxHtml)); + //model.setOnlineState(parseOnlineState(boxHtml)); + models.add(model); + } + response.close(); + return models; + } else { + int code = response.code(); + response.close(); + throw new IOException("HTTP status " + code); + } + } + } + + private String parseDesription(String boxHtml) { + try { + return HtmlParser.getText(boxHtml, "div[class~=statusMsg2]"); + } catch(Exception e) { + LOG.trace("Couldn't parse description for room"); + } + return ""; + } + }; + } + + public void setUrl(String url) { + this.url = url; + } + +} diff --git a/src/main/java/ctbrec/ui/CamrecApplication.java b/src/main/java/ctbrec/ui/CamrecApplication.java index 3e1f258f..acb92508 100644 --- a/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/src/main/java/ctbrec/ui/CamrecApplication.java @@ -27,6 +27,7 @@ import ctbrec.recorder.LocalRecorder; import ctbrec.recorder.Recorder; import ctbrec.recorder.RemoteRecorder; import ctbrec.sites.Site; +import ctbrec.sites.cam4.Cam4; import ctbrec.sites.chaturbate.Chaturbate; import ctbrec.sites.mfc.MyFreeCams; import javafx.application.Application; @@ -60,6 +61,7 @@ public class CamrecApplication extends Application { public void start(Stage primaryStage) throws Exception { sites.add(new Chaturbate()); sites.add(new MyFreeCams()); + sites.add(new Cam4()); loadConfig(); createHttpClient(); bus = new AsyncEventBus(Executors.newSingleThreadExecutor()); From e97b417480f39168e17325d61a6f4b2476902655 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Fri, 26 Oct 2018 16:13:44 +0200 Subject: [PATCH 03/21] Set backbround of ThumbCell to lightgray --- src/main/java/ctbrec/ui/ThumbCell.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 64342f3c..265b7e67 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -80,12 +80,12 @@ public class ThumbCell extends StackPane { this.model = model; this.recorder = recorder; recording = recorder.isRecording(model); + this.setStyle("-fx-background-color: lightgray"); iv = new ImageView(); - setImage(model.getPreview()); iv.setSmooth(true); iv.setPreserveRatio(true); - iv.setStyle("-fx-background-color: #000"); + setImage(model.getPreview()); getChildren().add(iv); nameBackground = new Rectangle(); @@ -251,9 +251,9 @@ public class ThumbCell extends StackPane { @Override public void changed(ObservableValue observable, Number oldValue, Number newValue) { if(newValue.doubleValue() == 1.0) { - imgAspectRatio = img.getHeight() / img.getWidth(); - setThumbWidth(Config.getInstance().getSettings().thumbWidth); + //imgAspectRatio = img.getHeight() / img.getWidth(); iv.setImage(img); + setThumbWidth(Config.getInstance().getSettings().thumbWidth); } } }); @@ -473,6 +473,7 @@ public class ThumbCell extends StackPane { public void setThumbWidth(int width) { int height = (int) (width * imgAspectRatio); + setPrefSize(width, height); setSize(width, height); } From 88d8430f98f61a9a37d7bb9f8bcc8e819e92b3e6 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 27 Oct 2018 23:27:44 +0200 Subject: [PATCH 04/21] Added sleep to resolution update task ...to throttle the number of request fired in a small amount of time --- src/main/java/ctbrec/ui/ThumbCell.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/ui/ThumbCell.java b/src/main/java/ctbrec/ui/ThumbCell.java index 265b7e67..e7306d3b 100644 --- a/src/main/java/ctbrec/ui/ThumbCell.java +++ b/src/main/java/ctbrec/ui/ThumbCell.java @@ -207,6 +207,8 @@ public class ThumbCell extends StackPane { LOG.trace("Removing invalid resolution value for {}", model.getName()); model.invalidateCacheEntries(); } + + Thread.sleep(500); } catch (ExecutionException | IOException | InterruptedException e1) { LOG.warn("Couldn't update resolution tag for model {}", model.getName(), e1); } finally { @@ -412,13 +414,11 @@ public class ThumbCell extends StackPane { } public void setModel(Model model) { - //this.model = model; this.model.setName(model.getName()); this.model.setDescription(model.getDescription()); this.model.setPreview(model.getPreview()); this.model.setTags(model.getTags()); this.model.setUrl(model.getUrl()); - update(); } From a841457a90aee2090d8ea43dfb46dfad7be06c45 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Sat, 27 Oct 2018 23:28:08 +0200 Subject: [PATCH 05/21] Add HD tab --- .../java/ctbrec/sites/cam4/Cam4TabProvider.java | 17 ++++++++++++----- .../ctbrec/sites/cam4/Cam4UpdateService.java | 3 ++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java index a07f97e7..b813057d 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java @@ -22,12 +22,19 @@ public class Cam4TabProvider extends TabProvider { @Override public List getTabs(Scene scene) { List tabs = new ArrayList<>(); - String url = cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS"; - Cam4UpdateService female = new Cam4UpdateService(url, false, cam4); - ThumbOverviewTab tab = new ThumbOverviewTab("Female", female, cam4); - tab.setRecorder(recorder); - tabs.add(tab); + + tabs.add(createTab("Female", cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS")); + tabs.add(createTab("HD", cam4.getBaseUrl() + "/directoryResults?online=true&hd=true&orderBy=VIDEO_QUALITY")); + + return tabs; } + private Tab createTab(String name, String url) { + Cam4UpdateService updateService = new Cam4UpdateService(url, false, cam4); + ThumbOverviewTab tab = new ThumbOverviewTab(name, updateService, cam4); + tab.setRecorder(recorder); + return tab; + } + } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java b/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java index 26dd5787..5823f285 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java @@ -72,7 +72,8 @@ public class Cam4UpdateService extends PaginatedScheduledService { Cam4Model model = (Cam4Model) site.createModel(slug); String playlistUrl = profileLink.attr("data-hls-preview-url"); model.setPlaylistUrl(playlistUrl); - model.setPreview(HtmlParser.getTag(boxHtml, "a img").attr("data-src")); + //model.setPreview(HtmlParser.getTag(boxHtml, "a img").attr("data-src")); + model.setPreview("https://snapshots.xcdnpro.com/thumbnails/"+model.getName()+"?s=" + System.currentTimeMillis()); model.setDescription(parseDesription(boxHtml)); //model.setOnlineState(parseOnlineState(boxHtml)); models.add(model); From 64beb44316b5e1c84b867c177d21aad8a798a56a Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 13:51:08 +0100 Subject: [PATCH 06/21] Fix typo in log statement --- src/main/java/ctbrec/io/ModelJsonAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ctbrec/io/ModelJsonAdapter.java b/src/main/java/ctbrec/io/ModelJsonAdapter.java index 0c6d8129..fdaefc39 100644 --- a/src/main/java/ctbrec/io/ModelJsonAdapter.java +++ b/src/main/java/ctbrec/io/ModelJsonAdapter.java @@ -69,7 +69,7 @@ public class ModelJsonAdapter extends JsonAdapter { } return model; } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { - throw new IOException("Couldn't instantiate mode class [" + type + "]", e); + throw new IOException("Couldn't instantiate model class [" + type + "]", e); } } From 43cb005fcdaa74b753721ac4253ea7f2d2c112e5 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 17:36:58 +0100 Subject: [PATCH 07/21] Add login dialog for Cam4 The login dialog uses a WebView to load the login form from cam4.com Username and password get filled in by ctbrec, the user has to solve the captcha. Afterwards all cookies (especially the session cookie) are extracted from the webview and transfered to the OkHttp client, so that it can be used for further requests. --- src/main/java/ctbrec/Settings.java | 2 + src/main/java/ctbrec/sites/cam4/Cam4.java | 8 +- .../ctbrec/sites/cam4/Cam4HttpClient.java | 64 +++++++++++ .../ctbrec/sites/cam4/Cam4LoginDialog.java | 101 ++++++++++++++++++ src/main/java/ctbrec/ui/CookieJarImpl.java | 7 +- 5 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java diff --git a/src/main/java/ctbrec/Settings.java b/src/main/java/ctbrec/Settings.java index 97b8bcd4..1da9aaf6 100644 --- a/src/main/java/ctbrec/Settings.java +++ b/src/main/java/ctbrec/Settings.java @@ -24,6 +24,8 @@ public class Settings { public String password = ""; // chaturbate password TODO maybe rename this onetime public String mfcUsername = ""; public String mfcPassword = ""; + public String cam4Username; + public String cam4Password; public String lastDownloadDir = ""; public List models = new ArrayList(); diff --git a/src/main/java/ctbrec/sites/cam4/Cam4.java b/src/main/java/ctbrec/sites/cam4/Cam4.java index 9198aa87..34c9572b 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4.java @@ -62,17 +62,13 @@ public class Cam4 extends AbstractSite { @Override public void login() throws IOException { + getHttpClient().login(); } @Override public HttpClient getHttpClient() { if(httpClient == null) { - httpClient = new HttpClient() { - @Override - public boolean login() throws IOException { - return false; - } - }; + httpClient = new Cam4HttpClient(); } return httpClient; } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java new file mode 100644 index 00000000..0e4dbbb1 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -0,0 +1,64 @@ +package ctbrec.sites.cam4; + +import java.io.IOException; +import java.net.HttpCookie; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.json.JSONObject; + +import ctbrec.io.HttpClient; +import okhttp3.Cookie; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; + +public class Cam4HttpClient extends HttpClient { + + @Override + public boolean login() throws IOException { + // login with javafx WebView + Cam4LoginDialog loginDialog = new Cam4LoginDialog(); + + // transfer cookies from WebView to OkHttp cookie jar + transferCookies(loginDialog); + + return checkLoginSuccess(); + } + + /** + * check, if the login worked by requesting unchecked mail + * @throws IOException + */ + private boolean checkLoginSuccess() throws IOException { + String mailUrl = "https://www.cam4.de.com/mail/unreadThreads"; + Request req = new Request.Builder().url(mailUrl).build(); + Response response = execute(req); + if(response.isSuccessful()) { + JSONObject json = new JSONObject(response.body().string()); + return json.has("status") && Objects.equals("success", json.getString("status")); + } else { + response.close(); + return false; + } + } + + private void transferCookies(Cam4LoginDialog loginDialog) { + HttpUrl redirectedUrl = HttpUrl.parse(loginDialog.getUrl()); + List cookies = new ArrayList<>(); + for (HttpCookie webViewCookie : loginDialog.getCookies()) { + Cookie cookie = Cookie.parse(redirectedUrl, webViewCookie.toString()); + cookies.add(cookie); + } + cookieJar.saveFromResponse(redirectedUrl, cookies); + + HttpUrl origUrl = HttpUrl.parse(Cam4LoginDialog.URL); + cookies = new ArrayList<>(); + for (HttpCookie webViewCookie : loginDialog.getCookies()) { + Cookie cookie = Cookie.parse(origUrl, webViewCookie.toString()); + cookies.add(cookie); + } + cookieJar.saveFromResponse(origUrl, cookies); + } +} diff --git a/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java b/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java new file mode 100644 index 00000000..d10bfec0 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java @@ -0,0 +1,101 @@ +package ctbrec.sites.cam4; + +import java.net.CookieHandler; +import java.net.CookieManager; +import java.net.HttpCookie; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; +import javafx.concurrent.Worker.State; +import javafx.scene.Scene; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.layout.Region; +import javafx.scene.layout.StackPane; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import javafx.stage.Stage; + +public class Cam4LoginDialog { + + private static final transient Logger LOG = LoggerFactory.getLogger(Cam4LoginDialog.class); + public static final String URL = Cam4.BASE_URI + "/login"; + private List cookies = null; + private String url; + private Region veil; + private ProgressIndicator p; + + public Cam4LoginDialog() { + Stage stage = new Stage(); + stage.setTitle("Cam4 Login"); + CookieManager cookieManager = new CookieManager(); + CookieHandler.setDefault(cookieManager); + WebView webView = createWebView(stage); + + veil = new Region(); + veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)"); + //veil.setPrefSize(240, 160); + p = new ProgressIndicator(); + p.setMaxSize(140, 140); + + StackPane stackPane = new StackPane(); + stackPane.getChildren().addAll(webView, veil, p); + + stage.setScene(new Scene(stackPane, 480, 854)); + stage.showAndWait(); + cookies = cookieManager.getCookieStore().getCookies(); + } + + private WebView createWebView(Stage stage) { + WebView browser = new WebView(); + WebEngine webEngine = browser.getEngine(); + webEngine.setJavaScriptEnabled(true); + webEngine.locationProperty().addListener((obs, oldV, newV) -> { + try { + URL _url = new URL(newV); + if (Objects.equals(_url.getPath(), "/")) { + stage.close(); + } + } catch (MalformedURLException e) { + LOG.error("Couldn't parse new url {}", newV, e); + } + url = newV.toString(); + }); + webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> { + System.out.println(newState); + if (newState == State.SUCCEEDED) { + String username = Config.getInstance().getSettings().cam4Username; + if (username != null && !username.trim().isEmpty()) { + webEngine.executeScript("$('input[name=username]').attr('value','" + username + "')"); + } + String password = Config.getInstance().getSettings().cam4Password; + if (password != null && !password.trim().isEmpty()) { + webEngine.executeScript("$('input[name=password]').attr('value','" + password + "')"); + } + webEngine.executeScript("$('div[class~=navbar]').css('display','none')"); + webEngine.executeScript("$('div#footer').css('display','none')"); + webEngine.executeScript("$('div#content').css('padding','0')"); + veil.setVisible(false); + p.setVisible(false); + } else if (newState == State.CANCELLED || newState == State.FAILED) { + veil.setVisible(false); + p.setVisible(false); + } + }); + webEngine.load(URL); + return browser; + } + + public List getCookies() { + return cookies; + } + + public String getUrl() { + return url; + } +} diff --git a/src/main/java/ctbrec/ui/CookieJarImpl.java b/src/main/java/ctbrec/ui/CookieJarImpl.java index e0bee9ee..ce18ea4d 100644 --- a/src/main/java/ctbrec/ui/CookieJarImpl.java +++ b/src/main/java/ctbrec/ui/CookieJarImpl.java @@ -6,6 +6,7 @@ import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +51,11 @@ public class CookieJarImpl implements CookieJar { public List loadForRequest(HttpUrl url) { String host = getHost(url); List cookies = cookieStore.get(host); - LOG.debug("Cookies for {}: {}", url.host(), cookies); + LOG.debug("Cookies for {}", url); + Optional.ofNullable(cookies).ifPresent(cookiez -> cookiez.forEach(c -> { + LOG.debug(" {} expires on:{}", c, c.expiresAt()); + })); + //LOG.debug("Cookies for {}: {}", url.host(), cookies); return cookies != null ? cookies : new ArrayList(); } From 936984c71da03afb4ecd26762805500b0a74dfc8 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 17:57:14 +0100 Subject: [PATCH 08/21] Add window icon to Cam4 login dialog --- src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java b/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java index d10bfec0..9aae0544 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4LoginDialog.java @@ -1,5 +1,6 @@ package ctbrec.sites.cam4; +import java.io.InputStream; import java.net.CookieHandler; import java.net.CookieManager; import java.net.HttpCookie; @@ -15,6 +16,7 @@ import ctbrec.Config; import javafx.concurrent.Worker.State; import javafx.scene.Scene; import javafx.scene.control.ProgressIndicator; +import javafx.scene.image.Image; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.scene.web.WebEngine; @@ -33,13 +35,14 @@ public class Cam4LoginDialog { public Cam4LoginDialog() { Stage stage = new Stage(); stage.setTitle("Cam4 Login"); + InputStream icon = getClass().getResourceAsStream("/icon.png"); + stage.getIcons().add(new Image(icon)); CookieManager cookieManager = new CookieManager(); CookieHandler.setDefault(cookieManager); WebView webView = createWebView(stage); veil = new Region(); veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.4)"); - //veil.setPrefSize(240, 160); p = new ProgressIndicator(); p.setMaxSize(140, 140); @@ -67,7 +70,6 @@ public class Cam4LoginDialog { url = newV.toString(); }); webEngine.getLoadWorker().stateProperty().addListener((observable, oldState, newState) -> { - System.out.println(newState); if (newState == State.SUCCEEDED) { String username = Config.getInstance().getSettings().cam4Username; if (username != null && !username.trim().isEmpty()) { From 4d1e8414485e87b4baacc16246a2a8f636ef6aa8 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 18:12:52 +0100 Subject: [PATCH 09/21] Improve login success check --- src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java index 0e4dbbb1..3649dc9d 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -32,10 +32,13 @@ public class Cam4HttpClient extends HttpClient { * @throws IOException */ private boolean checkLoginSuccess() throws IOException { - String mailUrl = "https://www.cam4.de.com/mail/unreadThreads"; - Request req = new Request.Builder().url(mailUrl).build(); + String mailUrl = Cam4.BASE_URI + "/mail/unreadThreads"; + Request req = new Request.Builder() + .url(mailUrl) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); Response response = execute(req); - if(response.isSuccessful()) { + if(response.isSuccessful() && response.body().contentLength() > 0) { JSONObject json = new JSONObject(response.body().string()); return json.has("status") && Objects.equals("success", json.getString("status")); } else { From dab3466cf61967659678bda3a467ace0b35ca621 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 21:53:15 +0100 Subject: [PATCH 10/21] Simplify login method --- src/main/java/ctbrec/sites/chaturbate/Chaturbate.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java b/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java index 2aa1fb7c..984e8c69 100644 --- a/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java +++ b/src/main/java/ctbrec/sites/chaturbate/Chaturbate.java @@ -26,7 +26,6 @@ import com.squareup.moshi.Moshi; import ctbrec.Config; import ctbrec.Model; -import ctbrec.Settings; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; @@ -121,8 +120,7 @@ public class Chaturbate extends AbstractSite { @Override public void login() { - Settings settings = Config.getInstance().getSettings(); - if (settings.username != null && !settings.username.isEmpty()) { + if (credentialsAvailable()) { new Thread() { @Override public void run() { From 2ffdbfa71ae1ddfd88aaa053d014266a0d229cb4 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 21:53:41 +0100 Subject: [PATCH 11/21] Add followed tab for Cam4 --- src/main/java/ctbrec/sites/cam4/Cam4.java | 58 ++++++++++-- .../ctbrec/sites/cam4/Cam4FollowedTab.java | 75 +++++++++++++++ .../sites/cam4/Cam4FollowedUpdateService.java | 94 +++++++++++++++++++ .../ctbrec/sites/cam4/Cam4HttpClient.java | 43 ++++++++- .../java/ctbrec/sites/cam4/Cam4Model.java | 60 +++++++++++- .../ctbrec/sites/cam4/Cam4TabProvider.java | 6 +- .../ctbrec/sites/cam4/Cam4UpdateService.java | 4 +- 7 files changed, 319 insertions(+), 21 deletions(-) create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4FollowedTab.java create mode 100644 src/main/java/ctbrec/sites/cam4/Cam4FollowedUpdateService.java diff --git a/src/main/java/ctbrec/sites/cam4/Cam4.java b/src/main/java/ctbrec/sites/cam4/Cam4.java index 34c9572b..5d2f1957 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4.java @@ -2,17 +2,31 @@ package ctbrec.sites.cam4; import java.io.IOException; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; import ctbrec.Model; import ctbrec.io.HttpClient; import ctbrec.recorder.Recorder; import ctbrec.sites.AbstractSite; +import ctbrec.ui.DesktopIntergation; +import ctbrec.ui.SettingsTab; import ctbrec.ui.TabProvider; +import javafx.geometry.Insets; import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; public class Cam4 extends AbstractSite { public static final String BASE_URI = "https://www.cam4.com"; + public static final String AFFILIATE_LINK = BASE_URI + "/?referrerId=1514a80d87b5effb456cca02f6743aa1"; + private HttpClient httpClient; private Recorder recorder; @@ -28,7 +42,7 @@ public class Cam4 extends AbstractSite { @Override public String getAffiliateLink() { - return getBaseUrl() + "/?referrerId=1514a80d87b5effb456cca02f6743aa1"; + return AFFILIATE_LINK; } @Override @@ -62,7 +76,10 @@ public class Cam4 extends AbstractSite { @Override public void login() throws IOException { - getHttpClient().login(); + if (credentialsAvailable()) { + boolean success = getHttpClient().login(); + LoggerFactory.getLogger(getClass()).debug("Login success: {}", success); + } } @Override @@ -89,7 +106,7 @@ public class Cam4 extends AbstractSite { @Override public boolean supportsFollow() { - return false; + return true; } @Override @@ -98,13 +115,38 @@ public class Cam4 extends AbstractSite { } @Override - public Node getConfigurationGui() { - return null; + public boolean credentialsAvailable() { + String username = Config.getInstance().getSettings().cam4Username; + return username != null && !username.trim().isEmpty(); } @Override - public boolean credentialsAvailable() { - return false; - } + public Node getConfigurationGui() { + GridPane layout = SettingsTab.createGridLayout(); + layout.add(new Label("Cam4 User"), 0, 0); + TextField username = new TextField(Config.getInstance().getSettings().cam4Username); + username.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Username = username.getText()); + GridPane.setFillWidth(username, true); + GridPane.setHgrow(username, Priority.ALWAYS); + GridPane.setColumnSpan(username, 2); + layout.add(username, 1, 0); + layout.add(new Label("Cam4 Password"), 0, 1); + PasswordField password = new PasswordField(); + password.setText(Config.getInstance().getSettings().cam4Password); + password.focusedProperty().addListener((e) -> Config.getInstance().getSettings().cam4Password = password.getText()); + GridPane.setFillWidth(password, true); + GridPane.setHgrow(password, Priority.ALWAYS); + GridPane.setColumnSpan(password, 2); + layout.add(password, 1, 1); + + Button createAccount = new Button("Create new Account"); + createAccount.setOnAction((e) -> DesktopIntergation.open(Cam4.AFFILIATE_LINK)); + layout.add(createAccount, 1, 2); + GridPane.setColumnSpan(createAccount, 2); + GridPane.setMargin(username, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); + GridPane.setMargin(password, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); + GridPane.setMargin(createAccount, new Insets(0, 0, 0, SettingsTab.CHECKBOX_MARGIN)); + return layout; + } } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4FollowedTab.java b/src/main/java/ctbrec/sites/cam4/Cam4FollowedTab.java new file mode 100644 index 00000000..bd25fa90 --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4FollowedTab.java @@ -0,0 +1,75 @@ +package ctbrec.sites.cam4; + +import ctbrec.ui.FollowedTab; +import ctbrec.ui.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 Cam4FollowedTab extends ThumbOverviewTab implements FollowedTab { + private Label status; + + public Cam4FollowedTab(Cam4 cam4) { + super("Followed", new Cam4FollowedUpdateService(cam4), cam4); + status = new Label("Logging in..."); + grid.getChildren().add(status); + } + + @Override + protected void createGui() { + super.createGui(); + addOnlineOfflineSelector(); + } + + private void addOnlineOfflineSelector() { + ToggleGroup group = new ToggleGroup(); + RadioButton online = new RadioButton("online"); + online.setToggleGroup(group); + RadioButton 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) -> { + ((Cam4FollowedUpdateService)updateService).setShowOnline(online.isSelected()); + 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()) { + if(event.getCode() == KeyCode.DELETE) { + follow(selectedThumbCells, false); + } + } + }); + } +} diff --git a/src/main/java/ctbrec/sites/cam4/Cam4FollowedUpdateService.java b/src/main/java/ctbrec/sites/cam4/Cam4FollowedUpdateService.java new file mode 100644 index 00000000..c752fb4a --- /dev/null +++ b/src/main/java/ctbrec/sites/cam4/Cam4FollowedUpdateService.java @@ -0,0 +1,94 @@ +package ctbrec.sites.cam4; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.stream.Collectors; + +import org.jsoup.nodes.Element; +import org.jsoup.select.Elements; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Config; +import ctbrec.Model; +import ctbrec.ui.HtmlParser; +import ctbrec.ui.PaginatedScheduledService; +import javafx.concurrent.Task; +import okhttp3.Request; +import okhttp3.Response; + +public class Cam4FollowedUpdateService extends PaginatedScheduledService { + + private static final transient Logger LOG = LoggerFactory.getLogger(Cam4FollowedUpdateService.class); + private Cam4 site; + private boolean showOnline = true; + + public Cam4FollowedUpdateService(Cam4 site) { + this.site = site; + ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName("ThumbOverviewTab UpdateService"); + return t; + } + }); + setExecutor(executor); + } + + @Override + protected Task> createTask() { + return new Task>() { + @Override + public List call() throws IOException { + List models = new ArrayList<>(); + String username = Config.getInstance().getSettings().cam4Username; + String url = site.getBaseUrl() + '/' + username + "/edit/friends_favorites"; + Request req = new Request.Builder().url(url).build(); + Response response = site.getHttpClient().execute(req, true); + if(response.isSuccessful()) { + String content = response.body().string(); + Elements cells = HtmlParser.getTags(content, "div#favorites div.ff_thumb"); + for (Element cell : cells) { + String cellHtml = cell.html(); + Element link = HtmlParser.getTag(cellHtml, "div.ff_img a"); + String path = link.attr("href"); + String modelName = path.substring(1); + Cam4Model model = (Cam4Model) site.createModel(modelName); + model.setPreview("https://snapshots.xcdnpro.com/thumbnails/"+model.getName()+"?s=" + System.currentTimeMillis()); + model.setOnlineState(parseOnlineState(cellHtml)); + models.add(model); + } + return models.stream() + .filter(m -> { + try { + return m.isOnline() == showOnline; + } catch (IOException | ExecutionException | InterruptedException e) { + LOG.error("Couldn't determine online state", e); + return false; + } + }).collect(Collectors.toList()); + } else { + IOException e = new IOException(response.code() + " " + response.message()); + response.close(); + throw e; + } + } + + private String parseOnlineState(String cellHtml) { + Element state = HtmlParser.getTag(cellHtml, "div.ff_name div"); + return state.attr("class").equals("online") ? "NORMAL" : "OFFLINE"; + } + }; + } + + public void setShowOnline(boolean online) { + this.showOnline = online; + } +} diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java index 3649dc9d..0a360b6b 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -5,10 +5,15 @@ import java.net.HttpCookie; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ctbrec.io.HttpClient; +import javafx.application.Platform; import okhttp3.Cookie; import okhttp3.HttpUrl; import okhttp3.Request; @@ -16,15 +21,43 @@ import okhttp3.Response; public class Cam4HttpClient extends HttpClient { + private static final transient Logger LOG = LoggerFactory.getLogger(Cam4HttpClient.class); + @Override public boolean login() throws IOException { - // login with javafx WebView - Cam4LoginDialog loginDialog = new Cam4LoginDialog(); + BlockingQueue queue = new LinkedBlockingQueue<>(); + LOG.debug("Launching dialog"); - // transfer cookies from WebView to OkHttp cookie jar - transferCookies(loginDialog); + Runnable showDialog = () -> { + // login with javafx WebView + Cam4LoginDialog loginDialog = new Cam4LoginDialog(); - return checkLoginSuccess(); + // transfer cookies from WebView to OkHttp cookie jar + transferCookies(loginDialog); + + try { + queue.put(true); + } catch (InterruptedException e) { + LOG.error("Error while signaling termination", e); + } + }; + + if(Platform.isFxApplicationThread()) { + showDialog.run(); + } else { + Platform.runLater(showDialog); + LOG.debug("waiting for queue"); + try { + queue.take(); + } catch (InterruptedException e) { + LOG.error("Error while waiting for login dialog to close", e); + throw new IOException(e); + } + } + + + loggedIn = checkLoginSuccess(); + return loggedIn; } /** diff --git a/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/src/main/java/ctbrec/sites/cam4/Cam4Model.java index 35b96268..6ef57fb3 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4Model.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4Model.java @@ -9,6 +9,7 @@ import java.util.concurrent.ExecutionException; import org.json.JSONArray; import org.json.JSONObject; +import org.jsoup.nodes.Element; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,9 +23,13 @@ import com.iheartradio.m3u8.data.Playlist; import com.iheartradio.m3u8.data.PlaylistData; import ctbrec.AbstractModel; +import ctbrec.Config; import ctbrec.recorder.download.StreamSource; import ctbrec.sites.Site; +import ctbrec.ui.HtmlParser; +import okhttp3.FormBody; import okhttp3.Request; +import okhttp3.RequestBody; import okhttp3.Response; public class Cam4Model extends AbstractModel { @@ -147,14 +152,57 @@ public class Cam4Model extends AbstractModel { @Override public boolean follow() throws IOException { - // TODO Auto-generated method stub - return false; + String url = site.getBaseUrl() + "/profiles/addFriendFavorite?action=addFavorite&object=" + getName() + "&_=" + System.currentTimeMillis(); + Request req = new Request.Builder() + .url(url) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + Response response = site.getHttpClient().execute(req, true); + boolean success = response.isSuccessful(); + response.close(); + return success; } @Override public boolean unfollow() throws IOException { - // TODO Auto-generated method stub - return false; + // get model user id + String url = site.getBaseUrl() + '/' + getName(); + Request req = new Request.Builder().url(url).build(); + Response response = site.getHttpClient().execute(req, true); + String broadCasterId = null; + if(response.isSuccessful()) { + String content = response.body().string(); + try { + Element tag = HtmlParser.getTag(content, "input[name=\"broadcasterId\"]"); + broadCasterId = tag.attr("value"); + } catch(Exception e) { + LOG.debug(content); + throw new IOException(e); + } + + // send unfollow request + String username = Config.getInstance().getSettings().cam4Username; + url = site.getBaseUrl() + '/' + username + "/edit/friends_favorites"; + RequestBody body = new FormBody.Builder() + .add("deleteFavorites", broadCasterId) + .add("simpleresult", "true") + .build(); + req = new Request.Builder() + .url(url) + .post(body) + .addHeader("X-Requested-With", "XMLHttpRequest") + .build(); + response = site.getHttpClient().execute(req, true); + if(response.isSuccessful()) { + return Objects.equals(response.body().string(), "Ok"); + } else { + response.close(); + return false; + } + } else { + response.close(); + return false; + } } @Override @@ -174,4 +222,8 @@ public class Cam4Model extends AbstractModel { public void setPlaylistUrl(String playlistUrl) { this.playlistUrl = playlistUrl; } + + public void setOnlineState(String onlineState) { + this.onlineState = onlineState; + } } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java index b813057d..f38254ca 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java @@ -25,7 +25,11 @@ public class Cam4TabProvider extends TabProvider { tabs.add(createTab("Female", cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS")); tabs.add(createTab("HD", cam4.getBaseUrl() + "/directoryResults?online=true&hd=true&orderBy=VIDEO_QUALITY")); - + if(cam4.credentialsAvailable()) { + Cam4FollowedTab followed = new Cam4FollowedTab(cam4); + followed.setRecorder(recorder); + tabs.add(followed); + } return tabs; } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java b/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java index 5823f285..bcd89205 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4UpdateService.java @@ -52,7 +52,7 @@ public class Cam4UpdateService extends PaginatedScheduledService { return new Task>() { @Override public List call() throws IOException { - if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().username)) { // FIXME change to cam4 username + if(loginRequired && StringUtil.isBlank(Config.getInstance().getSettings().cam4Username)) { return Collections.emptyList(); } else { String url = Cam4UpdateService.this.url + "&page=" + page; @@ -72,10 +72,8 @@ public class Cam4UpdateService extends PaginatedScheduledService { Cam4Model model = (Cam4Model) site.createModel(slug); String playlistUrl = profileLink.attr("data-hls-preview-url"); model.setPlaylistUrl(playlistUrl); - //model.setPreview(HtmlParser.getTag(boxHtml, "a img").attr("data-src")); model.setPreview("https://snapshots.xcdnpro.com/thumbnails/"+model.getName()+"?s=" + System.currentTimeMillis()); model.setDescription(parseDesription(boxHtml)); - //model.setOnlineState(parseOnlineState(boxHtml)); models.add(model); } response.close(); From 97e7466352aec64c99f93b1979c1137efba324d2 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 22:30:15 +0100 Subject: [PATCH 12/21] Fix UI freeze caused by OnlineMonitor The lock to prevent concurrent access to the list models caused the app to freeze, if the models list contained many models, because the OnlineMonitor would block access until it checked the online state of every model. --- .../java/ctbrec/recorder/LocalRecorder.java | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/ctbrec/recorder/LocalRecorder.java b/src/main/java/ctbrec/recorder/LocalRecorder.java index 4f3f983b..e1916d93 100644 --- a/src/main/java/ctbrec/recorder/LocalRecorder.java +++ b/src/main/java/ctbrec/recorder/LocalRecorder.java @@ -166,7 +166,12 @@ public class LocalRecorder implements Recorder { @Override public List getModelsRecording() { - return Collections.unmodifiableList(new ArrayList<>(models)); + lock.lock(); + try { + return Collections.unmodifiableList(new ArrayList<>(models)); + } finally { + lock.unlock(); + } } @Override @@ -308,24 +313,19 @@ public class LocalRecorder implements Recorder { public void run() { running = true; while (running) { - lock.lock(); - try { - for (Model model : getModelsRecording()) { - try { - if (!recordingProcesses.containsKey(model)) { - boolean isOnline = model.isOnline(IGNORE_CACHE); - LOG.trace("Checking online state for {}: {}", model, (isOnline ? "online" : "offline")); - if (isOnline) { - LOG.info("Model {}'s room back to public. Starting recording", model); - startRecordingProcess(model); - } + for (Model model : getModelsRecording()) { + try { + if (!recordingProcesses.containsKey(model)) { + boolean isOnline = model.isOnline(IGNORE_CACHE); + LOG.trace("Checking online state for {}: {}", model, (isOnline ? "online" : "offline")); + if (isOnline) { + LOG.info("Model {}'s room back to public. Starting recording", model); + startRecordingProcess(model); } - } catch (Exception e) { - LOG.error("Couldn't check if model {} is online", model.getName(), e); } + } catch (Exception e) { + LOG.error("Couldn't check if model {} is online", model.getName(), e); } - } finally { - lock.unlock(); } try { From 8009b3cf36e1149c2a4529bedc4a8f14a76381c1 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 22:37:32 +0100 Subject: [PATCH 13/21] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 857a6a63..af17e769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +1.6.1 +======================== +* Fixed UI freeze, which occured for a high number of recorded models + 1.6.0 ======================== * Added support for multiple cam sites From 7f3bedd3a1923e2ed3a6bce6d17d8a2443d0d648 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Mon, 29 Oct 2018 23:25:50 +0100 Subject: [PATCH 14/21] Clean up code --- src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java | 3 --- src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java | 8 +++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java index 0a360b6b..38673390 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -26,7 +26,6 @@ public class Cam4HttpClient extends HttpClient { @Override public boolean login() throws IOException { BlockingQueue queue = new LinkedBlockingQueue<>(); - LOG.debug("Launching dialog"); Runnable showDialog = () -> { // login with javafx WebView @@ -46,7 +45,6 @@ public class Cam4HttpClient extends HttpClient { showDialog.run(); } else { Platform.runLater(showDialog); - LOG.debug("waiting for queue"); try { queue.take(); } catch (InterruptedException e) { @@ -55,7 +53,6 @@ public class Cam4HttpClient extends HttpClient { } } - loggedIn = checkLoginSuccess(); return loggedIn; } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java index f38254ca..2c04e7d6 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java @@ -25,11 +25,9 @@ public class Cam4TabProvider extends TabProvider { tabs.add(createTab("Female", cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS")); tabs.add(createTab("HD", cam4.getBaseUrl() + "/directoryResults?online=true&hd=true&orderBy=VIDEO_QUALITY")); - if(cam4.credentialsAvailable()) { - Cam4FollowedTab followed = new Cam4FollowedTab(cam4); - followed.setRecorder(recorder); - tabs.add(followed); - } + Cam4FollowedTab followed = new Cam4FollowedTab(cam4); + followed.setRecorder(recorder); + tabs.add(followed); return tabs; } From c445e48d6d9b2b83d4f5b7719d3db1e31a17c96a Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 15:24:02 +0100 Subject: [PATCH 15/21] Don't try to log in twice --- src/main/java/ctbrec/sites/cam4/Cam4.java | 5 ++++- .../java/ctbrec/sites/cam4/Cam4HttpClient.java | 14 +++++++++++++- src/main/java/ctbrec/sites/cam4/Cam4Model.java | 7 +++---- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4.java b/src/main/java/ctbrec/sites/cam4/Cam4.java index 5d2f1957..5d791883 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4.java @@ -66,7 +66,10 @@ public class Cam4 extends AbstractSite { @Override public Integer getTokenBalance() throws IOException { - return 0; + if (!credentialsAvailable()) { + throw new IOException("Not logged in"); + } + return ((Cam4HttpClient)getHttpClient()).getTokenBalance(); } @Override diff --git a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java index 38673390..3a40c555 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4HttpClient.java @@ -24,7 +24,11 @@ public class Cam4HttpClient extends HttpClient { private static final transient Logger LOG = LoggerFactory.getLogger(Cam4HttpClient.class); @Override - public boolean login() throws IOException { + public synchronized boolean login() throws IOException { + if(loggedIn) { + return true; + } + BlockingQueue queue = new LinkedBlockingQueue<>(); Runnable showDialog = () -> { @@ -94,4 +98,12 @@ public class Cam4HttpClient extends HttpClient { } cookieJar.saveFromResponse(origUrl, cookies); } + + protected int getTokenBalance() throws IOException { + if(!loggedIn) { + login(); + } + + throw new RuntimeException("Not implemented, yet"); + } } diff --git a/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/src/main/java/ctbrec/sites/cam4/Cam4Model.java index 6ef57fb3..a77adefe 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4Model.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4Model.java @@ -124,14 +124,13 @@ public class Cam4Model extends AbstractModel { @Override public void invalidateCacheEntries() { - // TODO Auto-generated method stub - + resolution = null; + playlistUrl = null; } @Override public void receiveTip(int tokens) throws IOException { - // TODO Auto-generated method stub - + throw new RuntimeException("Not implemented for Cam4, yet"); } @Override From 007ed19d97d0c3659f3a9f7d9cad1ecb6f75a70b Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 15:57:24 +0100 Subject: [PATCH 16/21] Add more tabs Add tabs for male and couples --- src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java index 2c04e7d6..62136129 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4TabProvider.java @@ -23,8 +23,11 @@ public class Cam4TabProvider extends TabProvider { public List getTabs(Scene scene) { List tabs = new ArrayList<>(); - tabs.add(createTab("Female", cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS")); - tabs.add(createTab("HD", cam4.getBaseUrl() + "/directoryResults?online=true&hd=true&orderBy=VIDEO_QUALITY")); + tabs.add(createTab("Female", cam4.getBaseUrl() + "/directoryResults?online=true&gender=female&orderBy=MOST_VIEWERS")); + tabs.add(createTab("Male", cam4.getBaseUrl() + "/directoryResults?online=true&gender=male&orderBy=MOST_VIEWERS")); + tabs.add(createTab("Couples", cam4.getBaseUrl() + "/directoryResults?online=true&broadcastType=male_group&broadcastType=female_group&broadcastType=male_female_group&orderBy=MOST_VIEWERS")); + tabs.add(createTab("HD", cam4.getBaseUrl() + "/directoryResults?online=true&hd=true&orderBy=MOST_VIEWERS")); + Cam4FollowedTab followed = new Cam4FollowedTab(cam4); followed.setRecorder(recorder); tabs.add(followed); From 0c9fc5cba6d7d691f02822252a4b90500fbf65d9 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 15:57:44 +0100 Subject: [PATCH 17/21] Fix JSON parse exception --- src/main/java/ctbrec/sites/cam4/Cam4Model.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/ctbrec/sites/cam4/Cam4Model.java b/src/main/java/ctbrec/sites/cam4/Cam4Model.java index a77adefe..c8972af8 100644 --- a/src/main/java/ctbrec/sites/cam4/Cam4Model.java +++ b/src/main/java/ctbrec/sites/cam4/Cam4Model.java @@ -60,6 +60,9 @@ public class Cam4Model extends AbstractModel { Response response = site.getHttpClient().execute(req); if(response.isSuccessful()) { JSONArray json = new JSONArray(response.body().string()); + if(json.length() == 0) { + throw new IOException("Couldn't fetch model details"); + } JSONObject details = json.getJSONObject(0); onlineState = details.getString("showType"); playlistUrl = details.getString("hlsPreviewUrl"); From 3ad8410cde0432625fe5c5415d650e0cc3ee10a0 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 16:00:01 +0100 Subject: [PATCH 18/21] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af17e769..a4a84fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ 1.6.1 ======================== * Fixed UI freeze, which occured for a high number of recorded models +* Added Cam4 1.6.0 ======================== From b237f64a83eda76eaccfff5dabf30747e8a2f4c6 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 16:00:16 +0100 Subject: [PATCH 19/21] Bump version to 1.6.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a768d26..f38886a0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 ctbrec ctbrec - 1.6.0 + 1.6.1 UTF-8 From 57e6013142142feeda26f3f6e8ca9471b2ba5d42 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 16:10:19 +0100 Subject: [PATCH 20/21] Update the embedded JRE for the Windows bundles to 8u192 --- CHANGELOG.md | 1 + src/assembly/win32-jre.xml | 2 +- src/assembly/win64-jre.xml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4a84fa2..e3b95ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ======================== * Fixed UI freeze, which occured for a high number of recorded models * Added Cam4 +* Updated the embedded JRE for the Windows bundles to 8u192 1.6.0 ======================== diff --git a/src/assembly/win32-jre.xml b/src/assembly/win32-jre.xml index b9fba758..7efe1c9d 100644 --- a/src/assembly/win32-jre.xml +++ b/src/assembly/win32-jre.xml @@ -22,7 +22,7 @@ - jre/jre1.8.0_181_win32 + jre/jre1.8.0_192_win32 **/* diff --git a/src/assembly/win64-jre.xml b/src/assembly/win64-jre.xml index d81bd0ce..65b22409 100644 --- a/src/assembly/win64-jre.xml +++ b/src/assembly/win64-jre.xml @@ -22,7 +22,7 @@ - jre/jre1.8.0_181_win64 + jre/jre1.8.0_192_win64 **/* From 1b0b5f18a4b173d44dbe659b88b70ec3bbf41377 Mon Sep 17 00:00:00 2001 From: 0xboobface <0xboobface@gmail.com> Date: Tue, 30 Oct 2018 18:21:13 +0100 Subject: [PATCH 21/21] Add assembly descriptor for macOS --- ctbrec-macos.sh | 9 +++++++++ pom.xml | 3 ++- server-macos.sh | 9 +++++++++ src/assembly/macos-jre.xml | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100755 ctbrec-macos.sh create mode 100755 server-macos.sh create mode 100644 src/assembly/macos-jre.xml diff --git a/ctbrec-macos.sh b/ctbrec-macos.sh new file mode 100755 index 00000000..f28903cc --- /dev/null +++ b/ctbrec-macos.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +DIR=$(dirname $0) +pushd $DIR +JAVA_HOME="$DIR/jre/Contents/Home" +JAVA="$JAVA_HOME/bin/java" +$JAVA -version +$JAVA -cp ${name.final}.jar ctbrec.ui.Launcher +popd \ No newline at end of file diff --git a/pom.xml b/pom.xml index f38886a0..77b1df4a 100644 --- a/pom.xml +++ b/pom.xml @@ -66,6 +66,7 @@ src/assembly/win64-jre.xml src/assembly/win32-jre.xml src/assembly/linux.xml + src/assembly/macos-jre.xml @@ -77,7 +78,7 @@ 1.7.22 - l4j-clui + l4j-win package launch4j diff --git a/server-macos.sh b/server-macos.sh new file mode 100755 index 00000000..7ab059e4 --- /dev/null +++ b/server-macos.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +DIR=$(dirname $0) +pushd $DIR +JAVA_HOME="$DIR/jre/Contents/Home" +JAVA="$JAVA_HOME/bin/java" +$JAVA -version +$JAVA -cp ${name.final}.jar -Dctbrec.config=server.json ctbrec.recorder.server.HttpServer +popd diff --git a/src/assembly/macos-jre.xml b/src/assembly/macos-jre.xml new file mode 100644 index 00000000..c8a1c261 --- /dev/null +++ b/src/assembly/macos-jre.xml @@ -0,0 +1,34 @@ + + + macos-jre + + zip + + false + + + ${project.basedir}/ctbrec-macos.sh + ctbrec + true + + + ${project.basedir}/server-macos.sh + ctbrec + true + + + ${project.build.directory}/${name.final}.jar + ctbrec + + + + + jre/jre1.8.0_192_macos + + **/* + + ctbrec/jre + false + + +