Merge pull request #50 from studiokaiji/fix-undisplay-d-tag

identifierを後で指定した時、dTagが表示されない問題を解決
This commit is contained in:
kaiji
2023-10-26 21:20:59 +09:00
committed by GitHub
2 changed files with 187 additions and 102 deletions

View File

@@ -3,27 +3,145 @@ package deploy
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip19"
"github.com/studiokaiji/nostr-webhost/hostr/cmd/consts" "github.com/studiokaiji/nostr-webhost/hostr/cmd/consts"
"github.com/studiokaiji/nostr-webhost/hostr/cmd/keystore" "github.com/studiokaiji/nostr-webhost/hostr/cmd/keystore"
"github.com/studiokaiji/nostr-webhost/hostr/cmd/relays" "github.com/studiokaiji/nostr-webhost/hostr/cmd/relays"
"golang.org/x/exp/slices" "github.com/studiokaiji/nostr-webhost/hostr/cmd/tools"
"golang.org/x/net/html" "golang.org/x/net/html"
) )
func pathToKind(path string, replaceable bool) (int, error) {
// パスを分割
separatedPath := strings.Split(path, ".")
// 拡張子を取得
ex := separatedPath[len(separatedPath)-1]
// replaceable(NIP-33)の場合はReplaceableなkindを返す
switch ex {
case "html":
if replaceable {
return consts.KindWebhostHTML, nil
} else {
return consts.KindWebhostReplaceableHTML, nil
}
case "css":
if replaceable {
return consts.KindWebhostReplaceableCSS, nil
} else {
return consts.KindWebhostCSS, nil
}
case "js":
if replaceable {
return consts.KindWebhostReplaceableJS, nil
} else {
return consts.KindWebhostJS, nil
}
default:
return 0, fmt.Errorf("Invalid path")
}
}
// Replaceableにする場合のidentifier(dタグ)を取得
func getReplaceableIdentifier(indexHtmlIdentifier, filePath string) string {
return indexHtmlIdentifier + "/" + filePath[1:]
}
var nostrEventsQueue []*nostr.Event
func addNostrEventQueue(event *nostr.Event) {
nostrEventsQueue = append(nostrEventsQueue, event)
}
var allRelays []string
func publishEventsFromQueue(replaceable bool) (string, string) {
ctx := context.Background()
fmt.Println("Publishing...")
// 各リレーに接続
var relays []*nostr.Relay
for _, url := range allRelays {
relay, err := nostr.RelayConnect(ctx, url)
if err != nil {
fmt.Println("❌ Failed to connect to:", url)
continue
}
relays = append(relays, relay)
}
// Publishの進捗状況を表示
allEventsCount := len(nostrEventsQueue)
uploadedFilesCount := 0
var wg sync.WaitGroup
go func() {
wg.Add(1)
tools.DisplayProgressBar(&uploadedFilesCount, &allEventsCount)
wg.Done()
}()
var mutex sync.Mutex
// リレーへpublish
for _, ev := range nostrEventsQueue {
wg.Add(1)
go func(event *nostr.Event) {
for _, relay := range relays {
_, err := relay.Publish(ctx, *event)
if err != nil {
fmt.Println(err)
continue
}
}
mutex.Lock() // ロックして排他制御
uploadedFilesCount++ // カウントアップ
mutex.Unlock() // ロック解除
wg.Done() // ゴルーチンの終了を通知
}(ev)
}
wg.Wait()
if uploadedFilesCount < allEventsCount {
fmt.Println("Failed to deploy", allEventsCount-uploadedFilesCount, "files.")
}
indexEvent := nostrEventsQueue[len(nostrEventsQueue)-1]
encoded := ""
if !replaceable {
if enc, err := nip19.EncodeEvent(indexEvent.ID, allRelays, indexEvent.PubKey); err == nil {
encoded = enc
} else {
fmt.Println("❌ Failed to covert nevent:", err)
}
}
return indexEvent.ID, encoded
}
func isExternalURL(urlStr string) bool { func isExternalURL(urlStr string) bool {
u, err := url.Parse(urlStr) u, err := url.Parse(urlStr)
return err == nil && u.Scheme != "" && u.Host != "" return err == nil && u.Scheme != "" && u.Host != ""
} }
func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, string, error) { func isValidFileType(str string) bool {
return strings.HasSuffix(str, ".html") || strings.HasSuffix(str, ".css") || strings.HasSuffix(str, ".js")
}
func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, string, string, error) {
// 引数からデプロイしたいサイトのパスを受け取る。 // 引数からデプロイしたいサイトのパスを受け取る。
filePath := filepath.Join(basePath, "index.html") filePath := filepath.Join(basePath, "index.html")
@@ -31,26 +149,26 @@ func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, s
content, err := os.ReadFile(filePath) content, err := os.ReadFile(filePath)
if err != nil { if err != nil {
fmt.Println("❌ Failed to read index.html:", err) fmt.Println("❌ Failed to read index.html:", err)
return "", "", err return "", "", "", err
} }
// HTMLの解析 // HTMLの解析
doc, err := html.Parse(bytes.NewReader(content)) doc, err := html.Parse(bytes.NewReader(content))
if err != nil { if err != nil {
fmt.Println("❌ Failed to parse index.html:", err) fmt.Println("❌ Failed to parse index.html:", err)
return "", "", err return "", "", "", err
} }
// Eventの取得に必要になるキーペアを取得 // Eventの取得に必要になるキーペアを取得
priKey, err := keystore.GetSecret() priKey, err := keystore.GetSecret()
if err != nil { if err != nil {
fmt.Println("❌ Failed to get private key:", err) fmt.Println("❌ Failed to get private key:", err)
return "", "", err return "", "", "", err
} }
pubKey, err := nostr.GetPublicKey(priKey) pubKey, err := nostr.GetPublicKey(priKey)
if err != nil { if err != nil {
fmt.Println("❌ Failed to get public key:", err) fmt.Println("❌ Failed to get public key:", err)
return "", "", err return "", "", "", err
} }
// htmlIdentifierの存在チェック // htmlIdentifierの存在チェック
@@ -69,16 +187,7 @@ func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, s
// リレーを取得 // リレーを取得
allRelays, err = relays.GetAllRelays() allRelays, err = relays.GetAllRelays()
if err != nil { if err != nil {
fmt.Println("❌ Failed to get all relays:", err) return "", "", "", err
return "", "", err
}
// basePath以下のMedia Fileのパスを全て羅列しアップロード
err = uploadAllValidStaticMediaFiles(priKey, pubKey, basePath)
if err != nil {
fmt.Println("❌ Failed to upload media:", err)
return "", "", err
} }
// リンクの解析と変換 // リンクの解析と変換
@@ -106,103 +215,66 @@ func Deploy(basePath string, replaceable bool, htmlIdentifier string) (string, s
event, err := getEvent(priKey, pubKey, strHtml, indexHtmlKind, tags) event, err := getEvent(priKey, pubKey, strHtml, indexHtmlKind, tags)
if err != nil { if err != nil {
fmt.Println("❌ Failed to get public key:", err) fmt.Println("❌ Failed to get public key:", err)
return "", "", err return "", "", "", err
} }
addNostrEventQueue(event) addNostrEventQueue(event)
fmt.Println("Added", filePath, "event to publish queue") fmt.Println("Added", filePath, "event to publish queue")
eventId, encoded := publishEventsFromQueue(replaceable) eventId, encoded := publishEventsFromQueue(replaceable)
return eventId, encoded, err return eventId, encoded, htmlIdentifier, err
} }
func convertLinks( func convertLinks(priKey, pubKey, basePath string, replaceable bool, indexHtmlIdentifier string, n *html.Node) {
priKey, pubKey, basePath string, // <link> と <script> タグを対象とする
replaceable bool, if n.Type == html.ElementNode && (n.Data == "link" || n.Data == "script") {
indexHtmlIdentifier string, for i, a := range n.Attr {
n *html.Node, // href,srcのうち、外部URLでないものかつ. html, .css, .js のみ変換する
) { if (a.Key == "href" || a.Key == "src") && !isExternalURL(a.Val) && isValidFileType(a.Val) {
if n.Type == html.ElementNode { filePath := filepath.Join(basePath, a.Val)
if n.Data == "link" || n.Data == "script" {
// <link> と <script> タグを対象としてNostr Eventを作成
for i, a := range n.Attr {
// href,srcのうち、外部URLでないものかつ. html, .css, .js のみ変換する
if (a.Key == "href" || a.Key == "src") && !isExternalURL(a.Val) && isValidBasicFileType(a.Val) {
filePath := filepath.Join(basePath, a.Val)
// kindを取得 // kindを取得
kind, err := pathToKind(filePath, replaceable) kind, err := pathToKind(filePath, replaceable)
if err != nil { if err != nil {
break continue
}
// contentを取得
bytesContent, err := os.ReadFile(filePath)
if err != nil {
fmt.Println("❌ Failed to read", filePath, ":", err)
continue
}
content := string(bytesContent)
// Tagsを追加
tags := nostr.Tags{}
// 置き換え可能なイベントの場合
if replaceable {
fileIdentifier := getReplaceableIdentifier(indexHtmlIdentifier, a.Val)
tags = tags.AppendUnique(nostr.Tag{"d", fileIdentifier})
// 元のパスをfileIdentifierに置き換える
n.Attr[i].Val = fileIdentifier
}
// 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
}
addNostrEventQueue(event)
fmt.Println("Added", filePath, "event to publish queue")
// 置き換え可能なイベントでない場合
if !replaceable {
// neventを指定
nevent, err := nip19.EncodeEvent(event.ID, allRelays, pubKey)
if err != nil {
fmt.Println("❌ Failed to encode event", filePath, ":", err)
break
}
n.Attr[i].Val = nevent
}
} }
}
} else if slices.Contains(availableMediaHtmlTags, n.Data) {
// 内部mediaファイルを対象にUpload Requestを作成
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)
// contentを取得 // contentを取得
bytesContent, err := os.ReadFile(filePath) bytesContent, err := os.ReadFile(filePath)
if err != nil {
fmt.Println("❌ Failed to read", filePath, ":", err)
continue
}
// Tagsを追加
tags := nostr.Tags{}
// 置き換え可能なイベントの場合
if replaceable {
fileIdentifier := getReplaceableIdentifier(indexHtmlIdentifier, a.Val)
tags = tags.AppendUnique(nostr.Tag{"d", fileIdentifier})
// 元のパスをfileIdentifierに置き換える
n.Attr[i].Val = fileIdentifier
}
// Eventを生成し、キューに追加
event, err := getEvent(priKey, pubKey, string(bytesContent), kind, tags)
if err != nil {
fmt.Println("❌ Failed to get event for", filePath, ":", err)
break
}
addNostrEventQueue(event)
fmt.Println("Added", filePath, "event to publish queue")
// 置き換え可能なイベントでない場合
if !replaceable {
// neventを指定
nevent, err := nip19.EncodeEvent(event.ID, allRelays, pubKey)
if err != nil { if err != nil {
fmt.Println("❌ Failed to read", filePath, ":", err) fmt.Println("❌ Failed to encode event", filePath, ":", err)
continue break
}
content := string(bytesContent)
if url, ok := uploadedMediaFiles[filePath]; ok {
content = strings.ReplaceAll(content, filePath, url)
} }
n.Attr[i].Val = nevent
} }
} }
} }
@@ -214,6 +286,19 @@ func convertLinks(
} }
} }
func convertLinksFromJS() { func getEvent(priKey, pubKey, content string, kind int, tags nostr.Tags) (*nostr.Event, error) {
ev := nostr.Event{
PubKey: pubKey,
CreatedAt: nostr.Now(),
Kind: kind,
Content: content,
Tags: tags,
}
err := ev.Sign(priKey)
if err != nil {
return nil, err
}
return &ev, err
} }

View File

@@ -49,7 +49,7 @@ func main() {
replaceable := ctx.Bool("replaceable") replaceable := ctx.Bool("replaceable")
dTag := ctx.String("identifier") dTag := ctx.String("identifier")
_, encoded, err := deploy.Deploy(path, replaceable, dTag) _, encoded, dTag, err := deploy.Deploy(path, replaceable, dTag)
if err == nil { if err == nil {
fmt.Println("🌐 Deploy Complete!") fmt.Println("🌐 Deploy Complete!")