+26
src/feed/filter.zig
+26
src/feed/filter.zig
···
111
111
return false;
112
112
}
113
113
114
+
/// returns true if the post matched because it quotes a music post (not direct link)
115
+
pub fn isQuoteMatch(record: Record) bool {
116
+
const val: json.Value = .{ .object = record };
117
+
118
+
// if post has direct music links, it's not a quote match
119
+
if (zat.json.getString(val, "embed.external.uri")) |uri| {
120
+
if (containsMusicDomain(uri)) return false;
121
+
}
122
+
if (zat.json.getArray(val, "facets")) |facets| {
123
+
if (checkFacetsForMusic(facets)) return false;
124
+
}
125
+
126
+
// check if music is in quoted content
127
+
if (zat.json.getString(val, "embed.record.value.embed.external.uri")) |uri| {
128
+
if (containsMusicDomain(uri)) return true;
129
+
}
130
+
if (zat.json.getArray(val, "embed.record.value.facets")) |facets| {
131
+
if (checkFacetsForMusic(facets)) return true;
132
+
}
133
+
if (zat.json.getString(val, "embed.record.record.value.embed.external.uri")) |uri| {
134
+
if (containsMusicDomain(uri)) return true;
135
+
}
136
+
137
+
return false;
138
+
}
139
+
114
140
fn containsMusicDomain(uri: []const u8) bool {
115
141
for (music_domains) |domain| {
116
142
if (mem.indexOf(u8, uri, domain) != null) return true;
+6
-2
src/jetstream.zig
+6
-2
src/jetstream.zig
···
163
163
return;
164
164
};
165
165
166
-
stats.get().recordMatch();
166
+
const s = stats.get();
167
+
s.recordMatch();
167
168
168
169
// track all platforms in post
169
170
const platforms = filter.detectAllPlatformsFromRecord(post_record.object);
170
-
const s = stats.get();
171
171
if (platforms.soundcloud) s.recordPlatform(.soundcloud);
172
172
if (platforms.bandcamp) s.recordPlatform(.bandcamp);
173
173
if (platforms.spotify) s.recordPlatform(.spotify);
174
174
if (platforms.plyr) s.recordPlatform(.plyr);
175
175
if (platforms.apple) s.recordPlatform(.apple);
176
+
177
+
// track match types
178
+
if (filter.isQuoteMatch(post_record.object)) s.recordQuoteMatch();
179
+
if (platforms.count() >= 2) s.recordMultiPlatform();
176
180
177
181
std.debug.print("added: {s}\n", .{at_uri.string});
178
182
}
+34
src/server/dashboard.zig
+34
src/server/dashboard.zig
···
220
220
\\ </div>
221
221
\\
222
222
\\ <div class="criteria">
223
+
\\ <div class="criteria-title">insights</div>
224
+
\\ <div class="criteria-list">
225
+
);
226
+
227
+
// match rate
228
+
const match_rate = s.getMatchRate();
229
+
try w.print("{d:.1} posts/hour", .{match_rate});
230
+
231
+
// quote matches
232
+
const quote_matches = s.getQuoteMatches();
233
+
const multi_platform = s.getMultiPlatform();
234
+
const matches = s.getMatches();
235
+
236
+
if (matches > 0) {
237
+
try w.writeAll(" · ");
238
+
if (quote_matches > 0) {
239
+
const quote_pct: f64 = @as(f64, @floatFromInt(quote_matches)) / @as(f64, @floatFromInt(matches)) * 100.0;
240
+
try w.print("{d} quotes ({d:.1}%)", .{ quote_matches, quote_pct });
241
+
} else {
242
+
try w.writeAll("0 quotes");
243
+
}
244
+
try w.writeAll(" · ");
245
+
if (multi_platform > 0) {
246
+
try w.print("{d} multi-link", .{multi_platform});
247
+
} else {
248
+
try w.writeAll("0 multi-link");
249
+
}
250
+
}
251
+
252
+
try w.writeAll(
253
+
\\</div>
254
+
\\ </div>
255
+
\\
256
+
\\ <div class="criteria">
223
257
\\ <div class="criteria-title">excluded</div>
224
258
\\ <div class="criteria-list">posts with nsfw <a href="https://docs.bsky.app/docs/advanced-guides/moderation">labels</a></div>
225
259
\\ </div>
+46
-2
src/server/stats.zig
+46
-2
src/server/stats.zig
···
26
26
plyr: Atomic(u64),
27
27
apple: Atomic(u64),
28
28
29
+
// match type counters
30
+
quote_matches: Atomic(u64), // posts quoting music posts
31
+
multi_platform: Atomic(u64), // posts with 2+ platforms
32
+
29
33
// for lag trend tracking
30
34
prev_lag_ms: Atomic(i64),
31
35
···
44
48
.spotify = Atomic(u64).init(0),
45
49
.plyr = Atomic(u64).init(0),
46
50
.apple = Atomic(u64).init(0),
51
+
.quote_matches = Atomic(u64).init(0),
52
+
.multi_platform = Atomic(u64).init(0),
47
53
.prev_lag_ms = Atomic(i64).init(0),
48
54
};
49
55
self.load();
···
86
92
};
87
93
if (root.get("apple")) |v| if (v == .integer) {
88
94
self.apple.store(@intCast(@max(0, v.integer)), .monotonic);
95
+
};
96
+
if (root.get("quote_matches")) |v| if (v == .integer) {
97
+
self.quote_matches.store(@intCast(@max(0, v.integer)), .monotonic);
98
+
};
99
+
if (root.get("multi_platform")) |v| if (v == .integer) {
100
+
self.multi_platform.store(@intCast(@max(0, v.integer)), .monotonic);
89
101
};
90
102
91
103
std.debug.print("loaded stats from {s}\n", .{STATS_PATH});
···
99
111
const session_uptime: u64 = @intCast(@max(0, now - self.started_at));
100
112
const total_uptime = self.prior_uptime + session_uptime;
101
113
102
-
var buf: [512]u8 = undefined;
114
+
var buf: [768]u8 = undefined;
103
115
const data = std.fmt.bufPrint(&buf,
104
-
\\{{"messages":{},"matches":{},"cumulative_uptime":{},"soundcloud":{},"bandcamp":{},"spotify":{},"plyr":{},"apple":{}}}
116
+
\\{{"messages":{},"matches":{},"cumulative_uptime":{},"soundcloud":{},"bandcamp":{},"spotify":{},"plyr":{},"apple":{},"quote_matches":{},"multi_platform":{}}}
105
117
, .{
106
118
self.messages.load(.monotonic),
107
119
self.matches.load(.monotonic),
···
111
123
self.spotify.load(.monotonic),
112
124
self.plyr.load(.monotonic),
113
125
self.apple.load(.monotonic),
126
+
self.quote_matches.load(.monotonic),
127
+
self.multi_platform.load(.monotonic),
114
128
}) catch return;
115
129
116
130
file.writeAll(data) catch return;
···
172
186
};
173
187
}
174
188
189
+
pub fn recordQuoteMatch(self: *Stats) void {
190
+
_ = self.quote_matches.fetchAdd(1, .monotonic);
191
+
}
192
+
193
+
pub fn recordMultiPlatform(self: *Stats) void {
194
+
_ = self.multi_platform.fetchAdd(1, .monotonic);
195
+
}
196
+
197
+
pub fn getQuoteMatches(self: *const Stats) u64 {
198
+
return self.quote_matches.load(.monotonic);
199
+
}
200
+
201
+
pub fn getMultiPlatform(self: *const Stats) u64 {
202
+
return self.multi_platform.load(.monotonic);
203
+
}
204
+
205
+
pub fn getMatchRate(self: *Stats) f64 {
206
+
const uptime_sec = self.totalUptime();
207
+
if (uptime_sec <= 0) return 0;
208
+
const uptime_hours: f64 = @as(f64, @floatFromInt(uptime_sec)) / 3600.0;
209
+
const matches: f64 = @floatFromInt(self.matches.load(.monotonic));
210
+
return matches / uptime_hours;
211
+
}
212
+
175
213
pub fn recordConnected(self: *Stats) void {
176
214
self.connected_at.store(std.time.milliTimestamp(), .monotonic);
177
215
}
···
247
285
.spotify = Atomic(u64).init(0),
248
286
.plyr = Atomic(u64).init(0),
249
287
.apple = Atomic(u64).init(0),
288
+
.quote_matches = Atomic(u64).init(0),
289
+
.multi_platform = Atomic(u64).init(0),
250
290
.prev_lag_ms = Atomic(i64).init(0),
251
291
};
252
292
···
272
312
.spotify = Atomic(u64).init(0),
273
313
.plyr = Atomic(u64).init(0),
274
314
.apple = Atomic(u64).init(0),
315
+
.quote_matches = Atomic(u64).init(0),
316
+
.multi_platform = Atomic(u64).init(0),
275
317
.prev_lag_ms = Atomic(i64).init(0),
276
318
};
277
319
···
303
345
.spotify = Atomic(u64).init(0),
304
346
.plyr = Atomic(u64).init(0),
305
347
.apple = Atomic(u64).init(0),
348
+
.quote_matches = Atomic(u64).init(0),
349
+
.multi_platform = Atomic(u64).init(0),
306
350
.prev_lag_ms = Atomic(i64).init(0),
307
351
};
308
352