Better Post Formatting

This commit is contained in:
Tim Zöller 2026-04-06 21:31:53 +02:00
parent f92db29b0a
commit 3cba6de9f1
2 changed files with 46 additions and 43 deletions

View file

@ -291,52 +291,64 @@ public class ActivityPubController {
} }
/** /**
* Format activity content for ActivityPub. * Format activity content as HTML for ActivityPub.
* Uses plain text with Unicode symbols for maximum compatibility. * Mastodon and most Fediverse software expect HTML in the content field.
*/ */
private String formatActivityContent(Activity activity) { private String formatActivityContent(Activity activity) {
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();
// Title (if present) // Title
if (activity.getTitle() != null && !activity.getTitle().isEmpty()) { if (activity.getTitle() != null && !activity.getTitle().isEmpty()) {
content.append(activity.getTitle()).append("\n\n"); content.append("<p><strong>").append(escapeHtml(activity.getTitle())).append("</strong></p>");
} }
// Description (if present) // Description
if (activity.getDescription() != null && !activity.getDescription().isEmpty()) { if (activity.getDescription() != null && !activity.getDescription().isEmpty()) {
content.append(activity.getDescription()).append("\n\n"); content.append("<p>").append(escapeHtml(activity.getDescription())).append("</p>");
} }
// Activity type with emoji // Activity type with emoji
String activityEmoji = getActivityEmoji(activity.getActivityType()); String activityEmoji = getActivityEmoji(activity.getActivityType());
String formattedType = ActivityFormatter.formatActivityType(activity.getActivityType()); String formattedType = ActivityFormatter.formatActivityType(activity.getActivityType());
content.append(activityEmoji).append(" ").append(formattedType); content.append("<p>").append(activityEmoji).append(" ").append(escapeHtml(formattedType)).append("</p>");
// Metrics // Metrics
StringBuilder metrics = new StringBuilder();
if (activity.getTotalDistance() != null) { if (activity.getTotalDistance() != null) {
content.append("\n📏 ") metrics.append("📏 ")
.append(String.format("%.2f km", activity.getTotalDistance().doubleValue() / 1000.0)); .append(String.format("%.2f km", activity.getTotalDistance().doubleValue() / 1000.0))
.append("<br>");
} }
if (activity.getTotalDurationSeconds() != null) { if (activity.getTotalDurationSeconds() != null) {
long hours = activity.getTotalDurationSeconds() / 3600; long hours = activity.getTotalDurationSeconds() / 3600;
long minutes = (activity.getTotalDurationSeconds() % 3600) / 60; long minutes = (activity.getTotalDurationSeconds() % 3600) / 60;
long seconds = activity.getTotalDurationSeconds() % 60; long seconds = activity.getTotalDurationSeconds() % 60;
content.append("\n⏱️ "); metrics.append("⏱️ ");
if (hours > 0) { if (hours > 0) {
content.append(hours).append("h "); metrics.append(hours).append("h ");
} }
content.append(minutes).append("m ").append(seconds).append("s"); metrics.append(minutes).append("m ").append(seconds).append("s").append("<br>");
} }
if (activity.getElevationGain() != null) { if (activity.getElevationGain() != null) {
content.append("\n⛰ ") metrics.append("⛰️ ")
.append(String.format("%.0f m", activity.getElevationGain().doubleValue())); .append(String.format("%.0f m", activity.getElevationGain().doubleValue()))
.append("<br>");
}
if (metrics.length() > 0) {
content.append("<p>").append(metrics).append("</p>");
} }
return content.toString(); return content.toString();
} }
private static String escapeHtml(String text) {
if (text == null) return "";
return text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;");
}
/** /**
* Get emoji for activity type. * Get emoji for activity type.
*/ */

View file

@ -216,47 +216,33 @@ public class ActivityPostProcessingService {
} }
/** /**
* Format activity content for ActivityPub Note. * Format activity content as HTML for ActivityPub Note.
* Uses plain text with Unicode symbols for maximum compatibility across Fediverse platforms. * Mastodon and most Fediverse software expect HTML in the content field.
*
* Format:
* - Title (if present)
* - Description (if present)
* - Activity type with emoji
* - Distance (if present)
* - Duration (if present)
* - Elevation gain (if present)
* *
* @param activity the activity to format * @param activity the activity to format
* @return formatted content string * @return formatted HTML content string
*/ */
private String formatActivityContent(Activity activity) { private String formatActivityContent(Activity activity) {
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();
// Title (if present)
if (activity.getTitle() != null && !activity.getTitle().isEmpty()) { if (activity.getTitle() != null && !activity.getTitle().isEmpty()) {
content.append(activity.getTitle()).append("\n\n"); content.append("<p><strong>").append(escapeHtml(activity.getTitle())).append("</strong></p>");
} }
// Description (if present)
if (activity.getDescription() != null && !activity.getDescription().isEmpty()) { if (activity.getDescription() != null && !activity.getDescription().isEmpty()) {
content.append(activity.getDescription()).append("\n\n"); content.append("<p>").append(escapeHtml(activity.getDescription())).append("</p>");
} }
// Activity type with emoji
String activityEmoji = getActivityEmoji(activity.getActivityType()); String activityEmoji = getActivityEmoji(activity.getActivityType());
String formattedType = ActivityFormatter.formatActivityType(activity.getActivityType()); String formattedType = ActivityFormatter.formatActivityType(activity.getActivityType());
content.append(activityEmoji).append(" ").append(formattedType); content.append("<p>").append(activityEmoji).append(" ").append(escapeHtml(formattedType)).append("</p>");
// Metrics, each on its own line with a blank line separating from above
StringBuilder metrics = new StringBuilder(); StringBuilder metrics = new StringBuilder();
if (activity.getTotalDistance() != null) { if (activity.getTotalDistance() != null) {
metrics.append("📏 ") metrics.append("📏 ")
.append(String.format("%.2f km", activity.getTotalDistance().doubleValue() / 1000.0)) .append(String.format("%.2f km", activity.getTotalDistance().doubleValue() / 1000.0))
.append("\n"); .append("<br>");
} }
if (activity.getTotalDurationSeconds() != null) { if (activity.getTotalDurationSeconds() != null) {
long hours = activity.getTotalDurationSeconds() / 3600; long hours = activity.getTotalDurationSeconds() / 3600;
long minutes = (activity.getTotalDurationSeconds() % 3600) / 60; long minutes = (activity.getTotalDurationSeconds() % 3600) / 60;
@ -265,23 +251,28 @@ public class ActivityPostProcessingService {
if (hours > 0) { if (hours > 0) {
metrics.append(hours).append("h "); metrics.append(hours).append("h ");
} }
metrics.append(minutes).append("m ").append(seconds).append("s") metrics.append(minutes).append("m ").append(seconds).append("s").append("<br>");
.append("\n");
} }
if (activity.getElevationGain() != null) { if (activity.getElevationGain() != null) {
metrics.append("⛰️ ") metrics.append("⛰️ ")
.append(String.format("%.0f m", activity.getElevationGain())) .append(String.format("%.0f m", activity.getElevationGain()))
.append("\n"); .append("<br>");
} }
if (metrics.length() > 0) { if (metrics.length() > 0) {
content.append("\n\n").append(metrics.toString().stripTrailing()); content.append("<p>").append(metrics).append("</p>");
} }
return content.toString(); return content.toString();
} }
private static String escapeHtml(String text) {
if (text == null) return "";
return text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;");
}
/** /**
* Get an emoji for the activity type. * Get an emoji for the activity type.
* *