add fedipage datamodel
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
package de.jalin.fedipage;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
public class FediMedium implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 2056053678422715807L;
|
||||||
|
|
||||||
|
public static final String IMAGE = "image";
|
||||||
|
public static final String VIDEO = "video";
|
||||||
|
public static final String AUDIO = "audio";
|
||||||
|
public static final String CARD = "card";
|
||||||
|
|
||||||
|
private String mediumType;
|
||||||
|
private String description;
|
||||||
|
private String mediumUrl;
|
||||||
|
private String mediumImage;
|
||||||
|
|
||||||
|
public String getMediumType() {
|
||||||
|
return mediumType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediumType(String mediumType) {
|
||||||
|
this.mediumType = mediumType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMediumUrl() {
|
||||||
|
return mediumUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediumUrl(String mediumUrl) {
|
||||||
|
this.mediumUrl = mediumUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMediumImage() {
|
||||||
|
return mediumImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMediumImage(String mediumImage) {
|
||||||
|
this.mediumImage = mediumImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package de.jalin.fedipage;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FediPage implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 621351254124767964L;
|
||||||
|
|
||||||
|
private final String profileName;
|
||||||
|
private final String instance;
|
||||||
|
private final List<FediPost> timeline;
|
||||||
|
|
||||||
|
private String displayName;
|
||||||
|
private String description;
|
||||||
|
private String url;
|
||||||
|
private String avatarUrl;
|
||||||
|
private String headerUrl;
|
||||||
|
private int followers;
|
||||||
|
private int following;
|
||||||
|
private int posts;
|
||||||
|
|
||||||
|
public FediPage(final String handle, final String domain) {
|
||||||
|
this.profileName = handle;
|
||||||
|
this.instance = domain;
|
||||||
|
this.setDisplayName("Nobody");
|
||||||
|
this.setDescription("no text");
|
||||||
|
this.setUrl("undef");
|
||||||
|
this.setAvatarUrl("undef");
|
||||||
|
this.setHeaderUrl("undef");
|
||||||
|
this.setFollowers(0);
|
||||||
|
this.setFollowing(0);
|
||||||
|
this.setPosts(0);
|
||||||
|
this.timeline = new ArrayList<FediPost>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return "@" + getProfileName() + "@" + getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProfileName() {
|
||||||
|
return profileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayName(String displayName) {
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFollowers() {
|
||||||
|
return followers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFollowers(int followers) {
|
||||||
|
this.followers = followers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFollowing() {
|
||||||
|
return following;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFollowing(int following) {
|
||||||
|
this.following = following;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPosts() {
|
||||||
|
return posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosts(int posts) {
|
||||||
|
this.posts = posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUrl(String profileUrl) {
|
||||||
|
this.url = profileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAvatarUrl() {
|
||||||
|
return avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAvatarUrl(String avatarUrl) {
|
||||||
|
this.avatarUrl = avatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHeaderUrl() {
|
||||||
|
return headerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeaderUrl(String headerUrl) {
|
||||||
|
this.headerUrl = headerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void append(final FediPost post) {
|
||||||
|
timeline.add(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FediPost> getTimeline() {
|
||||||
|
return Collections.unmodifiableList(timeline);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package de.jalin.fedipage;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class FediPost implements Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 5177108486862427792L;
|
||||||
|
|
||||||
|
private boolean pinned;
|
||||||
|
private boolean boosted;
|
||||||
|
private String createdAt;
|
||||||
|
private String authorHandle;
|
||||||
|
private String authorDisplayName;
|
||||||
|
private String authorAvatarUrl;
|
||||||
|
private String authorUrl;
|
||||||
|
private String postUrl;
|
||||||
|
private String content;
|
||||||
|
private int boosts;
|
||||||
|
private int likes;
|
||||||
|
private int comments;
|
||||||
|
private final List<FediMedium> media;
|
||||||
|
|
||||||
|
public FediPost() {
|
||||||
|
setPinned(false);
|
||||||
|
setBoosted(false);
|
||||||
|
setCreatedAt("unknown");
|
||||||
|
setAuthorHandle("nobody");
|
||||||
|
setAuthorDisplayName("Nobody");
|
||||||
|
setAuthorAvatarUrl("undef");
|
||||||
|
setAuthorUrl("undef");
|
||||||
|
setPostUrl("undef");
|
||||||
|
setContent("<p>empty</p>");
|
||||||
|
setBoosts(0);
|
||||||
|
setLikes(0);
|
||||||
|
setComments(0);
|
||||||
|
this.media = new ArrayList<FediMedium>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPinned() {
|
||||||
|
return pinned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPinned(boolean pinned) {
|
||||||
|
this.pinned = pinned;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBoosted() {
|
||||||
|
return boosted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoosted(boolean boosted) {
|
||||||
|
this.boosted = boosted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorHandle() {
|
||||||
|
return authorHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorHandle(String authorHandle) {
|
||||||
|
this.authorHandle = authorHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorDisplayName() {
|
||||||
|
return authorDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorDisplayName(String authorDisplayName) {
|
||||||
|
this.authorDisplayName = authorDisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorAvatarUrl() {
|
||||||
|
return authorAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorAvatarUrl(String authorAvatarUrl) {
|
||||||
|
this.authorAvatarUrl = authorAvatarUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPostUrl() {
|
||||||
|
return postUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPostUrl(String postUrl) {
|
||||||
|
this.postUrl = postUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContent(String content) {
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBoosts() {
|
||||||
|
return boosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBoosts(int boosts) {
|
||||||
|
this.boosts = boosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLikes() {
|
||||||
|
return likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLikes(int likes) {
|
||||||
|
this.likes = likes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getComments() {
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setComments(int comments) {
|
||||||
|
this.comments = comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFediMedium(final FediMedium medium) {
|
||||||
|
this.media.add(medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<FediMedium> getMedia() {
|
||||||
|
return Collections.unmodifiableList(this.media);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthorUrl(final String authorUrl) {
|
||||||
|
this.authorUrl = authorUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthorUrl() {
|
||||||
|
return this.authorUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,10 +43,8 @@ public class FediServlet extends HttpServlet {
|
|||||||
try {
|
try {
|
||||||
final JSONLoader loader = new JSONLoader(instance, account);
|
final JSONLoader loader = new JSONLoader(instance, account);
|
||||||
final VelocityContext context = new VelocityContext();
|
final VelocityContext context = new VelocityContext();
|
||||||
context.put("profile", loader.loadProfile());
|
|
||||||
context.put("pinned", loader.loadPinned());
|
|
||||||
context.put("timeline", loader.loadTimeline());
|
|
||||||
context.put("dateformatter", new PostDateFormatter());
|
context.put("dateformatter", new PostDateFormatter());
|
||||||
|
context.put("page", loader.loadPage());
|
||||||
|
|
||||||
final Template template = velocityEngine.getTemplate("/templates/profil.vm");
|
final Template template = velocityEngine.getTemplate("/templates/profil.vm");
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
response.setContentType("text/html;charset=UTF-8");
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import java.net.HttpURLConnection;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -28,7 +27,74 @@ public class JSONLoader implements Serializable {
|
|||||||
this.id = -1L;
|
this.id = -1L;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONObject loadProfile() throws IOException, URISyntaxException {
|
public FediPage loadPage() throws IOException, URISyntaxException {
|
||||||
|
final FediPage fediPage = new FediPage(account, instance);
|
||||||
|
loadFediProfile(fediPage);
|
||||||
|
loadStatuses(fediPage, 0, true);
|
||||||
|
loadStatuses(fediPage, 20, false);
|
||||||
|
return fediPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadStatuses(final FediPage fedipage, final int limit, final boolean pinned) throws IOException, URISyntaxException {
|
||||||
|
String filter = "limit=" + limit;
|
||||||
|
if (pinned) {
|
||||||
|
filter = "pinned=true";
|
||||||
|
}
|
||||||
|
final JSONArray timelinePosts = loadJsonStatuses(filter);
|
||||||
|
for (int idx=0; idx<timelinePosts.length(); idx++) {
|
||||||
|
final JSONObject jsonPost = timelinePosts.getJSONObject(idx);
|
||||||
|
final FediPost post = new FediPost();
|
||||||
|
JSONObject jsonAccount = null;
|
||||||
|
JSONObject jsonStatus = null;
|
||||||
|
if (jsonPost.isNull("reblog")) {
|
||||||
|
post.setBoosted(false);
|
||||||
|
jsonStatus = jsonPost;
|
||||||
|
} else {
|
||||||
|
post.setBoosted(true);
|
||||||
|
jsonStatus = jsonPost.getJSONObject("reblog");
|
||||||
|
}
|
||||||
|
jsonAccount = jsonStatus.getJSONObject("account");
|
||||||
|
post.setAuthorAvatarUrl(jsonAccount.getString("avatar"));
|
||||||
|
post.setAuthorDisplayName(jsonAccount.getString("display_name"));
|
||||||
|
post.setAuthorHandle(jsonAccount.getString("acct"));
|
||||||
|
post.setAuthorUrl(jsonAccount.getString("url"));
|
||||||
|
post.setBoosts(jsonStatus.getInt("reblogs_count"));
|
||||||
|
post.setComments(jsonStatus.getInt("replies_count"));
|
||||||
|
post.setContent(jsonStatus.getString("content"));
|
||||||
|
post.setCreatedAt(jsonStatus.getString("created_at"));
|
||||||
|
post.setLikes(jsonStatus.getInt("favourites_count"));
|
||||||
|
post.setPinned(pinned);
|
||||||
|
post.setPostUrl(jsonStatus.getString("url"));
|
||||||
|
if (jsonStatus.getJSONArray("media_attachments").length() == 0) {
|
||||||
|
final JSONObject jsonCard = jsonStatus.getJSONObject("card");
|
||||||
|
if (jsonCard != null && jsonCard.getString("image") != null) {
|
||||||
|
final FediMedium medium = new FediMedium();
|
||||||
|
medium.setMediumType(FediMedium.CARD);
|
||||||
|
medium.setDescription(jsonCard.getString("description"));
|
||||||
|
medium.setMediumUrl(jsonCard.getString("url"));
|
||||||
|
medium.setMediumImage(jsonCard.getString("image"));
|
||||||
|
post.addFediMedium(medium);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
fedipage.append(post);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFediProfile(final FediPage fediPage) throws IOException, URISyntaxException {
|
||||||
|
final JSONObject jsonProfile = loadJsonProfile();
|
||||||
|
fediPage.setDescription(jsonProfile.getString("note"));
|
||||||
|
fediPage.setDisplayName(jsonProfile.getString("display_name"));
|
||||||
|
fediPage.setFollowers(jsonProfile.getInt("followers_count"));
|
||||||
|
fediPage.setFollowing(jsonProfile.getInt("following_count"));
|
||||||
|
fediPage.setPosts(jsonProfile.getInt("statuses_count"));
|
||||||
|
fediPage.setUrl(jsonProfile.getString("url"));
|
||||||
|
fediPage.setAvatarUrl(jsonProfile.getString("avatar"));
|
||||||
|
fediPage.setHeaderUrl(jsonProfile.getString("header"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject loadJsonProfile() throws IOException, URISyntaxException {
|
||||||
final URI uri = new URI("https://" + instance + "/api/v1/accounts/lookup?acct=" + account);
|
final URI uri = new URI("https://" + instance + "/api/v1/accounts/lookup?acct=" + account);
|
||||||
final URL url = uri.toURL();
|
final URL url = uri.toURL();
|
||||||
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
@@ -44,31 +110,9 @@ public class JSONLoader implements Serializable {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public JSONArray loadPinned() throws IOException, URISyntaxException {
|
private JSONArray loadJsonStatuses(final String filter) throws IOException, URISyntaxException {
|
||||||
final JSONArray pinnedStatusesArray = loadStatuses("pinned=true");
|
if (id < 0L) {
|
||||||
// final ArrayList<Status> pinnedStatuses = new ArrayList<>();
|
loadJsonProfile();
|
||||||
return pinnedStatusesArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONArray loadTimeline() throws IOException, URISyntaxException {
|
|
||||||
final JSONArray lastStatusesArray = loadStatuses("limit=20");
|
|
||||||
// for (int idx=0; idx< lastStatusesArray.length(); idx++) {
|
|
||||||
// final Object object = lastStatusesArray.get(idx);
|
|
||||||
// JSONObject json = (JSONObject) object;
|
|
||||||
// final Status status = new Status();
|
|
||||||
// JSONObject account = json.getJSONObject("account");
|
|
||||||
// if (json.get("reblog") != null) {
|
|
||||||
// status.setBoostedBy(account.getString("display_name"));
|
|
||||||
// json = json.getJSONObject("reblog");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// final ArrayList<Status> latestStatuses = new ArrayList<>();
|
|
||||||
return lastStatusesArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JSONArray loadStatuses(final String filter) throws IOException, URISyntaxException {
|
|
||||||
if (id < 0) {
|
|
||||||
loadProfile();
|
|
||||||
}
|
}
|
||||||
final URI uri = new URI("https://" + instance + "/api/v1/accounts/" + id + "/statuses?" + filter);
|
final URI uri = new URI("https://" + instance + "/api/v1/accounts/" + id + "/statuses?" + filter);
|
||||||
final URL url = uri.toURL();
|
final URL url = uri.toURL();
|
||||||
@@ -82,18 +126,4 @@ public class JSONLoader implements Serializable {
|
|||||||
}
|
}
|
||||||
return new JSONArray(content.toString());
|
return new JSONArray(content.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
// final JSONLoader loader = new JSONLoader(args[0], args[1]);
|
|
||||||
final JSONLoader loader = new JSONLoader("krefeld.life", "darestiet");
|
|
||||||
try {
|
|
||||||
final JSONArray pinned = loader.loadTimeline();
|
|
||||||
for (Object obj : pinned) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final Map<String, ?> json = (Map<String, ?>) obj;
|
|
||||||
System.out.println(json.toString());
|
|
||||||
}
|
|
||||||
} catch (IOException | URISyntaxException e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>$profile.display_name - $profile.username</title>
|
<title>$page.displayName - $page.username</title>
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -267,179 +267,58 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img id="banner" class="banner" alt="Banner" src="$profile.header">
|
<img id="banner" class="banner" alt="Banner" src="$page.headerUrl">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="profile-section">
|
<div class="profile-section">
|
||||||
<img id="avatar" class="profile-avatar" alt="Avatar" src="$profile.avatar">
|
<img id="avatar" class="profile-avatar" alt="Avatar" src="$page.avatarUrl">
|
||||||
<div class="profile-info">
|
<div class="profile-info">
|
||||||
<div id="displayName" class="display-name">$profile.display_name</div>
|
<div id="displayName" class="display-name">$page.displayName</div>
|
||||||
<div id="username" class="username"><a href="$profile.url">@$profile.username</a></div>
|
<div id="username" class="username"><a href="$page.url">$page.username</a></div>
|
||||||
<div id="bio" class="bio">$profile.note</div>
|
<div id="bio" class="bio">$page.description</div>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<span id="followingCount">$profile.following_count Folgt</span>
|
<span id="followingCount">$page.following Folgt</span>
|
||||||
<span id="followersCount">$profile.followers_count Follower</span>
|
<span id="followersCount">$page.followers Follower</span>
|
||||||
<span id="statusesCount">$profile.statuses_count Beiträge</span>
|
<span id="statusesCount">$page.posts Beiträge</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--
|
|
||||||
<div class="controls">
|
|
||||||
<div class="checkbox-group">
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="includeBoosts">
|
|
||||||
Geteilte Inhalte anzeigen
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
<input type="checkbox" id="includeReplies">
|
|
||||||
Antworten anzeigen
|
|
||||||
</label>
|
|
||||||
<button class="refresh-btn" onclick="loadPosts()">Aktualisieren</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
<div id="loading" class="loading">Lade Daten...</div>
|
|
||||||
<div id="error" class="error" style="display: none;"></div>
|
|
||||||
-->
|
|
||||||
<div id="posts" class="posts">
|
<div id="posts" class="posts">
|
||||||
#foreach( $post in $pinned )
|
#foreach( $post in $page.timeline )
|
||||||
#set ( $image_class = 'single' )
|
|
||||||
#set ( $post_date = $dateformatter.format( $post.created_at ) )
|
|
||||||
<div class="post">
|
<div class="post">
|
||||||
|
#set ( $image_class = 'single' )
|
||||||
|
#set ( $post_date = $dateformatter.format( $post.createdAt ) )
|
||||||
|
#if ( $post.pinned )
|
||||||
<div class="boost-indicator">📌 Angeheftet</div>
|
<div class="boost-indicator">📌 Angeheftet</div>
|
||||||
|
#end
|
||||||
|
#if ( $post.boosted )
|
||||||
|
<div class="boost-indicator">🔄 $page.displayName hat geteilt</div>
|
||||||
|
#end
|
||||||
<div class="post-header">
|
<div class="post-header">
|
||||||
<img class="post-avatar" src="$post.account.avatar" alt="Avatar">
|
<img class="post-avatar" src="$post.authorAvatarUrl" alt="Avatar">
|
||||||
<div class="post-user">
|
<div class="post-user">
|
||||||
<div class="post-display-name">$post.account.display_name</div>
|
<div class="post-display-name">$post.authorDisplayName</div>
|
||||||
<div class="post-username"><a href="$post.account.url">@$post.account.acct</a></div>
|
<div class="post-username"><a href="$post.authorUrl">$post.authorHandle</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-date"><a href="$post.url" target="_blank">$post_date</a></div>
|
<div class="post-date"><a href="$post.postUrl" target="_blank">$post_date</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="post-content">$post.content</div>
|
<div class="post-content">$post.content</div>
|
||||||
#if( $post.media_attachments.length > 0 )
|
|
||||||
#if( $post.media_attachments.length > 1 )
|
|
||||||
#set ( $image_class = 'double' )
|
|
||||||
#end
|
|
||||||
#if( $post.media_attachments.length > 2 )
|
|
||||||
#set ( $image_class = 'multiple' )
|
|
||||||
#end
|
|
||||||
<div class="post-media">
|
<div class="post-media">
|
||||||
<div class="media-grid $image_class">
|
<div class="media-grid $image_class">
|
||||||
#foreach( $media_item in $post.media_attachments )
|
#foreach( $medium in $post.media )
|
||||||
<div class="media-item">
|
<div class="media-item">
|
||||||
<img src="$media_item.preview_url" alt="$media_item.description" onclick="window.open('$media_item.url', '_blank')">
|
<img src="$medium.mediumImage" alt="$medium.description" onclick="window.open('$medium.mediumUrl', '_blank')">
|
||||||
</div>
|
</div>
|
||||||
#end
|
#end
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
#else
|
|
||||||
#if( $post.card )
|
|
||||||
<div class="post-media">
|
|
||||||
<div class="media-grid single">
|
|
||||||
<div class="media-item">
|
|
||||||
<img src="$post.card.image" alt="$post.card.description" onclick="window.open('$post.card.url', '_blank')">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
<div class="post-stats">
|
<div class="post-stats">
|
||||||
<span>🔄 $post.reblogs_count</span>
|
<span>🔄 $post.boosts</span>
|
||||||
<span>⭐ $post.favourites_count</span>
|
<span>⭐ $post.likes</span>
|
||||||
<span>💬 $post.replies_count</span>
|
<span>💬 $post.comments</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
#end
|
|
||||||
#foreach( $post in $timeline )
|
|
||||||
#set ( $image_class = 'single' )
|
|
||||||
<div class="post">
|
|
||||||
#if( $post.reblog.account )
|
|
||||||
#set ( $post_date = $dateformatter.format( $post.reblog.created_at ) )
|
|
||||||
#if( $post.reblog.media_attachments.length > 1 )
|
|
||||||
#set ( $image_class = 'double' )
|
|
||||||
#end
|
|
||||||
#if( $post.reblog.media_attachments.length > 2 )
|
|
||||||
#set ( $image_class = 'multiple' )
|
|
||||||
#end
|
|
||||||
<div class="boost-indicator">🔄 $post.account.display_name hat geteilt</div>
|
|
||||||
<div class="post-header">
|
|
||||||
<img class="post-avatar" src="$post.reblog.account.avatar" alt="Avatar">
|
|
||||||
<div class="post-user">
|
|
||||||
<div class="post-display-name">$post.reblog.account.display_name</div>
|
|
||||||
<div class="post-username"><a href="$post.reblog.account.url">@$post.reblog.account.acct</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="post-date"><a href="$post.reblog.url" target="_blank">$post_date</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="post-content">$post.reblog.content</div>
|
|
||||||
#if( $post.reblog.media_attachments.length > 0 )
|
|
||||||
<div class="post-media">
|
|
||||||
<div class="media-grid $image_class">
|
|
||||||
#foreach( $media_item in $post.reblog.media_attachments )
|
|
||||||
<div class="media-item">
|
|
||||||
<img src="$media_item.preview_url" alt="$media_item.description" onclick="window.open('$media_item.url', '_blank')">
|
|
||||||
</div>
|
|
||||||
#end
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
#else
|
|
||||||
#if( $post.reblog.card )
|
|
||||||
<div class="post-media">
|
|
||||||
<div class="media-grid single">
|
|
||||||
<div class="media-item">
|
|
||||||
<img src="$post.reblog.card.image" alt="$post.reblog.card.description" onclick="window.open('$post.reblog.card.url', '_blank')">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
#else
|
|
||||||
#set ( $post_date = $dateformatter.format( $post.created_at ) )
|
|
||||||
#if( $post.media_attachments.length > 1 )
|
|
||||||
#set ( $image_class = 'double' )
|
|
||||||
#end
|
|
||||||
#if( $post.media_attachments.length > 2 )
|
|
||||||
#set ( $image_class = 'multiple' )
|
|
||||||
#end
|
|
||||||
<div class="post-header">
|
|
||||||
<img class="post-avatar" src="$post.account.avatar" alt="Avatar">
|
|
||||||
<div class="post-user">
|
|
||||||
<div class="post-display-name">$post.account.display_name</div>
|
|
||||||
<div class="post-username"><a href="$post.account.url">@$post.account.acct</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="post-date"><a href="$post.url" target="_blank">$post_date</a></div>
|
|
||||||
</div>
|
|
||||||
<div class="post-content">$post.content</div>
|
|
||||||
#if( $post.media_attachments.length > 0 )
|
|
||||||
<div class="post-media">
|
|
||||||
<div class="media-grid $image_class">
|
|
||||||
#foreach( $media_item in $post.media_attachments )
|
|
||||||
<div class="media-item">
|
|
||||||
<img src="$media_item.preview_url" alt="$media_item.description" onclick="window.open('$media_item.url', '_blank')">
|
|
||||||
</div>
|
|
||||||
#end
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
#else
|
|
||||||
#if( $post.card )
|
|
||||||
<div class="post-media">
|
|
||||||
<div class="media-grid single">
|
|
||||||
<div class="media-item">
|
|
||||||
<img src="$post.card.image" alt="$post.card.description" onclick="window.open('$post.card.url', '_blank')">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
<div class="post-stats">
|
|
||||||
<span>🔄 $post.reblogs_count</span>
|
|
||||||
<span>⭐ $post.favourites_count</span>
|
|
||||||
<span>💬 $post.replies_count</span>
|
|
||||||
</div>
|
|
||||||
#end
|
|
||||||
</div>
|
|
||||||
#end
|
#end
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>$page.fediProfile.displayName - $page.fediProfile.username</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #ffffff;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #282c37;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
position: relative;
|
||||||
|
height: 200px;
|
||||||
|
background: linear-gradient(45deg, #6364ff, #563acc);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-section {
|
||||||
|
position: relative;
|
||||||
|
padding: 0 20px 20px;
|
||||||
|
background: #282c37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-avatar {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid #282c37;
|
||||||
|
margin-top: -40px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-info {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
color: #9baec8;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username a {
|
||||||
|
color: #9baec8;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #9baec8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
padding: 20px;
|
||||||
|
background: #313543;
|
||||||
|
border-bottom: 1px solid #393f4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #9baec8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group input[type="checkbox"] {
|
||||||
|
accent-color: #6364ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posts {
|
||||||
|
background: #282c37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
border-bottom: 1px solid #393f4f;
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-avatar {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-user {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-display-name {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-username {
|
||||||
|
color: #9baec8;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-username a {
|
||||||
|
color: #9baec8;
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-date {
|
||||||
|
color: #9baec8;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-date a {
|
||||||
|
color: #9baec8;
|
||||||
|
font-size: 13px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content p {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content a {
|
||||||
|
color: #6364ff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hashtag {
|
||||||
|
color: #00d4aa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mention {
|
||||||
|
color: #6364ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-media {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grid.single {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grid.double {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-grid.multiple {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-item {
|
||||||
|
position: relative;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 360px;
|
||||||
|
object-fit: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 15px;
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #9baec8;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boost-indicator {
|
||||||
|
color: #00d4aa;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #9baec8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px;
|
||||||
|
color: #ff5050;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn {
|
||||||
|
background: #6364ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.refresh-btn:hover {
|
||||||
|
background: #5a5bef;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<img id="banner" class="banner" alt="Banner" src="$page.fediProfile.headerUrl">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-section">
|
||||||
|
<img id="avatar" class="profile-avatar" alt="Avatar" src="$page.fediProfile.avatarUrl">
|
||||||
|
<div class="profile-info">
|
||||||
|
<div id="displayName" class="display-name">$page.fediProfile.displayName</div>
|
||||||
|
<div id="username" class="username"><a href="$profile.url">$page.fediProfile.username</a></div>
|
||||||
|
<div id="bio" class="bio">$page.fediProfile.description</div>
|
||||||
|
<div class="stats">
|
||||||
|
<span id="followingCount">$page.fediProfile.following Folgt</span>
|
||||||
|
<span id="followersCount">$page.fediProfile.followers Follower</span>
|
||||||
|
<span id="statusesCount">$page.fediProfile.posts Beiträge</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div class="controls">
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="includeBoosts">
|
||||||
|
Geteilte Inhalte anzeigen
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="includeReplies">
|
||||||
|
Antworten anzeigen
|
||||||
|
</label>
|
||||||
|
<button class="refresh-btn" onclick="loadPosts()">Aktualisieren</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<div id="loading" class="loading">Lade Daten...</div>
|
||||||
|
<div id="error" class="error" style="display: none;"></div>
|
||||||
|
-->
|
||||||
|
<div id="posts" class="posts">
|
||||||
|
#foreach( $post in $page.fediTimeline )
|
||||||
|
#set ( $image_class = 'single' )
|
||||||
|
#set ( $post_date = $dateformatter.format( $post.createdAt ) )
|
||||||
|
<div class="post">
|
||||||
|
#if ( $post.pinned )
|
||||||
|
<div class="boost-indicator">📌 Angeheftet</div>
|
||||||
|
#end
|
||||||
|
#if ( $post.boosted )
|
||||||
|
<div class="boost-indicator">🔄 $page.fediProfile.displayName hat geteilt</div>
|
||||||
|
#end
|
||||||
|
<div class="post-header">
|
||||||
|
<img class="post-avatar" src="$post.authorAvatarUrl" alt="Avatar">
|
||||||
|
<div class="post-user">
|
||||||
|
<div class="post-display-name">$post.authorDisplayName</div>
|
||||||
|
<div class="post-username"><a href="#">$post.authorHandle</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="post-date"><a href="$post.postUrl" target="_blank">$post_date</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="post-content">$post.content</div>
|
||||||
|
#if( $post.media_attachments.length > 0 )
|
||||||
|
#if( $post.media_attachments.length > 1 )
|
||||||
|
#set ( $image_class = 'double' )
|
||||||
|
#end
|
||||||
|
#if( $post.media_attachments.length > 2 )
|
||||||
|
#set ( $image_class = 'multiple' )
|
||||||
|
#end
|
||||||
|
<div class="post-media">
|
||||||
|
<div class="media-grid $image_class">
|
||||||
|
#foreach( $media_item in $post.media_attachments )
|
||||||
|
<div class="media-item">
|
||||||
|
<img src="$media_item.preview_url" alt="$media_item.description" onclick="window.open('$media_item.url', '_blank')">
|
||||||
|
</div>
|
||||||
|
#end
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
#else
|
||||||
|
#if( $post.card )
|
||||||
|
<div class="post-media">
|
||||||
|
<div class="media-grid single">
|
||||||
|
<div class="media-item">
|
||||||
|
<img src="$post.card.image" alt="$post.card.description" onclick="window.open('$post.card.url', '_blank')">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
#end
|
||||||
|
#end
|
||||||
|
<div class="post-stats">
|
||||||
|
<span>🔄 $post.boosts</span>
|
||||||
|
<span>⭐ $post.likes</span>
|
||||||
|
<span>💬 $post.comments</span>
|
||||||
|
js </div>
|
||||||
|
</div>
|
||||||
|
#end
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user