package main import ( "encoding/json" "errors" "html/template" "io" "io/fs" "log" "math/rand" "net/http" "net/url" "os" "path" "path/filepath" "strings" "time" ) type Status struct { NFTNum int TotalUSDValue float64 NFTs []downloadedOpenSeaAsset } const NFTS_DIR = "./nfts" func getDownloadedOpenSeaAsset(id string) (downloadedOpenSeaAsset, error) { asset := downloadedOpenSeaAsset{} file, err := os.Open(filepath.Join(NFTS_DIR, id+".json")) if err != nil { return asset, err } err = json.NewDecoder(file).Decode(&asset) if err != nil { return asset, err } return asset, nil } func getStatus() (Status, error) { status := Status{} entries, err := os.ReadDir(NFTS_DIR) if err != nil { return status, err } // https://yourbasic.org/golang/shuffle-slice-array/ rand.Seed(time.Now().UnixNano()) rand.Shuffle(len(entries), func(i, j int) { entries[i], entries[j] = entries[j], entries[i] }) for _, val := range entries { info, err := val.Info() if err != nil { return status, err } if filepath.Ext(info.Name()) != ".json" { continue } id := strings.Split(info.Name(), ".")[0] asset, err := getDownloadedOpenSeaAsset(id) if err != nil { return status, err } if len(status.NFTs) <= 20 { status.NFTs = append(status.NFTs, asset) } status.NFTNum += 1 status.TotalUSDValue += asset.GetUSDPrice() } return status, nil } func getOpenSeaId(id string) string { return "opensea:asset:" + id } func getRandomOpenSeaAsset() (Asset, error) { assets, err := GetAssets() if err != nil { log.Println("Error hablandole a OpenSea:", err) return Asset{}, err } for _, a := range assets { if len(a.ImageUrl) == 0 { continue } id := getOpenSeaId(a.Id) file, err := os.Open(filepath.Join(NFTS_DIR, id+".json")) defer file.Close() if errors.Is(err, fs.ErrNotExist) { return a, nil } } // TODO: paginar y cachear paginación return Asset{}, errors.New("No conseguí ningún asset") } type downloadedOpenSeaAsset struct { Id string `json:"Id"` Asset `json:"Asset"` FileName string `json:"FileName"` } func (d downloadedOpenSeaAsset) GetUSDPrice() float64 { return d.Asset.LastSale.TotalPrice * d.Asset.LastSale.PaymentToken.USDPrice } func (d downloadedOpenSeaAsset) IsVideo() bool { return path.Ext(d.FileName) == ".mp4" } func downloadOpenSeaAsset(asset Asset) (string, error) { id := getOpenSeaId(asset.Id) u, err := url.Parse(asset.ImageUrl) if err != nil { return "", err } ext := path.Ext(u.Path) filename := id + ext imageRes, err := http.DefaultClient.Get(asset.ImageUrl) if err != nil { return "", err } file, err := os.Create(filepath.Join(NFTS_DIR, filename)) if err != nil { return "", err } _, err = io.Copy(file, imageRes.Body) if err != nil { return "", err } jsonFile, err := os.Create(filepath.Join(NFTS_DIR, id+".json")) if err != nil { return "", err } err = json.NewEncoder(jsonFile).Encode(downloadedOpenSeaAsset{ Id: id, Asset: asset, FileName: filename, }) if err != nil { return "", err } return id, nil } func must(err error) { if err != nil { log.Panicln(err) } } func main() { indexTmpl, err := template.ParseFiles("./templates/base.tmpl", "./templates/embed-nft.tmpl", "./templates/index.tmpl") must(err) nftsTmpl, err := template.ParseFiles("./templates/base.tmpl", "./templates/embed-nft.tmpl", "./templates/nft.tmpl") must(err) http.Handle("/static/nfts/", http.StripPrefix("/static/nfts/", http.FileServer(http.Dir(NFTS_DIR)))) http.Handle("/static/assets/", http.StripPrefix("/static/assets/", http.FileServer(http.Dir("./assets")))) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { status, err := getStatus() if err != nil { log.Println("Error en Status:", err) internalError(w, "Error consiguiendo el contador de NFTs.") return } w.Header().Add("Content-Type", "text/html") err = indexTmpl.Execute(w, status) if err != nil { log.Panicln("No pude escribir index", err) } }) http.HandleFunc("/copiar", func(w http.ResponseWriter, r *http.Request) { asset, err := getRandomOpenSeaAsset() if err != nil { internalError(w, "Error hablandole a OpenSea.") return } log.Println(asset) id, err := downloadOpenSeaAsset(asset) if err != nil { log.Println("Error descargando OpenSea asset", err) internalError(w, "Error descargando NFT.") return } w.Header().Add("Location", "/nfts/"+id) w.WriteHeader(http.StatusTemporaryRedirect) w.Write([]byte("Redirijiendo al NFT...")) }) http.HandleFunc("/nfts/", func(w http.ResponseWriter, r *http.Request) { _, id := path.Split(r.URL.Path) nft, err := getDownloadedOpenSeaAsset(id) if err != nil { log.Panicln("No pude conseguir NFT", err) } w.Header().Add("Content-Type", "text/html") err = nftsTmpl.Execute(w, nft) if err != nil { log.Panicln("No pude escribir nft", err) } }) log.Println("Hola") http.ListenAndServe(":5050", nil) } func internalError(w http.ResponseWriter, err_str string) { w.WriteHeader(http.StatusInternalServerError) _, err := w.Write([]byte(err_str)) if err != nil { log.Panicln("No pude escribir el error xD") } }