bluesky appview implementation using microcosm and other services server.reddwarf.app
appview bluesky reddwarf microcosm

extremely terrible basic getFeed

Changed files
+217 -6
+168 -6
main.go
··· 10 10 "log" 11 11 "net/http" 12 12 "os" 13 + "strings" 13 14 "sync" 14 15 "time" 15 16 ··· 79 80 // spacedust is type definitions only 80 81 // jetstream types is probably available from jetstream/pkg/models 81 82 82 - router := gin.New() 83 - router.Use(gin.Logger()) 84 - router.Use(gin.Recovery()) 85 - router.Use(cors.Default()) 83 + r_unsafe := gin.New() 84 + r_unsafe.Use(gin.Logger()) 85 + r_unsafe.Use(gin.Recovery()) 86 + r_unsafe.Use(cors.Default()) 86 87 87 - router.GET("/.well-known/did.json", GetWellKnownDID) 88 + r_unsafe.GET("/.well-known/did.json", GetWellKnownDID) 88 89 89 90 auther, err := auth.NewAuth( 90 91 100_000, ··· 96 97 log.Fatalf("Failed to create Auth: %v", err) 97 98 } 98 99 100 + router := r_unsafe.Group("/") 101 + router.Use(auther.AuthenticateGinRequestViaJWT) 99 102 router.Use(auther.AuthenticateGinRequestViaJWT) 100 103 101 104 responsewow, err := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.actor.profile", "did:web:did12.whey.party", "self") ··· 410 413 }) 411 414 }) 412 415 416 + r_unsafe.GET("/xrpc/app.bsky.feed.getFeed", 417 + func(c *gin.Context) { 418 + ctx := c.Request.Context() 419 + 420 + feedGenAturiRaw := c.Query("feed") 421 + if feedGenAturiRaw == "" { 422 + c.JSON(http.StatusBadRequest, gin.H{"error": "Missing feed param"}) 423 + return 424 + } 425 + 426 + feedGenAturi, err := syntax.ParseATURI(feedGenAturiRaw) 427 + if err != nil { 428 + return 429 + } 430 + 431 + feedGeneratorRecordResponse, err := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.feed.generator", feedGenAturi.Authority().String(), feedGenAturi.RecordKey().String()) 432 + if err != nil { 433 + c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve feed generator record: %v", err)}) 434 + return 435 + } 436 + 437 + var feedGeneratorRecord appbsky.FeedGenerator 438 + if err := json.Unmarshal(*feedGeneratorRecordResponse.Value, &feedGeneratorRecord); err != nil { 439 + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse feed generator record JSON"}) 440 + return 441 + } 442 + 443 + feedGenDID := feedGeneratorRecord.Did 444 + 445 + didDoc, err := ResolveDID(feedGenDID) 446 + if err != nil { 447 + c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve DID: %v", err)}) 448 + return 449 + } 450 + 451 + if err != nil { 452 + c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve DID: %v", err)}) 453 + return 454 + } 455 + 456 + var targetEndpoint string 457 + for _, svc := range didDoc.Service { 458 + if svc.Type == "BskyFeedGenerator" && strings.HasSuffix(svc.ID, "#bsky_fg") { 459 + targetEndpoint = svc.ServiceEndpoint 460 + break 461 + } 462 + } 463 + if targetEndpoint == "" { 464 + c.JSON(http.StatusBadGateway, gin.H{"error": "Feed Generator service endpoint not found in DID document"}) 465 + return 466 + } 467 + upstreamURL := fmt.Sprintf("%s/xrpc/app.bsky.feed.getFeedSkeleton?%s", 468 + strings.TrimSuffix(targetEndpoint, "/"), 469 + c.Request.URL.RawQuery, 470 + ) 471 + req, err := http.NewRequestWithContext(ctx, "GET", upstreamURL, nil) 472 + if err != nil { 473 + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upstream request"}) 474 + return 475 + } 476 + headersToForward := []string{"Authorization", "Content-Type", "Accept", "User-Agent"} 477 + for _, k := range headersToForward { 478 + if v := c.GetHeader(k); v != "" { 479 + req.Header.Set(k, v) 480 + } 481 + } 482 + client := &http.Client{} 483 + resp, err := client.Do(req) 484 + if err != nil { 485 + c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Upstream request failed: %v", err)}) 486 + return 487 + } 488 + defer resp.Body.Close() 489 + 490 + bodyBytes, err := io.ReadAll(resp.Body) 491 + if err != nil { 492 + c.JSON(http.StatusBadGateway, gin.H{"error": "Failed to read upstream body"}) 493 + return 494 + } 495 + if resp.StatusCode != http.StatusOK { 496 + // Forward the upstream error raw 497 + c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), bodyBytes) 498 + return 499 + } 500 + 501 + var feekskeleton appbsky.FeedGetFeedSkeleton_Output 502 + if err := json.Unmarshal(bodyBytes, &feekskeleton); err != nil { 503 + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse upstream JSON"}) 504 + return 505 + } 506 + 507 + skeletonposts := feekskeleton.Feed 508 + 509 + type result struct { 510 + view *appbsky.FeedDefs_FeedViewPost 511 + } 512 + 513 + results := make([]result, len(skeletonposts)) 514 + 515 + var wg sync.WaitGroup 516 + wg.Add(len(skeletonposts)) 517 + 518 + for i, raw := range skeletonposts { 519 + go func(i int, raw appbsky.FeedDefs_SkeletonFeedPost) { 520 + defer wg.Done() 521 + 522 + post, _, err := appbskyfeeddefs.PostView(ctx, raw.Post, sl, BSKYIMAGECDN_URL) 523 + if err != nil || post == nil { 524 + return 525 + } 526 + 527 + feedviewpost := &appbsky.FeedDefs_FeedViewPost{ 528 + // FeedContext *string `json:"feedContext,omitempty" cborgen:"feedContext,omitempty"` 529 + // Post *FeedDefs_PostView `json:"post" cborgen:"post"` 530 + Post: post, 531 + // Reason *FeedDefs_FeedViewPost_Reason `json:"reason,omitempty" cborgen:"reason,omitempty"` 532 + // Reason: &appbsky.FeedDefs_FeedViewPost_Reason{ 533 + // // FeedDefs_ReasonRepost *FeedDefs_ReasonRepost 534 + // FeedDefs_ReasonRepost: &appbsky.FeedDefs_ReasonRepost{ 535 + // // LexiconTypeID string `json:"$type" cborgen:"$type,const=app.bsky.feed.defs#reasonRepost"` 536 + // LexiconTypeID: "app.bsky.feed.defs#reasonRepost", 537 + // // By *ActorDefs_ProfileViewBasic `json:"by" cborgen:"by"` 538 + // // Cid *string `json:"cid,omitempty" cborgen:"cid,omitempty"` 539 + // // IndexedAt string `json:"indexedAt" cborgen:"indexedAt"` 540 + // // Uri *string `json:"uri,omitempty" cborgen:"uri,omitempty"` 541 + // Uri: &raw.Reason.FeedDefs_SkeletonReasonRepost.Repost, 542 + // }, 543 + // // FeedDefs_ReasonPin *FeedDefs_ReasonPin 544 + // FeedDefs_ReasonPin: &appbsky.FeedDefs_ReasonPin{ 545 + // // LexiconTypeID string `json:"$type" cborgen:"$type,const=app.bsky.feed.defs#reasonPin"` 546 + // LexiconTypeID: "app.bsky.feed.defs#reasonPin", 547 + // }, 548 + // }, 549 + // Reply *FeedDefs_ReplyRef `json:"reply,omitempty" cborgen:"reply,omitempty"` 550 + // // reqId: Unique identifier per request that may be passed back alongside interactions. 551 + // ReqId *string `json:"reqId,omitempty" cborgen:"reqId,omitempty"` 552 + 553 + } 554 + 555 + results[i].view = feedviewpost 556 + }(i, *raw) 557 + } 558 + 559 + wg.Wait() 560 + 561 + // build final slice 562 + out := make([]*appbsky.FeedDefs_FeedViewPost, 0, len(results)) 563 + for _, r := range results { 564 + if r.view != nil && r.view.Post != nil { 565 + out = append(out, r.view) 566 + } 567 + } 568 + 569 + c.JSON(http.StatusOK, &appbsky.FeedGetFeed_Output{ 570 + Cursor: feekskeleton.Cursor, 571 + Feed: out, 572 + }) 573 + }) 574 + 413 575 yourJSONBytes, _ := os.ReadFile("./public/getConfig.json") 414 576 router.GET("/xrpc/app.bsky.unspecced.getConfig", func(c *gin.Context) { 415 577 c.DataFromReader(200, -1, "application/json", ··· 442 604 }(clientUUID) 443 605 } 444 606 }) 445 - router.Run(":7152") 607 + r_unsafe.Run(":7152") 446 608 } 447 609 448 610 func getPostThreadV2(w http.ResponseWriter, r *http.Request) {
+49
utils.go
··· 1 + package main 2 + 3 + import ( 4 + "encoding/json" 5 + "fmt" 6 + "net/http" 7 + "strings" 8 + ) 9 + 10 + type DIDDocument struct { 11 + ID string `json:"id"` 12 + Service []struct { 13 + ID string `json:"id"` 14 + Type string `json:"type"` 15 + ServiceEndpoint string `json:"serviceEndpoint"` 16 + } `json:"service"` 17 + } 18 + 19 + func ResolveDID(did string) (*DIDDocument, error) { 20 + var url string 21 + 22 + if strings.HasPrefix(did, "did:plc:") { 23 + // Resolve via PLC Directory 24 + url = "https://plc.directory/" + did 25 + } else if strings.HasPrefix(did, "did:web:") { 26 + // Resolve via Web (simplified) 27 + domain := strings.TrimPrefix(did, "did:web:") 28 + url = "https://" + domain + "/.well-known/did.json" 29 + } else { 30 + return nil, fmt.Errorf("unsupported DID format: %s", did) 31 + } 32 + 33 + resp, err := http.Get(url) 34 + if err != nil { 35 + return nil, err 36 + } 37 + defer resp.Body.Close() 38 + 39 + if resp.StatusCode != http.StatusOK { 40 + return nil, fmt.Errorf("resolver returned status: %d", resp.StatusCode) 41 + } 42 + 43 + var doc DIDDocument 44 + if err := json.NewDecoder(resp.Body).Decode(&doc); err != nil { 45 + return nil, err 46 + } 47 + 48 + return &doc, nil 49 + }