package main import ( "bytes" "context" "encoding/json" "flag" "fmt" "io" "log" "net/http" "os" "strings" "sync" "time" did "github.com/whyrusleeping/go-did" "tangled.org/whey.party/red-dwarf-server/auth" "tangled.org/whey.party/red-dwarf-server/microcosm/constellation" "tangled.org/whey.party/red-dwarf-server/microcosm/slingshot" appbskyactordefs "tangled.org/whey.party/red-dwarf-server/shims/lex/app/bsky/actor/defs" appbskyfeeddefs "tangled.org/whey.party/red-dwarf-server/shims/lex/app/bsky/feed/defs" "tangled.org/whey.party/red-dwarf-server/shims/utils" "tangled.org/whey.party/red-dwarf-server/sticket" "tangled.org/whey.party/red-dwarf-server/store" // "github.com/bluesky-social/indigo/atproto/atclient" // comatproto "github.com/bluesky-social/indigo/api/atproto" appbsky "github.com/bluesky-social/indigo/api/bsky" "github.com/bluesky-social/indigo/atproto/syntax" // "github.com/bluesky-social/indigo/atproto/atclient" // "github.com/bluesky-social/indigo/atproto/identity" // "github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/indigo/api/agnostic" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" // "github.com/bluesky-social/jetstream/pkg/models" ) var ( JETSTREAM_URL string SPACEDUST_URL string SLINGSHOT_URL string CONSTELLATION_URL string ) func initURLs(prod bool) { if !prod { JETSTREAM_URL = "wss://jetstream.whey.party/subscribe" SPACEDUST_URL = "wss://spacedust.whey.party/subscribe" SLINGSHOT_URL = "https://slingshot.whey.party" CONSTELLATION_URL = "https://constellation.whey.party" } else { JETSTREAM_URL = "ws://localhost:6008/subscribe" SPACEDUST_URL = "ws://localhost:9998/subscribe" SLINGSHOT_URL = "http://localhost:7729" CONSTELLATION_URL = "http://localhost:7728" } } const ( BSKYIMAGECDN_URL = "https://cdn.bsky.app" BSKYVIDEOCDN_URL = "https://video.bsky.app" serviceWebDID = "did:web:server.reddwarf.app" serviceWebHost = "https://server.reddwarf.app" ) func main() { log.Println("red-dwarf-server started") prod := flag.Bool("prod", false, "use production URLs instead of localhost") flag.Parse() initURLs(*prod) ctx := context.Background() mailbox := sticket.New() sl := slingshot.NewSlingshot(SLINGSHOT_URL) cs := constellation.NewConstellation(CONSTELLATION_URL) // spacedust is type definitions only // jetstream types is probably available from jetstream/pkg/models router_raw := gin.New() router_raw.Use(gin.Logger()) router_raw.Use(gin.Recovery()) router_raw.Use(cors.Default()) router_raw.GET("/.well-known/did.json", GetWellKnownDID) auther, err := auth.NewAuth( 100_000, time.Hour*12, 5, serviceWebDID, //+"#bsky_appview", ) if err != nil { log.Fatalf("Failed to create Auth: %v", err) } router := router_raw.Group("/") router.Use(auther.AuthenticateGinRequestViaJWT) router_unsafe := router_raw.Group("/") router_unsafe.Use(auther.AuthenticateGinRequestViaJWTUnsafe) responsewow, err := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.actor.profile", "did:web:did12.whey.party", "self") if err != nil { log.Println(err) } log.Println(responsewow.Uri) var didtest *utils.DID didval, errdid := utils.NewDID("did:web:did12.whey.party") if errdid != nil { didtest = nil } else { didtest = &didval } profiletest, _, _ := appbskyactordefs.ProfileViewBasic(ctx, *didtest, sl, BSKYIMAGECDN_URL) log.Println(*profiletest.DisplayName) log.Println(*profiletest.Avatar) router.GET("/ws", func(c *gin.Context) { mailbox.HandleWS(c.Writer, c.Request) }) kv := store.NewKV() // sad attempt to get putpref working. tldr it wont work without a client fork // https://bsky.app/profile/did:web:did12.whey.party/post/3m75xtomd722n router.GET("/xrpc/app.bsky.actor.putPreferences", func(c *gin.Context) { c.Status(200) }) router.PUT("/xrpc/app.bsky.actor.putPreferences", func(c *gin.Context) { c.Status(200) }) router.POST("/xrpc/app.bsky.actor.putPreferences", func(c *gin.Context) { c.Status(200) userDID := c.GetString("user_did") body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(400, gin.H{"error": "invalid body"}) return } kv.Set(userDID, body) }) router.GET("/xrpc/app.bsky.actor.getPreferences", func(c *gin.Context) { userDID := c.GetString("user_did") val, ok := kv.Get(userDID) if !ok { c.JSON(200, gin.H{"preferences": []any{}}) return } c.Data(200, "application/json", val) }) bskyappdid, _ := utils.NewDID("did:plc:z72i7hdynmk6r22z27h6tvur") profiletest2, _, _ := appbskyactordefs.ProfileViewDetailed(ctx, bskyappdid, sl, cs, BSKYIMAGECDN_URL) data, err := json.MarshalIndent(profiletest2, "", " ") if err != nil { panic(err) } fmt.Println(string(data)) router.GET("/xrpc/app.bsky.actor.getProfiles", func(c *gin.Context) { actors := c.QueryArray("actors") profiles := make([]*appbsky.ActorDefs_ProfileViewDetailed, 0, len(actors)) for _, v := range actors { did, err := utils.NewDID(v) if err != nil { continue } profile, _, _ := appbskyactordefs.ProfileViewDetailed(ctx, did, sl, cs, BSKYIMAGECDN_URL) profiles = append(profiles, profile) } c.JSON(http.StatusOK, &appbsky.ActorGetProfiles_Output{ Profiles: profiles, }) }) router.GET("/xrpc/app.bsky.actor.getProfile", func(c *gin.Context) { actor := c.Query("actor") did, err := utils.NewDID(actor) if err != nil { c.JSON(http.StatusBadRequest, nil) return } profile, _, _ := appbskyactordefs.ProfileViewDetailed(ctx, did, sl, cs, BSKYIMAGECDN_URL) c.JSON(http.StatusOK, profile) }) // really bad actually router.GET("/xrpc/app.bsky.notification.listNotifications", func(c *gin.Context) { emptyarray := []*appbsky.NotificationListNotifications_Notification{} notifshim := &appbsky.NotificationListNotifications_Output{ Notifications: emptyarray, } c.JSON(http.StatusOK, notifshim) }) router.GET("/xrpc/app.bsky.labeler.getServices", func(c *gin.Context) { dids := c.QueryArray("dids") labelers := make([]*appbsky.LabelerGetServices_Output_Views_Elem, 0, len(dids)) //profiles := make([]*appbsky.ActorDefs_ProfileViewDetailed, 0, len(dids)) for _, v := range dids { did, err := utils.NewDID(v) if err != nil { continue } labelerprofile, _, _ := appbskyactordefs.ProfileView(ctx, did, sl, BSKYIMAGECDN_URL) labelerserviceresponse, _ := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.labeler.service", string(did), "self") var labelerservice appbsky.LabelerService if labelerserviceresponse != nil { if err := json.Unmarshal(*labelerserviceresponse.Value, &labelerservice); err != nil { continue } } a := "account" b := "record" c := "chat" placeholderTypes := []*string{&a, &b, &c} labeler := &appbsky.LabelerGetServices_Output_Views_Elem{ LabelerDefs_LabelerView: &appbsky.LabelerDefs_LabelerView{ // LexiconTypeID string `json:"$type" cborgen:"$type,const=app.bsky.labeler.defs#labelerView"` LexiconTypeID: "app.bsky.labeler.defs#labelerView", // Cid string `json:"cid" cborgen:"cid"` Cid: *labelerserviceresponse.Cid, // Creator *ActorDefs_ProfileView `json:"creator" cborgen:"creator"` Creator: labelerprofile, // IndexedAt string `json:"indexedAt" cborgen:"indexedAt"` IndexedAt: labelerservice.CreatedAt, // Labels []*comatproto.LabelDefs_Label `json:"labels,omitempty" cborgen:"labels,omitempty"` Labels: nil, // seems to always be empty? // LikeCount *int64 `json:"likeCount,omitempty" cborgen:"likeCount,omitempty"` LikeCount: nil, // placeholder sorry // Uri string `json:"uri" cborgen:"uri"` Uri: labelerserviceresponse.Uri, // Viewer *LabelerDefs_LabelerViewerState `json:"viewer,omitempty" cborgen:"viewer,omitempty"` Viewer: nil, }, LabelerDefs_LabelerViewDetailed: &appbsky.LabelerDefs_LabelerViewDetailed{ // LexiconTypeID string `json:"$type" cborgen:"$type,const=app.bsky.labeler.defs#labelerViewDetailed"` LexiconTypeID: "app.bsky.labeler.defs#labelerViewDetailed", // Cid string `json:"cid" cborgen:"cid"` Cid: *labelerserviceresponse.Cid, // Creator *ActorDefs_ProfileView `json:"creator" cborgen:"creator"` Creator: labelerprofile, // IndexedAt string `json:"indexedAt" cborgen:"indexedAt"` IndexedAt: labelerservice.CreatedAt, // Labels []*comatproto.LabelDefs_Label `json:"labels,omitempty" cborgen:"labels,omitempty"` Labels: nil, // seems to always be empty? // LikeCount *int64 `json:"likeCount,omitempty" cborgen:"likeCount,omitempty"` LikeCount: nil, // placeholder sorry // Policies *LabelerDefs_LabelerPolicies `json:"policies" cborgen:"policies"` Policies: labelerservice.Policies, // // reasonTypes: The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. // ReasonTypes []*string `json:"reasonTypes,omitempty" cborgen:"reasonTypes,omitempty"` ReasonTypes: nil, //usually not even present // // subjectCollections: Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. // SubjectCollections []string `json:"subjectCollections,omitempty" cborgen:"subjectCollections,omitempty"` SubjectCollections: nil, //usually not even present // // subjectTypes: The set of subject types (account, record, etc) this service accepts reports on. // SubjectTypes []*string `json:"subjectTypes,omitempty" cborgen:"subjectTypes,omitempty"` SubjectTypes: placeholderTypes, // Uri string `json:"uri" cborgen:"uri"` Uri: labelerserviceresponse.Uri, // Viewer *LabelerDefs_LabelerViewerState `json:"viewer,omitempty" cborgen:"viewer,omitempty"` Viewer: nil, }, } labelers = append(labelers, labeler) } c.JSON(http.StatusOK, &appbsky.LabelerGetServices_Output{ Views: labelers, }) }) router.GET("/xrpc/app.bsky.feed.getFeedGenerators", func(c *gin.Context) { feeds := c.QueryArray("feeds") ctx := c.Request.Context() type result struct { view *appbsky.FeedDefs_GeneratorView } results := make([]result, len(feeds)) var wg sync.WaitGroup wg.Add(len(feeds)) for i, raw := range feeds { go func(i int, raw string) { defer wg.Done() aturi, err := syntax.ParseATURI(raw) if err != nil { return } did := aturi.Authority().String() collection := aturi.Collection().String() rkey := aturi.RecordKey().String() repoDID, err := utils.NewDID(did) if err != nil { return } // fetch profile and record in parallel too (optional) // but to keep it simple, do serial inside this goroutine profile, _, _ := appbskyactordefs.ProfileView(ctx, repoDID, sl, BSKYIMAGECDN_URL) rec, err := agnostic.RepoGetRecord(ctx, sl, "", collection, did, rkey) if err != nil || rec.Value == nil { return } var genRec appbsky.FeedGenerator if err := json.Unmarshal(*rec.Value, &genRec); err != nil { return } var avatar *string if genRec.Avatar != nil { u := utils.MakeImageCDN(repoDID, BSKYIMAGECDN_URL, "avatar", genRec.Avatar.Ref.String()) avatar = &u } results[i].view = &appbsky.FeedDefs_GeneratorView{ LexiconTypeID: "app.bsky.feed.defs#generatorView", AcceptsInteractions: genRec.AcceptsInteractions, Avatar: avatar, Cid: *rec.Cid, ContentMode: genRec.ContentMode, Creator: profile, Description: genRec.Description, DescriptionFacets: genRec.DescriptionFacets, Did: did, DisplayName: genRec.DisplayName, IndexedAt: genRec.CreatedAt, Uri: rec.Uri, } }(i, raw) } wg.Wait() // build final slice out := make([]*appbsky.FeedDefs_GeneratorView, 0, len(results)) for _, r := range results { if r.view != nil { out = append(out, r.view) } } c.JSON(http.StatusOK, &appbsky.FeedGetFeedGenerators_Output{ Feeds: out, }) }) router.GET("/xrpc/app.bsky.feed.getPosts", func(c *gin.Context) { rawdid := c.GetString("user_did") var viewer *utils.DID didval, errdid := utils.NewDID(rawdid) if errdid != nil { viewer = nil } else { viewer = &didval } postsreq := c.QueryArray("uris") ctx := c.Request.Context() type result struct { view *appbsky.FeedDefs_PostView } results := make([]result, len(postsreq)) var wg sync.WaitGroup wg.Add(len(postsreq)) for i, raw := range postsreq { go func(i int, raw string) { defer wg.Done() post, _, _ := appbskyfeeddefs.PostView(ctx, raw, sl, cs, BSKYIMAGECDN_URL, viewer, 2) results[i].view = post }(i, raw) } wg.Wait() // build final slice out := make([]*appbsky.FeedDefs_PostView, 0, len(results)) for _, r := range results { if r.view != nil { out = append(out, r.view) } } c.JSON(http.StatusOK, &appbsky.FeedGetPosts_Output{ Posts: out, }) }) router_unsafe.GET("/xrpc/app.bsky.feed.getFeed", func(c *gin.Context) { ctx := c.Request.Context() rawdid := c.GetString("user_did") log.Println("getFeed router_unsafe user_did: " + rawdid) var viewer *utils.DID didval, errdid := utils.NewDID(rawdid) if errdid != nil { viewer = nil } else { viewer = &didval } feedGenAturiRaw := c.Query("feed") if feedGenAturiRaw == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Missing feed param"}) return } feedGenAturi, err := syntax.ParseATURI(feedGenAturiRaw) if err != nil { return } feedGeneratorRecordResponse, err := agnostic.RepoGetRecord(ctx, sl, "", "app.bsky.feed.generator", feedGenAturi.Authority().String(), feedGenAturi.RecordKey().String()) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve feed generator record: %v", err)}) return } var feedGeneratorRecord appbsky.FeedGenerator if err := json.Unmarshal(*feedGeneratorRecordResponse.Value, &feedGeneratorRecord); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse feed generator record JSON"}) return } feedGenDID := feedGeneratorRecord.Did didDoc, err := ResolveDID(feedGenDID) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve DID: %v", err)}) return } if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Failed to resolve DID: %v", err)}) return } var targetEndpoint string for _, svc := range didDoc.Service { if svc.Type == "BskyFeedGenerator" && strings.HasSuffix(svc.ID, "#bsky_fg") { targetEndpoint = svc.ServiceEndpoint break } } if targetEndpoint == "" { c.JSON(http.StatusBadGateway, gin.H{"error": "Feed Generator service endpoint not found in DID document"}) return } upstreamURL := fmt.Sprintf("%s/xrpc/app.bsky.feed.getFeedSkeleton?%s", strings.TrimSuffix(targetEndpoint, "/"), c.Request.URL.RawQuery, ) req, err := http.NewRequestWithContext(ctx, "GET", upstreamURL, nil) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upstream request"}) return } headersToForward := []string{"Authorization", "Content-Type", "Accept", "User-Agent"} for _, k := range headersToForward { if v := c.GetHeader(k); v != "" { req.Header.Set(k, v) } } client := &http.Client{} resp, err := client.Do(req) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": fmt.Sprintf("Upstream request failed: %v", err)}) return } defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": "Failed to read upstream body"}) return } if resp.StatusCode != http.StatusOK { // Forward the upstream error raw c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), bodyBytes) return } var feekskeleton appbsky.FeedGetFeedSkeleton_Output if err := json.Unmarshal(bodyBytes, &feekskeleton); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse upstream JSON"}) return } skeletonposts := feekskeleton.Feed concurrentResults := MapConcurrent( ctx, skeletonposts, 20, func(ctx context.Context, raw *appbsky.FeedDefs_SkeletonFeedPost) (*appbsky.FeedDefs_FeedViewPost, error) { post, _, err := appbskyfeeddefs.PostView(ctx, raw.Post, sl, cs, BSKYIMAGECDN_URL, viewer, 2) if err != nil { return nil, err } if post == nil { return nil, fmt.Errorf("post not found") } return &appbsky.FeedDefs_FeedViewPost{ // FeedContext *string `json:"feedContext,omitempty" cborgen:"feedContext,omitempty"` // Post *FeedDefs_PostView `json:"post" cborgen:"post"` Post: post, // Reason *FeedDefs_FeedViewPost_Reason `json:"reason,omitempty" cborgen:"reason,omitempty"` // Reason: &appbsky.FeedDefs_FeedViewPost_Reason{ // // FeedDefs_ReasonRepost *FeedDefs_ReasonRepost // FeedDefs_ReasonRepost: &appbsky.FeedDefs_ReasonRepost{ // // LexiconTypeID string `json:"$type" cborgen:"$type,const=app.bsky.feed.defs#reasonRepost"` // LexiconTypeID: "app.bsky.feed.defs#reasonRepost", // // By *ActorDefs_ProfileViewBasic `json:"by" cborgen:"by"` // // Cid *string `json:"cid,omitempty" cborgen:"cid,omitempty"` // // IndexedAt string `json:"indexedAt" cborgen:"indexedAt"` // // Uri *string `json:"uri,omitempty" cborgen:"uri,omitempty"` // Uri: &raw.Reason.FeedDefs_SkeletonReasonRepost.Repost, // }, // // FeedDefs_ReasonPin *FeedDefs_ReasonPin // FeedDefs_ReasonPin: &appbsky.FeedDefs_ReasonPin{ // // LexiconTypeID string `json:"$type" cborgen:"$type,const=app.bsky.feed.defs#reasonPin"` // LexiconTypeID: "app.bsky.feed.defs#reasonPin", // }, // }, // Reply *FeedDefs_ReplyRef `json:"reply,omitempty" cborgen:"reply,omitempty"` // // reqId: Unique identifier per request that may be passed back alongside interactions. // ReqId *string `json:"reqId,omitempty" cborgen:"reqId,omitempty"` }, nil }, ) // build final slice out := make([]*appbsky.FeedDefs_FeedViewPost, 0, len(concurrentResults)) for _, r := range concurrentResults { if r.Err == nil && r.Value != nil && r.Value.Post != nil { out = append(out, r.Value) } } c.JSON(http.StatusOK, &appbsky.FeedGetFeed_Output{ Cursor: feekskeleton.Cursor, Feed: out, }) }) yourJSONBytes, _ := os.ReadFile("./public/getConfig.json") router.GET("/xrpc/app.bsky.unspecced.getConfig", func(c *gin.Context) { c.DataFromReader(200, -1, "application/json", bytes.NewReader(yourJSONBytes), nil) }) router.GET("/", func(c *gin.Context) { log.Println("hello worldio !") clientUUID := sticket.GetUUIDFromRequest(c.Request) hasSticket := clientUUID != "" if hasSticket { go func(targetUUID string) { // simulated heavy processing time.Sleep(2 * time.Second) lateData := map[string]any{ "postId": 101, "newComments": []string{ "Wow great tutorial!", "I am stuck on step 1.", }, } success := mailbox.SendToClient(targetUUID, "post_thread_update", lateData) if success { log.Println("Successfully sent late data via Sticket") } else { log.Println("Failed to send late data (client disconnected?)") } }(clientUUID) } }) router_raw.Run(":7152") } func getPostThreadV2(w http.ResponseWriter, r *http.Request) { log.Println("hello worldio !") } type DidResponse struct { Context []string `json:"@context"` ID string `json:"id"` Service []did.Service `json:"service"` } /* { id: "#bsky_appview", type: "BskyAppView", serviceEndpoint: endpoint, }, */ func GetWellKnownDID(c *gin.Context) { // Use a custom struct to fix missing omitempty on did.Document serviceEndpoint := serviceWebHost serviceDID, err := did.ParseDID(serviceWebDID) if err != nil { log.Println(fmt.Errorf("error parsing serviceDID: %w", err)) return } serviceID, err := did.ParseDID("#bsky_appview") if err != nil { panic(err) } didDoc := did.Document{ Context: []string{did.CtxDIDv1}, ID: serviceDID, Service: []did.Service{ { ID: serviceID, Type: "BskyAppView", ServiceEndpoint: serviceEndpoint, }, }, } didResponse := DidResponse{ Context: didDoc.Context, ID: didDoc.ID.String(), Service: didDoc.Service, } c.JSON(http.StatusOK, didResponse) }