semantic bufo search find-bufo.com
bufo
1
fork

Configure Feed

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

fix: use correct PDS host for video upload service auth

Fetch PDS host from PLC directory after login and use did:web:<pds_host>
as the audience for getServiceAuth instead of hardcoded value.

Source: https://docs.bsky.app/docs/advanced-guides/service-auth

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

zzstoatzz 22f9763d c3083f16

+62 -4
+62 -4
bot/src/bsky.zig
··· 11 11 app_password: []const u8, 12 12 access_jwt: ?[]const u8 = null, 13 13 did: ?[]const u8 = null, 14 + pds_host: ?[]const u8 = null, 14 15 15 16 pub fn init(allocator: Allocator, handle: []const u8, app_password: []const u8) BskyClient { 16 17 return .{ ··· 23 24 pub fn deinit(self: *BskyClient) void { 24 25 if (self.access_jwt) |jwt| self.allocator.free(jwt); 25 26 if (self.did) |did| self.allocator.free(did); 27 + if (self.pds_host) |host| self.allocator.free(host); 26 28 } 27 29 28 30 fn httpClient(self: *BskyClient) http.Client { ··· 73 75 self.access_jwt = try self.allocator.dupe(u8, jwt_val.string); 74 76 self.did = try self.allocator.dupe(u8, did_val.string); 75 77 76 - std.debug.print("logged in as {s} (did: {s})\n", .{ self.handle, self.did.? }); 78 + // fetch PDS host from PLC directory 79 + try self.fetchPdsHost(); 80 + 81 + std.debug.print("logged in as {s} (did: {s}, pds: {s})\n", .{ self.handle, self.did.?, self.pds_host.? }); 82 + } 83 + 84 + fn fetchPdsHost(self: *BskyClient) !void { 85 + var client = self.httpClient(); 86 + defer client.deinit(); 87 + 88 + var url_buf: [256]u8 = undefined; 89 + const url = std.fmt.bufPrint(&url_buf, "https://plc.directory/{s}", .{self.did.?}) catch return error.UrlTooLong; 90 + 91 + var aw: Io.Writer.Allocating = .init(self.allocator); 92 + defer aw.deinit(); 93 + 94 + const result = client.fetch(.{ 95 + .location = .{ .url = url }, 96 + .method = .GET, 97 + .response_writer = &aw.writer, 98 + }) catch |err| { 99 + std.debug.print("fetch PDS host failed: {}\n", .{err}); 100 + return err; 101 + }; 102 + 103 + if (result.status != .ok) { 104 + std.debug.print("fetch PDS host failed with status: {}\n", .{result.status}); 105 + return error.PlcLookupFailed; 106 + } 107 + 108 + const response = aw.toArrayList(); 109 + const parsed = json.parseFromSlice(json.Value, self.allocator, response.items, .{}) catch return error.ParseError; 110 + defer parsed.deinit(); 111 + 112 + // find the atproto_pds service endpoint 113 + const service = parsed.value.object.get("service") orelse return error.NoService; 114 + if (service != .array) return error.NoService; 115 + 116 + for (service.array.items) |svc| { 117 + if (svc != .object) continue; 118 + const id_val = svc.object.get("id") orelse continue; 119 + if (id_val != .string) continue; 120 + if (!mem.eql(u8, id_val.string, "#atproto_pds")) continue; 121 + 122 + const endpoint_val = svc.object.get("serviceEndpoint") orelse continue; 123 + if (endpoint_val != .string) continue; 124 + 125 + // extract host from URL like "https://phellinus.us-west.host.bsky.network" 126 + const endpoint = endpoint_val.string; 127 + const prefix = "https://"; 128 + if (mem.startsWith(u8, endpoint, prefix)) { 129 + self.pds_host = try self.allocator.dupe(u8, endpoint[prefix.len..]); 130 + return; 131 + } 132 + } 133 + 134 + return error.NoPdsService; 77 135 } 78 136 79 137 pub fn uploadBlob(self: *BskyClient, data: []const u8, content_type: []const u8) ![]const u8 { ··· 231 289 } 232 290 233 291 pub fn getServiceAuth(self: *BskyClient) ![]const u8 { 234 - if (self.access_jwt == null or self.did == null) return error.NotLoggedIn; 292 + if (self.access_jwt == null or self.did == null or self.pds_host == null) return error.NotLoggedIn; 235 293 236 294 var client = self.httpClient(); 237 295 defer client.deinit(); 238 296 239 - var url_buf: [256]u8 = undefined; 240 - const url = std.fmt.bufPrint(&url_buf, "https://bsky.social/xrpc/com.atproto.server.getServiceAuth?aud=did:web:bsky.social&lxm=com.atproto.repo.uploadBlob", .{}) catch return error.UrlTooLong; 297 + var url_buf: [512]u8 = undefined; 298 + const url = std.fmt.bufPrint(&url_buf, "https://bsky.social/xrpc/com.atproto.server.getServiceAuth?aud=did:web:{s}&lxm=com.atproto.repo.uploadBlob", .{self.pds_host.?}) catch return error.UrlTooLong; 241 299 242 300 var auth_buf: [512]u8 = undefined; 243 301 const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong;