bsky feeds about music music-atmosphere-feed.plyr.fm/
bsky feed zig
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

upgrade zat to v0.3.0-alpha.25, fix bot detection to be rate-based

- zat: v0.3.0-alpha.7 -> v0.3.0-alpha.25
- websocket now pulled transitively from zat instead of direct dep
- bot detection: replace broken "500 posts ever" threshold with
"50 posts in last 24h" time-windowed rate check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

zzstoatzz b6f69579 372e4a01

+14 -18
+4 -6
build.zig
··· 4 4 const target = b.standardTargetOptions(.{}); 5 5 const optimize = b.standardOptimizeOption(.{}); 6 6 7 - const websocket = b.dependency("websocket", .{ 8 - .target = target, 9 - .optimize = optimize, 10 - }); 11 - 12 7 const zqlite = b.dependency("zqlite", .{ 13 8 .target = target, 14 9 .optimize = optimize, ··· 20 15 }); 21 16 22 17 const imports: []const std.Build.Module.Import = &.{ 23 - .{ .name = "websocket", .module = websocket.module("websocket") }, 18 + .{ .name = "websocket", .module = zat.builder.dependency("websocket", .{ 19 + .target = target, 20 + .optimize = optimize, 21 + }).module("websocket") }, 24 22 .{ .name = "zqlite", .module = zqlite.module("zqlite") }, 25 23 .{ .name = "zat", .module = zat.module("zat") }, 26 24 };
+2 -6
build.zig.zon
··· 4 4 .fingerprint = 0x4645ec2d4c828c20, 5 5 .minimum_zig_version = "0.16.0", 6 6 .dependencies = .{ 7 - .websocket = .{ 8 - .url = "https://github.com/zzstoatzz/websocket.zig/archive/edeca26.tar.gz", 9 - .hash = "websocket-0.1.0-ZPISdSmqAwCbwcFtrAQC_q9cegdw-iHyrjCftgfMz-Nf", 10 - }, 11 7 .zqlite = .{ 12 8 .url = "https://github.com/karlseguin/zqlite.zig/archive/refs/heads/master.tar.gz", 13 9 .hash = "zqlite-0.0.1-RWLaYz6bmAAT7E_jxopXf-j5Ea8VQldnxsd6TU8sa0Bb", 14 10 }, 15 11 .zat = .{ 16 - .url = "https://tangled.org/zat.dev/zat/archive/v0.3.0-alpha.7.tar.gz", 17 - .hash = "zat-0.3.0-alpha.7-5PuC7uNjBQDv28db31DEKkFn1tU5I4f1GfJs-RrG8_pS", 12 + .url = "https://tangled.org/zat.dev/zat/archive/v0.3.0-alpha.25.tar.gz", 13 + .hash = "zat-0.3.0-alpha.24-5PuC7vB8CACd9OTAS8jDuwVN4hQBYiAdfArjFjo_sWyD", 18 14 }, 19 15 }, 20 16 .paths = .{
+2 -2
src/server/http.zig
··· 232 232 return; 233 233 } 234 234 } else if (feed_type == .organic) { 235 - // get high-volume posters (>500 posts = likely bots) 236 - exclude_dids = posts.getHighVolumePosters(alloc, 500) catch |err| blk: { 235 + // get high-volume posters (>50 posts in last 24h = likely bots) 236 + exclude_dids = posts.getHighVolumePosters(alloc, 50, 24) catch |err| blk: { 237 237 std.debug.print("failed to get high-volume posters: {}\n", .{err}); 238 238 break :blk null; 239 239 };
+6 -4
src/store/posts.zig
··· 171 171 alloc.free(result_posts); 172 172 } 173 173 174 - /// get DIDs with more than threshold posts (for bot detection) 175 - pub fn getHighVolumePosters(alloc: std.mem.Allocator, threshold: usize) ![][]const u8 { 174 + /// get DIDs posting more than `threshold` posts within the last `window_hours` hours 175 + pub fn getHighVolumePosters(alloc: std.mem.Allocator, threshold: usize, window_hours: usize) ![][]const u8 { 176 176 db.lock(); 177 177 defer db.unlock(); 178 178 179 179 const conn = db.getConn() orelse return error.NotInitialized; 180 180 181 + const cutoff_ms: i64 = milliTimestamp() - @as(i64, @intCast(window_hours)) * std.time.ms_per_hour; 182 + 181 183 var did_list: std.ArrayList([]const u8) = .empty; 182 184 errdefer { 183 185 for (did_list.items) |did| alloc.free(did); ··· 186 188 187 189 var rows = conn.rows( 188 190 \\SELECT author_did, COUNT(*) as cnt FROM posts 189 - \\WHERE author_did IS NOT NULL 191 + \\WHERE author_did IS NOT NULL AND indexed_at > ? 190 192 \\GROUP BY author_did 191 193 \\HAVING cnt > ? 192 - , .{threshold}) catch return error.QueryFailed; 194 + , .{ cutoff_ms, threshold }) catch return error.QueryFailed; 193 195 defer rows.deinit(); 194 196 195 197 while (rows.next()) |row| {