1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import Foundation
enum LabelAction: Comparable {
case none
case badge
case warnMedia
case warnContent
case hide
var severity: Int {
switch self {
case .none: 0
case .badge: 1
case .warnMedia: 2
case .warnContent: 3
case .hide: 4
}
}
static func < (lhs: LabelAction, rhs: LabelAction) -> Bool {
lhs.severity < rhs.severity
}
}
struct LabelResolution {
let action: LabelAction
let label: String
let name: String
static let none = LabelResolution(action: .none, label: "", name: "")
}
/// Well-known ATProto label fallbacks (used when server definitions unavailable)
private let fallbackDefinitions: [String: (blurs: String, defaultSetting: String, name: String)] = [
"porn": ("media", "warn", "Adult Content"),
"sexual": ("media", "warn", "Sexual Content"),
"nudity": ("media", "warn", "Nudity"),
"nsfl": ("media", "hide", "NSFL"),
"gore": ("media", "hide", "Graphic Violence"),
"dmca-violation": ("content", "hide", "DMCA Violation"),
"doxxing": ("content", "hide", "Doxxing"),
"!hide": ("content", "hide", "Hidden"),
"!warn": ("content", "warn", "Warning"),
]
/// Resolve the most restrictive label action for a set of labels.
func resolveLabels(_ labels: [ATLabel]?, definitions: [LabelDefinition]) -> LabelResolution {
guard let labels, !labels.isEmpty else { return .none }
var worst = LabelResolution.none
for label in labels {
guard let val = label.val, !val.isEmpty else { continue }
let resolution: LabelResolution = if let def = definitions.first(where: { $0.identifier == val }) {
resolveFromDefinition(val: val, def: def)
} else if let fallback = fallbackDefinitions[val] {
resolveFromFallback(val: val, fallback: fallback)
} else {
// Unknown label — treat as badge warning
LabelResolution(action: .badge, label: val, name: val)
}
if resolution.action > worst.action {
worst = resolution
}
}
return worst
}
private func resolveFromDefinition(val: String, def: LabelDefinition) -> LabelResolution {
let blurs = def.blurs ?? "none"
let setting = def.defaultSetting ?? "ignore"
let name = def.displayName
return resolveAction(val: val, name: name, blurs: blurs, setting: setting)
}
private func resolveFromFallback(val: String, fallback: (blurs: String, defaultSetting: String, name: String)) -> LabelResolution {
resolveAction(val: val, name: fallback.name, blurs: fallback.blurs, setting: fallback.defaultSetting)
}
private func resolveAction(val: String, name: String, blurs: String, setting: String) -> LabelResolution {
let action: LabelAction = switch (blurs, setting) {
case (_, "hide") where blurs != "none":
blurs == "media" ? .warnMedia : .hide
case ("content", "warn"):
.warnContent
case ("media", "warn"):
.warnMedia
case ("none", "warn"):
.badge
default:
.none
}
return LabelResolution(action: action, label: val, name: name)
}