Files
andros be5030ac0d Add BOT property support; fix %2B cache-buster bug; bump 1.3 (18)
- OrgSocialPost: add bot: String? field (never displayed, reserved for
  bot-aware rendering per spec)
- OrgSocialParser: parse :BOT: property into post.bot
- RelayClient.appendCacheBuster: use percentEncodedQueryItems instead of
  queryItems so %2B timezone offsets are preserved; fixes poll-votes 404
- Tests: add BotPropertyTests (parsed, not-in-body, nil-when-absent);
  all 240 tests now pass
2026-05-21 20:16:22 +02:00

100 lines
3.5 KiB
Swift

import Foundation
public struct OrgSocialPost: Sendable, Equatable {
/// RFC 3339 timestamp, unique identifier for the post.
public var timestamp: String
/// Parsed date from `timestamp`.
public var date: Date
/// Body text of the post (Org Mode syntax preserved, comment and property lines stripped).
public var text: String
/// ISO 639-1 language code.
public var lang: String?
/// Space-separated tags parsed into individual strings.
public var tags: [String]
/// Client that created the post.
public let client: String?
/// URL#timestamp of the post being replied to.
public let replyTo: String?
/// URL#timestamp of the post being boosted/reshared.
public let include: String?
/// Poll close date.
public let pollEnd: Date?
/// Selected option when this post is a poll vote.
public let pollOption: String?
/// "Group Name relay-url" when posted to a group.
public let group: String?
/// Mood/reaction emoji.
public var mood: String?
/// "old-url new-url" for account migration posts.
public let migration: String?
/// `"public"` or `"mention"`. `nil` defaults to public.
public var visibility: String?
/// Raw BOT property value (`<type> [params...]`). Never displayed; reserved for bot-aware rendering.
public let bot: String?
// Populated during timeline assembly, not by the parser.
public var authorNick: String?
public var authorURL: URL?
public var authorAvatar: URL?
public var feedURL: URL?
/// Stable unique ID combining feed URL and timestamp safe for ForEach in merged timelines.
public var stableID: String {
"\(feedURL?.absoluteString ?? "unknown")#\(timestamp)"
}
/// Whether this post has nothing to render as a standalone timeline row
/// i.e. its body is empty. Covers every protocol artefact (pure reaction,
/// pure boost, poll vote, migration announcement) plus the accidental
/// empty post. Quote-boosts, text posts and polls all carry a body and
/// are NOT flagged.
///
/// Used by timeline and profile views to hide these rows so the original
/// content they reference is shown only in its authoritative location.
public var isPureInteraction: Bool {
text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
}
public init(
timestamp: String,
date: Date,
text: String,
lang: String? = nil,
tags: [String] = [],
client: String? = nil,
replyTo: String? = nil,
include: String? = nil,
pollEnd: Date? = nil,
pollOption: String? = nil,
group: String? = nil,
mood: String? = nil,
migration: String? = nil,
visibility: String? = nil,
bot: String? = nil,
authorNick: String? = nil,
authorURL: URL? = nil,
authorAvatar: URL? = nil,
feedURL: URL? = nil
) {
self.timestamp = timestamp
self.date = date
self.text = text
self.lang = lang
self.tags = tags
self.client = client
self.replyTo = replyTo
self.include = include
self.pollEnd = pollEnd
self.pollOption = pollOption
self.group = group
self.mood = mood
self.migration = migration
self.visibility = visibility
self.bot = bot
self.authorNick = authorNick
self.authorURL = authorURL
self.authorAvatar = authorAvatar
self.feedURL = feedURL
}
}