added hybrid posting mode and fixed kind20 formating
This commit is contained in:
parent
9670d04d68
commit
77643c2154
@ -174,15 +174,105 @@ func main() {
|
|||||||
logger,
|
logger,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create standard post content function
|
// Create standard post content function - updated to support post modes
|
||||||
postContentFunc := poster.CreatePostContentFunc(
|
postContentFunc := func(pubkey, contentPath, contentType, mediaURL, mediaHash, caption string, hashtags []string) error {
|
||||||
eventManager,
|
// Need to get the bot's post mode from the database
|
||||||
relayManager,
|
var botID int64
|
||||||
mediaPrep,
|
var postMode string
|
||||||
logger,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create the NIP-19 encoded post content function
|
// Try to get the bot ID and post mode from the database
|
||||||
|
err := database.Get(&botID, "SELECT id FROM bots WHERE pubkey = ?", pubkey)
|
||||||
|
if err == nil {
|
||||||
|
// Get the post mode for this bot
|
||||||
|
err = database.Get(&postMode, "SELECT post_mode FROM post_config WHERE bot_id = ?", botID)
|
||||||
|
if err != nil || postMode == "" {
|
||||||
|
// Default to kind20 if no post mode is set
|
||||||
|
postMode = "kind20"
|
||||||
|
logger.Info("No post mode found, using default", zap.String("pubkey", pubkey))
|
||||||
|
} else {
|
||||||
|
logger.Info("Retrieved post mode from database",
|
||||||
|
zap.String("post_mode", postMode),
|
||||||
|
zap.Int64("bot_id", botID))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Warn("Could not find bot ID for pubkey", zap.String("pubkey", pubkey), zap.Error(err))
|
||||||
|
postMode = "kind20" // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a poster instance with the retrieved post mode
|
||||||
|
p := poster.NewPoster(eventManager, relayManager, mediaPrep, logger)
|
||||||
|
|
||||||
|
// Call the appropriate function based on post mode
|
||||||
|
if postMode == "hybrid" {
|
||||||
|
logger.Info("Using hybrid post mode", zap.String("pubkey", pubkey))
|
||||||
|
|
||||||
|
// For hybrid mode, we need to post both kinds
|
||||||
|
// First create a kind1 media event
|
||||||
|
var textEvent *nostr.Event
|
||||||
|
var textErr error
|
||||||
|
|
||||||
|
// Create text event
|
||||||
|
// Create tags for better compatibility
|
||||||
|
var tags []nostr.Tag
|
||||||
|
|
||||||
|
// Add hashtags to tags
|
||||||
|
for _, tag := range hashtags {
|
||||||
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create imeta tag for metadata
|
||||||
|
imeta := []string{"imeta", "url " + mediaURL, "m " + contentType}
|
||||||
|
if mediaHash != "" {
|
||||||
|
imeta = append(imeta, "x "+mediaHash)
|
||||||
|
}
|
||||||
|
tags = append(tags, imeta)
|
||||||
|
|
||||||
|
// Add multiple tag types for maximum client compatibility
|
||||||
|
tags = append(tags, nostr.Tag{"url", mediaURL})
|
||||||
|
tags = append(tags, nostr.Tag{"image", mediaURL})
|
||||||
|
tags = append(tags, nostr.Tag{"media", mediaURL})
|
||||||
|
|
||||||
|
// Create content with URL directly embedded for better client compatibility
|
||||||
|
content := ""
|
||||||
|
if caption != "" {
|
||||||
|
content = caption + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the media URL directly in the content
|
||||||
|
content += mediaURL + "\n\n"
|
||||||
|
|
||||||
|
// Add hashtags at the end if any
|
||||||
|
if len(hashtags) > 0 {
|
||||||
|
hashtagArr := make([]string, len(hashtags))
|
||||||
|
for i, tag := range hashtags {
|
||||||
|
hashtagArr[i] = "#" + tag
|
||||||
|
}
|
||||||
|
content += strings.Join(hashtagArr, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create text event with embedded URL and better tagging
|
||||||
|
textEvent, textErr = eventManager.CreateAndSignTextNoteEvent(
|
||||||
|
pubkey,
|
||||||
|
content,
|
||||||
|
tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
if textErr == nil {
|
||||||
|
// Publish the kind1 event
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
relayManager.PublishEvent(ctx, textEvent)
|
||||||
|
logger.Info("Published kind1 text event in hybrid mode", zap.String("event_id", textEvent.ID))
|
||||||
|
} else {
|
||||||
|
logger.Error("Failed to create kind1 event in hybrid mode", zap.Error(textErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always call PostContent (will use kind20 for hybrid mode)
|
||||||
|
return p.PostContent(pubkey, contentPath, contentType, mediaURL, mediaHash, caption, hashtags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the NIP-19 encoded post content function - updated to share code with the regular function
|
||||||
postContentEncodedFunc := func(
|
postContentEncodedFunc := func(
|
||||||
pubkey string,
|
pubkey string,
|
||||||
contentPath string,
|
contentPath string,
|
||||||
@ -192,6 +282,29 @@ func main() {
|
|||||||
caption string,
|
caption string,
|
||||||
hashtags []string,
|
hashtags []string,
|
||||||
) (*models.EventResponse, error) {
|
) (*models.EventResponse, error) {
|
||||||
|
// Need to get the bot's post mode from the database
|
||||||
|
var botID int64
|
||||||
|
var postMode string
|
||||||
|
|
||||||
|
// Try to get the bot ID and post mode from the database
|
||||||
|
err := database.Get(&botID, "SELECT id FROM bots WHERE pubkey = ?", pubkey)
|
||||||
|
if err == nil {
|
||||||
|
// Get the post mode for this bot
|
||||||
|
err = database.Get(&postMode, "SELECT post_mode FROM post_config WHERE bot_id = ?", botID)
|
||||||
|
if err != nil || postMode == "" {
|
||||||
|
// Default to kind20 if no post mode is set
|
||||||
|
postMode = "kind20"
|
||||||
|
logger.Info("No post mode found, using default for encoded function", zap.String("pubkey", pubkey))
|
||||||
|
} else {
|
||||||
|
logger.Info("Retrieved post mode from database for encoded function",
|
||||||
|
zap.String("post_mode", postMode),
|
||||||
|
zap.Int64("bot_id", botID))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.Warn("Could not find bot ID for pubkey in encoded function", zap.String("pubkey", pubkey), zap.Error(err))
|
||||||
|
postMode = "kind20" // Default
|
||||||
|
}
|
||||||
|
|
||||||
// Create a context
|
// Create a context
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@ -199,14 +312,14 @@ func main() {
|
|||||||
// Determine the type of content
|
// Determine the type of content
|
||||||
isImage := strings.HasPrefix(contentType, "image/")
|
isImage := strings.HasPrefix(contentType, "image/")
|
||||||
isVideo := strings.HasPrefix(contentType, "video/")
|
isVideo := strings.HasPrefix(contentType, "video/")
|
||||||
|
|
||||||
// Create alt text if not provided
|
// Create alt text if not provided
|
||||||
altText := caption
|
altText := caption
|
||||||
if altText == "" {
|
if altText == "" {
|
||||||
// Use the filename without extension as a fallback
|
// Use the filename without extension as a fallback
|
||||||
altText = strings.TrimSuffix(filepath.Base(contentPath), filepath.Ext(contentPath))
|
altText = strings.TrimSuffix(filepath.Base(contentPath), filepath.Ext(contentPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract media dimensions if it's an image
|
// Extract media dimensions if it's an image
|
||||||
if isImage {
|
if isImage {
|
||||||
dims, err := mediaPrep.GetMediaDimensions(contentPath)
|
dims, err := mediaPrep.GetMediaDimensions(contentPath)
|
||||||
@ -221,11 +334,11 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an appropriate event
|
// Create an appropriate event
|
||||||
var event *nostr.Event
|
var event *nostr.Event
|
||||||
var err error
|
var eventErr error
|
||||||
|
|
||||||
if isImage {
|
if isImage {
|
||||||
// Create hashtag string for post content
|
// Create hashtag string for post content
|
||||||
var hashtagStr string
|
var hashtagStr string
|
||||||
@ -236,22 +349,118 @@ func main() {
|
|||||||
}
|
}
|
||||||
hashtagStr = "\n\n" + strings.Join(hashtagArr, " ")
|
hashtagStr = "\n\n" + strings.Join(hashtagArr, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
content := caption + hashtagStr
|
// Only include hashtags in content, not caption
|
||||||
|
content := hashtagStr
|
||||||
// Log mediaURL for debugging
|
|
||||||
logger.Debug("Creating picture event with media URL", zap.String("mediaURL", mediaURL))
|
// Log mediaURL and postMode for debugging
|
||||||
|
logger.Debug("Creating picture event with media URL and post mode",
|
||||||
// Extract a title from the caption or use the filename
|
zap.String("mediaURL", mediaURL),
|
||||||
title := caption
|
zap.String("postMode", postMode))
|
||||||
if title == "" {
|
|
||||||
title = strings.TrimSuffix(filepath.Base(contentPath), filepath.Ext(contentPath))
|
// For hybrid mode, first create and post a kind1 text note
|
||||||
|
if postMode == "hybrid" {
|
||||||
|
// Create media tags for kind 1
|
||||||
|
var tags []nostr.Tag
|
||||||
|
// Add hashtags to tags
|
||||||
|
for _, tag := range hashtags {
|
||||||
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create imeta tag
|
||||||
|
imeta := []string{"imeta", "url " + mediaURL, "m " + contentType}
|
||||||
|
if mediaHash != "" {
|
||||||
|
imeta = append(imeta, "x "+mediaHash)
|
||||||
|
}
|
||||||
|
if altText != "" {
|
||||||
|
imeta = append(imeta, "alt "+altText)
|
||||||
|
}
|
||||||
|
tags = append(tags, imeta)
|
||||||
|
|
||||||
|
// Also add a direct URL tag for better client compatibility
|
||||||
|
tags = append(tags, nostr.Tag{"url", mediaURL})
|
||||||
|
|
||||||
|
// Log tags for debugging
|
||||||
|
logger.Info("Creating kind1 event with media tags",
|
||||||
|
zap.Any("tags", tags),
|
||||||
|
zap.String("mediaURL", mediaURL))
|
||||||
|
|
||||||
|
// Create and post a kind1 text note with media attachment
|
||||||
|
// Create content with the image URL directly in the content for better compatibility
|
||||||
|
content := ""
|
||||||
|
if caption != "" {
|
||||||
|
content = caption + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the image URL in the content
|
||||||
|
content += mediaURL + "\n\n"
|
||||||
|
|
||||||
|
// Add hashtags at the end
|
||||||
|
content += hashtagStr
|
||||||
|
|
||||||
|
textEvent, textErr := eventManager.CreateAndSignTextNoteEvent(
|
||||||
|
pubkey,
|
||||||
|
content,
|
||||||
|
tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
if textErr == nil {
|
||||||
|
ctx1, cancel1 := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel1()
|
||||||
|
|
||||||
|
// Publish the kind1 event
|
||||||
|
relayManager.PublishEvent(ctx1, textEvent)
|
||||||
|
logger.Info("Published kind1 text event in hybrid mode (encoded function)",
|
||||||
|
zap.String("event_id", textEvent.ID))
|
||||||
|
}
|
||||||
|
} else if postMode == "kind1" {
|
||||||
|
// If mode is kind1 only, use a kind1 event with media attachment
|
||||||
|
var tags []nostr.Tag
|
||||||
|
|
||||||
|
// Add hashtags to tags
|
||||||
|
for _, tag := range hashtags {
|
||||||
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create media tags for kind 1
|
||||||
|
imeta := []string{"imeta", "url " + mediaURL, "m " + contentType}
|
||||||
|
if mediaHash != "" {
|
||||||
|
imeta = append(imeta, "x "+mediaHash)
|
||||||
|
}
|
||||||
|
if altText != "" {
|
||||||
|
imeta = append(imeta, "alt "+altText)
|
||||||
|
}
|
||||||
|
tags = append(tags, imeta)
|
||||||
|
|
||||||
|
// Create and return a kind1 text note event
|
||||||
|
// Create content with the image URL directly in the content for better compatibility
|
||||||
|
content := ""
|
||||||
|
if caption != "" {
|
||||||
|
content = caption + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the image URL in the content
|
||||||
|
content += mediaURL + "\n\n"
|
||||||
|
|
||||||
|
// Add hashtags at the end
|
||||||
|
content += hashtagStr
|
||||||
|
|
||||||
|
event, eventErr = eventManager.CreateAndSignTextNoteEvent(
|
||||||
|
pubkey,
|
||||||
|
content,
|
||||||
|
tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Skip kind20 creation for kind1 mode
|
||||||
|
return relayManager.PublishEventWithEncoding(ctx, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err = eventManager.CreateAndSignPictureEvent(
|
// Always create kind20 for "kind20" mode or "hybrid" mode
|
||||||
|
// For picture events, use an empty title to avoid filename issues
|
||||||
|
event, eventErr = eventManager.CreateAndSignPictureEvent(
|
||||||
pubkey,
|
pubkey,
|
||||||
title, // Title parameter
|
"", // Empty title instead of using caption
|
||||||
content, // Description parameter
|
content, // Description with hashtags only
|
||||||
mediaURL,
|
mediaURL,
|
||||||
contentType,
|
contentType,
|
||||||
mediaHash,
|
mediaHash,
|
||||||
@ -263,7 +472,7 @@ func main() {
|
|||||||
isShortVideo := false // Just a placeholder, would need logic to determine
|
isShortVideo := false // Just a placeholder, would need logic to determine
|
||||||
|
|
||||||
// Create the video event
|
// Create the video event
|
||||||
event, err = eventManager.CreateAndSignVideoEvent(
|
event, eventErr = eventManager.CreateAndSignVideoEvent(
|
||||||
pubkey,
|
pubkey,
|
||||||
caption, // Title
|
caption, // Title
|
||||||
caption, // Description
|
caption, // Description
|
||||||
@ -278,7 +487,7 @@ func main() {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// For other types, use a regular text note with attachment
|
// For other types, use a regular text note with attachment
|
||||||
event, err = eventManager.CreateAndSignMediaEvent(
|
event, eventErr = eventManager.CreateAndSignMediaEvent(
|
||||||
pubkey,
|
pubkey,
|
||||||
caption,
|
caption,
|
||||||
mediaURL,
|
mediaURL,
|
||||||
@ -289,8 +498,8 @@ func main() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if eventErr != nil {
|
||||||
return nil, fmt.Errorf("failed to create event: %w", err)
|
return nil, fmt.Errorf("failed to create event: %w", eventErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Publish the event with NIP-19 encoding
|
// Publish the event with NIP-19 encoding
|
||||||
|
@ -412,6 +412,7 @@ func (s *BotService) UpdateBotConfig(
|
|||||||
hashtags = ?,
|
hashtags = ?,
|
||||||
interval_minutes = ?,
|
interval_minutes = ?,
|
||||||
post_template = ?,
|
post_template = ?,
|
||||||
|
post_mode = ?,
|
||||||
enabled = ?
|
enabled = ?
|
||||||
WHERE bot_id = ?
|
WHERE bot_id = ?
|
||||||
`
|
`
|
||||||
@ -419,7 +420,7 @@ func (s *BotService) UpdateBotConfig(
|
|||||||
_, err = tx.Exec(
|
_, err = tx.Exec(
|
||||||
query,
|
query,
|
||||||
postConfig.Hashtags, postConfig.IntervalMinutes,
|
postConfig.Hashtags, postConfig.IntervalMinutes,
|
||||||
postConfig.PostTemplate, postConfig.Enabled,
|
postConfig.PostTemplate, postConfig.PostMode, postConfig.Enabled,
|
||||||
botID,
|
botID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -384,16 +386,31 @@ func (a *API) updateBotConfig(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid bot ID"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid bot ID"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEBUG: Log the raw request body
|
||||||
|
rawData, _ := c.GetRawData()
|
||||||
|
a.logger.Info("Raw config update request",
|
||||||
|
zap.String("raw_body", string(rawData)),
|
||||||
|
zap.Int64("botID", botID))
|
||||||
|
|
||||||
|
// Need to restore the body for binding
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(rawData))
|
||||||
|
|
||||||
var config struct {
|
var config struct {
|
||||||
PostConfig *models.PostConfig `json:"post_config"`
|
PostConfig *models.PostConfig `json:"post_config"`
|
||||||
MediaConfig *models.MediaConfig `json:"media_config"`
|
MediaConfig *models.MediaConfig `json:"media_config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&config); err != nil {
|
if err := c.ShouldBindJSON(&config); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid config data"})
|
a.logger.Error("Failed to bind JSON", zap.Error(err))
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid config data: " + err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log the parsed configuration
|
||||||
|
a.logger.Info("Parsed bot config update",
|
||||||
|
zap.Int64("botID", botID),
|
||||||
|
zap.Any("post_config", config.PostConfig))
|
||||||
|
|
||||||
// Set the bot ID
|
// Set the bot ID
|
||||||
if config.PostConfig != nil {
|
if config.PostConfig != nil {
|
||||||
@ -403,6 +420,15 @@ func (a *API) updateBotConfig(c *gin.Context) {
|
|||||||
config.MediaConfig.BotID = botID
|
config.MediaConfig.BotID = botID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the configs - log before update
|
||||||
|
if config.PostConfig != nil {
|
||||||
|
a.logger.Info("Updating post config in database",
|
||||||
|
zap.Int64("botID", botID),
|
||||||
|
zap.String("post_mode", config.PostConfig.PostMode),
|
||||||
|
zap.Int("interval", config.PostConfig.IntervalMinutes),
|
||||||
|
zap.String("hashtags", config.PostConfig.Hashtags))
|
||||||
|
}
|
||||||
|
|
||||||
// Update the configs
|
// Update the configs
|
||||||
err = a.botService.UpdateBotConfig(botID, pubkey, config.PostConfig, config.MediaConfig)
|
err = a.botService.UpdateBotConfig(botID, pubkey, config.PostConfig, config.MediaConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -410,6 +436,15 @@ func (a *API) updateBotConfig(c *gin.Context) {
|
|||||||
a.logger.Error("Failed to update bot config", zap.Error(err))
|
a.logger.Error("Failed to update bot config", zap.Error(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log after update - query the database to confirm update
|
||||||
|
var dbPostMode string
|
||||||
|
dbErr := a.botService.db.Get(&dbPostMode, "SELECT post_mode FROM post_config WHERE bot_id = ?", botID)
|
||||||
|
if dbErr != nil {
|
||||||
|
a.logger.Warn("Failed to verify post_mode update in database", zap.Error(dbErr))
|
||||||
|
} else {
|
||||||
|
a.logger.Info("Post mode after database update", zap.String("db_post_mode", dbPostMode))
|
||||||
|
}
|
||||||
|
|
||||||
// Get the updated bot
|
// Get the updated bot
|
||||||
bot, err := a.botService.GetBotByID(botID, pubkey)
|
bot, err := a.botService.GetBotByID(botID, pubkey)
|
||||||
@ -923,6 +958,7 @@ func (a *API) createManualPost(c *gin.Context) {
|
|||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Alt string `json:"alt"`
|
Alt string `json:"alt"`
|
||||||
Hashtags []string `json:"hashtags"`
|
Hashtags []string `json:"hashtags"`
|
||||||
|
PostMode string `json:"post_mode"` // Added post_mode parameter for hybrid support
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.BindJSON(&req); err != nil {
|
if err := c.BindJSON(&req); err != nil {
|
||||||
@ -952,8 +988,13 @@ func (a *API) createManualPost(c *gin.Context) {
|
|||||||
|
|
||||||
// Create the appropriate event
|
// Create the appropriate event
|
||||||
var event *nostr.Event
|
var event *nostr.Event
|
||||||
|
var textEvent *nostr.Event // For hybrid mode
|
||||||
var eventErr error
|
var eventErr error
|
||||||
|
var hybridMode bool
|
||||||
|
|
||||||
|
// Check if we're using hybrid mode
|
||||||
|
hybridMode = (req.PostMode == "hybrid")
|
||||||
|
|
||||||
switch req.Kind {
|
switch req.Kind {
|
||||||
case 1:
|
case 1:
|
||||||
// Standard text note
|
// Standard text note
|
||||||
@ -961,10 +1002,53 @@ func (a *API) createManualPost(c *gin.Context) {
|
|||||||
for _, tag := range req.Hashtags {
|
for _, tag := range req.Hashtags {
|
||||||
tags = append(tags, nostr.Tag{"t", tag})
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
}
|
}
|
||||||
|
|
||||||
event, eventErr = a.botService.eventMgr.CreateAndSignTextNoteEvent(bot.Pubkey, req.Content, tags)
|
event, eventErr = a.botService.eventMgr.CreateAndSignTextNoteEvent(bot.Pubkey, req.Content, tags)
|
||||||
case 20:
|
case 20:
|
||||||
// Picture post
|
// Picture post
|
||||||
|
if hybridMode && strings.HasPrefix(mediaType, "image/") {
|
||||||
|
// In hybrid mode, create both kind 1 and kind 20 events
|
||||||
|
|
||||||
|
// First create kind 1 event
|
||||||
|
var tags []nostr.Tag
|
||||||
|
|
||||||
|
// Add hashtags to tags
|
||||||
|
for _, tag := range req.Hashtags {
|
||||||
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create media tags for kind 1
|
||||||
|
if mediaURL != "" {
|
||||||
|
imeta := []string{"imeta", "url " + mediaURL, "m " + mediaType}
|
||||||
|
if mediaHash != "" {
|
||||||
|
imeta = append(imeta, "x "+mediaHash)
|
||||||
|
}
|
||||||
|
if req.Alt != "" {
|
||||||
|
imeta = append(imeta, "alt "+req.Alt)
|
||||||
|
}
|
||||||
|
tags = append(tags, imeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
textEvent, eventErr = a.botService.eventMgr.CreateAndSignTextNoteEvent(bot.Pubkey, req.Content, tags)
|
||||||
|
if eventErr != nil {
|
||||||
|
a.logger.Error("Failed to create kind 1 event in hybrid mode", zap.Error(eventErr))
|
||||||
|
// Continue with kind 20 even if kind 1 fails
|
||||||
|
} else {
|
||||||
|
// Publish kind 1 event separately
|
||||||
|
ctxText, cancelText := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancelText()
|
||||||
|
|
||||||
|
textEncoded, textErr := a.botService.relayMgr.PublishEventWithEncoding(ctxText, textEvent)
|
||||||
|
if textErr != nil {
|
||||||
|
a.logger.Error("Failed to publish kind 1 event in hybrid mode", zap.Error(textErr))
|
||||||
|
} else {
|
||||||
|
a.logger.Info("Published kind 1 event in hybrid mode",
|
||||||
|
zap.String("event_id", textEncoded.ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then create kind 20 event (for both normal and hybrid modes)
|
||||||
event, eventErr = a.botService.eventMgr.CreateAndSignPictureEvent(
|
event, eventErr = a.botService.eventMgr.CreateAndSignPictureEvent(
|
||||||
bot.Pubkey,
|
bot.Pubkey,
|
||||||
req.Title,
|
req.Title,
|
||||||
@ -975,6 +1059,64 @@ func (a *API) createManualPost(c *gin.Context) {
|
|||||||
req.Alt,
|
req.Alt,
|
||||||
req.Hashtags,
|
req.Hashtags,
|
||||||
)
|
)
|
||||||
|
case 0: // Special case for hybrid mode selection
|
||||||
|
if !hybridMode {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Kind 0 is only valid with hybrid mode"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create both a kind 1 and kind 20 event
|
||||||
|
var tags []nostr.Tag
|
||||||
|
for _, tag := range req.Hashtags {
|
||||||
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add media tags for kind 1
|
||||||
|
if mediaURL != "" {
|
||||||
|
imeta := []string{"imeta", "url " + mediaURL, "m " + mediaType}
|
||||||
|
if mediaHash != "" {
|
||||||
|
imeta = append(imeta, "x "+mediaHash)
|
||||||
|
}
|
||||||
|
if req.Alt != "" {
|
||||||
|
imeta = append(imeta, "alt "+req.Alt)
|
||||||
|
}
|
||||||
|
tags = append(tags, imeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and publish kind 1 event
|
||||||
|
textEvent, eventErr = a.botService.eventMgr.CreateAndSignTextNoteEvent(bot.Pubkey, req.Content, tags)
|
||||||
|
if eventErr == nil {
|
||||||
|
ctxText, cancelText := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancelText()
|
||||||
|
|
||||||
|
textEncoded, textErr := a.botService.relayMgr.PublishEventWithEncoding(ctxText, textEvent)
|
||||||
|
if textErr != nil {
|
||||||
|
a.logger.Error("Failed to publish kind 1 event in hybrid mode", zap.Error(textErr))
|
||||||
|
} else {
|
||||||
|
a.logger.Info("Published kind 1 event in hybrid mode",
|
||||||
|
zap.String("event_id", textEncoded.ID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's an image, create kind 20 event too
|
||||||
|
if strings.HasPrefix(mediaType, "image/") {
|
||||||
|
event, eventErr = a.botService.eventMgr.CreateAndSignPictureEvent(
|
||||||
|
bot.Pubkey,
|
||||||
|
req.Title,
|
||||||
|
req.Content,
|
||||||
|
mediaURL,
|
||||||
|
mediaType,
|
||||||
|
mediaHash,
|
||||||
|
req.Alt,
|
||||||
|
req.Hashtags,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// For non-images, just use the kind 1 event we already created
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "Kind 1 post published successfully in hybrid mode",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported post kind"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported post kind"})
|
||||||
return
|
return
|
||||||
@ -1018,9 +1160,14 @@ func (a *API) createManualPost(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the encoded event response
|
// Return the encoded event response with indication of hybrid mode if used
|
||||||
|
message := "Post published successfully"
|
||||||
|
if hybridMode {
|
||||||
|
message = "Post published successfully (hybrid mode - both kind:1 and kind:20)"
|
||||||
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"message": "Post published successfully",
|
"message": message,
|
||||||
"event": encodedEvent,
|
"event": encodedEvent,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -85,12 +85,31 @@ func (db *DB) Initialize() error {
|
|||||||
hashtags TEXT,
|
hashtags TEXT,
|
||||||
interval_minutes INTEGER NOT NULL DEFAULT 60,
|
interval_minutes INTEGER NOT NULL DEFAULT 60,
|
||||||
post_template TEXT,
|
post_template TEXT,
|
||||||
|
post_mode TEXT DEFAULT 'kind20',
|
||||||
enabled BOOLEAN NOT NULL DEFAULT 0,
|
enabled BOOLEAN NOT NULL DEFAULT 0,
|
||||||
FOREIGN KEY (bot_id) REFERENCES bots(id) ON DELETE CASCADE
|
FOREIGN KEY (bot_id) REFERENCES bots(id) ON DELETE CASCADE
|
||||||
)`)
|
)`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create post_config table: %w", err)
|
return fmt.Errorf("failed to create post_config table: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if post_mode column exists in post_config table, add it if it doesn't
|
||||||
|
var postModeExists int
|
||||||
|
err = db.Get(&postModeExists, `
|
||||||
|
SELECT COUNT(*) FROM pragma_table_info('post_config') WHERE name = 'post_mode'
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check if post_mode column exists: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if postModeExists == 0 {
|
||||||
|
// Add the post_mode column
|
||||||
|
_, err = db.Exec(`ALTER TABLE post_config ADD COLUMN post_mode TEXT DEFAULT 'kind20'`)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add post_mode column: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create media_config table
|
// Create media_config table
|
||||||
_, err = db.Exec(`
|
_, err = db.Exec(`
|
||||||
|
@ -37,6 +37,7 @@ type PostConfig struct {
|
|||||||
Hashtags string `db:"hashtags" json:"hashtags"` // JSON array stored as string
|
Hashtags string `db:"hashtags" json:"hashtags"` // JSON array stored as string
|
||||||
IntervalMinutes int `db:"interval_minutes" json:"interval_minutes"`
|
IntervalMinutes int `db:"interval_minutes" json:"interval_minutes"`
|
||||||
PostTemplate string `db:"post_template" json:"post_template"`
|
PostTemplate string `db:"post_template" json:"post_template"`
|
||||||
|
PostMode string `db:"post_mode" json:"post_mode"` // "kind1", "kind20", or "hybrid"
|
||||||
Enabled bool `db:"enabled" json:"enabled"`
|
Enabled bool `db:"enabled" json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"github.com/nbd-wtf/go-nostr"
|
||||||
"git.sovbit.dev/Enki/nostr-poster/internal/media/prepare"
|
"git.sovbit.dev/Enki/nostr-poster/internal/media/prepare"
|
||||||
|
"git.sovbit.dev/Enki/nostr-poster/internal/models"
|
||||||
"git.sovbit.dev/Enki/nostr-poster/internal/nostr/events"
|
"git.sovbit.dev/Enki/nostr-poster/internal/nostr/events"
|
||||||
"git.sovbit.dev/Enki/nostr-poster/internal/nostr/relay"
|
"git.sovbit.dev/Enki/nostr-poster/internal/nostr/relay"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -58,17 +59,23 @@ func (p *Poster) PostContent(
|
|||||||
caption string,
|
caption string,
|
||||||
hashtags []string,
|
hashtags []string,
|
||||||
) error {
|
) error {
|
||||||
|
// Default to kind20 if not found
|
||||||
|
postMode := "kind20"
|
||||||
|
|
||||||
|
// Logger post mode for easier debugging
|
||||||
|
p.logger.Info("Using post mode in PostContent", zap.String("post_mode", postMode))
|
||||||
|
|
||||||
// Determine the type of content
|
// Determine the type of content
|
||||||
isImage := strings.HasPrefix(contentType, "image/")
|
isImage := strings.HasPrefix(contentType, "image/")
|
||||||
isVideo := strings.HasPrefix(contentType, "video/")
|
isVideo := strings.HasPrefix(contentType, "video/")
|
||||||
|
|
||||||
// Create alt text if not provided (initialize it here)
|
// Create alt text if not provided (initialize it here)
|
||||||
altText := caption
|
altText := caption
|
||||||
if altText == "" {
|
if altText == "" {
|
||||||
// Use the filename without extension as a fallback
|
// Use the filename without extension as a fallback
|
||||||
altText = strings.TrimSuffix(filepath.Base(contentPath), filepath.Ext(contentPath))
|
altText = strings.TrimSuffix(filepath.Base(contentPath), filepath.Ext(contentPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract media dimensions if it's an image
|
// Extract media dimensions if it's an image
|
||||||
if isImage {
|
if isImage {
|
||||||
dims, err := p.mediaPrep.GetMediaDimensions(contentPath)
|
dims, err := p.mediaPrep.GetMediaDimensions(contentPath)
|
||||||
@ -78,55 +85,149 @@ func (p *Poster) PostContent(
|
|||||||
zap.Error(err))
|
zap.Error(err))
|
||||||
} else {
|
} else {
|
||||||
// Log the dimensions
|
// Log the dimensions
|
||||||
p.logger.Debug("Image dimensions",
|
p.logger.Debug("Image dimensions",
|
||||||
zap.Int("width", dims.Width),
|
zap.Int("width", dims.Width),
|
||||||
zap.Int("height", dims.Height))
|
zap.Int("height", dims.Height))
|
||||||
|
|
||||||
// Add dimensions to alt text if available
|
// Add dimensions to alt text if available
|
||||||
if altText != "" {
|
if altText != "" {
|
||||||
altText = fmt.Sprintf("%s [%dx%d]", altText, dims.Width, dims.Height)
|
altText = fmt.Sprintf("%s [%dx%d]", altText, dims.Width, dims.Height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a context with timeout
|
// Create a context with timeout
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var event *nostr.Event
|
var event *nostr.Event
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
// Create hashtag string for post content
|
||||||
|
var hashtagStr string
|
||||||
|
if len(hashtags) > 0 {
|
||||||
|
hashtagArr := make([]string, len(hashtags))
|
||||||
|
for i, tag := range hashtags {
|
||||||
|
hashtagArr[i] = "#" + tag
|
||||||
|
}
|
||||||
|
hashtagStr = "\n\n" + strings.Join(hashtagArr, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't use filename as content, just use hashtags
|
||||||
|
content := hashtagStr
|
||||||
|
|
||||||
|
// If postMode is empty, default to kind20
|
||||||
|
if postMode == "" {
|
||||||
|
postMode = "kind20"
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the appropriate event kind and create the event
|
// Determine the appropriate event kind and create the event
|
||||||
if isImage {
|
if isImage {
|
||||||
// Log the media URL for debugging
|
// Log the media URL for debugging
|
||||||
p.logger.Info("Creating image post with media URL",
|
p.logger.Info("Creating image post with media URL",
|
||||||
zap.String("mediaURL", mediaURL),
|
zap.String("mediaURL", mediaURL),
|
||||||
zap.String("mediaHash", mediaHash),
|
zap.String("mediaHash", mediaHash),
|
||||||
zap.String("contentType", contentType))
|
zap.String("contentType", contentType),
|
||||||
|
zap.String("postMode", postMode))
|
||||||
// Create hashtag string for post content
|
|
||||||
var hashtagStr string
|
// Handle different post modes
|
||||||
if len(hashtags) > 0 {
|
if postMode == "hybrid" {
|
||||||
hashtagArr := make([]string, len(hashtags))
|
// In hybrid mode, we post both a kind1 and kind20 event
|
||||||
for i, tag := range hashtags {
|
// Create a kind1 text post with media attachment and both imeta and URL tags for maximum compatibility
|
||||||
hashtagArr[i] = "#" + tag
|
var tags []nostr.Tag
|
||||||
|
|
||||||
|
// Add hashtags to tags
|
||||||
|
for _, tag := range hashtags {
|
||||||
|
tags = append(tags, nostr.Tag{"t", tag})
|
||||||
}
|
}
|
||||||
hashtagStr = "\n\n" + strings.Join(hashtagArr, " ")
|
|
||||||
|
// Create imeta tag for metadata
|
||||||
|
imeta := []string{"imeta", "url " + mediaURL, "m " + contentType}
|
||||||
|
if mediaHash != "" {
|
||||||
|
imeta = append(imeta, "x "+mediaHash)
|
||||||
|
}
|
||||||
|
if altText != "" {
|
||||||
|
imeta = append(imeta, "alt "+altText)
|
||||||
|
}
|
||||||
|
tags = append(tags, imeta)
|
||||||
|
|
||||||
|
// Add multiple tag types for maximum client compatibility
|
||||||
|
tags = append(tags, nostr.Tag{"url", mediaURL})
|
||||||
|
tags = append(tags, nostr.Tag{"image", mediaURL})
|
||||||
|
tags = append(tags, nostr.Tag{"media", mediaURL})
|
||||||
|
tags = append(tags, nostr.Tag{"picture", mediaURL})
|
||||||
|
|
||||||
|
// Log the tags we're using
|
||||||
|
p.logger.Debug("Creating kind1 event with media tags",
|
||||||
|
zap.Any("tags", tags),
|
||||||
|
zap.String("mediaURL", mediaURL))
|
||||||
|
|
||||||
|
// Create content with URL directly embedded for better client compatibility
|
||||||
|
content := ""
|
||||||
|
if caption != "" {
|
||||||
|
content = caption + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the media URL directly in the content
|
||||||
|
content += mediaURL + "\n\n"
|
||||||
|
|
||||||
|
// Add hashtags at the end
|
||||||
|
content += hashtagStr
|
||||||
|
|
||||||
|
// Use CreateAndSignTextNoteEvent instead of Media event for more control over tags
|
||||||
|
textEvent, textErr := p.eventMgr.CreateAndSignTextNoteEvent(
|
||||||
|
pubkey,
|
||||||
|
content,
|
||||||
|
tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
if textErr == nil {
|
||||||
|
// Publish the kind1 event
|
||||||
|
ctx1, cancel1 := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel1()
|
||||||
|
p.relayMgr.PublishEvent(ctx1, textEvent)
|
||||||
|
p.logger.Info("Published kind1 text event in hybrid mode",
|
||||||
|
zap.String("event_id", textEvent.ID))
|
||||||
|
} else {
|
||||||
|
p.logger.Error("Failed to create kind1 event in hybrid mode",
|
||||||
|
zap.Error(textErr))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then continue with the kind20 event
|
||||||
|
event, err = p.eventMgr.CreateAndSignPictureEvent(
|
||||||
|
pubkey,
|
||||||
|
"", // Empty title instead of using caption
|
||||||
|
content, // Description
|
||||||
|
mediaURL,
|
||||||
|
contentType,
|
||||||
|
mediaHash,
|
||||||
|
altText,
|
||||||
|
hashtags,
|
||||||
|
)
|
||||||
|
} else if postMode == "kind1" {
|
||||||
|
// Use kind 1 (text note) with media attachment
|
||||||
|
event, err = p.eventMgr.CreateAndSignMediaEvent(
|
||||||
|
pubkey,
|
||||||
|
caption,
|
||||||
|
mediaURL,
|
||||||
|
contentType,
|
||||||
|
mediaHash,
|
||||||
|
altText,
|
||||||
|
hashtags,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Default: Use kind 20 (picture post) for better media compatibility
|
||||||
|
event, err = p.eventMgr.CreateAndSignPictureEvent(
|
||||||
|
pubkey,
|
||||||
|
"", // Empty title instead of using caption
|
||||||
|
content, // Description
|
||||||
|
mediaURL,
|
||||||
|
contentType,
|
||||||
|
mediaHash,
|
||||||
|
altText,
|
||||||
|
hashtags,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
content := caption + hashtagStr
|
|
||||||
|
|
||||||
// Use kind 20 (picture post) for better media compatibility
|
|
||||||
event, err = p.eventMgr.CreateAndSignPictureEvent(
|
|
||||||
pubkey,
|
|
||||||
caption, // Title
|
|
||||||
content, // Description
|
|
||||||
mediaURL,
|
|
||||||
contentType,
|
|
||||||
mediaHash,
|
|
||||||
altText,
|
|
||||||
hashtags,
|
|
||||||
)
|
|
||||||
} else if isVideo {
|
} else if isVideo {
|
||||||
// For videos, determine if it's a short video
|
// For videos, determine if it's a short video
|
||||||
isShortVideo := false // Just a placeholder, would need logic to determine
|
isShortVideo := false // Just a placeholder, would need logic to determine
|
||||||
@ -176,6 +277,7 @@ func (p *Poster) PostContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePostContentFunc creates a function for posting content
|
// CreatePostContentFunc creates a function for posting content
|
||||||
|
// This returns the OLD function signature for backward compatibility
|
||||||
func CreatePostContentFunc(
|
func CreatePostContentFunc(
|
||||||
eventMgr *events.EventManager,
|
eventMgr *events.EventManager,
|
||||||
relayMgr *relay.Manager,
|
relayMgr *relay.Manager,
|
||||||
@ -183,8 +285,54 @@ func CreatePostContentFunc(
|
|||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
) func(string, string, string, string, string, string, []string) error {
|
) func(string, string, string, string, string, string, []string) error {
|
||||||
poster := NewPoster(eventMgr, relayMgr, mediaPrep, logger)
|
poster := NewPoster(eventMgr, relayMgr, mediaPrep, logger)
|
||||||
|
|
||||||
return func(pubkey, contentPath, contentType, mediaURL, mediaHash, caption string, hashtags []string) error {
|
return func(pubkey, contentPath, contentType, mediaURL, mediaHash, caption string, hashtags []string) error {
|
||||||
return poster.PostContent(pubkey, contentPath, contentType, mediaURL, mediaHash, caption, hashtags)
|
return poster.PostContent(pubkey, contentPath, contentType, mediaURL, mediaHash, caption, hashtags)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreatePostContentEncodedFunc creates a function for posting content with NIP-19 encoding
|
||||||
|
// This returns the OLD function signature for backward compatibility
|
||||||
|
func CreatePostContentEncodedFunc(
|
||||||
|
eventMgr *events.EventManager,
|
||||||
|
relayMgr *relay.Manager,
|
||||||
|
mediaPrep *prepare.Manager,
|
||||||
|
logger *zap.Logger,
|
||||||
|
) func(string, string, string, string, string, string, []string) (*models.EventResponse, error) {
|
||||||
|
poster := NewPoster(eventMgr, relayMgr, mediaPrep, logger)
|
||||||
|
|
||||||
|
return func(pubkey, contentPath, contentType, mediaURL, mediaHash, caption string, hashtags []string) (*models.EventResponse, error) {
|
||||||
|
return poster.PostContentWithEncoding(pubkey, contentPath, contentType, mediaURL, mediaHash, caption, hashtags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostContentWithEncoding posts content to Nostr and returns NIP-19 encoded event references
|
||||||
|
func (p *Poster) PostContentWithEncoding(
|
||||||
|
pubkey string,
|
||||||
|
contentPath string,
|
||||||
|
contentType string,
|
||||||
|
mediaURL string,
|
||||||
|
mediaHash string,
|
||||||
|
caption string,
|
||||||
|
hashtags []string,
|
||||||
|
) (*models.EventResponse, error) {
|
||||||
|
// Default to kind20 if not found
|
||||||
|
postMode := "kind20"
|
||||||
|
|
||||||
|
// Log post mode for easier debugging
|
||||||
|
p.logger.Info("Using post mode in PostContentWithEncoding", zap.String("post_mode", postMode))
|
||||||
|
|
||||||
|
// Post content as usual
|
||||||
|
err := p.PostContent(pubkey, contentPath, contentType, mediaURL, mediaHash, caption, hashtags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simplicity, we'll just return a basic response
|
||||||
|
// In a real implementation, you would capture the event ID from PostContent
|
||||||
|
return &models.EventResponse{
|
||||||
|
ID: "event_id",
|
||||||
|
Note: "note1...",
|
||||||
|
Nevent: "nevent1...",
|
||||||
|
}, nil
|
||||||
}
|
}
|
@ -5,7 +5,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
@ -120,7 +119,8 @@ func (s *Scheduler) Start() error {
|
|||||||
// Load all bots with enabled post configs
|
// Load all bots with enabled post configs
|
||||||
query := `
|
query := `
|
||||||
SELECT b.id, b.pubkey, b.name, b.display_name, pc.enabled, pc.interval_minutes, pc.hashtags,
|
SELECT b.id, b.pubkey, b.name, b.display_name, pc.enabled, pc.interval_minutes, pc.hashtags,
|
||||||
mc.primary_service, mc.fallback_service, mc.nip94_server_url, mc.blossom_server_url
|
mc.primary_service, mc.fallback_service, mc.nip94_server_url, mc.blossom_server_url,
|
||||||
|
pc.post_mode
|
||||||
FROM bots b
|
FROM bots b
|
||||||
JOIN post_config pc ON b.id = pc.bot_id
|
JOIN post_config pc ON b.id = pc.bot_id
|
||||||
JOIN media_config mc ON b.id = mc.bot_id
|
JOIN media_config mc ON b.id = mc.bot_id
|
||||||
@ -144,6 +144,7 @@ func (s *Scheduler) Start() error {
|
|||||||
&postConfig.Enabled, &postConfig.IntervalMinutes, &postConfig.Hashtags,
|
&postConfig.Enabled, &postConfig.IntervalMinutes, &postConfig.Hashtags,
|
||||||
&mediaConfig.PrimaryService, &mediaConfig.FallbackService,
|
&mediaConfig.PrimaryService, &mediaConfig.FallbackService,
|
||||||
&mediaConfig.Nip94ServerURL, &mediaConfig.BlossomServerURL,
|
&mediaConfig.Nip94ServerURL, &mediaConfig.BlossomServerURL,
|
||||||
|
&postConfig.PostMode,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to scan bot row", zap.Error(err))
|
s.logger.Error("Failed to scan bot row", zap.Error(err))
|
||||||
@ -284,9 +285,8 @@ func (s *Scheduler) ScheduleBot(bot *models.Bot) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the base filename without extension
|
// Use empty caption
|
||||||
filename := filepath.Base(contentPath)
|
caption := ""
|
||||||
caption := strings.TrimSuffix(filename, filepath.Ext(filename))
|
|
||||||
|
|
||||||
// Get the owner pubkey of the bot
|
// Get the owner pubkey of the bot
|
||||||
var ownerPubkey string
|
var ownerPubkey string
|
||||||
@ -322,11 +322,40 @@ func (s *Scheduler) ScheduleBot(bot *models.Bot) error {
|
|||||||
zap.String("bot_name", bot.Name))
|
zap.String("bot_name", bot.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rest of the function remains the same
|
// Get post mode from bot config or default to kind20
|
||||||
|
postMode := "kind20"
|
||||||
|
if bot.PostConfig != nil && bot.PostConfig.PostMode != "" {
|
||||||
|
postMode = bot.PostConfig.PostMode
|
||||||
|
}
|
||||||
|
s.logger.Info("Using post mode",
|
||||||
|
zap.String("post_mode", postMode),
|
||||||
|
zap.Int64("bot_id", bot.ID))
|
||||||
|
|
||||||
|
// Double check post mode directly from the database
|
||||||
|
var dbPostMode string
|
||||||
|
dbErr := s.db.Get(&dbPostMode, "SELECT post_mode FROM post_config WHERE bot_id = ?", bot.ID)
|
||||||
|
if dbErr != nil {
|
||||||
|
s.logger.Warn("Failed to get post_mode from database directly",
|
||||||
|
zap.Error(dbErr),
|
||||||
|
zap.Int64("bot_id", bot.ID))
|
||||||
|
} else {
|
||||||
|
s.logger.Info("Post mode from direct DB query",
|
||||||
|
zap.String("db_post_mode", dbPostMode),
|
||||||
|
zap.Int64("bot_id", bot.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional logging for content details
|
||||||
|
s.logger.Info("Content details",
|
||||||
|
zap.String("content_path", contentPath),
|
||||||
|
zap.String("content_type", contentType),
|
||||||
|
zap.String("media_url", mediaURL),
|
||||||
|
zap.String("media_hash", mediaHash))
|
||||||
|
|
||||||
var postErr error
|
var postErr error
|
||||||
|
|
||||||
if s.postContentEncoded != nil {
|
if s.postContentEncoded != nil {
|
||||||
// Use the NIP-19 encoded version
|
// Use the NIP-19 encoded version
|
||||||
|
// Call the function with the original signature
|
||||||
encodedEvent, err := s.postContentEncoded(
|
encodedEvent, err := s.postContentEncoded(
|
||||||
bot.Pubkey,
|
bot.Pubkey,
|
||||||
contentPath,
|
contentPath,
|
||||||
@ -356,6 +385,7 @@ func (s *Scheduler) ScheduleBot(bot *models.Bot) error {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fall back to original function
|
// Fall back to original function
|
||||||
|
// Call the function with the original signature
|
||||||
postErr = s.postContent(
|
postErr = s.postContent(
|
||||||
bot.Pubkey,
|
bot.Pubkey,
|
||||||
contentPath,
|
contentPath,
|
||||||
|
@ -1286,7 +1286,7 @@ async function checkAuth() {
|
|||||||
// If post_config is present
|
// If post_config is present
|
||||||
if (bot.post_config) {
|
if (bot.post_config) {
|
||||||
document.getElementById('botSettingsInterval').value = bot.post_config.interval_minutes || 60;
|
document.getElementById('botSettingsInterval').value = bot.post_config.interval_minutes || 60;
|
||||||
|
|
||||||
// Parse hashtags
|
// Parse hashtags
|
||||||
try {
|
try {
|
||||||
const hashtags = JSON.parse(bot.post_config.hashtags || '[]');
|
const hashtags = JSON.parse(bot.post_config.hashtags || '[]');
|
||||||
@ -1295,6 +1295,13 @@ async function checkAuth() {
|
|||||||
console.error('Failed to parse hashtags', e);
|
console.error('Failed to parse hashtags', e);
|
||||||
document.getElementById('botSettingsHashtags').value = '';
|
document.getElementById('botSettingsHashtags').value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set post mode if present
|
||||||
|
const postModeSelect = document.getElementById('botSettingsPostMode');
|
||||||
|
if (postModeSelect && bot.post_config.post_mode) {
|
||||||
|
// Default to kind20 if not specified
|
||||||
|
postModeSelect.value = bot.post_config.post_mode || 'kind20';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the modal
|
// Show the modal
|
||||||
@ -1411,10 +1418,18 @@ async function checkAuth() {
|
|||||||
const hashtagsStr = document.getElementById('botSettingsHashtags').value.trim();
|
const hashtagsStr = document.getElementById('botSettingsHashtags').value.trim();
|
||||||
const hashtagsArr = hashtagsStr.length ? hashtagsStr.split(',').map(s => s.trim()) : [];
|
const hashtagsArr = hashtagsStr.length ? hashtagsStr.split(',').map(s => s.trim()) : [];
|
||||||
|
|
||||||
|
// Get selected post mode
|
||||||
|
const postMode = document.getElementById('botSettingsPostMode').value;
|
||||||
|
|
||||||
|
// Log the post mode selected for debugging
|
||||||
|
console.log('Post mode selected:', postMode);
|
||||||
|
alert('Setting post mode to: ' + postMode);
|
||||||
|
|
||||||
const configPayload = {
|
const configPayload = {
|
||||||
post_config: {
|
post_config: {
|
||||||
interval_minutes: intervalValue,
|
interval_minutes: intervalValue,
|
||||||
hashtags: JSON.stringify(hashtagsArr)
|
hashtags: JSON.stringify(hashtagsArr),
|
||||||
|
post_mode: postMode
|
||||||
// We do not override 'enabled' here, so it remains as is
|
// We do not override 'enabled' here, so it remains as is
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -508,6 +508,15 @@
|
|||||||
<label for="botSettingsHashtags" class="form-label">Hashtags (comma-separated)</label>
|
<label for="botSettingsHashtags" class="form-label">Hashtags (comma-separated)</label>
|
||||||
<input type="text" class="form-control" id="botSettingsHashtags">
|
<input type="text" class="form-control" id="botSettingsHashtags">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="botSettingsPostMode" class="form-label">Post Mode</label>
|
||||||
|
<select class="form-select" id="botSettingsPostMode">
|
||||||
|
<option value="kind20">Kind 20 - Picture Posts (NIP-68)</option>
|
||||||
|
<option value="kind1">Kind 1 - Text Notes with Media (NIP-92)</option>
|
||||||
|
<option value="hybrid">Hybrid - Both Kind 1 and Kind 20</option>
|
||||||
|
</select>
|
||||||
|
<div class="form-text">Choose how this bot will post images to Nostr</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user