package main import ( "bytes" "context" "encoding/json" "fmt" "os" "path" "strings" "github.com/bluesky-social/indigo/api/atproto" "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/lex/util" "github.com/bluesky-social/indigo/xrpc" "tangled.org/core/api/tangled" "tangled.org/core/types" ) type Config struct { Handle string AppPassword string } func loadConfig() Config { handle, ok := os.LookupEnv("ATPROTO_HANDLE") if !ok { panic(fmt.Errorf("Environment variable 'ATPROTO_HANDLE' is not set\n")) } appPassword, ok := os.LookupEnv("ATPROTO_APP_PASSWORD") if !ok { panic(fmt.Errorf("Environment variable 'ATPROTO_APP_PASSWORD' is not set\n")) } return Config{ Handle: handle, AppPassword: appPassword, } } func getRepository(client *atclient.APIClient, handle *syntax.AtIdentifier, repositoryName string) (*tangled.Repo, string, error) { repos, err := atproto.RepoListRecords(context.Background(), client, tangled.RepoNSID, "", 0, handle.String(), false) if err != nil { panic(err) } var repo *tangled.Repo var uri string for _, r := range repos.Records { if r == nil { continue } var tempRecord *tangled.Repo tempRecord = r.Value.Val.(*tangled.Repo) if tempRecord.Name == repositoryName { repo = tempRecord uri = r.Uri break } } if repo == nil { return nil, "", fmt.Errorf("Cannot find repository %s\n", repositoryName) } return repo, uri, nil } func getTag(repo *tangled.Repo, repoUri string, tag string) (*types.TagReference, error) { xrpcc := &xrpc.Client{ Host: fmt.Sprintf("https://%s", repo.Knot), } xrpcBytes, err := tangled.RepoTags(context.Background(), xrpcc, "", 0, fmt.Sprintf("%s/%s", strings.Split(repoUri, "/")[2], repo.Name)) if err != nil { panic(err) } var result types.RepoTagsResponse if err := json.Unmarshal(xrpcBytes, &result); err != nil { panic(err) } var tagReference *types.TagReference for _, t := range result.Tags { if t.Tag != nil { if t.Reference.Name == tag { tagReference = t } } } if tagReference == nil { return nil, fmt.Errorf("Cannot find a tag reference to tag %s\n", tag) } return tagReference, nil } func uploadArtifact(client *atclient.APIClient, repo *tangled.Repo, tag *types.TagReference, repoUri string, filePath string) error { fileBytes, err := os.ReadFile(filePath) if err != nil { return err } blob, err := atproto.RepoUploadBlob(context.Background(), client, bytes.NewReader(fileBytes)) if err != nil { return err } var artifact = tangled.RepoArtifact{ CreatedAt: syntax.DatetimeNow().String(), Name: path.Base(filePath), Repo: repoUri, Tag: tag.Tag.Hash[:], Artifact: blob.Blob, } record := atproto.RepoCreateRecord_Input{ Collection: tangled.RepoArtifactNSID, Record: &util.LexiconTypeDecoder{ Val: &artifact, }, Repo: strings.Split(repoUri, "/")[2], } _, err = atproto.RepoCreateRecord(context.Background(), client, &record) if err != nil { return err } return nil } func main() { args := os.Args if len(args) != 4 { fmt.Fprintf(os.Stderr, "Wrong usage: %s [repo] [tag] [file]\n", os.Args[0]) os.Exit(1) } config := loadConfig() handle, err := syntax.ParseAtIdentifier(config.Handle) if err != nil { panic(err) } client, err := atclient.LoginWithPassword(context.Background(), identity.DefaultDirectory(), *handle, config.AppPassword, "", nil) if err != nil { panic(err) } repo, uri, err := getRepository(client, handle, args[1]) if err != nil { panic(err) } tag, err := getTag(repo, uri, args[2]) if err != nil { panic(err) } if err = uploadArtifact(client, repo, tag, uri, args[3]); err != nil { panic(err) } fmt.Println("Artifact successfuly uploaded !") }