mirror of
https://github.com/studiokaiji/nostr-webhost.git
synced 2026-01-31 12:44:42 +01:00
画像のアップロード機能を実装
This commit is contained in:
@@ -3,9 +3,7 @@ package deploy
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -71,19 +69,21 @@ func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, s
|
||||
// リレーを取得
|
||||
allRelays, err = relays.GetAllRelays()
|
||||
if err != nil {
|
||||
fmt.Println("❌ Failed to get all relays:", err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
|
||||
// basePath以下のMedia Fileのパスを全て羅列しアップロード
|
||||
err = uploadAllValidStaticMediaFiles(priKey, pubKey, basePath)
|
||||
if err != nil {
|
||||
fmt.Println("❌ Failed to upload media:", err)
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
// リンクの解析と変換
|
||||
convertLinks(priKey, pubKey, basePath, replaceable, htmlIdentifier, doc)
|
||||
|
||||
if len(mediaUploadRequestQueue) > 0 {
|
||||
// メディアのアップロード
|
||||
fmt.Println("📷 Uploading media files")
|
||||
uploadMediaFilesFromQueue()
|
||||
fmt.Println("📷 Media upload finished.")
|
||||
}
|
||||
|
||||
// 更新されたHTML
|
||||
var buf bytes.Buffer
|
||||
html.Render(&buf, doc)
|
||||
@@ -116,7 +116,12 @@ func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, s
|
||||
return eventId, encoded, err
|
||||
}
|
||||
|
||||
func convertLinks(priKey, pubKey, basePath string, replaceable bool, indexHtmlIdentifier string, n *html.Node) {
|
||||
func convertLinks(
|
||||
priKey, pubKey, basePath string,
|
||||
replaceable bool,
|
||||
indexHtmlIdentifier string,
|
||||
n *html.Node,
|
||||
) {
|
||||
if n.Type == html.ElementNode {
|
||||
if n.Data == "link" || n.Data == "script" {
|
||||
// <link> と <script> タグを対象としてNostr Eventを作成
|
||||
@@ -128,7 +133,7 @@ func convertLinks(priKey, pubKey, basePath string, replaceable bool, indexHtmlId
|
||||
// kindを取得
|
||||
kind, err := pathToKind(filePath, replaceable)
|
||||
if err != nil {
|
||||
continue
|
||||
break
|
||||
}
|
||||
|
||||
// contentを取得
|
||||
@@ -138,10 +143,7 @@ func convertLinks(priKey, pubKey, basePath string, replaceable bool, indexHtmlId
|
||||
continue
|
||||
}
|
||||
|
||||
// jsファイルを解析する
|
||||
if strings.HasSuffix(basePath, ".js") {
|
||||
jsContent := string(bytesContent)
|
||||
}
|
||||
content := string(bytesContent)
|
||||
|
||||
// Tagsを追加
|
||||
tags := nostr.Tags{}
|
||||
@@ -153,8 +155,16 @@ func convertLinks(priKey, pubKey, basePath string, replaceable bool, indexHtmlId
|
||||
n.Attr[i].Val = fileIdentifier
|
||||
}
|
||||
|
||||
// Eventを生成し、キューに追加
|
||||
event, err := getEvent(priKey, pubKey, string(bytesContent), kind, tags)
|
||||
// jsファイルを解析する
|
||||
if strings.HasSuffix(a.Val, ".js") {
|
||||
// アップロード済みファイルの元パスとURLを取得
|
||||
for path, url := range uploadedMediaFiles {
|
||||
// JS内に該当ファイルがあったら置換
|
||||
content = strings.ReplaceAll(content, path, url)
|
||||
}
|
||||
}
|
||||
|
||||
event, err := getEvent(priKey, pubKey, content, kind, tags)
|
||||
if err != nil {
|
||||
fmt.Println("❌ Failed to get event for", filePath, ":", err)
|
||||
break
|
||||
@@ -177,45 +187,22 @@ func convertLinks(priKey, pubKey, basePath string, replaceable bool, indexHtmlId
|
||||
}
|
||||
} else if slices.Contains(availableMediaHtmlTags, n.Data) {
|
||||
// 内部mediaファイルを対象にUpload Requestを作成
|
||||
for i, a := range n.Attr {
|
||||
for _, a := range n.Attr {
|
||||
if (a.Key == "href" || a.Key == "src" || a.Key == "data") && !isExternalURL(a.Val) && isValidMediaFileType(a.Val) {
|
||||
filePath := filepath.Join(basePath, a.Val)
|
||||
|
||||
// アップロードのためのHTTPリクエストを取得
|
||||
request, err := filePathToUploadMediaRequest(filePath, priKey, pubKey)
|
||||
// contentを取得
|
||||
bytesContent, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
fmt.Println("❌ Failed generate upload request: ", err)
|
||||
fmt.Println("❌ Failed to read", filePath, ":", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// アップロード処理を代入
|
||||
uploadFunc := func(client *http.Client) (*MediaResult, error) {
|
||||
response, err := client.Do(request)
|
||||
// リクエストを送信
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error sending request: %w", err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
content := string(bytesContent)
|
||||
|
||||
var result *MediaResult
|
||||
// ResultのDecode
|
||||
err = json.NewDecoder(response.Body).Decode(result)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error decoding response: %w", err)
|
||||
}
|
||||
|
||||
// アップロードに失敗した場合
|
||||
if !result.result {
|
||||
return nil, fmt.Errorf("Failed to upload file: %w", err)
|
||||
}
|
||||
|
||||
// URLを割り当て
|
||||
n.Attr[i].Val = result.url
|
||||
|
||||
return result, nil
|
||||
if url, ok := uploadedMediaFiles[filePath]; ok {
|
||||
content = strings.ReplaceAll(content, filePath, url)
|
||||
}
|
||||
|
||||
// Queueにアップロード処理を追加
|
||||
addMediaUploadRequestFuncQueue(uploadFunc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package deploy
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@@ -68,68 +70,85 @@ func isValidMediaFileType(path string) bool {
|
||||
const uploadEndpoint = "https://nostrcheck.me/api/v1/media"
|
||||
|
||||
type MediaResult struct {
|
||||
result bool
|
||||
description string
|
||||
status string
|
||||
id int
|
||||
pubkey string
|
||||
url string
|
||||
hash string
|
||||
magnet string
|
||||
tags []string
|
||||
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"`
|
||||
}
|
||||
|
||||
var mediaUploadRequestQueue []func(*http.Client) (*MediaResult, error)
|
||||
// [元パス]:[URL]の形で記録する
|
||||
var uploadedMediaFiles = map[string]string{}
|
||||
|
||||
func addNostrEventQueue(event *nostr.Event) {
|
||||
nostrEventsQueue = append(nostrEventsQueue, event)
|
||||
}
|
||||
func uploadMediaFiles(filePaths []string, requests []*http.Request) {
|
||||
client := &http.Client{}
|
||||
|
||||
func addMediaUploadRequestFuncQueue(reqFunc func(client *http.Client) (*MediaResult, error)) {
|
||||
mediaUploadRequestQueue = append(mediaUploadRequestQueue, reqFunc)
|
||||
}
|
||||
|
||||
var allRelays []string
|
||||
|
||||
func uploadMediaFilesFromQueue() {
|
||||
// Publishの進捗状況を表示
|
||||
allEventsCount := len(mediaUploadRequestQueue)
|
||||
uploadedFilesCount := 0
|
||||
var uploadedMediaFilesCount = 0
|
||||
var allMediaFilesCount = len(requests)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
tools.DisplayProgressBar(&uploadedFilesCount, &allEventsCount)
|
||||
tools.DisplayProgressBar(&uploadedMediaFilesCount, &allMediaFilesCount)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
var mutex sync.Mutex
|
||||
|
||||
client := &http.Client{}
|
||||
|
||||
// アップロードを並列処理
|
||||
for _, reqFunc := range mediaUploadRequestQueue {
|
||||
for i, req := range requests {
|
||||
wg.Add(1)
|
||||
go func(reqFun func(*http.Client) (*MediaResult, error)) {
|
||||
_, err := reqFun(client)
|
||||
filePath := filePaths[i]
|
||||
|
||||
go func(filePath string, req *http.Request) {
|
||||
defer wg.Done()
|
||||
|
||||
response, err := client.Do(req)
|
||||
// リクエストを送信
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("\n❌ Error sending request:", filePath, err)
|
||||
return
|
||||
}
|
||||
mutex.Lock() // ロックして排他制御
|
||||
uploadedFilesCount++ // カウントアップ
|
||||
mutex.Unlock() // ロック解除
|
||||
wg.Done() // ゴルーチンの終了を通知
|
||||
}(reqFunc)
|
||||
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() // ロックして排他制御
|
||||
uploadedMediaFilesCount++ // カウントアップ
|
||||
uploadedMediaFiles[filePath] = result.Url
|
||||
mutex.Unlock() // ロック解除
|
||||
}(filePath, req)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func filePathToUploadMediaRequest(filePath, priKey, pubKey string) (*http.Request, error) {
|
||||
func filePathToUploadMediaRequest(basePath, filePath, priKey, pubKey string) (*http.Request, error) {
|
||||
// ファイルを開く
|
||||
file, err := os.Open(filePath)
|
||||
file, err := os.Open(filepath.Join(basePath, filePath))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to read %s: %w", filePath, err)
|
||||
}
|
||||
@@ -140,39 +159,38 @@ func filePathToUploadMediaRequest(filePath, priKey, pubKey string) (*http.Reques
|
||||
// multipart writerを作成
|
||||
writer := multipart.NewWriter(&requestBody)
|
||||
|
||||
// uploadtypeフィールドを設定
|
||||
err = writer.WriteField("uploadtype", "media")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error writing field: %w", err)
|
||||
}
|
||||
|
||||
// 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{}
|
||||
// タグを追加
|
||||
tags.AppendUnique(nostr.Tag{"u", uploadEndpoint})
|
||||
tags.AppendUnique(nostr.Tag{"method", "POST"})
|
||||
tags.AppendUnique(nostr.Tag{"payload", ""})
|
||||
tags := nostr.Tags{
|
||||
nostr.Tag{"u", uploadEndpoint},
|
||||
nostr.Tag{"method", "POST"},
|
||||
nostr.Tag{"payload", ""},
|
||||
}
|
||||
|
||||
// イベントを生成
|
||||
ev, err := getEvent(priKey, pubKey, "", 27533, tags)
|
||||
ev, err := getEvent(priKey, pubKey, "", 27235, tags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error get event: %d", err)
|
||||
}
|
||||
@@ -190,9 +208,68 @@ func filePathToUploadMediaRequest(filePath, priKey, pubKey string) (*http.Reques
|
||||
}
|
||||
|
||||
// ヘッダーを設定
|
||||
request.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
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) {
|
||||
mediaFilePaths := []string{}
|
||||
|
||||
err := filepath.Walk(basePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ディレクトリはスキップ
|
||||
if !info.IsDir() {
|
||||
// 各サフィックスに対してマッチングを試みる
|
||||
for _, suffix := range availableContentSuffixes {
|
||||
// ファイル名とサフィックスがマッチした場合
|
||||
if strings.HasSuffix(strings.ToLower(info.Name()), strings.ToLower(suffix)) {
|
||||
// フルパスからbasePathまでの相対パスを計算
|
||||
relPath, err := filepath.Rel(basePath, path)
|
||||
if err != nil {
|
||||
fmt.Println("❌ Error calculating relative path:", err)
|
||||
continue
|
||||
}
|
||||
// マッチするファイルの相対パスをスライスに追加
|
||||
mediaFilePaths = append(mediaFilePaths, "/"+relPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mediaFilePaths, nil
|
||||
}
|
||||
|
||||
// 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(filesPaths, requests)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"github.com/studiokaiji/nostr-webhost/hostr/cmd/tools"
|
||||
)
|
||||
|
||||
var allRelays []string
|
||||
|
||||
func getEvent(priKey, pubKey, content string, kind int, tags nostr.Tags) (*nostr.Event, error) {
|
||||
ev := nostr.Event{
|
||||
PubKey: pubKey,
|
||||
@@ -51,13 +53,13 @@ func publishEventsFromQueue(replaceable bool) (string, string) {
|
||||
|
||||
// Publishの進捗状況を表示
|
||||
allEventsCount := len(nostrEventsQueue)
|
||||
uploadedFilesCount := 0
|
||||
uploadedMediaFilesCount := 0
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
go func() {
|
||||
wg.Add(1)
|
||||
tools.DisplayProgressBar(&uploadedFilesCount, &allEventsCount)
|
||||
tools.DisplayProgressBar(&uploadedMediaFilesCount, &allEventsCount)
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
@@ -74,17 +76,17 @@ func publishEventsFromQueue(replaceable bool) (string, string) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
mutex.Lock() // ロックして排他制御
|
||||
uploadedFilesCount++ // カウントアップ
|
||||
mutex.Unlock() // ロック解除
|
||||
wg.Done() // ゴルーチンの終了を通知
|
||||
mutex.Lock() // ロックして排他制御
|
||||
uploadedMediaFilesCount++ // カウントアップ
|
||||
mutex.Unlock() // ロック解除
|
||||
wg.Done() // ゴルーチンの終了を通知
|
||||
}(ev)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if uploadedFilesCount < allEventsCount {
|
||||
fmt.Println("Failed to deploy", allEventsCount-uploadedFilesCount, "files.")
|
||||
if uploadedMediaFilesCount < allEventsCount {
|
||||
fmt.Println("Failed to deploy", allEventsCount-uploadedMediaFilesCount, "files.")
|
||||
}
|
||||
|
||||
indexEvent := nostrEventsQueue[len(nostrEventsQueue)-1]
|
||||
|
||||
Reference in New Issue
Block a user