package deploy import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "os" "strings" "sync" "github.com/nbd-wtf/go-nostr" "github.com/studiokaiji/nostr-webhost/hostr/cmd/tools" ) var availableContentSuffixes = []string{ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".mp4", ".quicktime", ".mpeg", ".webm", ".mpeg", ".mpg", ".mpeg3", ".mp3", } const uploadEndpoint = "https://nostrcheck.me/api/v1/media" type MediaResult struct { Result bool `json:"result,omitempty"` Description string `json:"description"` Status string `json:"status,omitempty"` Id int `json:"id,omitempty"` Pubkey string `json:"pubkey,omitempty"` Url string `json:"url,omitempty"` Hash string `json:"hash,omitempty"` Magnet string `json:"magnet,omitempty"` Tags []string `json:"tags,omitempty"` } // [元パス]:[URL]の形で記録する var uploadedMediaFilePathToURL = map[string]string{} func uploadMediaFiles(basePath string, filePaths []string, requests []*http.Request) { fmt.Println("Uploading media files...") client := &http.Client{} var uploadedMediaFilePathToURLCount = 0 var allMediaFilesCount = len(requests) var wg sync.WaitGroup go func() { wg.Add(1) tools.DisplayProgressBar(&uploadedMediaFilePathToURLCount, &allMediaFilesCount) wg.Done() }() var mutex sync.Mutex // アップロードを並列処理 for i, req := range requests { wg.Add(1) filePath := filePaths[i] fmt.Println("Added upload request", filePath) go func(filePath string, req *http.Request) { defer wg.Done() response, err := client.Do(req) // リクエストを送信 if err != nil { fmt.Println("\n❌ Error sending request:", filePath, err) return } defer response.Body.Close() if !strings.HasPrefix(fmt.Sprint(response.StatusCode), "2") { fmt.Println("\n❌ Failed to upload:", response.StatusCode, filePath) return } var result *MediaResult // ResultのDecode err = json.NewDecoder(response.Body).Decode(&result) if err != nil { fmt.Println("\n❌ Error decoding response:", err) return } // アップロードに失敗した場合 if !result.Result { fmt.Println("\n❌ Failed to upload file:", filePath, err) return } mutex.Lock() // ロックして排他制御 uploadedMediaFilePathToURLCount++ // カウントアップ uploadedMediaFilePathToURL[strings.Replace(filePath, basePath, "", 1)] = result.Url mutex.Unlock() // ロック解除 }(filePath, req) } wg.Wait() } func filePathToUploadMediaRequest(basePath, filePath, priKey, pubKey string) (*http.Request, error) { // ファイルを開く file, err := os.Open(filePath) if err != nil { return nil, fmt.Errorf("Failed to read %s: %w", filePath, err) } defer file.Close() // リクエストボディのバッファを初期化 var requestBody bytes.Buffer // multipart writerを作成 writer := multipart.NewWriter(&requestBody) // mediafileフィールドを作成 part, err := writer.CreateFormFile("mediafile", filePath) if err != nil { return nil, fmt.Errorf("Error creating form file: %w", err) } // ファイルの内容をpartにコピー _, err = io.Copy(part, file) if err != nil { return nil, fmt.Errorf("Error copying file: %w", err) } // uploadtypeフィールドを設定 err = writer.WriteField("uploadtype", "media") if err != nil { return nil, fmt.Errorf("Error writing field: %w", err) } // writerを閉じてリクエストボディを完成させる err = writer.Close() if err != nil { return nil, fmt.Errorf("Error closing writer: %w", err) } // タグを追加 tags := nostr.Tags{ nostr.Tag{"u", uploadEndpoint}, nostr.Tag{"method", "POST"}, nostr.Tag{"payload", ""}, } // イベントを生成 ev, err := getEvent(priKey, pubKey, "", 27235, tags) if err != nil { return nil, fmt.Errorf("Error get event: %d", err) } // イベントをJSONにマーシャル evJson, err := ev.MarshalJSON() if err != nil { return nil, fmt.Errorf("Error marshaling event: %d", err) } // HTTPリクエストを作成 request, err := http.NewRequest("POST", uploadEndpoint, &requestBody) if err != nil { return nil, fmt.Errorf("Error creating request: %d", err) } // ヘッダーを設定 request.Header.Set("Authorization", "Nostr "+base64.StdEncoding.EncodeToString(evJson)) request.Header.Set("Accept", "application/json") request.Header.Set("Content-Type", writer.FormDataContentType()) return request, nil } // basePath以下のMedia Fileのパスを全て羅列する func listAllValidStaticMediaFilePaths(basePath string) ([]string, error) { return tools.FindFilesWithBasePathBySuffixes(basePath, availableContentSuffixes) } // basePath以下のMedia Fileのパスを全て羅列しアップロード func uploadAllValidStaticMediaFiles(priKey, pubKey, basePath string) error { filesPaths, err := listAllValidStaticMediaFilePaths(basePath) if err != nil { return err } requests := []*http.Request{} for _, filePath := range filesPaths { request, err := filePathToUploadMediaRequest(basePath, filePath, priKey, pubKey) if err != nil { return err } requests = append(requests, request) } uploadMediaFiles(basePath, filesPaths, requests) return nil }