๐Ÿ€ ATproto library for gleam
atproto library gleam
34

Configure Feed

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

possum: functions now take the host as a parameter

kacaii.dev (Jul 12, 2026, 5:28 PM -0300) 628d4a17 2a4aaf8a

+104 -139
+8
CHANGELOG.md
··· 5 5 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 6 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 7 8 + ## v3.0.0 - Unreleased 9 + 10 + ### Changed 11 + 12 + - [possum] Functions now take the PDS host as an argument. 13 + - [possum] Most functions now create a new request internally when being called, 14 + there's no need to pipe `request.new()` into them. 15 + 8 16 ## v2.1.0 - 2026-07-12 9 17 10 18 ### Added
+19 -21
README.md
··· 55 55 let assert Ok(collection) = nsid.parse("sh.tangled.repo") 56 56 57 57 // Reading public data from repositories, no Authorization needed. 58 - request.new() 59 - |> request.set_host("personal.data-server.pds") 60 - |> possum.list_records( 61 - did, 62 - collection:, 63 - limit: option.Some(3), 64 - cursor: option.None, 65 - reverse: option.None, 66 - ) 58 + let request = 59 + possum.list_records( 60 + did, 61 + pds: "personal.data-server.pds", 62 + collection:, 63 + limit: option.Some(3), 64 + cursor: option.None, 65 + reverse: option.None, 66 + ) 67 67 ``` 68 68 69 69 ### Authorized Requests ··· 77 77 78 78 // App Passwords can be generated in your Bluesky settings, 79 79 // It allows authorized access to your PDS. 80 - request.new() 81 - |> request.set_host("personal.data-server.pds") 82 - |> possum.create_session( 83 - did, 84 - app_password: "bsky-app-password", 85 - allow_takendown: option.None, 86 - auth_factor_token: option.None 87 - ) 80 + let requests = 81 + possum.create_session( 82 + did, 83 + pds: "personal.data-server.pds", 84 + app_password: "bsky-app-password", 85 + allow_takendown: option.None, 86 + auth_factor_token: option.None 87 + ) 88 88 89 89 // Include header for authorized requests. 90 90 request.new() 91 - |> request.set_host("personal.data-server.pds") 92 91 |> possum.authorized("access-token") 93 92 94 93 // Refreshing an authorized Session. 95 - request.new() 96 - |> request.set_host("personal.data-server.pds") 97 - |> possum.refresh_session("refresh-token") 94 + let request = 95 + possum.refresh_session("refresh-token", pds: "personal.data-server.pds") 98 96 ```
+2 -2
gleam.toml
··· 1 1 name = "possum" 2 - version = "2.1.0" 2 + version = "3.0.0" 3 3 4 - description = "๐Ÿ€ ATproto library for gleam" 4 + description = "๐Ÿ€ ATproto library for gleam" 5 5 licences = ["Apache-2.0"] 6 6 repository = { type = "tangled", user = "kacaii.dev", repo = "possum" } 7 7
+1 -2
pages/accessing-your-pds.md
··· 13 13 let assert Ok(did) = did.parse("did:plc:z72i7hdynmk6r22z27h6tvur") 14 14 15 15 // Send a request to plc.directory 16 - request.new() 17 - |> possum.get_plc_data(did) 16 + let request = possum.get_plc_data(did) 18 17 19 18 // You can use a decoder like this for your PDS endpoint 20 19 let decoder =
+5 -7
pages/consulting-your-did.md
··· 17 17 don't usually need to implement resolution directly themselves. 18 18 19 19 ```gleam 20 - let assert Ok(handle) = handle.parse("gleam.run") 21 - 22 20 // You can consult your identifier by sending a request directly to your PDS, 23 21 // or you can use projects like [Slingshot](https://slingshot.microcosm.blue) 24 22 // for easy access to cached data when resolving a handle. 25 - request.new() 26 - |> request.set_host("slingshot.microcosm.blue") 27 - |> possum.resolve_handle(handle) 23 + let assert Ok(handle) = handle.parse("gleam.run") 24 + let request = possum.resolve_handle(handle, pds: "slingshot.microcosm.blue") 28 25 ``` 29 26 30 27 ## HTTPS well-known Method ··· 34 31 status (2xx) and include the DID as the HTTP body with no prefix or wrapper formatting. 35 32 36 33 ```gleam 34 + import possum 35 + 37 36 // You also verify your DID without DNS by sending a request to `/.well-know/atproto-did` 38 37 // Your server can then respond with the DID value as plain text. 39 38 // 40 39 // The response Content-Type header does not need to be strictly verified. 41 - request.new() 42 - |> possum.resolve_well_known_did("gleam.run") 40 + let request = possum.resolve_well_known_did(host: "gleam.run") 43 41 ```
+59 -82
src/possum.gleam
··· 66 66 /// import possum 67 67 /// 68 68 /// let assert Ok(did) = did.parse("did:plc:z72i7hdynmk6r22z27h6tvur") 69 - /// 70 - /// let request = 71 - /// request.new() 72 - /// |> possum.get_plc_data(did) 69 + /// let request = possum.get_plc_data(did) 73 70 /// 74 71 /// // You can use this decoder to extract the server endpoint 75 72 /// let decoder = decode.at(["services", "atproto_pds", "endpoint"], decode.string) ··· 99 96 /// ``` 100 97 /// 101 98 /// Documentation: [DID PLC Directory](https://web.plc.directory/) 102 - pub fn get_plc_data( 103 - request: request.Request(String), 104 - did: did.Did, 105 - ) -> request.Request(String) { 106 - request 99 + pub fn get_plc_data(did: did.Did) -> request.Request(String) { 100 + request.new() 107 101 |> request.prepend_header("accept", "application/json") 108 102 |> request.set_host("plc.directory") 109 103 |> request.set_method(http.Get) ··· 116 110 /// ## Examples 117 111 /// 118 112 /// ```gleam 119 - /// import gleam/http/request 120 - /// import possum/handle 121 113 /// import possum 122 114 /// 123 - /// let assert Ok(handle) = handle.parse("gleam.run") 124 - /// 125 - /// request.new() 126 - /// |> possum.resolve_well_known_did(handle) 115 + /// let request = possum.resolve_well_known_did(host: "gleam.run") 127 116 /// ``` 128 117 /// 129 118 /// ## Response ··· 133 122 /// ``` 134 123 /// 135 124 /// Documentation: [HTTPS well-known Method](https://atproto.com/specs/handle#https-well-known-method) 136 - pub fn resolve_well_known_did( 137 - request: request.Request(String), 138 - host: String, 139 - ) -> request.Request(String) { 140 - request 125 + pub fn resolve_well_known_did(host host: String) -> request.Request(String) { 126 + request.new() 141 127 |> request.set_host(host) 142 128 |> request.set_method(http.Get) 143 129 |> request.set_path(".well-known/atproto-did") ··· 157 143 /// import possum 158 144 /// 159 145 /// let assert Ok(handle) = handle.parse("gleam.run") 160 - /// 161 - /// let request = 162 - /// request.new() 163 - /// |> request.set_host("slingshot.microcosm.blue") 164 - /// |> possum.resolve_handle(handle) 146 + /// let request = possum.resolve_handle(handle, pds: "slingshot.microcosm.blue") 165 147 /// ``` 166 148 /// 167 149 /// ## Response ··· 174 156 /// 175 157 /// Endpoint Docs: [/xrpc/com.atproto.identity.resolveHandle](https://endpoints.bsky.app/#bluesky-app/tag/comatprotoidentity/GET/xrpc/com.atproto.identity.resolveHandle) 176 158 pub fn resolve_handle( 177 - request: request.Request(String), 178 159 handle: handle.Handle, 160 + pds host: String, 179 161 ) -> request.Request(String) { 180 - request 162 + request.new() 181 163 |> request.prepend_header("accept", "application/json") 164 + |> request.set_host(host) 182 165 |> request.set_method(http.Get) 183 166 |> request.set_path(com_atproto_identity <> ".resolveHandle") 184 167 |> request.set_query([#("handle", handle.to_string(handle))]) ··· 205 188 /// let assert Ok(did) = did.parse("did:plc:ewvi7nxzyoun6zhxrhs64oiz") 206 189 /// 207 190 /// let request = 208 - /// request.new() 209 - /// |> request.set_host("personal.data-server.pds") 210 - /// |> possum.create_session( 191 + /// possum.create_session( 211 192 /// did, 193 + /// pds: "personal.data-server.pds", 212 194 /// app_password: "bsky-app-password", 213 195 /// allow_takendown: option.None, 214 196 /// auth_factor_token: option.None ··· 234 216 /// 235 217 /// Endpoint Docs: [/xrpc/com.atproto.server.createSession](https://endpoints.bsky.app/#bluesky-app/tag/comatprotoserver/POST/xrpc/com.atproto.server.createSession) 236 218 pub fn create_session( 237 - request: request.Request(String), 238 219 did: did.Did, 220 + pds host: String, 239 221 app_password app_password: String, 240 222 allow_takendown allow_takendown: option.Option(Bool), 241 223 auth_factor_token auth_factor_token: option.Option(String), ··· 255 237 option.None -> values 256 238 } 257 239 258 - request 240 + request.new() 259 241 |> request.prepend_header("accept", "application/json") 260 242 |> request.prepend_header("content-type", "application/json") 261 243 |> request.set_body(json.to_string(json.object(values))) 244 + |> request.set_host(host) 262 245 |> request.set_method(http.Post) 263 246 |> request.set_path(com_atproto_server <> ".createSession") 264 247 } ··· 269 252 /// ## Examples 270 253 /// 271 254 /// ```gleam 272 - /// import gleam/http/request 273 255 /// import possum 274 256 /// 275 257 /// let request = 276 - /// request.new() 277 - /// |> request.set_host("personal.data-server.pds") 278 - /// |> possum.refresh_session("refresh-token") 258 + /// possum.refresh_session("refresh-token", pds: "personal.data-server.pds") 279 259 /// ``` 280 260 /// 281 261 /// ## Response ··· 297 277 /// 298 278 /// Endpoint Docs: [/xrpc/com.atproto.server.refreshSession](https://endpoints.bsky.app/#bluesky-app/tag/comatprotoserver/POST/xrpc/com.atproto.server.refreshSession) 299 279 pub fn refresh_session( 300 - request: request.Request(String), 301 - refresh token: String, 280 + refresh_token token: String, 281 + pds host: String, 302 282 ) -> request.Request(String) { 303 - request 283 + request.new() 304 284 |> authorized(token) 305 285 |> request.prepend_header("accept", "application/json") 286 + |> request.set_host(host) 306 287 |> request.set_method(http.Post) 307 288 |> request.set_path(com_atproto_server <> ".refreshSession") 308 289 } ··· 317 298 /// import possum 318 299 /// 319 300 /// let request = 320 - /// request.new() 321 - /// |> request.set_host("personal.data-server.pds") 301 + /// possum.get_session_info(pds: "personal.data-server.pds") 322 302 /// |> possum.authorized("access-token") 323 - /// |> possum.get_session_info 324 303 /// ``` 325 304 /// 326 305 /// ## Response ··· 339 318 /// ``` 340 319 /// 341 320 /// Endpoint Docs: [/xrpc/com.atproto.server.getSession](https://endpoints.bsky.app/#bluesky-app/tag/comatprotoserver/POST/xrpc/com.atproto.server.getSession) 342 - pub fn get_session_info( 343 - request: request.Request(String), 344 - ) -> request.Request(String) { 345 - request 321 + pub fn get_session_info(pds host: String) -> request.Request(String) { 322 + request.new() 346 323 |> request.prepend_header("accept", "application/json") 324 + |> request.set_host(host) 347 325 |> request.set_method(http.Get) 348 326 |> request.set_path(com_atproto_server <> ".getSession") 349 327 } ··· 359 337 /// 360 338 /// let request = 361 339 /// request.new() 362 - /// |> request.set_host("personal.data-server.pds") 363 - /// |> possum.authorized("access-token") 340 + /// |> possum.authorized(access_token:"access-token") 364 341 /// ``` 365 342 /// 366 343 pub fn authorized( ··· 380 357 /// import possum 381 358 /// 382 359 /// let assert Ok(did) = did.parse("did:plc:ewvi7nxzyoun6zhxrhs64oiz") 383 - /// 384 360 /// let request = 385 - /// request.new() 386 - /// |> request.set_host("personal.data-server.pds") 387 - /// |> possum.describe_repository(did) 361 + /// possum.describe_repository(did, pds: "personal.data-server.pds") 388 362 /// ``` 389 363 /// 390 364 /// ## Response ··· 403 377 /// 404 378 /// Endpoint Docs: [/xrpc/com.atproto.repo.describeRepo](https://endpoints.bsky.app/#bluesky-app/tag/comatprotorepo/GET/xrpc/com.atproto.repo.describeRepo) 405 379 pub fn describe_repository( 406 - request: request.Request(String), 407 380 did: did.Did, 381 + pds host: String, 408 382 ) -> request.Request(String) { 409 - request 383 + request.new() 410 384 |> request.prepend_header("accept", "application/json") 385 + |> request.set_host(host) 411 386 |> request.set_method(http.Get) 412 387 |> request.set_path(com_atproto_repo <> ".describeRepo") 413 388 |> request.set_query([#("repo", did.to_string(did))]) ··· 438 413 /// let assert Ok(collection) = nsid.parse("wibble.wobble.woo") 439 414 /// 440 415 /// let request = 441 - /// request.new() 442 - /// |> request.set_host("personal.data-server.pds") 443 - /// |> possum.authorized("access-token") 444 - /// |> possum.create_record( 416 + /// possum.create_record( 445 417 /// did, 418 + /// pds: "personal.data-server.pds", 446 419 /// collection:, 447 420 /// rkey: "wibble", 448 421 /// record: [], 449 422 /// swap_commit: option.None, 450 423 /// validate: option.None, 451 424 /// ) 425 + /// |> possum.authorized("access-token") 452 426 /// ``` 453 427 /// 454 428 /// ## Response ··· 467 441 /// 468 442 /// Endpoint Docs: [/xrpc/com.atproto.repo.createRecord](https://endpoints.bsky.app/#bluesky-app/tag/comatprotorepo/POST/xrpc/com.atproto.repo.createRecord) 469 443 pub fn create_record( 470 - request: request.Request(String), 471 444 did: did.Did, 445 + pds host: String, 472 446 collection nsid: nsid.Nsid, 473 447 rkey rkey: option.Option(String), 474 448 record record: List(#(String, json.Json)), ··· 496 470 option.None -> body 497 471 } 498 472 499 - request 473 + request.new() 500 474 |> request.prepend_header("accept", "application/json") 501 475 |> request.prepend_header("content-type", "application/json") 502 476 |> request.set_body(json.to_string(json.object(body))) 477 + |> request.set_host(host) 503 478 |> request.set_method(http.Post) 504 479 |> request.set_path(com_atproto_repo <> ".createRecord") 505 480 } ··· 519 494 /// let assert Ok(collection) = nsid.parse("site.standard.document") 520 495 /// 521 496 /// let request = 522 - /// request.new() 523 - /// |> request.set_host("slingshot.microcosm.blue") 524 - /// |> possum.get_record( 497 + /// possum.get_record( 525 498 /// did, 499 + /// pds: "slingshot.microcosm.blue", 526 500 /// collection:, 527 501 /// rkey: "mn6n3lvibc2b", 528 502 /// cursor: option.None, ··· 543 517 /// Endpoint Docs: [/xrpc/com.atproto.repo.getRecord](https://endpoints.bsky.app/#bluesky-app/tag/comatprotorepo/GET/xrpc/com.atproto.repo.getRecord) 544 518 /// Documentation: [atproto.com](https://atproto.com/specs/record-key) 545 519 pub fn get_record( 546 - request: request.Request(String), 547 520 did: did.Did, 521 + pds host: String, 548 522 collection nsid: nsid.Nsid, 549 523 rkey rkey: String, 550 524 cid cid: option.Option(String), ··· 560 534 option.None -> query 561 535 } 562 536 563 - request 537 + request.new() 564 538 |> request.prepend_header("accept", "application/json") 539 + |> request.set_host(host) 565 540 |> request.set_method(http.Get) 566 541 |> request.set_path(com_atproto_repo <> ".getRecord") 567 542 |> request.set_query(query) ··· 581 556 /// let assert Ok(collection) = nsid.parse("site.standard.document") 582 557 /// 583 558 /// let request = 584 - /// request.new() 585 - /// |> request.set_host("personal.data-server.pds") 586 - /// |> possum.list_records( 559 + /// possum.list_records( 587 560 /// did, 561 + /// pds: "personal.data-server.pds", 588 562 /// collection:, 589 563 /// limit: option.Some(3), 590 564 /// cursor: option.None, ··· 609 583 /// 610 584 /// Endpoint Docs: [/xrpc/com.atproto.repo.listRecords](https://endpoints.bsky.app/#bluesky-app/tag/comatprotorepo/GET/xrpc/com.atproto.repo.listRecords) 611 585 pub fn list_records( 612 - request: request.Request(String), 613 586 did: did.Did, 587 + pds host: String, 614 588 collection nsid: nsid.Nsid, 615 589 limit limit: option.Option(Int), 616 590 cursor cursor: option.Option(String), ··· 636 610 option.None -> query 637 611 } 638 612 639 - request 613 + request.new() 640 614 |> request.prepend_header("accept", "application/json") 615 + |> request.set_host(host) 641 616 |> request.set_method(http.Get) 642 617 |> request.set_path(com_atproto_repo <> ".listRecords") 643 618 |> request.set_query(query) ··· 664 639 /// let assert Ok(collection) = nsid.parse("wibble.wobble.woo") 665 640 /// 666 641 /// let request = 667 - /// request.new() 668 - /// |> request.set_host("personal.data-server.pds") 669 - /// |> possum.authorized("access-token") 670 - /// |> possum.delete_record( 642 + /// possum.delete_record( 671 643 /// did, 644 + /// pds: "personal.data-server.pds" 672 645 /// collection:, 673 646 /// rkey: "wibble-wobble", 674 647 /// swap_commit: option.None, 675 648 /// swap_record: option.None, 676 649 /// ) 650 + /// |> possum.authorized("access-token") 677 651 /// ``` 678 652 /// 679 653 /// ## Response ··· 689 663 /// 690 664 /// Endpoint Docs: [/xrpc/com.atproto.repo.deleteRecord](https://endpoints.bsky.app/#bluesky-app/tag/comatprotorepo/POST/xrpc/com.atproto.repo.deleteRecord) 691 665 pub fn delete_record( 692 - request: request.Request(String), 693 666 did: did.Did, 667 + pds host: String, 694 668 collection nsid: nsid.Nsid, 695 669 rkey rkey: String, 696 670 swap_commit swap_commit: option.Option(String), ··· 712 686 option.None -> body 713 687 } 714 688 715 - request 689 + request.new() 716 690 |> request.prepend_header("accept", "application/json") 717 691 |> request.prepend_header("content-type", "application/json") 718 692 |> request.set_body(json.to_string(json.object(body))) 693 + |> request.set_host(host) 719 694 |> request.set_method(http.Post) 720 695 |> request.set_path(com_atproto_repo <> ".deleteRecord") 721 696 } ··· 746 721 /// let assert Ok(collection) = nsid.parse("target.collection.data") 747 722 /// 748 723 /// let request = 749 - /// request.new() 750 - /// |> request.set_host("personal.data-server.pds") 751 - /// |> possum.authorized("access-token") 752 - /// |> possum.put_record( 724 + /// possum.put_record( 753 725 /// did, 726 + /// pds: "personal.data-server.pds", 754 727 /// collection:, 755 728 /// rkey: "wibble-wobble", 756 729 /// record: [], ··· 758 731 /// swap_record: option.None, 759 732 /// validate: option.None, 760 733 /// ) 734 + /// |> possum.authorized("access-token") 761 735 /// ``` 762 736 /// 763 737 /// ## Response ··· 778 752 pub fn put_record( 779 753 request: request.Request(String), 780 754 did: did.Did, 755 + pds host: String, 781 756 collection nsid: nsid.Nsid, 782 757 rkey rkey: String, 783 758 record record: List(#(String, json.Json)), ··· 811 786 |> request.prepend_header("accept", "application/json") 812 787 |> request.prepend_header("content-type", "application/json") 813 788 |> request.set_body(json.to_string(json.object(body))) 789 + |> request.set_host(host) 814 790 |> request.set_method(http.Post) 815 791 |> request.set_path(com_atproto_repo <> ".putRecord") 816 792 } ··· 821 797 /// 822 798 /// Endpoint Docs: [/xrpc/com.atproto.sync.getBlob](https://endpoints.bsky.app/#bluesky-app/tag/comatprotosync/GET/xrpc/com.atproto.sync.getBlob) 823 799 pub fn get_blob( 824 - request: request.Request(String), 825 800 did: did.Did, 826 801 cid: String, 802 + pds host: String, 827 803 ) -> request.Request(BitArray) { 828 804 let query = [#("did", did.to_string(did)), #("cid", cid)] 829 805 830 - request 806 + request.new() 831 807 |> request.prepend_header("accept", "*/*") 832 808 |> request.set_body(<<>>) 809 + |> request.set_host(host) 833 810 |> request.set_method(http.Get) 834 811 |> request.set_path(com_atproto_sync <> ".getBlob") 835 812 |> request.set_query(query)
+10 -25
test/possum_test.gleam
··· 22 22 23 23 const slingshot = "slingshot.microcosm.blue" 24 24 25 - const plc_directory = "plc.directory" 26 - 27 25 pub type Context { 28 26 Context(handle: handle.Handle, did: did.Did, pds: String) 29 27 } ··· 48 46 49 47 let assert Ok(handle) = handle.parse("bsky.app") 50 48 let assert Ok(did_response) = 51 - request.new() 52 - |> request.set_host(slingshot) 53 - |> possum.resolve_handle(handle) 49 + possum.resolve_handle(handle, pds: slingshot) 54 50 |> httpc.send 55 51 56 52 let assert Ok(did) = 57 53 json.parse(did_response.body, decode.at(["did"], did.decoder())) 58 54 59 55 let assert Ok(pds_response) = 60 - request.new() 61 - |> request.set_host(plc_directory) 62 - |> possum.get_plc_data(did) 56 + possum.get_plc_data(did) 63 57 |> httpc.send 64 58 65 59 let decoder = ··· 74 68 let context = global_data() 75 69 76 70 let assert Ok(response) = 77 - request.new() 78 - |> possum.get_plc_data(context.did) 71 + possum.get_plc_data(context.did) 79 72 |> httpc.send() 80 73 81 74 let decoder = { ··· 95 88 let context = global_data() 96 89 97 90 let assert Ok(response) = 98 - request.new() 99 - |> request.set_host(slingshot) 100 - |> possum.resolve_handle(context.handle) 91 + possum.resolve_handle(context.handle, pds: slingshot) 101 92 |> httpc.send 102 93 103 94 let decoder = decode.at(["did"], decode.string) ··· 111 102 let context = global_data() 112 103 113 104 let assert Ok(response) = 114 - request.new() 115 - |> request.set_host(context.pds) 116 - |> possum.describe_repository(context.did) 105 + possum.describe_repository(context.did, pds: context.pds) 117 106 |> httpc.send 118 107 119 108 let decoder = { ··· 143 132 let assert Ok(collection) = nsid.parse("app.bsky.actor.profile") 144 133 145 134 let assert Ok(response) = 146 - request.new() 147 - |> request.set_host(slingshot) 148 - |> possum.get_record( 135 + possum.get_record( 149 136 context.did, 137 + pds: context.pds, 150 138 collection:, 151 139 rkey: "self", 152 140 cid: option.None, ··· 169 157 let assert Ok(bsky_profile) = nsid.parse("app.bsky.actor.profile") 170 158 171 159 let assert Ok(bsky_query) = 172 - request.new() 173 - |> request.set_host(slingshot) 174 - |> possum.get_record( 160 + possum.get_record( 175 161 context.did, 162 + pds: slingshot, 176 163 collection: bsky_profile, 177 164 rkey: "self", 178 165 cid: option.None, ··· 186 173 ) 187 174 188 175 let assert Ok(response) = 189 - request.new() 190 - |> request.set_host(context.pds) 191 - |> possum.get_blob(context.did, rkey) 176 + possum.get_blob(context.did, rkey, pds: context.pds) 192 177 |> httpc.send_bits 193 178 194 179 assert bit_array.byte_size(response.body) != 0