mirror of
https://github.com/studiokaiji/nostr-webhost.git
synced 2025-12-18 15:24:19 +01:00
Merge pull request #50 from studiokaiji/fix-undisplay-d-tag
identifierを後で指定した時、dTagが表示されない問題を解決
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user