rewrite bot in zig for better memory efficiency

replaces python bot with zig implementation:
- uses std.http.Client for bsky api
- websocket.zig for jetstream consumption
- same matching logic (phrase extraction, cooldown)
- reduces memory from 1GB to 256MB

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

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

+4
.gitignore
··· 4 4 .env 5 5 server.log 6 6 test_*.py 7 + 8 + # zig build artifacts 9 + .zig-cache/ 10 + zig-out/
-7
bot/.env.example
··· 1 - BSKY_HANDLE=find-bufo.com 2 - BSKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx 3 - MIN_PHRASE_WORDS=4 4 - POSTING_ENABLED=false 5 - COOLDOWN_MINUTES=120 6 - EXCLUDE_PATTERNS=what-have-you-done,what-have-i-done,sad,crying,cant-take 7 - QUOTE_CHANCE=0.5
+26 -6
bot/Dockerfile
··· 1 - FROM python:3.11-slim 1 + # build stage 2 + FROM debian:bookworm-slim AS builder 2 3 3 - COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ 4 + RUN apt-get update && apt-get install -y --no-install-recommends \ 5 + ca-certificates \ 6 + curl \ 7 + xz-utils \ 8 + && rm -rf /var/lib/apt/lists/* 9 + 10 + # install zig 0.15.2 11 + RUN curl -L https://ziglang.org/download/0.15.2/zig-x86_64-linux-0.15.2.tar.xz | tar -xJ -C /usr/local \ 12 + && ln -s /usr/local/zig-x86_64-linux-0.15.2/zig /usr/local/bin/zig 4 13 5 14 WORKDIR /app 15 + COPY build.zig build.zig.zon ./ 16 + COPY src ./src 6 17 7 - COPY pyproject.toml . 8 - COPY src/ src/ 18 + RUN zig build -Doptimize=ReleaseSafe 9 19 10 - RUN uv sync --no-dev 20 + # runtime stage 21 + FROM debian:bookworm-slim 11 22 12 - CMD ["uv", "run", "bufo-bot"] 23 + RUN apt-get update && apt-get install -y --no-install-recommends \ 24 + ca-certificates \ 25 + && rm -rf /var/lib/apt/lists/* \ 26 + # prefer IPv4 over IPv6 for outbound connections (IPv6 times out in Fly.io) 27 + && echo 'precedence ::ffff:0:0/96 100' >> /etc/gai.conf 28 + 29 + WORKDIR /app 30 + COPY --from=builder /app/zig-out/bin/bufo-bot . 31 + 32 + CMD ["./bufo-bot"]
+7 -13
bot/README.md
··· 1 - # bufo-bot 1 + # bufo-bot (zig) 2 2 3 - bluesky bot that listens to the firehose and quote-posts matching bufo images. 3 + bluesky bot that listens to the jetstream firehose and quote-posts matching bufo images. 4 4 5 5 ## how it works 6 6 7 7 1. connects to bluesky jetstream (firehose) 8 8 2. for each post, checks if text contains an exact phrase matching a bufo name 9 - 3. if matched, quote-posts with the corresponding bufo image 9 + 3. if matched, quote-posts with the corresponding bufo image (or posts without quote based on quote_chance) 10 10 11 11 ## matching logic 12 12 ··· 23 23 | `MIN_PHRASE_WORDS` | `4` | minimum words in phrase to match | 24 24 | `POSTING_ENABLED` | `false` | must be `true` to actually post | 25 25 | `COOLDOWN_MINUTES` | `120` | don't repost same bufo within this time | 26 - | `EXCLUDE_PATTERNS` | `sad,crying,...` | exclude bufos matching these patterns | 27 26 | `QUOTE_CHANCE` | `0.5` | probability of quoting vs just posting with rkey | 27 + | `EXCLUDE_PATTERNS` | `...` | exclude bufos matching these patterns | 28 28 | `JETSTREAM_ENDPOINT` | `jetstream2.us-east.bsky.network` | jetstream server | 29 29 30 30 ## local dev 31 31 32 32 ```bash 33 - # copy and edit .env 34 - cp .env.example .env 33 + # build 34 + zig build 35 35 36 36 # run locally (dry run by default) 37 - uv run bufo-bot 38 - 39 - # or test matching against firehose 40 - uv run python -m bufo_bot.dry_run 37 + ./zig-out/bin/bufo-bot 41 38 ``` 42 39 43 40 ## deploy ··· 51 48 52 49 # enable posting 53 50 fly secrets set POSTING_ENABLED=true -a bufo-bot 54 - 55 - # disable posting 56 - fly secrets set POSTING_ENABLED=false -a bufo-bot 57 51 58 52 # check logs 59 53 fly logs -a bufo-bot
+34
bot/build.zig
··· 1 + const std = @import("std"); 2 + 3 + pub fn build(b: *std.Build) void { 4 + const target = b.standardTargetOptions(.{}); 5 + const optimize = b.standardOptimizeOption(.{}); 6 + 7 + const websocket = b.dependency("websocket", .{ 8 + .target = target, 9 + .optimize = optimize, 10 + }); 11 + 12 + const exe = b.addExecutable(.{ 13 + .name = "bufo-bot", 14 + .root_module = b.createModule(.{ 15 + .root_source_file = b.path("src/main.zig"), 16 + .target = target, 17 + .optimize = optimize, 18 + .imports = &.{ 19 + .{ .name = "websocket", .module = websocket.module("websocket") }, 20 + }, 21 + }), 22 + }); 23 + 24 + b.installArtifact(exe); 25 + 26 + const run_cmd = b.addRunArtifact(exe); 27 + run_cmd.step.dependOn(b.getInstallStep()); 28 + if (b.args) |args| { 29 + run_cmd.addArgs(args); 30 + } 31 + 32 + const run_step = b.step("run", "Run the bot"); 33 + run_step.dependOn(&run_cmd.step); 34 + }
+17
bot/build.zig.zon
··· 1 + .{ 2 + .name = .bufo_bot, 3 + .version = "0.0.1", 4 + .fingerprint = 0xe143490f82fa96db, 5 + .minimum_zig_version = "0.15.0", 6 + .dependencies = .{ 7 + .websocket = .{ 8 + .url = "https://github.com/karlseguin/websocket.zig/archive/refs/heads/master.tar.gz", 9 + .hash = "websocket-0.1.0-ZPISdRNzAwAGszh62EpRtoQxu8wb1MSMVI6Ow0o2dmyJ", 10 + }, 11 + }, 12 + .paths = .{ 13 + "build.zig", 14 + "build.zig.zon", 15 + "src", 16 + }, 17 + }
+3 -4
bot/fly.toml
··· 7 7 [env] 8 8 JETSTREAM_ENDPOINT = "jetstream2.us-east.bsky.network" 9 9 10 - # This is a worker process, not an HTTP service 11 - # It runs continuously and doesn't expose any ports 10 + # worker process - no http service 12 11 [processes] 13 - worker = "uv run bufo-bot" 12 + worker = "./bufo-bot" 14 13 15 14 [[vm]] 16 - memory = "1024mb" 15 + memory = "256mb" 17 16 cpu_kind = "shared" 18 17 cpus = 1 19 18
+8 -1
bot/justfile
··· 1 1 # bot/justfile 2 2 set shell := ["bash", "-eu", "-o", "pipefail", "-c"] 3 3 4 + default: 5 + @just --list 6 + 7 + # build the bot 8 + build: 9 + zig build 10 + 4 11 # run the bot locally 5 12 run: 6 - uv run bufo-bot 13 + zig build run 7 14 8 15 # deploy to fly.io 9 16 deploy:
-28
bot/pyproject.toml
··· 1 - [project] 2 - name = "bufo-bot" 3 - version = "0.1.0" 4 - description = "bluesky bot that responds to posts with matching bufo images" 5 - requires-python = ">=3.11" 6 - dependencies = [ 7 - "atproto>=0.0.59", 8 - "httpx-ws>=0.7.1", 9 - "pydantic-settings>=2.7.1", 10 - "httpx>=0.28.1", 11 - ] 12 - 13 - [project.scripts] 14 - bufo-bot = "bufo_bot.main:main" 15 - 16 - [build-system] 17 - requires = ["hatchling"] 18 - build-backend = "hatchling.build" 19 - 20 - [tool.hatch.build.targets.wheel] 21 - packages = ["src/bufo_bot"] 22 - 23 - [tool.ruff] 24 - extend-select = ["I", "UP"] 25 - target-version = "py311" 26 - 27 - [tool.pyright] 28 - pythonVersion = "3.11"
+257
bot/src/bsky.zig
··· 1 + const std = @import("std"); 2 + const mem = std.mem; 3 + const json = std.json; 4 + const http = std.http; 5 + const Allocator = mem.Allocator; 6 + const Io = std.Io; 7 + 8 + pub const BskyClient = struct { 9 + allocator: Allocator, 10 + handle: []const u8, 11 + app_password: []const u8, 12 + access_jwt: ?[]const u8 = null, 13 + did: ?[]const u8 = null, 14 + client: http.Client, 15 + 16 + pub fn init(allocator: Allocator, handle: []const u8, app_password: []const u8) BskyClient { 17 + return .{ 18 + .allocator = allocator, 19 + .handle = handle, 20 + .app_password = app_password, 21 + .client = .{ .allocator = allocator }, 22 + }; 23 + } 24 + 25 + pub fn deinit(self: *BskyClient) void { 26 + if (self.access_jwt) |jwt| self.allocator.free(jwt); 27 + if (self.did) |did| self.allocator.free(did); 28 + self.client.deinit(); 29 + } 30 + 31 + pub fn login(self: *BskyClient) !void { 32 + std.debug.print("logging in as {s}...\n", .{self.handle}); 33 + 34 + var body_buf: std.ArrayList(u8) = .{}; 35 + defer body_buf.deinit(self.allocator); 36 + try body_buf.print(self.allocator, "{{\"identifier\":\"{s}\",\"password\":\"{s}\"}}", .{ self.handle, self.app_password }); 37 + 38 + var aw: Io.Writer.Allocating = .init(self.allocator); 39 + defer aw.deinit(); 40 + 41 + const result = self.client.fetch(.{ 42 + .location = .{ .url = "https://bsky.social/xrpc/com.atproto.server.createSession" }, 43 + .method = .POST, 44 + .headers = .{ .content_type = .{ .override = "application/json" } }, 45 + .payload = body_buf.items, 46 + .response_writer = &aw.writer, 47 + }) catch |err| { 48 + std.debug.print("login request failed: {}\n", .{err}); 49 + return err; 50 + }; 51 + 52 + if (result.status != .ok) { 53 + std.debug.print("login failed with status: {}\n", .{result.status}); 54 + return error.LoginFailed; 55 + } 56 + 57 + const response = aw.toArrayList(); 58 + const parsed = json.parseFromSlice(json.Value, self.allocator, response.items, .{}) catch return error.ParseError; 59 + defer parsed.deinit(); 60 + 61 + const root = parsed.value.object; 62 + 63 + const jwt_val = root.get("accessJwt") orelse return error.NoJwt; 64 + if (jwt_val != .string) return error.NoJwt; 65 + 66 + const did_val = root.get("did") orelse return error.NoDid; 67 + if (did_val != .string) return error.NoDid; 68 + 69 + self.access_jwt = try self.allocator.dupe(u8, jwt_val.string); 70 + self.did = try self.allocator.dupe(u8, did_val.string); 71 + 72 + std.debug.print("logged in as {s} (did: {s})\n", .{ self.handle, self.did.? }); 73 + } 74 + 75 + pub fn uploadBlob(self: *BskyClient, data: []const u8, content_type: []const u8) ![]const u8 { 76 + if (self.access_jwt == null) return error.NotLoggedIn; 77 + 78 + var auth_buf: [512]u8 = undefined; 79 + const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong; 80 + 81 + var aw: Io.Writer.Allocating = .init(self.allocator); 82 + defer aw.deinit(); 83 + 84 + const result = self.client.fetch(.{ 85 + .location = .{ .url = "https://bsky.social/xrpc/com.atproto.repo.uploadBlob" }, 86 + .method = .POST, 87 + .headers = .{ 88 + .content_type = .{ .override = content_type }, 89 + .authorization = .{ .override = auth_header }, 90 + }, 91 + .payload = data, 92 + .response_writer = &aw.writer, 93 + }) catch |err| { 94 + std.debug.print("upload blob failed: {}\n", .{err}); 95 + return err; 96 + }; 97 + 98 + if (result.status != .ok) { 99 + std.debug.print("upload blob failed with status: {}\n", .{result.status}); 100 + return error.UploadFailed; 101 + } 102 + 103 + const response = aw.toArrayList(); 104 + const parsed = json.parseFromSlice(json.Value, self.allocator, response.items, .{}) catch return error.ParseError; 105 + defer parsed.deinit(); 106 + 107 + const root = parsed.value.object; 108 + const blob = root.get("blob") orelse return error.NoBlobRef; 109 + if (blob != .object) return error.NoBlobRef; 110 + 111 + return json.Stringify.valueAlloc(self.allocator, blob, .{}) catch return error.SerializeError; 112 + } 113 + 114 + pub fn createQuotePost(self: *BskyClient, quote_uri: []const u8, quote_cid: []const u8, blob_json: []const u8, alt_text: []const u8) !void { 115 + if (self.access_jwt == null or self.did == null) return error.NotLoggedIn; 116 + 117 + var body_buf: std.ArrayList(u8) = .{}; 118 + defer body_buf.deinit(self.allocator); 119 + 120 + try body_buf.print(self.allocator, 121 + \\{{"repo":"{s}","collection":"app.bsky.feed.post","record":{{"$type":"app.bsky.feed.post","text":"","createdAt":"{s}","embed":{{"$type":"app.bsky.embed.recordWithMedia","record":{{"$type":"app.bsky.embed.record","record":{{"uri":"{s}","cid":"{s}"}}}},"media":{{"$type":"app.bsky.embed.images","images":[{{"image":{s},"alt":"{s}"}}]}}}}}}}} 122 + , .{ self.did.?, getIsoTimestamp(), quote_uri, quote_cid, blob_json, alt_text }); 123 + 124 + var auth_buf: [512]u8 = undefined; 125 + const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong; 126 + 127 + var aw: Io.Writer.Allocating = .init(self.allocator); 128 + defer aw.deinit(); 129 + 130 + const result = self.client.fetch(.{ 131 + .location = .{ .url = "https://bsky.social/xrpc/com.atproto.repo.createRecord" }, 132 + .method = .POST, 133 + .headers = .{ 134 + .content_type = .{ .override = "application/json" }, 135 + .authorization = .{ .override = auth_header }, 136 + }, 137 + .payload = body_buf.items, 138 + .response_writer = &aw.writer, 139 + }) catch |err| { 140 + std.debug.print("create post failed: {}\n", .{err}); 141 + return err; 142 + }; 143 + 144 + if (result.status != .ok) { 145 + const response = aw.toArrayList(); 146 + std.debug.print("create post failed with status: {} - {s}\n", .{ result.status, response.items }); 147 + return error.PostFailed; 148 + } 149 + 150 + std.debug.print("posted successfully!\n", .{}); 151 + } 152 + 153 + pub fn createSimplePost(self: *BskyClient, text: []const u8, blob_json: []const u8, alt_text: []const u8) !void { 154 + if (self.access_jwt == null or self.did == null) return error.NotLoggedIn; 155 + 156 + var body_buf: std.ArrayList(u8) = .{}; 157 + defer body_buf.deinit(self.allocator); 158 + 159 + try body_buf.print(self.allocator, 160 + \\{{"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}"}}]}}}}}} 161 + , .{ self.did.?, text, getIsoTimestamp(), blob_json, alt_text }); 162 + 163 + var auth_buf: [512]u8 = undefined; 164 + const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong; 165 + 166 + var aw: Io.Writer.Allocating = .init(self.allocator); 167 + defer aw.deinit(); 168 + 169 + const result = self.client.fetch(.{ 170 + .location = .{ .url = "https://bsky.social/xrpc/com.atproto.repo.createRecord" }, 171 + .method = .POST, 172 + .headers = .{ 173 + .content_type = .{ .override = "application/json" }, 174 + .authorization = .{ .override = auth_header }, 175 + }, 176 + .payload = body_buf.items, 177 + .response_writer = &aw.writer, 178 + }) catch |err| { 179 + std.debug.print("create post failed: {}\n", .{err}); 180 + return err; 181 + }; 182 + 183 + if (result.status != .ok) { 184 + const response = aw.toArrayList(); 185 + std.debug.print("create post failed with status: {} - {s}\n", .{ result.status, response.items }); 186 + return error.PostFailed; 187 + } 188 + 189 + std.debug.print("posted successfully!\n", .{}); 190 + } 191 + 192 + pub fn getPostCid(self: *BskyClient, uri: []const u8) ![]const u8 { 193 + if (self.access_jwt == null) return error.NotLoggedIn; 194 + 195 + var parts = mem.splitScalar(u8, uri[5..], '/'); 196 + const did = parts.next() orelse return error.InvalidUri; 197 + _ = parts.next(); 198 + const rkey = parts.next() orelse return error.InvalidUri; 199 + 200 + var url_buf: [512]u8 = undefined; 201 + const url = std.fmt.bufPrint(&url_buf, "https://bsky.social/xrpc/com.atproto.repo.getRecord?repo={s}&collection=app.bsky.feed.post&rkey={s}", .{ did, rkey }) catch return error.UrlTooLong; 202 + 203 + var auth_buf: [512]u8 = undefined; 204 + const auth_header = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.access_jwt.?}) catch return error.AuthTooLong; 205 + 206 + var aw: Io.Writer.Allocating = .init(self.allocator); 207 + defer aw.deinit(); 208 + 209 + const result = self.client.fetch(.{ 210 + .location = .{ .url = url }, 211 + .method = .GET, 212 + .headers = .{ .authorization = .{ .override = auth_header } }, 213 + .response_writer = &aw.writer, 214 + }) catch |err| { 215 + std.debug.print("get record failed: {}\n", .{err}); 216 + return err; 217 + }; 218 + 219 + if (result.status != .ok) { 220 + return error.GetRecordFailed; 221 + } 222 + 223 + const response = aw.toArrayList(); 224 + const parsed = json.parseFromSlice(json.Value, self.allocator, response.items, .{}) catch return error.ParseError; 225 + defer parsed.deinit(); 226 + 227 + const cid_val = parsed.value.object.get("cid") orelse return error.NoCid; 228 + if (cid_val != .string) return error.NoCid; 229 + 230 + return try self.allocator.dupe(u8, cid_val.string); 231 + } 232 + 233 + pub fn fetchImage(self: *BskyClient, url: []const u8) ![]const u8 { 234 + var aw: Io.Writer.Allocating = .init(self.allocator); 235 + errdefer aw.deinit(); 236 + 237 + const result = self.client.fetch(.{ 238 + .location = .{ .url = url }, 239 + .method = .GET, 240 + .response_writer = &aw.writer, 241 + }) catch |err| { 242 + std.debug.print("fetch image failed: {}\n", .{err}); 243 + return err; 244 + }; 245 + 246 + if (result.status != .ok) { 247 + aw.deinit(); 248 + return error.FetchFailed; 249 + } 250 + 251 + return try aw.toOwnedSlice(); 252 + } 253 + }; 254 + 255 + fn getIsoTimestamp() []const u8 { 256 + return "2025-01-01T00:00:00.000Z"; 257 + }
bot/src/bufo_bot/__init__.py

This is a binary file and will not be displayed.

bot/src/bufo_bot/__pycache__/__init__.cpython-314.pyc

This is a binary file and will not be displayed.

bot/src/bufo_bot/__pycache__/dry_run.cpython-314.pyc

This is a binary file and will not be displayed.

bot/src/bufo_bot/__pycache__/jetstream.cpython-314.pyc

This is a binary file and will not be displayed.

bot/src/bufo_bot/__pycache__/matcher.cpython-314.pyc

This is a binary file and will not be displayed.

-28
bot/src/bufo_bot/config.py
··· 1 - from pydantic_settings import BaseSettings 2 - 3 - 4 - class Settings(BaseSettings): 5 - bsky_handle: str 6 - bsky_app_password: str 7 - jetstream_endpoint: str = "jetstream2.us-east.bsky.network" 8 - 9 - # minimum words in bufo phrase to consider for matching 10 - min_phrase_words: int = 4 11 - 12 - # must be explicitly enabled to post 13 - posting_enabled: bool = False 14 - 15 - # cooldown: don't repost same bufo within this many minutes 16 - cooldown_minutes: int = 120 17 - 18 - # exclude bufos matching these patterns (comma-separated regex) 19 - exclude_patterns: str = "what-have-you-done,what-have-i-done,sad,crying,cant-take" 20 - 21 - # probability of quoting the matched post (0.0-1.0) 22 - # when not quoting, posts bufo with rkey reference instead 23 - quote_chance: float = 0.5 24 - 25 - model_config = {"env_file": ".env", "extra": "ignore"} 26 - 27 - 28 - settings = Settings()
-55
bot/src/bufo_bot/dry_run.py
··· 1 - """dry run the matcher against the firehose without posting""" 2 - import logging 3 - 4 - import httpx 5 - 6 - from bufo_bot.config import settings 7 - from bufo_bot.jetstream import JetstreamClient 8 - from bufo_bot.matcher import Bufo, BufoMatcher 9 - 10 - logging.basicConfig( 11 - level=logging.INFO, 12 - format="%(asctime)s | %(message)s", 13 - ) 14 - logger = logging.getLogger(__name__) 15 - 16 - 17 - def load_bufos() -> list[Bufo]: 18 - """fetch the list of bufos from the find-bufo API""" 19 - api_url = "https://find-bufo.com/api/search?query=bufo&top_k=2000&alpha=0" 20 - resp = httpx.get(api_url, timeout=30) 21 - resp.raise_for_status() 22 - data = resp.json() 23 - return [ 24 - Bufo(name=r["name"], url=r["url"]) 25 - for r in data.get("results", []) 26 - if r.get("name") and r.get("url") 27 - ] 28 - 29 - 30 - def main(): 31 - bufos = load_bufos() 32 - logger.info(f"loaded {len(bufos)} bufos") 33 - 34 - matcher = BufoMatcher(bufos, min_words=settings.min_phrase_words) 35 - 36 - jetstream = JetstreamClient(settings.jetstream_endpoint) 37 - 38 - match_count = 0 39 - for post in jetstream.stream_posts(): 40 - match = matcher.find_match(post.text) 41 - if match: 42 - match_count += 1 43 - print(f"\n{'='*60}") 44 - print(f"POST: {post.text[:200]}") 45 - print(f"BUFO: {match.name}") 46 - print(f"PHRASE: {match.phrase}") 47 - print(f"{'='*60}") 48 - 49 - if match_count >= 20: 50 - print("\n--- stopping after 20 matches ---") 51 - break 52 - 53 - 54 - if __name__ == "__main__": 55 - main()
-66
bot/src/bufo_bot/jetstream.py
··· 1 - import json 2 - import logging 3 - from collections.abc import Iterator 4 - from dataclasses import dataclass 5 - 6 - from httpx_ws import connect_ws 7 - 8 - logger = logging.getLogger(__name__) 9 - 10 - 11 - @dataclass 12 - class Post: 13 - """a bluesky post from the firehose""" 14 - did: str 15 - rkey: str 16 - text: str 17 - 18 - @property 19 - def uri(self) -> str: 20 - return f"at://{self.did}/app.bsky.feed.post/{self.rkey}" 21 - 22 - 23 - class JetstreamClient: 24 - """subscribes to bluesky jetstream and yields posts""" 25 - 26 - def __init__(self, endpoint: str): 27 - self.endpoint = endpoint 28 - 29 - @property 30 - def url(self) -> str: 31 - return f"wss://{self.endpoint}/subscribe?wantedCollections=app.bsky.feed.post" 32 - 33 - def stream_posts(self) -> Iterator[Post]: 34 - """yield posts from the firehose, reconnecting on failure""" 35 - import time 36 - 37 - while True: 38 - try: 39 - logger.info(f"connecting to jetstream at {self.endpoint}") 40 - with connect_ws(self.url) as ws: 41 - logger.info("connected to jetstream") 42 - while True: 43 - try: 44 - message = ws.receive_text() 45 - data = json.loads(message) 46 - # only process commit messages for new posts 47 - if data.get("kind") != "commit": 48 - continue 49 - commit = data.get("commit", {}) 50 - if commit.get("operation") != "create": 51 - continue 52 - record = commit.get("record", {}) 53 - text = record.get("text", "") 54 - if not text: 55 - continue 56 - yield Post( 57 - did=data.get("did", ""), 58 - rkey=commit.get("rkey", ""), 59 - text=text, 60 - ) 61 - except json.JSONDecodeError as e: 62 - logger.debug(f"skipping malformed message: {e}") 63 - continue 64 - except Exception as e: 65 - logger.warning(f"jetstream connection error: {e}, reconnecting in 5s...") 66 - time.sleep(5)
-236
bot/src/bufo_bot/main.py
··· 1 - import logging 2 - import random 3 - from datetime import datetime, timezone 4 - 5 - import httpx 6 - from atproto import Client, models 7 - 8 - from bufo_bot.config import settings 9 - from bufo_bot.jetstream import JetstreamClient, Post 10 - from bufo_bot.matcher import Bufo, BufoMatcher, BufoMatch 11 - 12 - logging.basicConfig( 13 - level=logging.INFO, 14 - format="%(asctime)s | %(levelname)s | %(message)s", 15 - ) 16 - logger = logging.getLogger(__name__) 17 - 18 - 19 - def get_recent_bufo_names(client: Client, minutes: int) -> set[str]: 20 - """fetch bufo names we've posted recently (from alt text)""" 21 - try: 22 - # fetch our recent posts 23 - feed = client.app.bsky.feed.get_author_feed( 24 - {"actor": settings.bsky_handle, "limit": 50} 25 - ) 26 - cutoff = datetime.now(timezone.utc).timestamp() - (minutes * 60) 27 - recent = set() 28 - 29 - for item in feed.feed: 30 - post = item.post 31 - # parse created_at timestamp 32 - created = post.record.created_at 33 - if isinstance(created, str): 34 - # parse ISO format 35 - try: 36 - ts = datetime.fromisoformat(created.replace("Z", "+00:00")) 37 - if ts.timestamp() < cutoff: 38 - break # posts are sorted newest first 39 - except ValueError: 40 - continue 41 - 42 - # extract alt text from embedded image 43 - embed = post.record.embed 44 - if embed and hasattr(embed, "media"): 45 - media = embed.media 46 - if hasattr(media, "images"): 47 - for img in media.images: 48 - if img.alt: 49 - # convert alt text back to name format (no extension) 50 - recent.add(img.alt.replace(" ", "-")) 51 - 52 - logger.info(f"found {len(recent)} bufos posted in last {minutes} minutes") 53 - return recent 54 - except Exception as e: 55 - logger.warning(f"failed to fetch recent posts: {e}") 56 - return set() 57 - 58 - 59 - def load_bufos() -> list[Bufo]: 60 - """fetch the list of bufos from the find-bufo API""" 61 - params = { 62 - "query": "bufo", 63 - "top_k": 2000, 64 - "alpha": 0, 65 - "exclude": settings.exclude_patterns, 66 - } 67 - 68 - try: 69 - resp = httpx.get("https://find-bufo.com/api/search", params=params, timeout=30) 70 - resp.raise_for_status() 71 - data = resp.json() 72 - bufos = [ 73 - Bufo(name=r["name"], url=r["url"]) 74 - for r in data.get("results", []) 75 - if r.get("name") and r.get("url") 76 - ] 77 - logger.info(f"loaded {len(bufos)} bufos from API") 78 - return bufos 79 - except Exception as e: 80 - logger.error(f"failed to load bufos from API: {e}") 81 - return [] 82 - 83 - 84 - def quote_post_with_bufo(client: Client, post: Post, match: BufoMatch) -> None: 85 - """quote the post with the matching bufo image""" 86 - # fetch the bufo image 87 - logger.info(f"fetching bufo image: {match.url}") 88 - 89 - try: 90 - img_data = httpx.get(match.url, timeout=10).content 91 - except Exception as e: 92 - logger.error(f"failed to fetch bufo image: {e}") 93 - return 94 - 95 - # upload the image blob 96 - try: 97 - uploaded = client.upload_blob(img_data) 98 - except Exception as e: 99 - logger.error(f"failed to upload blob: {e}") 100 - return 101 - 102 - # create the quote post with image 103 - # we need to resolve the post to get a strong ref 104 - try: 105 - post_ref = models.create_strong_ref( 106 - models.ComAtprotoRepoStrongRef.Main(uri=post.uri, cid="") # CID will be fetched 107 - ) 108 - # actually, we need to fetch the post to get its CID 109 - # use the repo.getRecord API 110 - parts = post.uri.replace("at://", "").split("/") 111 - did, collection, rkey = parts[0], parts[1], parts[2] 112 - record = client.app.bsky.feed.post.get(did, rkey) 113 - post_ref = models.create_strong_ref(record) 114 - except Exception as e: 115 - logger.error(f"failed to resolve post: {e}") 116 - return 117 - 118 - # build the embed: quote + image 119 - embed = models.AppBskyEmbedRecordWithMedia.Main( 120 - record=models.AppBskyEmbedRecord.Main(record=post_ref), 121 - media=models.AppBskyEmbedImages.Main( 122 - images=[ 123 - models.AppBskyEmbedImages.Image( 124 - image=uploaded.blob, 125 - alt=match.name.replace("-", " "), 126 - ) 127 - ] 128 - ), 129 - ) 130 - 131 - # post it 132 - try: 133 - client.send_post(text="", embed=embed) 134 - logger.info(f"posted bufo reply: {match.name} (phrase: {match.phrase})") 135 - except Exception as e: 136 - logger.error(f"failed to send post: {e}") 137 - 138 - 139 - def post_bufo_without_quote(client: Client, post: Post, match: BufoMatch) -> None: 140 - """post bufo image with rkey reference (no quote) to reduce spam""" 141 - logger.info(f"fetching bufo image: {match.url}") 142 - 143 - try: 144 - img_data = httpx.get(match.url, timeout=10).content 145 - except Exception as e: 146 - logger.error(f"failed to fetch bufo image: {e}") 147 - return 148 - 149 - try: 150 - uploaded = client.upload_blob(img_data) 151 - except Exception as e: 152 - logger.error(f"failed to upload blob: {e}") 153 - return 154 - 155 - # extract rkey from URI 156 - rkey = post.uri.split("/")[-1] 157 - 158 - # build embed with just the image 159 - embed = models.AppBskyEmbedImages.Main( 160 - images=[ 161 - models.AppBskyEmbedImages.Image( 162 - image=uploaded.blob, 163 - alt=match.name.replace("-", " "), 164 - ) 165 - ] 166 - ) 167 - 168 - # post with rkey reference 169 - text = f"matched {rkey} 🐸 (not quoting to reduce spam)" 170 - try: 171 - client.send_post(text=text, embed=embed) 172 - logger.info(f"posted bufo (no quote): {match.name} for {rkey}") 173 - except Exception as e: 174 - logger.error(f"failed to send post: {e}") 175 - 176 - 177 - def run_bot(): 178 - """main bot loop""" 179 - logger.info("starting bufo bot...") 180 - 181 - # load bufos from API 182 - bufos = load_bufos() 183 - if not bufos: 184 - logger.error("no bufos loaded, exiting") 185 - return 186 - 187 - # initialize matcher 188 - matcher = BufoMatcher(bufos, min_words=settings.min_phrase_words) 189 - 190 - # initialize bluesky client 191 - client = Client() 192 - client.login(settings.bsky_handle, settings.bsky_app_password) 193 - logger.info(f"logged in as {settings.bsky_handle}") 194 - 195 - # connect to jetstream 196 - jetstream = JetstreamClient(settings.jetstream_endpoint) 197 - 198 - # track recently posted bufos (refreshed periodically) 199 - recent_bufos: set[str] = set() 200 - last_refresh = 0.0 201 - 202 - # process posts 203 - for post in jetstream.stream_posts(): 204 - match = matcher.find_match(post.text) 205 - if match: 206 - logger.info(f"match: '{match.phrase}' -> {match.name}") 207 - 208 - if not settings.posting_enabled: 209 - logger.info("posting disabled, skipping") 210 - continue 211 - 212 - # refresh cooldown cache every 5 minutes 213 - now = datetime.now(timezone.utc).timestamp() 214 - if now - last_refresh > 300: 215 - recent_bufos = get_recent_bufo_names(client, settings.cooldown_minutes) 216 - last_refresh = now 217 - 218 - # check cooldown 219 - if match.name in recent_bufos: 220 - logger.info(f"cooldown: {match.name} posted recently, skipping") 221 - continue 222 - 223 - # randomly decide whether to quote or just post with rkey 224 - if random.random() < settings.quote_chance: 225 - quote_post_with_bufo(client, post, match) 226 - else: 227 - post_bufo_without_quote(client, post, match) 228 - recent_bufos.add(match.name) # add to local cache immediately 229 - 230 - 231 - def main(): 232 - run_bot() 233 - 234 - 235 - if __name__ == "__main__": 236 - main()
-73
bot/src/bufo_bot/matcher.py
··· 1 - import re 2 - import logging 3 - from dataclasses import dataclass 4 - 5 - logger = logging.getLogger(__name__) 6 - 7 - 8 - @dataclass 9 - class BufoMatch: 10 - """a matched bufo image""" 11 - name: str 12 - url: str 13 - phrase: str # the matched phrase 14 - 15 - 16 - def extract_phrase(filename: str) -> list[str]: 17 - """extract phrase words from a bufo filename (preserving order)""" 18 - # remove extension 19 - name = filename.rsplit(".", 1)[0] 20 - # replace separators with spaces, extract words 21 - name = re.sub(r"[-_]", " ", name) 22 - words = re.findall(r"[a-z]+", name.lower()) 23 - # strip leading "bufo" if present 24 - if words and words[0] == "bufo": 25 - words = words[1:] 26 - return words 27 - 28 - 29 - @dataclass 30 - class Bufo: 31 - """a bufo with name and URL""" 32 - name: str 33 - url: str 34 - 35 - 36 - class BufoMatcher: 37 - """matches post text against bufo image names using exact phrase matching""" 38 - 39 - def __init__(self, bufos: list[Bufo], min_words: int = 4): 40 - self.min_words = min_words 41 - # precompute phrases from bufo names 42 - self.bufos: list[tuple[Bufo, list[str]]] = [] 43 - for bufo in bufos: 44 - phrase = extract_phrase(bufo.name) 45 - if len(phrase) >= min_words: 46 - self.bufos.append((bufo, phrase)) 47 - logger.info(f"loaded {len(self.bufos)} bufos with >= {min_words} word phrases") 48 - 49 - def find_match(self, post_text: str) -> BufoMatch | None: 50 - """find a matching bufo if post contains exact phrase""" 51 - # normalize post text to lowercase words 52 - post_words = re.findall(r"[a-z]+", post_text.lower()) 53 - if len(post_words) < self.min_words: 54 - return None 55 - 56 - # check each bufo phrase for exact sequential match 57 - for bufo, phrase in self.bufos: 58 - if self._contains_phrase(post_words, phrase): 59 - return BufoMatch( 60 - name=bufo.name, 61 - url=bufo.url, 62 - phrase=" ".join(phrase), 63 - ) 64 - 65 - return None 66 - 67 - def _contains_phrase(self, post_words: list[str], phrase: list[str]) -> bool: 68 - """check if post_words contains phrase as consecutive subsequence""" 69 - phrase_len = len(phrase) 70 - for i in range(len(post_words) - phrase_len + 1): 71 - if post_words[i : i + phrase_len] == phrase: 72 - return True 73 - return False
+47
bot/src/config.zig
··· 1 + const std = @import("std"); 2 + const posix = std.posix; 3 + 4 + pub const Config = struct { 5 + bsky_handle: []const u8, 6 + bsky_app_password: []const u8, 7 + jetstream_endpoint: []const u8, 8 + min_phrase_words: u32, 9 + posting_enabled: bool, 10 + cooldown_minutes: u32, 11 + quote_chance: f32, 12 + exclude_patterns: []const u8, 13 + 14 + pub fn fromEnv() Config { 15 + return .{ 16 + .bsky_handle = posix.getenv("BSKY_HANDLE") orelse "find-bufo.com", 17 + .bsky_app_password = posix.getenv("BSKY_APP_PASSWORD") orelse "", 18 + .jetstream_endpoint = posix.getenv("JETSTREAM_ENDPOINT") orelse "jetstream2.us-east.bsky.network", 19 + .min_phrase_words = parseU32(posix.getenv("MIN_PHRASE_WORDS"), 4), 20 + .posting_enabled = parseBool(posix.getenv("POSTING_ENABLED")), 21 + .cooldown_minutes = parseU32(posix.getenv("COOLDOWN_MINUTES"), 120), 22 + .quote_chance = parseF32(posix.getenv("QUOTE_CHANCE"), 0.5), 23 + .exclude_patterns = posix.getenv("EXCLUDE_PATTERNS") orelse "what-have-you-done,what-have-i-done,sad,crying,cant-take", 24 + }; 25 + } 26 + }; 27 + 28 + fn parseU32(str: ?[]const u8, default: u32) u32 { 29 + if (str) |s| { 30 + return std.fmt.parseInt(u32, s, 10) catch default; 31 + } 32 + return default; 33 + } 34 + 35 + fn parseF32(str: ?[]const u8, default: f32) f32 { 36 + if (str) |s| { 37 + return std.fmt.parseFloat(f32, s) catch default; 38 + } 39 + return default; 40 + } 41 + 42 + fn parseBool(str: ?[]const u8) bool { 43 + if (str) |s| { 44 + return std.mem.eql(u8, s, "true") or std.mem.eql(u8, s, "1"); 45 + } 46 + return false; 47 + }
+147
bot/src/jetstream.zig
··· 1 + const std = @import("std"); 2 + const mem = std.mem; 3 + const json = std.json; 4 + const posix = std.posix; 5 + const Allocator = mem.Allocator; 6 + const websocket = @import("websocket"); 7 + 8 + pub const Post = struct { 9 + uri: []const u8, 10 + text: []const u8, 11 + did: []const u8, 12 + rkey: []const u8, 13 + }; 14 + 15 + pub const JetstreamClient = struct { 16 + allocator: Allocator, 17 + host: []const u8, 18 + callback: *const fn (Post) void, 19 + 20 + pub fn init(allocator: Allocator, host: []const u8, callback: *const fn (Post) void) JetstreamClient { 21 + return .{ 22 + .allocator = allocator, 23 + .host = host, 24 + .callback = callback, 25 + }; 26 + } 27 + 28 + pub fn run(self: *JetstreamClient) void { 29 + // exponential backoff: 1s -> 2s -> 4s -> ... -> 60s cap 30 + var backoff: u64 = 1; 31 + const max_backoff: u64 = 60; 32 + 33 + while (true) { 34 + self.connect() catch |err| { 35 + std.debug.print("jetstream error: {}, reconnecting in {}s...\n", .{ err, backoff }); 36 + }; 37 + posix.nanosleep(backoff, 0); 38 + backoff = @min(backoff * 2, max_backoff); 39 + } 40 + } 41 + 42 + fn connect(self: *JetstreamClient) !void { 43 + const path = "/subscribe?wantedCollections=app.bsky.feed.post"; 44 + 45 + std.debug.print("connecting to wss://{s}{s}\n", .{ self.host, path }); 46 + 47 + var client = websocket.Client.init(self.allocator, .{ 48 + .host = self.host, 49 + .port = 443, 50 + .tls = true, 51 + }) catch |err| { 52 + std.debug.print("websocket client init failed: {}\n", .{err}); 53 + return err; 54 + }; 55 + defer client.deinit(); 56 + 57 + var host_header_buf: [256]u8 = undefined; 58 + const host_header = std.fmt.bufPrint(&host_header_buf, "Host: {s}\r\n", .{self.host}) catch self.host; 59 + 60 + client.handshake(path, .{ .headers = host_header }) catch |err| { 61 + std.debug.print("websocket handshake failed: {}\n", .{err}); 62 + return err; 63 + }; 64 + 65 + std.debug.print("jetstream connected!\n", .{}); 66 + 67 + var handler = Handler{ .allocator = self.allocator, .callback = self.callback }; 68 + client.readLoop(&handler) catch |err| { 69 + std.debug.print("websocket read loop error: {}\n", .{err}); 70 + return err; 71 + }; 72 + } 73 + }; 74 + 75 + const Handler = struct { 76 + allocator: Allocator, 77 + callback: *const fn (Post) void, 78 + msg_count: usize = 0, 79 + 80 + pub fn serverMessage(self: *Handler, data: []const u8) !void { 81 + self.msg_count += 1; 82 + if (self.msg_count % 1000 == 1) { 83 + std.debug.print("jetstream: processed {} messages\n", .{self.msg_count}); 84 + } 85 + self.processMessage(data) catch |err| { 86 + if (err != error.NotAPost) { 87 + std.debug.print("message processing error: {}\n", .{err}); 88 + } 89 + }; 90 + } 91 + 92 + pub fn close(_: *Handler) void { 93 + std.debug.print("jetstream connection closed\n", .{}); 94 + } 95 + 96 + fn processMessage(self: *Handler, payload: []const u8) !void { 97 + // jetstream format: 98 + // { "did": "...", "kind": "commit", "commit": { "collection": "app.bsky.feed.post", "rkey": "...", "record": { "text": "...", ... } } } 99 + const parsed = json.parseFromSlice(json.Value, self.allocator, payload, .{}) catch return error.ParseError; 100 + defer parsed.deinit(); 101 + 102 + const root = parsed.value.object; 103 + 104 + // check kind 105 + const kind = root.get("kind") orelse return error.NotAPost; 106 + if (kind != .string or !mem.eql(u8, kind.string, "commit")) return error.NotAPost; 107 + 108 + // get did 109 + const did_val = root.get("did") orelse return error.NotAPost; 110 + if (did_val != .string) return error.NotAPost; 111 + 112 + // get commit 113 + const commit = root.get("commit") orelse return error.NotAPost; 114 + if (commit != .object) return error.NotAPost; 115 + 116 + // check collection 117 + const collection = commit.object.get("collection") orelse return error.NotAPost; 118 + if (collection != .string or !mem.eql(u8, collection.string, "app.bsky.feed.post")) return error.NotAPost; 119 + 120 + // check operation (create only) 121 + const operation = commit.object.get("operation") orelse return error.NotAPost; 122 + if (operation != .string or !mem.eql(u8, operation.string, "create")) return error.NotAPost; 123 + 124 + // get rkey 125 + const rkey_val = commit.object.get("rkey") orelse return error.NotAPost; 126 + if (rkey_val != .string) return error.NotAPost; 127 + 128 + // get record 129 + const record = commit.object.get("record") orelse return error.NotAPost; 130 + if (record != .object) return error.NotAPost; 131 + 132 + // get text 133 + const text_val = record.object.get("text") orelse return error.NotAPost; 134 + if (text_val != .string) return error.NotAPost; 135 + 136 + // construct uri 137 + var uri_buf: [256]u8 = undefined; 138 + const uri = std.fmt.bufPrint(&uri_buf, "at://{s}/app.bsky.feed.post/{s}", .{ did_val.string, rkey_val.string }) catch return error.UriTooLong; 139 + 140 + self.callback(.{ 141 + .uri = uri, 142 + .text = text_val.string, 143 + .did = did_val.string, 144 + .rkey = rkey_val.string, 145 + }); 146 + } 147 + };
+216
bot/src/main.zig
··· 1 + const std = @import("std"); 2 + const mem = std.mem; 3 + const json = std.json; 4 + const http = std.http; 5 + const Thread = std.Thread; 6 + const Allocator = mem.Allocator; 7 + const config = @import("config.zig"); 8 + const matcher = @import("matcher.zig"); 9 + const jetstream = @import("jetstream.zig"); 10 + const bsky = @import("bsky.zig"); 11 + 12 + var global_state: ?*BotState = null; 13 + 14 + const BotState = struct { 15 + allocator: Allocator, 16 + config: config.Config, 17 + matcher: matcher.Matcher, 18 + bsky_client: bsky.BskyClient, 19 + recent_bufos: std.StringHashMap(i64), // name -> timestamp 20 + mutex: Thread.Mutex = .{}, 21 + rng: std.Random.DefaultPrng, 22 + }; 23 + 24 + pub fn main() !void { 25 + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 26 + defer _ = gpa.deinit(); 27 + const allocator = gpa.allocator(); 28 + 29 + std.debug.print("starting bufo bot...\n", .{}); 30 + 31 + const cfg = config.Config.fromEnv(); 32 + 33 + // load bufos from API 34 + var m = matcher.Matcher.init(allocator, cfg.min_phrase_words); 35 + try loadBufos(allocator, &m, cfg.exclude_patterns); 36 + std.debug.print("loaded {} bufos with >= {} word phrases\n", .{ m.count(), cfg.min_phrase_words }); 37 + 38 + if (m.count() == 0) { 39 + std.debug.print("no bufos loaded, exiting\n", .{}); 40 + return; 41 + } 42 + 43 + // init bluesky client 44 + var bsky_client = bsky.BskyClient.init(allocator, cfg.bsky_handle, cfg.bsky_app_password); 45 + defer bsky_client.deinit(); 46 + 47 + if (cfg.posting_enabled) { 48 + try bsky_client.login(); 49 + } else { 50 + std.debug.print("posting disabled, running in dry-run mode\n", .{}); 51 + } 52 + 53 + // init state 54 + var state = BotState{ 55 + .allocator = allocator, 56 + .config = cfg, 57 + .matcher = m, 58 + .bsky_client = bsky_client, 59 + .recent_bufos = std.StringHashMap(i64).init(allocator), 60 + .rng = std.Random.DefaultPrng.init(@intCast(std.time.timestamp())), 61 + }; 62 + defer state.recent_bufos.deinit(); 63 + 64 + global_state = &state; 65 + 66 + // start jetstream consumer 67 + var js = jetstream.JetstreamClient.init(allocator, cfg.jetstream_endpoint, onPost); 68 + js.run(); 69 + } 70 + 71 + fn onPost(post: jetstream.Post) void { 72 + const state = global_state orelse return; 73 + 74 + // check for match 75 + const match = state.matcher.findMatch(post.text) orelse return; 76 + 77 + std.debug.print("match: '{s}' -> {s}\n", .{ match.phrase, match.name }); 78 + 79 + if (!state.config.posting_enabled) { 80 + std.debug.print("posting disabled, skipping\n", .{}); 81 + return; 82 + } 83 + 84 + state.mutex.lock(); 85 + defer state.mutex.unlock(); 86 + 87 + // check cooldown 88 + const now = std.time.timestamp(); 89 + const cooldown_secs = @as(i64, @intCast(state.config.cooldown_minutes)) * 60; 90 + 91 + if (state.recent_bufos.get(match.name)) |last_posted| { 92 + if (now - last_posted < cooldown_secs) { 93 + std.debug.print("cooldown: {s} posted recently, skipping\n", .{match.name}); 94 + return; 95 + } 96 + } 97 + 98 + // random quote chance 99 + const should_quote = state.rng.random().float(f32) < state.config.quote_chance; 100 + 101 + // fetch bufo image 102 + const img_data = state.bsky_client.fetchImage(match.url) catch |err| { 103 + std.debug.print("failed to fetch bufo image: {}\n", .{err}); 104 + return; 105 + }; 106 + defer state.allocator.free(img_data); 107 + 108 + // determine content type from URL 109 + const content_type = if (mem.endsWith(u8, match.url, ".gif")) 110 + "image/gif" 111 + else if (mem.endsWith(u8, match.url, ".png")) 112 + "image/png" 113 + else 114 + "image/jpeg"; 115 + 116 + // upload blob 117 + const blob_json = state.bsky_client.uploadBlob(img_data, content_type) catch |err| { 118 + std.debug.print("failed to upload blob: {}\n", .{err}); 119 + return; 120 + }; 121 + defer state.allocator.free(blob_json); 122 + 123 + // build alt text (name without extension, dashes to spaces) 124 + var alt_buf: [128]u8 = undefined; 125 + var alt_len: usize = 0; 126 + for (match.name) |c| { 127 + if (c == '-') { 128 + alt_buf[alt_len] = ' '; 129 + } else if (c == '.') { 130 + break; // stop at extension 131 + } else { 132 + alt_buf[alt_len] = c; 133 + } 134 + alt_len += 1; 135 + if (alt_len >= alt_buf.len - 1) break; 136 + } 137 + const alt_text = alt_buf[0..alt_len]; 138 + 139 + if (should_quote) { 140 + // get post CID for quote 141 + const cid = state.bsky_client.getPostCid(post.uri) catch |err| { 142 + std.debug.print("failed to get post CID: {}\n", .{err}); 143 + return; 144 + }; 145 + defer state.allocator.free(cid); 146 + 147 + state.bsky_client.createQuotePost(post.uri, cid, blob_json, alt_text) catch |err| { 148 + std.debug.print("failed to create quote post: {}\n", .{err}); 149 + return; 150 + }; 151 + std.debug.print("posted bufo quote: {s} (phrase: {s})\n", .{ match.name, match.phrase }); 152 + } else { 153 + // post without quote 154 + var text_buf: [256]u8 = undefined; 155 + const text = std.fmt.bufPrint(&text_buf, "matched {s} (not quoting to reduce spam)", .{post.rkey}) catch "matched a post"; 156 + 157 + state.bsky_client.createSimplePost(text, blob_json, alt_text) catch |err| { 158 + std.debug.print("failed to create simple post: {}\n", .{err}); 159 + return; 160 + }; 161 + std.debug.print("posted bufo (no quote): {s} for {s}\n", .{ match.name, post.rkey }); 162 + } 163 + 164 + // update cooldown cache 165 + state.recent_bufos.put(match.name, now) catch {}; 166 + } 167 + 168 + fn loadBufos(allocator: Allocator, m: *matcher.Matcher, exclude_patterns: []const u8) !void { 169 + var client = http.Client{ .allocator = allocator }; 170 + defer client.deinit(); 171 + 172 + var url_buf: [512]u8 = undefined; 173 + const url = std.fmt.bufPrint(&url_buf, "https://find-bufo.com/api/search?query=bufo&top_k=2000&alpha=0&exclude={s}", .{exclude_patterns}) catch return error.UrlTooLong; 174 + 175 + var aw: std.Io.Writer.Allocating = .init(allocator); 176 + defer aw.deinit(); 177 + 178 + const result = client.fetch(.{ 179 + .location = .{ .url = url }, 180 + .method = .GET, 181 + .response_writer = &aw.writer, 182 + }) catch |err| { 183 + std.debug.print("failed to fetch bufos: {}\n", .{err}); 184 + return err; 185 + }; 186 + 187 + if (result.status != .ok) { 188 + std.debug.print("failed to fetch bufos, status: {}\n", .{result.status}); 189 + return error.FetchFailed; 190 + } 191 + 192 + const response_list = aw.toArrayList(); 193 + const response = response_list.items; 194 + 195 + const parsed = json.parseFromSlice(json.Value, allocator, response, .{}) catch return error.ParseError; 196 + defer parsed.deinit(); 197 + 198 + const results = parsed.value.object.get("results") orelse return; 199 + if (results != .array) return; 200 + 201 + var loaded: usize = 0; 202 + for (results.array.items) |item| { 203 + if (item != .object) continue; 204 + 205 + const name_val = item.object.get("name") orelse continue; 206 + if (name_val != .string) continue; 207 + 208 + const url_val = item.object.get("url") orelse continue; 209 + if (url_val != .string) continue; 210 + 211 + m.addBufo(name_val.string, url_val.string) catch continue; 212 + loaded += 1; 213 + } 214 + 215 + std.debug.print("loaded {} bufos from API\n", .{loaded}); 216 + }
+164
bot/src/matcher.zig
··· 1 + const std = @import("std"); 2 + const mem = std.mem; 3 + const Allocator = mem.Allocator; 4 + 5 + pub const Bufo = struct { 6 + name: []const u8, 7 + url: []const u8, 8 + phrase: []const []const u8, 9 + }; 10 + 11 + pub const Match = struct { 12 + name: []const u8, 13 + url: []const u8, 14 + phrase: []const u8, 15 + }; 16 + 17 + pub const Matcher = struct { 18 + bufos: std.ArrayList(Bufo) = .{}, 19 + allocator: Allocator, 20 + min_words: u32, 21 + 22 + pub fn init(allocator: Allocator, min_words: u32) Matcher { 23 + return .{ 24 + .allocator = allocator, 25 + .min_words = min_words, 26 + }; 27 + } 28 + 29 + pub fn deinit(self: *Matcher) void { 30 + for (self.bufos.items) |bufo| { 31 + self.allocator.free(bufo.name); 32 + self.allocator.free(bufo.url); 33 + for (bufo.phrase) |word| { 34 + self.allocator.free(word); 35 + } 36 + self.allocator.free(bufo.phrase); 37 + } 38 + self.bufos.deinit(self.allocator); 39 + } 40 + 41 + pub fn addBufo(self: *Matcher, name: []const u8, url: []const u8) !void { 42 + const phrase = try extractPhrase(self.allocator, name); 43 + 44 + if (phrase.len < self.min_words) { 45 + for (phrase) |word| self.allocator.free(word); 46 + self.allocator.free(phrase); 47 + return; 48 + } 49 + 50 + try self.bufos.append(self.allocator, .{ 51 + .name = try self.allocator.dupe(u8, name), 52 + .url = try self.allocator.dupe(u8, url), 53 + .phrase = phrase, 54 + }); 55 + } 56 + 57 + pub fn findMatch(self: *Matcher, text: []const u8) ?Match { 58 + var words: std.ArrayList([]const u8) = .{}; 59 + defer words.deinit(self.allocator); 60 + 61 + var i: usize = 0; 62 + while (i < text.len) { 63 + while (i < text.len and !isAlpha(text[i])) : (i += 1) {} 64 + if (i >= text.len) break; 65 + 66 + const start = i; 67 + while (i < text.len and isAlpha(text[i])) : (i += 1) {} 68 + 69 + const word = text[start..i]; 70 + if (word.len > 0) { 71 + words.append(self.allocator, word) catch continue; 72 + } 73 + } 74 + 75 + for (self.bufos.items) |bufo| { 76 + if (containsPhrase(words.items, bufo.phrase)) { 77 + var phrase_buf: [256]u8 = undefined; 78 + var phrase_len: usize = 0; 79 + for (bufo.phrase, 0..) |word, j| { 80 + if (j > 0) { 81 + phrase_buf[phrase_len] = ' '; 82 + phrase_len += 1; 83 + } 84 + @memcpy(phrase_buf[phrase_len .. phrase_len + word.len], word); 85 + phrase_len += word.len; 86 + } 87 + return .{ 88 + .name = bufo.name, 89 + .url = bufo.url, 90 + .phrase = phrase_buf[0..phrase_len], 91 + }; 92 + } 93 + } 94 + return null; 95 + } 96 + 97 + pub fn count(self: *Matcher) usize { 98 + return self.bufos.items.len; 99 + } 100 + }; 101 + 102 + fn extractPhrase(allocator: Allocator, name: []const u8) ![]const []const u8 { 103 + var start: usize = 0; 104 + if (mem.startsWith(u8, name, "bufo-")) { 105 + start = 5; 106 + } 107 + var end = name.len; 108 + if (mem.endsWith(u8, name, ".gif")) { 109 + end -= 4; 110 + } else if (mem.endsWith(u8, name, ".png")) { 111 + end -= 4; 112 + } else if (mem.endsWith(u8, name, ".jpg")) { 113 + end -= 4; 114 + } else if (mem.endsWith(u8, name, ".jpeg")) { 115 + end -= 5; 116 + } 117 + 118 + const slug = name[start..end]; 119 + 120 + var words: std.ArrayList([]const u8) = .{}; 121 + errdefer { 122 + for (words.items) |word| allocator.free(word); 123 + words.deinit(allocator); 124 + } 125 + 126 + var iter = mem.splitScalar(u8, slug, '-'); 127 + while (iter.next()) |word| { 128 + if (word.len > 0) { 129 + const lower = try allocator.alloc(u8, word.len); 130 + for (word, 0..) |c, j| { 131 + lower[j] = std.ascii.toLower(c); 132 + } 133 + try words.append(allocator, lower); 134 + } 135 + } 136 + 137 + return try words.toOwnedSlice(allocator); 138 + } 139 + 140 + fn containsPhrase(post_words: []const []const u8, phrase: []const []const u8) bool { 141 + if (phrase.len == 0 or post_words.len < phrase.len) return false; 142 + 143 + outer: for (0..post_words.len - phrase.len + 1) |i| { 144 + for (phrase, 0..) |phrase_word, j| { 145 + if (!eqlIgnoreCase(post_words[i + j], phrase_word)) { 146 + continue :outer; 147 + } 148 + } 149 + return true; 150 + } 151 + return false; 152 + } 153 + 154 + fn eqlIgnoreCase(a: []const u8, b: []const u8) bool { 155 + if (a.len != b.len) return false; 156 + for (a, b) |ca, cb| { 157 + if (std.ascii.toLower(ca) != std.ascii.toLower(cb)) return false; 158 + } 159 + return true; 160 + } 161 + 162 + fn isAlpha(c: u8) bool { 163 + return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z'); 164 + }
-582
bot/uv.lock
··· 1 - version = 1 2 - revision = 3 3 - requires-python = ">=3.11" 4 - 5 - [[package]] 6 - name = "annotated-types" 7 - version = "0.7.0" 8 - source = { registry = "https://pypi.org/simple" } 9 - sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } 10 - wheels = [ 11 - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, 12 - ] 13 - 14 - [[package]] 15 - name = "anyio" 16 - version = "4.12.0" 17 - source = { registry = "https://pypi.org/simple" } 18 - dependencies = [ 19 - { name = "idna" }, 20 - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 21 - ] 22 - sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } 23 - wheels = [ 24 - { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, 25 - ] 26 - 27 - [[package]] 28 - name = "atproto" 29 - version = "0.0.65" 30 - source = { registry = "https://pypi.org/simple" } 31 - dependencies = [ 32 - { name = "click" }, 33 - { name = "cryptography" }, 34 - { name = "dnspython" }, 35 - { name = "httpx" }, 36 - { name = "libipld" }, 37 - { name = "pydantic" }, 38 - { name = "typing-extensions" }, 39 - { name = "websockets" }, 40 - ] 41 - sdist = { url = "https://files.pythonhosted.org/packages/b2/0f/b6e26f99ef730f1e5779f5833ba794343df78ee1e02041d3b05bd5005066/atproto-0.0.65.tar.gz", hash = "sha256:027c6ed98746a9e6f1bb24bc18db84b80b386037709ff3af9ef927dce3dd4938", size = 210996, upload-time = "2025-12-08T15:53:44.585Z" } 42 - wheels = [ 43 - { url = "https://files.pythonhosted.org/packages/e3/d9/360149e7bd9bac580496ce9fddc0ef320b3813aadd72be6abc011600862d/atproto-0.0.65-py3-none-any.whl", hash = "sha256:ea53dea57454c9e56318b5d25ceb35854d60ba238b38b0e5ca79aa1a2df85846", size = 446650, upload-time = "2025-12-08T15:53:43.029Z" }, 44 - ] 45 - 46 - [[package]] 47 - name = "bufo-bot" 48 - version = "0.1.0" 49 - source = { editable = "." } 50 - dependencies = [ 51 - { name = "atproto" }, 52 - { name = "httpx" }, 53 - { name = "httpx-ws" }, 54 - { name = "pydantic-settings" }, 55 - ] 56 - 57 - [package.metadata] 58 - requires-dist = [ 59 - { name = "atproto", specifier = ">=0.0.59" }, 60 - { name = "httpx", specifier = ">=0.28.1" }, 61 - { name = "httpx-ws", specifier = ">=0.7.1" }, 62 - { name = "pydantic-settings", specifier = ">=2.7.1" }, 63 - ] 64 - 65 - [[package]] 66 - name = "certifi" 67 - version = "2025.11.12" 68 - source = { registry = "https://pypi.org/simple" } 69 - sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } 70 - wheels = [ 71 - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, 72 - ] 73 - 74 - [[package]] 75 - name = "cffi" 76 - version = "2.0.0" 77 - source = { registry = "https://pypi.org/simple" } 78 - dependencies = [ 79 - { name = "pycparser", marker = "implementation_name != 'PyPy'" }, 80 - ] 81 - sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } 82 - wheels = [ 83 - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, 84 - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, 85 - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, 86 - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, 87 - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, 88 - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, 89 - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, 90 - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, 91 - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, 92 - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, 93 - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, 94 - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, 95 - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, 96 - { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, 97 - { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, 98 - { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, 99 - { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, 100 - { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, 101 - { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, 102 - { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, 103 - { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, 104 - { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, 105 - { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, 106 - { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, 107 - { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, 108 - { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, 109 - { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, 110 - { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, 111 - { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, 112 - { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, 113 - { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, 114 - { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, 115 - { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, 116 - { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, 117 - { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, 118 - { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, 119 - { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, 120 - { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, 121 - { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, 122 - { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, 123 - { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, 124 - { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, 125 - { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, 126 - { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, 127 - { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, 128 - { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, 129 - { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, 130 - { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, 131 - { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, 132 - { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, 133 - { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, 134 - { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, 135 - { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, 136 - { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, 137 - { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, 138 - { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, 139 - { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, 140 - { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, 141 - { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, 142 - ] 143 - 144 - [[package]] 145 - name = "click" 146 - version = "8.3.1" 147 - source = { registry = "https://pypi.org/simple" } 148 - dependencies = [ 149 - { name = "colorama", marker = "sys_platform == 'win32'" }, 150 - ] 151 - sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } 152 - wheels = [ 153 - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, 154 - ] 155 - 156 - [[package]] 157 - name = "colorama" 158 - version = "0.4.6" 159 - source = { registry = "https://pypi.org/simple" } 160 - sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 161 - wheels = [ 162 - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 163 - ] 164 - 165 - [[package]] 166 - name = "cryptography" 167 - version = "46.0.3" 168 - source = { registry = "https://pypi.org/simple" } 169 - dependencies = [ 170 - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, 171 - ] 172 - sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } 173 - wheels = [ 174 - { url = "https://files.pythonhosted.org/packages/1d/42/9c391dd801d6cf0d561b5890549d4b27bafcc53b39c31a817e69d87c625b/cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a", size = 7225004, upload-time = "2025-10-15T23:16:52.239Z" }, 175 - { url = "https://files.pythonhosted.org/packages/1c/67/38769ca6b65f07461eb200e85fc1639b438bdc667be02cf7f2cd6a64601c/cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc", size = 4296667, upload-time = "2025-10-15T23:16:54.369Z" }, 176 - { url = "https://files.pythonhosted.org/packages/5c/49/498c86566a1d80e978b42f0d702795f69887005548c041636df6ae1ca64c/cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d", size = 4450807, upload-time = "2025-10-15T23:16:56.414Z" }, 177 - { url = "https://files.pythonhosted.org/packages/4b/0a/863a3604112174c8624a2ac3c038662d9e59970c7f926acdcfaed8d61142/cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb", size = 4299615, upload-time = "2025-10-15T23:16:58.442Z" }, 178 - { url = "https://files.pythonhosted.org/packages/64/02/b73a533f6b64a69f3cd3872acb6ebc12aef924d8d103133bb3ea750dc703/cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849", size = 4016800, upload-time = "2025-10-15T23:17:00.378Z" }, 179 - { url = "https://files.pythonhosted.org/packages/25/d5/16e41afbfa450cde85a3b7ec599bebefaef16b5c6ba4ec49a3532336ed72/cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8", size = 4984707, upload-time = "2025-10-15T23:17:01.98Z" }, 180 - { url = "https://files.pythonhosted.org/packages/c9/56/e7e69b427c3878352c2fb9b450bd0e19ed552753491d39d7d0a2f5226d41/cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec", size = 4482541, upload-time = "2025-10-15T23:17:04.078Z" }, 181 - { url = "https://files.pythonhosted.org/packages/78/f6/50736d40d97e8483172f1bb6e698895b92a223dba513b0ca6f06b2365339/cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91", size = 4299464, upload-time = "2025-10-15T23:17:05.483Z" }, 182 - { url = "https://files.pythonhosted.org/packages/00/de/d8e26b1a855f19d9994a19c702fa2e93b0456beccbcfe437eda00e0701f2/cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e", size = 4950838, upload-time = "2025-10-15T23:17:07.425Z" }, 183 - { url = "https://files.pythonhosted.org/packages/8f/29/798fc4ec461a1c9e9f735f2fc58741b0daae30688f41b2497dcbc9ed1355/cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926", size = 4481596, upload-time = "2025-10-15T23:17:09.343Z" }, 184 - { url = "https://files.pythonhosted.org/packages/15/8d/03cd48b20a573adfff7652b76271078e3045b9f49387920e7f1f631d125e/cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71", size = 4426782, upload-time = "2025-10-15T23:17:11.22Z" }, 185 - { url = "https://files.pythonhosted.org/packages/fa/b1/ebacbfe53317d55cf33165bda24c86523497a6881f339f9aae5c2e13e57b/cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac", size = 4698381, upload-time = "2025-10-15T23:17:12.829Z" }, 186 - { url = "https://files.pythonhosted.org/packages/96/92/8a6a9525893325fc057a01f654d7efc2c64b9de90413adcf605a85744ff4/cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018", size = 3055988, upload-time = "2025-10-15T23:17:14.65Z" }, 187 - { url = "https://files.pythonhosted.org/packages/7e/bf/80fbf45253ea585a1e492a6a17efcb93467701fa79e71550a430c5e60df0/cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb", size = 3514451, upload-time = "2025-10-15T23:17:16.142Z" }, 188 - { url = "https://files.pythonhosted.org/packages/2e/af/9b302da4c87b0beb9db4e756386a7c6c5b8003cd0e742277888d352ae91d/cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c", size = 2928007, upload-time = "2025-10-15T23:17:18.04Z" }, 189 - { url = "https://files.pythonhosted.org/packages/f5/e2/a510aa736755bffa9d2f75029c229111a1d02f8ecd5de03078f4c18d91a3/cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217", size = 7158012, upload-time = "2025-10-15T23:17:19.982Z" }, 190 - { url = "https://files.pythonhosted.org/packages/73/dc/9aa866fbdbb95b02e7f9d086f1fccfeebf8953509b87e3f28fff927ff8a0/cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5", size = 4288728, upload-time = "2025-10-15T23:17:21.527Z" }, 191 - { url = "https://files.pythonhosted.org/packages/c5/fd/bc1daf8230eaa075184cbbf5f8cd00ba9db4fd32d63fb83da4671b72ed8a/cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715", size = 4435078, upload-time = "2025-10-15T23:17:23.042Z" }, 192 - { url = "https://files.pythonhosted.org/packages/82/98/d3bd5407ce4c60017f8ff9e63ffee4200ab3e23fe05b765cab805a7db008/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54", size = 4293460, upload-time = "2025-10-15T23:17:24.885Z" }, 193 - { url = "https://files.pythonhosted.org/packages/26/e9/e23e7900983c2b8af7a08098db406cf989d7f09caea7897e347598d4cd5b/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459", size = 3995237, upload-time = "2025-10-15T23:17:26.449Z" }, 194 - { url = "https://files.pythonhosted.org/packages/91/15/af68c509d4a138cfe299d0d7ddb14afba15233223ebd933b4bbdbc7155d3/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422", size = 4967344, upload-time = "2025-10-15T23:17:28.06Z" }, 195 - { url = "https://files.pythonhosted.org/packages/ca/e3/8643d077c53868b681af077edf6b3cb58288b5423610f21c62aadcbe99f4/cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7", size = 4466564, upload-time = "2025-10-15T23:17:29.665Z" }, 196 - { url = "https://files.pythonhosted.org/packages/0e/43/c1e8726fa59c236ff477ff2b5dc071e54b21e5a1e51aa2cee1676f1c986f/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044", size = 4292415, upload-time = "2025-10-15T23:17:31.686Z" }, 197 - { url = "https://files.pythonhosted.org/packages/42/f9/2f8fefdb1aee8a8e3256a0568cffc4e6d517b256a2fe97a029b3f1b9fe7e/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665", size = 4931457, upload-time = "2025-10-15T23:17:33.478Z" }, 198 - { url = "https://files.pythonhosted.org/packages/79/30/9b54127a9a778ccd6d27c3da7563e9f2d341826075ceab89ae3b41bf5be2/cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3", size = 4466074, upload-time = "2025-10-15T23:17:35.158Z" }, 199 - { url = "https://files.pythonhosted.org/packages/ac/68/b4f4a10928e26c941b1b6a179143af9f4d27d88fe84a6a3c53592d2e76bf/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20", size = 4420569, upload-time = "2025-10-15T23:17:37.188Z" }, 200 - { url = "https://files.pythonhosted.org/packages/a3/49/3746dab4c0d1979888f125226357d3262a6dd40e114ac29e3d2abdf1ec55/cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de", size = 4681941, upload-time = "2025-10-15T23:17:39.236Z" }, 201 - { url = "https://files.pythonhosted.org/packages/fd/30/27654c1dbaf7e4a3531fa1fc77986d04aefa4d6d78259a62c9dc13d7ad36/cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914", size = 3022339, upload-time = "2025-10-15T23:17:40.888Z" }, 202 - { url = "https://files.pythonhosted.org/packages/f6/30/640f34ccd4d2a1bc88367b54b926b781b5a018d65f404d409aba76a84b1c/cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db", size = 3494315, upload-time = "2025-10-15T23:17:42.769Z" }, 203 - { url = "https://files.pythonhosted.org/packages/ba/8b/88cc7e3bd0a8e7b861f26981f7b820e1f46aa9d26cc482d0feba0ecb4919/cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21", size = 2919331, upload-time = "2025-10-15T23:17:44.468Z" }, 204 - { url = "https://files.pythonhosted.org/packages/fd/23/45fe7f376a7df8daf6da3556603b36f53475a99ce4faacb6ba2cf3d82021/cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936", size = 7218248, upload-time = "2025-10-15T23:17:46.294Z" }, 205 - { url = "https://files.pythonhosted.org/packages/27/32/b68d27471372737054cbd34c84981f9edbc24fe67ca225d389799614e27f/cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683", size = 4294089, upload-time = "2025-10-15T23:17:48.269Z" }, 206 - { url = "https://files.pythonhosted.org/packages/26/42/fa8389d4478368743e24e61eea78846a0006caffaf72ea24a15159215a14/cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d", size = 4440029, upload-time = "2025-10-15T23:17:49.837Z" }, 207 - { url = "https://files.pythonhosted.org/packages/5f/eb/f483db0ec5ac040824f269e93dd2bd8a21ecd1027e77ad7bdf6914f2fd80/cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0", size = 4297222, upload-time = "2025-10-15T23:17:51.357Z" }, 208 - { url = "https://files.pythonhosted.org/packages/fd/cf/da9502c4e1912cb1da3807ea3618a6829bee8207456fbbeebc361ec38ba3/cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc", size = 4012280, upload-time = "2025-10-15T23:17:52.964Z" }, 209 - { url = "https://files.pythonhosted.org/packages/6b/8f/9adb86b93330e0df8b3dcf03eae67c33ba89958fc2e03862ef1ac2b42465/cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3", size = 4978958, upload-time = "2025-10-15T23:17:54.965Z" }, 210 - { url = "https://files.pythonhosted.org/packages/d1/a0/5fa77988289c34bdb9f913f5606ecc9ada1adb5ae870bd0d1054a7021cc4/cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971", size = 4473714, upload-time = "2025-10-15T23:17:56.754Z" }, 211 - { url = "https://files.pythonhosted.org/packages/14/e5/fc82d72a58d41c393697aa18c9abe5ae1214ff6f2a5c18ac470f92777895/cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac", size = 4296970, upload-time = "2025-10-15T23:17:58.588Z" }, 212 - { url = "https://files.pythonhosted.org/packages/78/06/5663ed35438d0b09056973994f1aec467492b33bd31da36e468b01ec1097/cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04", size = 4940236, upload-time = "2025-10-15T23:18:00.897Z" }, 213 - { url = "https://files.pythonhosted.org/packages/fc/59/873633f3f2dcd8a053b8dd1d38f783043b5fce589c0f6988bf55ef57e43e/cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506", size = 4472642, upload-time = "2025-10-15T23:18:02.749Z" }, 214 - { url = "https://files.pythonhosted.org/packages/3d/39/8e71f3930e40f6877737d6f69248cf74d4e34b886a3967d32f919cc50d3b/cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963", size = 4423126, upload-time = "2025-10-15T23:18:04.85Z" }, 215 - { url = "https://files.pythonhosted.org/packages/cd/c7/f65027c2810e14c3e7268353b1681932b87e5a48e65505d8cc17c99e36ae/cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4", size = 4686573, upload-time = "2025-10-15T23:18:06.908Z" }, 216 - { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, 217 - { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, 218 - { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, 219 - { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, 220 - { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, 221 - { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, 222 - { url = "https://files.pythonhosted.org/packages/99/55/181022996c4063fc0e7666a47049a1ca705abb9c8a13830f074edb347495/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717", size = 4242957, upload-time = "2025-10-15T23:18:22.18Z" }, 223 - { url = "https://files.pythonhosted.org/packages/ba/af/72cd6ef29f9c5f731251acadaeb821559fe25f10852f44a63374c9ca08c1/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9", size = 4409447, upload-time = "2025-10-15T23:18:24.209Z" }, 224 - { url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" }, 225 - ] 226 - 227 - [[package]] 228 - name = "dnspython" 229 - version = "2.8.0" 230 - source = { registry = "https://pypi.org/simple" } 231 - sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } 232 - wheels = [ 233 - { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, 234 - ] 235 - 236 - [[package]] 237 - name = "h11" 238 - version = "0.16.0" 239 - source = { registry = "https://pypi.org/simple" } 240 - sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 241 - wheels = [ 242 - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 243 - ] 244 - 245 - [[package]] 246 - name = "httpcore" 247 - version = "1.0.9" 248 - source = { registry = "https://pypi.org/simple" } 249 - dependencies = [ 250 - { name = "certifi" }, 251 - { name = "h11" }, 252 - ] 253 - sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } 254 - wheels = [ 255 - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, 256 - ] 257 - 258 - [[package]] 259 - name = "httpx" 260 - version = "0.28.1" 261 - source = { registry = "https://pypi.org/simple" } 262 - dependencies = [ 263 - { name = "anyio" }, 264 - { name = "certifi" }, 265 - { name = "httpcore" }, 266 - { name = "idna" }, 267 - ] 268 - sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 269 - wheels = [ 270 - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 271 - ] 272 - 273 - [[package]] 274 - name = "httpx-ws" 275 - version = "0.8.2" 276 - source = { registry = "https://pypi.org/simple" } 277 - dependencies = [ 278 - { name = "anyio" }, 279 - { name = "httpcore" }, 280 - { name = "httpx" }, 281 - { name = "wsproto" }, 282 - ] 283 - sdist = { url = "https://files.pythonhosted.org/packages/4a/32/6f7198f55d94063ea84487a31cdd3e149d2702dc0804fc5de06ed12ef2c2/httpx_ws-0.8.2.tar.gz", hash = "sha256:ba0d4aa76e1c8a27bd5e88984ecdcdc28f7bf30b40cb0989a4c1438d07fa52c7", size = 105734, upload-time = "2025-11-07T12:57:36.566Z" } 284 - wheels = [ 285 - { url = "https://files.pythonhosted.org/packages/29/cd/2008972ddc4c2139b9813d8a097e53dcc74b2a16a85b4069294457954232/httpx_ws-0.8.2-py3-none-any.whl", hash = "sha256:f8898ddb84cbf98c562e8e796675bc68c215fa1d453d54a7fcd935aca8198cc8", size = 15404, upload-time = "2025-11-07T12:57:35.176Z" }, 286 - ] 287 - 288 - [[package]] 289 - name = "idna" 290 - version = "3.11" 291 - source = { registry = "https://pypi.org/simple" } 292 - sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } 293 - wheels = [ 294 - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, 295 - ] 296 - 297 - [[package]] 298 - name = "libipld" 299 - version = "3.3.2" 300 - source = { registry = "https://pypi.org/simple" } 301 - sdist = { url = "https://files.pythonhosted.org/packages/83/2b/4e84e033268d2717c692e5034e016b1d82501736cd297586fd1c7378ccd5/libipld-3.3.2.tar.gz", hash = "sha256:7e85ccd9136110e63943d95232b193c893e369c406273d235160e5cc4b39c9ce", size = 4401259, upload-time = "2025-12-05T13:00:20.34Z" } 302 - wheels = [ 303 - { url = "https://files.pythonhosted.org/packages/c8/51/9b4472b60313f68c6ecfd7ced560ae5dc9add1f767d1e842c2647671cca3/libipld-3.3.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d1549bfab54a1f422ec48d3141c9ec643444d8e80135b2a6e8ec8e109df05ff3", size = 267959, upload-time = "2025-12-05T12:58:07.076Z" }, 304 - { url = "https://files.pythonhosted.org/packages/d2/bf/ef17b76a69f7de01100dbfb94236892ef4f3dc9f5779c863ccfc71276d6b/libipld-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d9f8849acfb2e13d10aa4e8095c6eb65db7f431d3a38c60760de0f0abebcd58d", size = 259820, upload-time = "2025-12-05T12:58:08.879Z" }, 305 - { url = "https://files.pythonhosted.org/packages/01/3f/052cc8f8c017d023ba8c154ae991c6b0bfcd5a65a95a5d3e7b90ee6117ec/libipld-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf7f553e13916b67572c8ae8ea1b3227d709dfc4d98699a0451027174dc3739a", size = 279995, upload-time = "2025-12-05T12:58:11.268Z" }, 306 - { url = "https://files.pythonhosted.org/packages/b2/96/534a43dfd057b292c86e2e53eab0cdd12cbffce6e6d1375c929546a4a53d/libipld-3.3.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f09281bebc3c4d397a58606663ee915290702db0323366f9f1bd30b8b4a3fc1", size = 290046, upload-time = "2025-12-05T12:58:12.512Z" }, 307 - { url = "https://files.pythonhosted.org/packages/58/7b/8bd7e036c8d9e496644c103b4ec144085a83434548665a9f6028040077b8/libipld-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cb4e0cd56d66c2b751eef7288aba919a8a73f320c8f8b7060d522fdffb3cd5b", size = 316121, upload-time = "2025-12-05T12:58:13.872Z" }, 308 - { url = "https://files.pythonhosted.org/packages/93/24/0fcc5b27a4541009e10f7f475623466017b1356134a035dabcb562e04c19/libipld-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6c2817982506d1ccda516da7d6cc2662b58a363fc84a163bea297ff61b789106", size = 329905, upload-time = "2025-12-05T12:58:15.725Z" }, 309 - { url = "https://files.pythonhosted.org/packages/48/15/1831cd2aad430e995c10ad80696dd9ca4eeb0e42d0953379aa27494ae92e/libipld-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b438d42efc857768db97f2a5ca8114b933e73b827d91344ec9dac6734687f060", size = 282861, upload-time = "2025-12-05T12:58:17.317Z" }, 310 - { url = "https://files.pythonhosted.org/packages/17/44/93f92a20b4fbf64dd1718c18d9e13aae8abeff0734f15f259c60288730f9/libipld-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:249b74fcd3ae8bb6d0c9b3e0ec23a75db60f7b40f8b07ef378dfc9227c921ef5", size = 307929, upload-time = "2025-12-05T12:58:18.547Z" }, 311 - { url = "https://files.pythonhosted.org/packages/67/7c/56003d61a8c0046d273fcbd4e0d2f361d69238d1b6bab1a97bedbe10d699/libipld-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1767f287c4137521f6b2dfa30583030b128174515dc4ddc481598b570de72ca4", size = 463622, upload-time = "2025-12-05T12:58:20.132Z" }, 312 - { url = "https://files.pythonhosted.org/packages/e5/47/f25c8830afc9c6d60e8033d61b1ad64531c86edaf57467d88764227831a0/libipld-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf0c82b53b595cd91c3920081acd73b287c5aeb81e756e6b1a636a10e01eaaa3", size = 459427, upload-time = "2025-12-05T12:58:21.616Z" }, 313 - { url = "https://files.pythonhosted.org/packages/7f/b2/6ee177c24726cf325d7524aa5ce5c84368d2e2aa0bcb4f5852c7f09c51a1/libipld-3.3.2-cp311-cp311-win32.whl", hash = "sha256:ad07c8574d59088ed8bcec7cd21544a50f51a324290e77b0b042a0e4c1ed1b07", size = 159359, upload-time = "2025-12-05T12:58:22.778Z" }, 314 - { url = "https://files.pythonhosted.org/packages/d4/86/7cfe92c1f49949a03f9d1916977a80211d315c5e229744c021dec8b39969/libipld-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:847d6f481f8e8e10347479490db824cc568689e7bc1a9f5e5aa67235494dcdf6", size = 158424, upload-time = "2025-12-05T12:58:23.983Z" }, 315 - { url = "https://files.pythonhosted.org/packages/e4/1d/417288feaf3d74e6ba772d5f1fb0b4bcf1928db688fb92e2f69b32a4d364/libipld-3.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:d6ed1bbdd5380a23ca84d4460c65f46d85f12ca860ef801212806ffe5ad497fa", size = 149127, upload-time = "2025-12-05T12:58:25.283Z" }, 316 - { url = "https://files.pythonhosted.org/packages/3d/0b/f65e7d56d0dec2804c1508aef4cf5d3a775273a090ae3047123f6f3e0f63/libipld-3.3.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3f033e98c9e95e8448c97bbc904271908076974d790a895abade2ae89433715e", size = 269020, upload-time = "2025-12-05T12:58:26.503Z" }, 317 - { url = "https://files.pythonhosted.org/packages/19/20/01a3be66e8945aaef9959ce80a07bf959e31b2bd2216bd199b24b463235a/libipld-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88ac549eb6c56287785ad20d0e7785d3e8b153b6a322fd5d7edf0e7fda2b182e", size = 260450, upload-time = "2025-12-05T12:58:27.735Z" }, 318 - { url = "https://files.pythonhosted.org/packages/af/06/a052e57bc99ec592d4b40c641d492f5fb225d25cc17f9edbf4f5918d7ff4/libipld-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:627035693460bae559d2e7f46bc577a27504d6e38e8715fcf9a8d905f6b1c72d", size = 280170, upload-time = "2025-12-05T12:58:28.977Z" }, 319 - { url = "https://files.pythonhosted.org/packages/eb/34/f20ff8a1b28a76d28f20895b1cb7d88422946e6ff6d8bc3d26a0b444e990/libipld-3.3.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:36be4ce9cb417eedec253eda9f55b92f29a35cbfcb24d108b496c72934fea7a2", size = 290219, upload-time = "2025-12-05T12:58:30.376Z" }, 320 - { url = "https://files.pythonhosted.org/packages/bb/0c/253c1d433e01c95d70c1b146e065fd5a3e1284ed0072f082603b5daf9223/libipld-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:908630dc28b16a517cf323293f0843f481b0872649cba7d4cfdbc6eb258f5674", size = 315833, upload-time = "2025-12-05T12:58:31.61Z" }, 321 - { url = "https://files.pythonhosted.org/packages/72/4a/2b8da906680e7379b31e1b31a4e49d90725a767e53510eb88f85f91e71c6/libipld-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ac45e3aef416fe2eccbe84e562d81714416790bfd0756a1aa49ba895d4c7010", size = 330068, upload-time = "2025-12-05T12:58:32.94Z" }, 322 - { url = "https://files.pythonhosted.org/packages/0b/73/be4031e3e1f839c286a6d9277fcacd756160a18009aa649adee308531698/libipld-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3344f30d47dcab9cba41dd8f2243874af91939e38e3c31f20d586383ca74296e", size = 283716, upload-time = "2025-12-05T12:58:34.166Z" }, 323 - { url = "https://files.pythonhosted.org/packages/8f/f2/35ebdb7b53cc4a97a2a8d580d5c302bf30a66d918273a0d01c3cd77b9336/libipld-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4443d047fd1a9679534a87a6ee35c3a10793d4453801281341bb1e8390087c69", size = 309913, upload-time = "2025-12-05T12:58:35.392Z" }, 324 - { url = "https://files.pythonhosted.org/packages/9d/d7/a1ffdb1b2986e60dd59d094c86e5bb318739c6d709b9e8af255667b7c578/libipld-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37ea7cb7afb94277e4e095bcc0ae888ed4b6e0fe8082c41dccd6e9487ccfd729", size = 463850, upload-time = "2025-12-05T12:58:36.702Z" }, 325 - { url = "https://files.pythonhosted.org/packages/1d/7d/440e372c3b8070cbf9200e1ddf3dff7409bcbc9243aade08e99c9e845e90/libipld-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:634d176664cf295360712157b5c5a83539da2f4416f3e0491340064d49e74fd8", size = 460370, upload-time = "2025-12-05T12:58:38.032Z" }, 326 - { url = "https://files.pythonhosted.org/packages/86/3e/cfcbbe21b30752482afa22fd528635a96901b39e517a10b73fc422f3d29b/libipld-3.3.2-cp312-cp312-win32.whl", hash = "sha256:071de5acf902e9a21d761572755afc8403cbaadd4b8199e7504ad52ee45b6b5e", size = 159380, upload-time = "2025-12-05T12:58:39.266Z" }, 327 - { url = "https://files.pythonhosted.org/packages/af/b5/b1cbc3347cf831c0599bb9b5579ed286939455d11d6f70110a3b8fb7d695/libipld-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:e35a8735b8a4bdd09b9edfbf1ae36e9ba9a804de50c99352c9a06aa3da109a62", size = 158896, upload-time = "2025-12-05T12:58:40.457Z" }, 328 - { url = "https://files.pythonhosted.org/packages/c2/cd/4ac32a0297c1d91d7147178927144dcb4456c35076388efb7c7f76e90695/libipld-3.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:36fe9cd1b5a75a315cab30091579242d05df39692f773f7d8221250503753e3a", size = 149432, upload-time = "2025-12-05T12:58:41.691Z" }, 329 - { url = "https://files.pythonhosted.org/packages/67/a6/2bf577bde352fdb81ebe2e271e542b85f1aeae630405cae1b9d07a97b5e9/libipld-3.3.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:63bc6858d73c324e29d74155bdb339e14330a88bb1a8cc8fdc295048337dca09", size = 269326, upload-time = "2025-12-05T12:58:42.967Z" }, 330 - { url = "https://files.pythonhosted.org/packages/cc/83/850a0bb214c31c128447e29cdbea816225ee2c8fbb397a8c865f895198e4/libipld-3.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4140f030eb3cfff17d04b9481f13aaed0b2910d1371fe7489394120ed1d09ae5", size = 260709, upload-time = "2025-12-05T12:58:44.232Z" }, 331 - { url = "https://files.pythonhosted.org/packages/73/f8/0c02a2acb246603f5351d0a71055d0c835bc0bc5332c5ca5d29a1d95b04c/libipld-3.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a48bc2f7845825143a36f6a305680823a2816488593024803064d0803e3cee35", size = 280309, upload-time = "2025-12-05T12:58:46.137Z" }, 332 - { url = "https://files.pythonhosted.org/packages/f4/2e/ca50530aed1911d99a730f30ab73e7731da8299a933b909a96fcdbb1baf6/libipld-3.3.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7627f371682160cae818f817eb846bc8c267a5daa028748a7c73103d8df00eb", size = 290446, upload-time = "2025-12-05T12:58:47.49Z" }, 333 - { url = "https://files.pythonhosted.org/packages/68/09/dd0f39cf78dbc7f5f2ca1208fc9ff284b56c2b90edf3dbf98c4b36491b6c/libipld-3.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a7de390a3eb897d3194f6c96067c21338fbe6e0fc1145ab6b51af276aa7a08e", size = 316193, upload-time = "2025-12-05T12:58:49.057Z" }, 334 - { url = "https://files.pythonhosted.org/packages/bf/75/ca6fe1673c80f7f4164edf9647dd2cb622455a73890e96648c44c361c918/libipld-3.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:196a8fcd86ae0c8096cea85ff308edf315d77fbb677ef3dd9eff0be9da526499", size = 330556, upload-time = "2025-12-05T12:58:50.471Z" }, 335 - { url = "https://files.pythonhosted.org/packages/bd/41/aff762ccf5a80b66a911c576afcd850f0d64cb43d51cb63c29429dc68230/libipld-3.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b040dab7eb04b0ff730e68840f40eb225c9f14e73ad21238b76c7b8ded3ad99d", size = 283970, upload-time = "2025-12-05T12:58:52.131Z" }, 336 - { url = "https://files.pythonhosted.org/packages/2e/56/3a19a6067bde8827146cd771583e8930cf952709f036328579747647f38f/libipld-3.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d7cd1e7e88b0fbc8f4aa267bdea2d10452c9dd0e1aafa82a5e0751427f222b0", size = 309885, upload-time = "2025-12-05T12:58:53.406Z" }, 337 - { url = "https://files.pythonhosted.org/packages/de/9b/0b4ee60ede82cdd301e2266a8172e8ee6f1b40c7dbd797510e632314ddf6/libipld-3.3.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:76731ebebd824fa45e51cc85506b108aa5da7322e43864909895f1779e9e4b41", size = 464028, upload-time = "2025-12-05T12:58:54.755Z" }, 338 - { url = "https://files.pythonhosted.org/packages/9d/c2/8edf65cf2c98bfbf6535b65f4bcc461ecec65ae6b9e3fb5a4308b9a5fb7a/libipld-3.3.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7b8e7100bffbe579b7c92a3c6a8852ce333e0de171e696a2063e1e39ec9cc50a", size = 460526, upload-time = "2025-12-05T12:58:56.231Z" }, 339 - { url = "https://files.pythonhosted.org/packages/17/3f/d6d2aa42f07855be6b7e1fb43d76e39945469fc54fe9366bf8c9a81ca38e/libipld-3.3.2-cp313-cp313-win32.whl", hash = "sha256:06f766cec75f3d78339caa3ce3c6977e290e1a97f37e5f4ba358da2e77340196", size = 159501, upload-time = "2025-12-05T12:58:57.482Z" }, 340 - { url = "https://files.pythonhosted.org/packages/12/2a/83f634329f1d1912e5d37aec717396c76ef689fa8c8997b16cf0866a1985/libipld-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:8be484f1dc5525453e17f07f02202180c708213f2b6ea06d3b9247a5702e0229", size = 159090, upload-time = "2025-12-05T12:58:58.628Z" }, 341 - { url = "https://files.pythonhosted.org/packages/d5/f4/5b55acce9f3626f8cbd54163f22a0917430d7307bf56fd30d88df7a0a897/libipld-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:4446cae7584a446b58de66942f89f155d95c2cbfb9ad215af359086824d4e3b9", size = 149497, upload-time = "2025-12-05T12:59:00.191Z" }, 342 - { url = "https://files.pythonhosted.org/packages/de/d6/9ab52adf13ee501b50624ef1265657aa30b3267998dfadcb44d77bbeef42/libipld-3.3.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5947e99b40e923170094a3313c9f3629c6ed475465ba95eadce6cdcf08f1f65a", size = 268909, upload-time = "2025-12-05T12:59:02.485Z" }, 343 - { url = "https://files.pythonhosted.org/packages/c2/12/d6f04fb3d6911a276940c89b5ad3e6168d79fda9ae79a812d4da91c433d6/libipld-3.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f46179c722baf74c627c01c0bf85be7fcbde66bbf7c5f8c1bbb57bd3a17b861b", size = 261052, upload-time = "2025-12-05T12:59:03.829Z" }, 344 - { url = "https://files.pythonhosted.org/packages/d8/23/6cade33d39f00eb71fde1c8fe6f73c5db5274ef8abeac3d2e6d989e65718/libipld-3.3.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e3e9be4bdeb90dbc537a53f8d06e8b2c703f4b7868f9316958e1bbde526a143", size = 280280, upload-time = "2025-12-05T12:59:05.13Z" }, 345 - { url = "https://files.pythonhosted.org/packages/2c/42/50445b6c1c418a3514feb7d267d308e9fb9fe473fbbfaa205bc288ffe5ed/libipld-3.3.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b155c02626b194439f4b519a53985aedc8637ae56cf640ea6acf6172a37465de", size = 290306, upload-time = "2025-12-05T12:59:06.372Z" }, 346 - { url = "https://files.pythonhosted.org/packages/bf/b1/7c197e21f1635ba31b2f4e893d3368598a48d990cebc4308ba496bad1409/libipld-3.3.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a1d84c630961cff188deaa2129c86d69f5779c8d02046fbe0c629ef162bc3df", size = 315801, upload-time = "2025-12-05T12:59:07.918Z" }, 347 - { url = "https://files.pythonhosted.org/packages/83/df/51a549e3017cc496a80852063124793007cb9b4cf2cae2e8a99f5c3dd814/libipld-3.3.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5393886a7e387751904681ecfa7e5912471b46043f044baa041a2b4772e4f839", size = 330420, upload-time = "2025-12-05T12:59:09.185Z" }, 348 - { url = "https://files.pythonhosted.org/packages/2e/f8/84107ad6431311283dadf697fd238ea271e0af1068a0d13e574be5027f32/libipld-3.3.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ca1ba44cb801686557e9544d248e013a2d5d1ab9fed796f090bb0d51d8f4ef", size = 283791, upload-time = "2025-12-05T12:59:10.481Z" }, 349 - { url = "https://files.pythonhosted.org/packages/35/c5/e3c5116b66383f7e54b9d1feb6d6e254a383311a4cce2940942f07d45893/libipld-3.3.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd0877ef4a1bd6e42ba52659769b5b766583c67b3cfb4e7143f9d10b81fb7a74", size = 309401, upload-time = "2025-12-05T12:59:11.711Z" }, 350 - { url = "https://files.pythonhosted.org/packages/bd/b5/b9345d47569806e6f0041d339c9a1ec0be765fd8a3588308a7a40c383dd9/libipld-3.3.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:91b02da059a6ae7f783efa826f640ab1ca5eb5dd370bfd3f41071693a363c4fb", size = 463929, upload-time = "2025-12-05T12:59:13.344Z" }, 351 - { url = "https://files.pythonhosted.org/packages/92/4b/ae985a308191771e5a9e8e3108a3a4ed7090147e21a7cda0c0e345adc22a/libipld-3.3.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:95a2c4f507c88c01a797ec97ce10603bea684c03208227703e007485dc631971", size = 460308, upload-time = "2025-12-05T12:59:14.702Z" }, 352 - { url = "https://files.pythonhosted.org/packages/5c/d6/98aafc9721dd239e578e2826cbb1e9ef438d76c0ec125bce64346e439041/libipld-3.3.2-cp314-cp314-win32.whl", hash = "sha256:5a50cbf5b3b73164fbb88169573ed3e824024c12fbc5f9efd87fb5c8f236ccc1", size = 159315, upload-time = "2025-12-05T12:59:16.004Z" }, 353 - { url = "https://files.pythonhosted.org/packages/e2/9c/6b7b91a417162743d9ea109e142fe485b2f6dafadb276c6e5a393f772715/libipld-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:c1f3ed8f70b215a294b5c6830e91af48acde96b3c8a6cae13304291f8240b939", size = 159168, upload-time = "2025-12-05T12:59:17.308Z" }, 354 - { url = "https://files.pythonhosted.org/packages/22/19/bb42dc53bb8855c1f40b4a431ed3cb2df257bd5a6af61842626712c83073/libipld-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:08261503b7307c6d9acbd3b2a221da9294b457204dcefce446f627893abb077e", size = 149324, upload-time = "2025-12-05T12:59:18.815Z" }, 355 - { url = "https://files.pythonhosted.org/packages/b8/41/dccfed4544e40c6de036f9eaf68d2235d68defa1988dbb6d282509756a66/libipld-3.3.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b3404a11e7e7803f7bc87a819e694e2568828c1f13a5f7fce74d3240d5a07aba", size = 267882, upload-time = "2025-12-05T13:00:05.921Z" }, 356 - { url = "https://files.pythonhosted.org/packages/c9/81/d6a2d64a28a8adee8dd351d48900f341b4944e4875ef31c85c50b2ade869/libipld-3.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3dbed6b1f2559c4ba8b898043a96c763a16063a1810cc1378cfdc92bcbdbb7e6", size = 253734, upload-time = "2025-12-05T13:00:08.648Z" }, 357 - { url = "https://files.pythonhosted.org/packages/48/25/7d831a1ed3d14296d9a57dbe1f78d7046660662096417af05a62456f3304/libipld-3.3.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:717c4630c4b1ad81c6896291cd93bcaca4ddc3f66412d81459e2641a505ec04a", size = 280400, upload-time = "2025-12-05T13:00:10.885Z" }, 358 - { url = "https://files.pythonhosted.org/packages/bf/a3/51aab0551e54b38c17a386ca0fc752dea698eb6801d66afdb34802dae896/libipld-3.3.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4596509ef4c2f00df2d0914e8efc632e4eb84bf837e57a8f53036dcf4d0d3169", size = 283748, upload-time = "2025-12-05T13:00:12.585Z" }, 359 - { url = "https://files.pythonhosted.org/packages/1b/fe/8cce8eac58f8183526bdfbe67f8b86980454fdebec76d9f06bd36c460a22/libipld-3.3.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:67c2e2a6604fe8b5aa8960c31989dfb84275693ba411d5139cdb87b2855bf009", size = 308869, upload-time = "2025-12-05T13:00:13.974Z" }, 360 - { url = "https://files.pythonhosted.org/packages/70/f1/edaf3934060a7b3c63b687fb2488cd847779251f85b2e8e5e7bf92c36bb6/libipld-3.3.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c8957b49c946d4ddd70f37bfe289494fab9088529db452f1cfcce31501369917", size = 464111, upload-time = "2025-12-05T13:00:15.452Z" }, 361 - { url = "https://files.pythonhosted.org/packages/fa/1e/dc9645d8072b9df38c9805a92ae18d0797a4fc534dec8387fd7eadbafd7a/libipld-3.3.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0fae0158971bf97aa6662e5f9cb5f497c35d5196336047ca4a9ebc6492b1f582", size = 459985, upload-time = "2025-12-05T13:00:16.917Z" }, 362 - { url = "https://files.pythonhosted.org/packages/a7/3e/ea35a256b3f26aa9fae1d5746d7312a20b683d23e54b738973bf59f18829/libipld-3.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a81fbf42a8edf094c7f8ebd77eb14d8cc7c3d6b3b51d786d47ecb4c6763da2fe", size = 156905, upload-time = "2025-12-05T13:00:18.301Z" }, 363 - ] 364 - 365 - [[package]] 366 - name = "pycparser" 367 - version = "2.23" 368 - source = { registry = "https://pypi.org/simple" } 369 - sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } 370 - wheels = [ 371 - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, 372 - ] 373 - 374 - [[package]] 375 - name = "pydantic" 376 - version = "2.12.5" 377 - source = { registry = "https://pypi.org/simple" } 378 - dependencies = [ 379 - { name = "annotated-types" }, 380 - { name = "pydantic-core" }, 381 - { name = "typing-extensions" }, 382 - { name = "typing-inspection" }, 383 - ] 384 - sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } 385 - wheels = [ 386 - { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, 387 - ] 388 - 389 - [[package]] 390 - name = "pydantic-core" 391 - version = "2.41.5" 392 - source = { registry = "https://pypi.org/simple" } 393 - dependencies = [ 394 - { name = "typing-extensions" }, 395 - ] 396 - sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } 397 - wheels = [ 398 - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, 399 - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, 400 - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, 401 - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, 402 - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, 403 - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, 404 - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, 405 - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, 406 - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, 407 - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, 408 - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, 409 - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, 410 - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, 411 - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, 412 - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, 413 - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, 414 - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, 415 - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, 416 - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, 417 - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, 418 - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, 419 - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, 420 - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, 421 - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, 422 - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, 423 - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, 424 - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, 425 - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, 426 - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, 427 - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, 428 - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, 429 - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, 430 - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, 431 - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, 432 - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, 433 - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, 434 - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, 435 - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, 436 - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, 437 - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, 438 - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, 439 - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, 440 - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, 441 - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, 442 - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, 443 - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, 444 - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, 445 - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, 446 - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, 447 - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, 448 - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, 449 - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, 450 - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, 451 - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, 452 - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, 453 - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, 454 - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, 455 - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, 456 - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, 457 - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, 458 - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, 459 - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, 460 - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, 461 - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, 462 - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, 463 - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, 464 - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, 465 - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, 466 - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, 467 - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, 468 - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, 469 - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, 470 - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, 471 - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, 472 - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, 473 - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, 474 - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, 475 - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, 476 - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, 477 - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, 478 - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, 479 - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, 480 - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, 481 - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, 482 - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, 483 - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, 484 - ] 485 - 486 - [[package]] 487 - name = "pydantic-settings" 488 - version = "2.12.0" 489 - source = { registry = "https://pypi.org/simple" } 490 - dependencies = [ 491 - { name = "pydantic" }, 492 - { name = "python-dotenv" }, 493 - { name = "typing-inspection" }, 494 - ] 495 - sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } 496 - wheels = [ 497 - { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, 498 - ] 499 - 500 - [[package]] 501 - name = "python-dotenv" 502 - version = "1.2.1" 503 - source = { registry = "https://pypi.org/simple" } 504 - sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } 505 - wheels = [ 506 - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, 507 - ] 508 - 509 - [[package]] 510 - name = "typing-extensions" 511 - version = "4.15.0" 512 - source = { registry = "https://pypi.org/simple" } 513 - sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } 514 - wheels = [ 515 - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, 516 - ] 517 - 518 - [[package]] 519 - name = "typing-inspection" 520 - version = "0.4.2" 521 - source = { registry = "https://pypi.org/simple" } 522 - dependencies = [ 523 - { name = "typing-extensions" }, 524 - ] 525 - sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } 526 - wheels = [ 527 - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, 528 - ] 529 - 530 - [[package]] 531 - name = "websockets" 532 - version = "15.0.1" 533 - source = { registry = "https://pypi.org/simple" } 534 - sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } 535 - wheels = [ 536 - { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, 537 - { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, 538 - { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, 539 - { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, 540 - { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, 541 - { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, 542 - { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, 543 - { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, 544 - { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, 545 - { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, 546 - { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, 547 - { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, 548 - { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, 549 - { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, 550 - { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, 551 - { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, 552 - { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, 553 - { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, 554 - { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, 555 - { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, 556 - { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, 557 - { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, 558 - { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, 559 - { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, 560 - { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, 561 - { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, 562 - { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, 563 - { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, 564 - { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, 565 - { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, 566 - { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, 567 - { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, 568 - { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, 569 - { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, 570 - ] 571 - 572 - [[package]] 573 - name = "wsproto" 574 - version = "1.3.2" 575 - source = { registry = "https://pypi.org/simple" } 576 - dependencies = [ 577 - { name = "h11" }, 578 - ] 579 - sdist = { url = "https://files.pythonhosted.org/packages/c7/79/12135bdf8b9c9367b8701c2c19a14c913c120b882d50b014ca0d38083c2c/wsproto-1.3.2.tar.gz", hash = "sha256:b86885dcf294e15204919950f666e06ffc6c7c114ca900b060d6e16293528294", size = 50116, upload-time = "2025-11-20T18:18:01.871Z" } 580 - wheels = [ 581 - { url = "https://files.pythonhosted.org/packages/a4/f5/10b68b7b1544245097b2a1b8238f66f2fc6dcaeb24ba5d917f52bd2eed4f/wsproto-1.3.2-py3-none-any.whl", hash = "sha256:61eea322cdf56e8cc904bd3ad7573359a242ba65688716b0710a5eb12beab584", size = 24405, upload-time = "2025-11-20T18:18:00.454Z" }, 582 - ]