semantic bufo search find-bufo.com
bufo
1
fork

Configure Feed

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

refactor: use freshClient helper for all http requests

removes stored client field, creates fresh client per request
to prevent stale connection errors (HttpConnectionClosing)

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

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

zzstoatzz e475f1a0 66b42def

+22 -49
+22 -49
bot/src/bsky.zig
··· 11 11 app_password: []const u8, 12 12 access_jwt: ?[]const u8 = null, 13 13 did: ?[]const u8 = null, 14 - client: http.Client, 15 14 16 15 pub fn init(allocator: Allocator, handle: []const u8, app_password: []const u8) BskyClient { 17 16 return .{ 18 17 .allocator = allocator, 19 18 .handle = handle, 20 19 .app_password = app_password, 21 - .client = .{ .allocator = allocator }, 22 20 }; 23 21 } 24 22 25 23 pub fn deinit(self: *BskyClient) void { 26 24 if (self.access_jwt) |jwt| self.allocator.free(jwt); 27 25 if (self.did) |did| self.allocator.free(did); 28 - self.client.deinit(); 26 + } 27 + 28 + // create fresh http client for each request to avoid stale connections 29 + fn freshClient(self: *BskyClient) http.Client { 30 + return .{ .allocator = self.allocator }; 29 31 } 30 32 31 33 pub fn login(self: *BskyClient) !void { 32 34 std.debug.print("logging in as {s}...\n", .{self.handle}); 33 35 36 + var client = self.freshClient(); 37 + defer client.deinit(); 38 + 34 39 var body_buf: std.ArrayList(u8) = .{}; 35 40 defer body_buf.deinit(self.allocator); 36 41 try body_buf.print(self.allocator, "{{\"identifier\":\"{s}\",\"password\":\"{s}\"}}", .{ self.handle, self.app_password }); ··· 38 43 var aw: Io.Writer.Allocating = .init(self.allocator); 39 44 defer aw.deinit(); 40 45 41 - const result = self.client.fetch(.{ 46 + const result = client.fetch(.{ 42 47 .location = .{ .url = "https://bsky.social/xrpc/com.atproto.server.createSession" }, 43 48 .method = .POST, 44 49 .headers = .{ .content_type = .{ .override = "application/json" } }, ··· 75 80 pub fn uploadBlob(self: *BskyClient, data: []const u8, content_type: []const u8) ![]const u8 { 76 81 if (self.access_jwt == null) return error.NotLoggedIn; 77 82 83 + var client = self.freshClient(); 84 + defer client.deinit(); 85 + 78 86 var auth_buf: [512]u8 = undefined; 79 87 const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong; 80 88 81 89 var aw: Io.Writer.Allocating = .init(self.allocator); 82 90 defer aw.deinit(); 83 91 84 - const result = self.client.fetch(.{ 92 + const result = client.fetch(.{ 85 93 .location = .{ .url = "https://bsky.social/xrpc/com.atproto.repo.uploadBlob" }, 86 94 .method = .POST, 87 95 .headers = .{ ··· 114 122 pub fn createQuotePost(self: *BskyClient, quote_uri: []const u8, quote_cid: []const u8, blob_json: []const u8, alt_text: []const u8) !void { 115 123 if (self.access_jwt == null or self.did == null) return error.NotLoggedIn; 116 124 125 + var client = self.freshClient(); 126 + defer client.deinit(); 127 + 117 128 var body_buf: std.ArrayList(u8) = .{}; 118 129 defer body_buf.deinit(self.allocator); 119 130 ··· 128 139 var aw: Io.Writer.Allocating = .init(self.allocator); 129 140 defer aw.deinit(); 130 141 131 - const result = self.client.fetch(.{ 132 - .location = .{ .url = "https://bsky.social/xrpc/com.atproto.repo.createRecord" }, 133 - .method = .POST, 134 - .headers = .{ 135 - .content_type = .{ .override = "application/json" }, 136 - .authorization = .{ .override = auth_header }, 137 - }, 138 - .payload = body_buf.items, 139 - .response_writer = &aw.writer, 140 - }) catch |err| { 141 - std.debug.print("create post failed: {}\n", .{err}); 142 - return err; 143 - }; 144 - 145 - if (result.status != .ok) { 146 - const response = aw.toArrayList(); 147 - std.debug.print("create post failed with status: {} - {s}\n", .{ result.status, response.items }); 148 - return error.PostFailed; 149 - } 150 - 151 - std.debug.print("posted successfully!\n", .{}); 152 - } 153 - 154 - pub fn createSimplePost(self: *BskyClient, text: []const u8, blob_json: []const u8, alt_text: []const u8) !void { 155 - if (self.access_jwt == null or self.did == null) return error.NotLoggedIn; 156 - 157 - var body_buf: std.ArrayList(u8) = .{}; 158 - defer body_buf.deinit(self.allocator); 159 - 160 - var ts_buf: [30]u8 = undefined; 161 - try body_buf.print(self.allocator, 162 - \\{{"repo":"{s}","collection":"app.bsky.feed.post","record":{{"$type":"app.bsky.feed.post","text":"{s}","createdAt":"{s}","embed":{{"$type":"app.bsky.embed.images","images":[{{"image":{s},"alt":"{s}"}}]}}}}}} 163 - , .{ self.did.?, text, getIsoTimestamp(&ts_buf), blob_json, alt_text }); 164 - 165 - var auth_buf: [512]u8 = undefined; 166 - const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong; 167 - 168 - var aw: Io.Writer.Allocating = .init(self.allocator); 169 - defer aw.deinit(); 170 - 171 - const result = self.client.fetch(.{ 142 + const result = client.fetch(.{ 172 143 .location = .{ .url = "https://bsky.social/xrpc/com.atproto.repo.createRecord" }, 173 144 .method = .POST, 174 145 .headers = .{ ··· 194 165 pub fn getPostCid(self: *BskyClient, uri: []const u8) ![]const u8 { 195 166 if (self.access_jwt == null) return error.NotLoggedIn; 196 167 168 + var client = self.freshClient(); 169 + defer client.deinit(); 170 + 197 171 var parts = mem.splitScalar(u8, uri[5..], '/'); 198 172 const did = parts.next() orelse return error.InvalidUri; 199 173 _ = parts.next(); ··· 208 182 var aw: Io.Writer.Allocating = .init(self.allocator); 209 183 defer aw.deinit(); 210 184 211 - const result = self.client.fetch(.{ 185 + const result = client.fetch(.{ 212 186 .location = .{ .url = url }, 213 187 .method = .GET, 214 188 .headers = .{ .authorization = .{ .override = auth_header } }, ··· 233 207 } 234 208 235 209 pub fn fetchImage(self: *BskyClient, url: []const u8) ![]const u8 { 236 - // use fresh client to avoid stale connection issues 237 - var client: http.Client = .{ .allocator = self.allocator }; 210 + var client = self.freshClient(); 238 211 defer client.deinit(); 239 212 240 213 var aw: Io.Writer.Allocating = .init(self.allocator);