diff --git a/client/src/main/java/ctbrec/ui/CamrecApplication.java b/client/src/main/java/ctbrec/ui/CamrecApplication.java index e87efb53..0dd0be64 100644 --- a/client/src/main/java/ctbrec/ui/CamrecApplication.java +++ b/client/src/main/java/ctbrec/ui/CamrecApplication.java @@ -44,6 +44,7 @@ import ctbrec.sites.fc2live.Fc2Live; import ctbrec.sites.jasmin.LiveJasmin; import ctbrec.sites.mfc.MyFreeCams; import ctbrec.sites.streamate.Streamate; +import ctbrec.ui.news.NewsTab; import ctbrec.ui.settings.SettingsTab; import javafx.application.Application; import javafx.application.HostServices; @@ -141,6 +142,7 @@ public class CamrecApplication extends Application { rootPane.getTabs().add(recordingsTab); settingsTab = new SettingsTab(sites, recorder); rootPane.getTabs().add(settingsTab); + rootPane.getTabs().add(new NewsTab()); rootPane.getTabs().add(new DonateTabFx()); switchToStartTab(); @@ -368,11 +370,11 @@ public class CamrecApplication extends Application { updateCheck.start(); } - private Version getVersion() throws IOException { + public static Version getVersion() throws IOException { if (Objects.equals(System.getenv("CTBREC_DEV"), "1")) { return Version.of("0.0.0-DEV"); } else { - try (InputStream is = getClass().getClassLoader().getResourceAsStream("version")) { + try (InputStream is = CamrecApplication.class.getClassLoader().getResourceAsStream("version")) { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); String versionString = reader.readLine(); Version version = Version.of(versionString); diff --git a/client/src/main/java/ctbrec/ui/news/Account.java b/client/src/main/java/ctbrec/ui/news/Account.java new file mode 100644 index 00000000..786755dc --- /dev/null +++ b/client/src/main/java/ctbrec/ui/news/Account.java @@ -0,0 +1,190 @@ +package ctbrec.ui.news; + +import java.util.List; + +import com.squareup.moshi.Json; + +public class Account { + + @Json(name = "emojis") + private List emojis = null; + @Json(name = "note") + private String note; + @Json(name = "bot") + private Boolean bot; + @Json(name = "created_at") + private String createdAt; + @Json(name = "avatar") + private String avatar; + @Json(name = "display_name") + private String displayName; + @Json(name = "header_static") + private String headerStatic; + @Json(name = "url") + private String url; + @Json(name = "following_count") + private Integer followingCount; + @Json(name = "statuses_count") + private Integer statusesCount; + @Json(name = "followers_count") + private Integer followersCount; + @Json(name = "header") + private String header; + @Json(name = "id") + private String id; + @Json(name = "locked") + private Boolean locked; + @Json(name = "avatar_static") + private String avatarStatic; + @Json(name = "fields") + private List fields = null; + @Json(name = "acct") + private String acct; + @Json(name = "username") + private String username; + + public List getEmojis() { + return emojis; + } + + public void setEmojis(List emojis) { + this.emojis = emojis; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + + public Boolean getBot() { + return bot; + } + + public void setBot(Boolean bot) { + this.bot = bot; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getHeaderStatic() { + return headerStatic; + } + + public void setHeaderStatic(String headerStatic) { + this.headerStatic = headerStatic; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public Integer getFollowingCount() { + return followingCount; + } + + public void setFollowingCount(Integer followingCount) { + this.followingCount = followingCount; + } + + public Integer getStatusesCount() { + return statusesCount; + } + + public void setStatusesCount(Integer statusesCount) { + this.statusesCount = statusesCount; + } + + public Integer getFollowersCount() { + return followersCount; + } + + public void setFollowersCount(Integer followersCount) { + this.followersCount = followersCount; + } + + public String getHeader() { + return header; + } + + public void setHeader(String header) { + this.header = header; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean getLocked() { + return locked; + } + + public void setLocked(Boolean locked) { + this.locked = locked; + } + + public String getAvatarStatic() { + return avatarStatic; + } + + public void setAvatarStatic(String avatarStatic) { + this.avatarStatic = avatarStatic; + } + + public List getFields() { + return fields; + } + + public void setFields(List fields) { + this.fields = fields; + } + + public String getAcct() { + return acct; + } + + public void setAcct(String acct) { + this.acct = acct; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + +} diff --git a/client/src/main/java/ctbrec/ui/news/NewsTab.java b/client/src/main/java/ctbrec/ui/news/NewsTab.java new file mode 100644 index 00000000..dbb67752 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/news/NewsTab.java @@ -0,0 +1,85 @@ +package ctbrec.ui.news; + + +import java.io.IOException; + +import org.json.JSONObject; + +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + +import ctbrec.io.HttpException; +import ctbrec.ui.CamrecApplication; +import ctbrec.ui.TabSelectionListener; +import ctbrec.ui.controls.Dialogs; +import javafx.application.Platform; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.Tab; +import javafx.scene.layout.VBox; +import okhttp3.Request; +import okhttp3.Response; + +public class NewsTab extends Tab implements TabSelectionListener { + private static final String ACCESS_TOKEN = "a2804d73a89951a22e0f8483a6fcec8943afd88b7ba17c459c095aa9e6f94fd0"; + private static final String URL = "https://mastodon.cloud/api/v1/timelines/home"; + + private VBox layout = new VBox(); + + public NewsTab() { + setText("News"); + layout.setMaxWidth(800); + layout.setAlignment(Pos.CENTER); + setContent(new ScrollPane(layout)); + } + + @Override + public void selected() { + new Thread(() -> { + try { + Request request = new Request.Builder() + .url(URL) + .header("Authorization", "Bearer " + ACCESS_TOKEN) + .header("User-Agent", "ctbrec " + CamrecApplication.getVersion().toString()) + .build(); + try(Response response = CamrecApplication.httpClient.execute(request)) { + if(response.isSuccessful()) { + String body = response.body().string(); + if(body.startsWith("[")) { + Moshi moshi = new Moshi.Builder().build(); + JsonAdapter statusListAdapter = moshi.adapter(Status[].class); + Status[] statusArray = statusListAdapter.fromJson(body); + Platform.runLater(() -> { + layout.getChildren().clear(); + for (Status status : statusArray) { + StatusPane stp = new StatusPane(status); + layout.getChildren().add(stp); + VBox.setMargin(stp, new Insets(10)); + } + }); + } else if(body.startsWith("{")) { + JSONObject json = new JSONObject(response.body().string()); + if(json.has("error")) { + throw new IOException("Request not successful: " + json.getString("error")); + } else { + throw new IOException("Unexpected response: " + body); + } + } else { + throw new IOException("Unexpected response: " + body); + } + } else { + throw new HttpException(response.code(), response.message()); + } + } + } catch (IOException e) { + Dialogs.showError("News", "Couldn't load news from mastodon", e); + } + }).start(); + } + + @Override + public void deselected() { + // TODO Auto-generated method stub + } +} diff --git a/client/src/main/java/ctbrec/ui/news/Status.java b/client/src/main/java/ctbrec/ui/news/Status.java new file mode 100644 index 00000000..e800ceac --- /dev/null +++ b/client/src/main/java/ctbrec/ui/news/Status.java @@ -0,0 +1,279 @@ +package ctbrec.ui.news; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +import com.squareup.moshi.Json; + +public class Status { + + @Json(name = "pinned") + private Boolean pinned; + @Json(name = "in_reply_to_id") + private Object inReplyToId; + @Json(name = "favourites_count") + private Integer favouritesCount; + @Json(name = "media_attachments") + private List mediaAttachments = null; + @Json(name = "created_at") + private String createdAt; + @Json(name = "replies_count") + private Integer repliesCount; + @Json(name = "language") + private String language; + @Json(name = "in_reply_to_account_id") + private Object inReplyToAccountId; + @Json(name = "content") + private String content; + @Json(name = "reblog") + private Object reblog; + @Json(name = "spoiler_text") + private String spoilerText; + @Json(name = "id") + private String id; + @Json(name = "reblogged") + private Boolean reblogged; + @Json(name = "muted") + private Boolean muted; + @Json(name = "emojis") + private List emojis = null; + @Json(name = "reblogs_count") + private Integer reblogsCount; + @Json(name = "visibility") + private String visibility; + @Json(name = "sensitive") + private Boolean sensitive; + @Json(name = "uri") + private String uri; + @Json(name = "url") + private String url; + @Json(name = "tags") + private List tags = null; + @Json(name = "application") + private Object application; + @Json(name = "favourited") + private Boolean favourited; + @Json(name = "mentions") + private List mentions = null; + @Json(name = "account") + private Account account; + @Json(name = "card") + private Object card; + + public Boolean getPinned() { + return pinned; + } + + public void setPinned(Boolean pinned) { + this.pinned = pinned; + } + + public Object getInReplyToId() { + return inReplyToId; + } + + public void setInReplyToId(Object inReplyToId) { + this.inReplyToId = inReplyToId; + } + + public Integer getFavouritesCount() { + return favouritesCount; + } + + public void setFavouritesCount(Integer favouritesCount) { + this.favouritesCount = favouritesCount; + } + + public List getMediaAttachments() { + return mediaAttachments; + } + + public void setMediaAttachments(List mediaAttachments) { + this.mediaAttachments = mediaAttachments; + } + + public String getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(String createdAt) { + this.createdAt = createdAt; + } + + public Integer getRepliesCount() { + return repliesCount; + } + + public void setRepliesCount(Integer repliesCount) { + this.repliesCount = repliesCount; + } + + public String getLanguage() { + return language; + } + + public void setLanguage(String language) { + this.language = language; + } + + public Object getInReplyToAccountId() { + return inReplyToAccountId; + } + + public void setInReplyToAccountId(Object inReplyToAccountId) { + this.inReplyToAccountId = inReplyToAccountId; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Object getReblog() { + return reblog; + } + + public void setReblog(Object reblog) { + this.reblog = reblog; + } + + public String getSpoilerText() { + return spoilerText; + } + + public void setSpoilerText(String spoilerText) { + this.spoilerText = spoilerText; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public Boolean getReblogged() { + return reblogged; + } + + public void setReblogged(Boolean reblogged) { + this.reblogged = reblogged; + } + + public Boolean getMuted() { + return muted; + } + + public void setMuted(Boolean muted) { + this.muted = muted; + } + + public List getEmojis() { + return emojis; + } + + public void setEmojis(List emojis) { + this.emojis = emojis; + } + + public Integer getReblogsCount() { + return reblogsCount; + } + + public void setReblogsCount(Integer reblogsCount) { + this.reblogsCount = reblogsCount; + } + + public String getVisibility() { + return visibility; + } + + public void setVisibility(String visibility) { + this.visibility = visibility; + } + + public Boolean getSensitive() { + return sensitive; + } + + public void setSensitive(Boolean sensitive) { + this.sensitive = sensitive; + } + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public List getTags() { + return tags; + } + + public void setTags(List tags) { + this.tags = tags; + } + + public Object getApplication() { + return application; + } + + public void setApplication(Object application) { + this.application = application; + } + + public Boolean getFavourited() { + return favourited; + } + + public void setFavourited(Boolean favourited) { + this.favourited = favourited; + } + + public List getMentions() { + return mentions; + } + + public void setMentions(List mentions) { + this.mentions = mentions; + } + + public Account getAccount() { + return account; + } + + public void setAccount(Account account) { + this.account = account; + } + + public Object getCard() { + return card; + } + + public void setCard(Object card) { + this.card = card; + } + + public ZonedDateTime getCreationTime() { + String timestamp = getCreatedAt(); + Instant instant = Instant.parse(timestamp); + ZonedDateTime time = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault()); + return time; + } +} diff --git a/client/src/main/java/ctbrec/ui/news/StatusPane.java b/client/src/main/java/ctbrec/ui/news/StatusPane.java new file mode 100644 index 00000000..2ffdd107 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/news/StatusPane.java @@ -0,0 +1,39 @@ +package ctbrec.ui.news; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; + +import ctbrec.io.HtmlParser; +import ctbrec.ui.DesktopIntegration; +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.StackPane; + +public class StatusPane extends StackPane { + + public StatusPane(Status status) { + String text = HtmlParser.getText("
" + status.getContent() + "
", "div"); + TextArea content = new TextArea(text); + content.setMaxHeight(130); + content.setEditable(false); + content.setWrapText(true); + getChildren().add(content); + + ZonedDateTime createdAt = status.getCreationTime(); + String creationTime = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT, FormatStyle.SHORT).format(createdAt); + Label time = new Label(creationTime); + getChildren().add(time); + StackPane.setMargin(time, new Insets(5)); + StackPane.setAlignment(time, Pos.BOTTOM_LEFT); + + Button reply = new Button("Reply"); + reply.setOnAction((evt) -> DesktopIntegration.open(status.getUrl())); + getChildren().add(reply); + StackPane.setMargin(reply, new Insets(5)); + StackPane.setAlignment(reply, Pos.BOTTOM_RIGHT); + } +}