100 lines
2.5 KiB
Go
100 lines
2.5 KiB
Go
|
|
package threadspost
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"fmt"
|
|||
|
|
"strings"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// Threads Graph API text post hard limit (main post body).
|
|||
|
|
const MaxPublishRunes = 500
|
|||
|
|
|
|||
|
|
// Threads reply/comment limit (outreach, not main post).
|
|||
|
|
const MaxReplyRunes = 1000
|
|||
|
|
|
|||
|
|
// ViralSweetMin/Max: engagement sweet spot on Threads feed (Buffer / creator data, 2024–2026).
|
|||
|
|
const ViralSweetMin = 80
|
|||
|
|
const ViralSweetMax = 220
|
|||
|
|
|
|||
|
|
// ViralSoftWarn: engagement tends to drop beyond this length in the main visible post.
|
|||
|
|
const ViralSoftWarn = 300
|
|||
|
|
|
|||
|
|
type LengthBand string
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
BandEmpty LengthBand = "empty"
|
|||
|
|
BandTooShort LengthBand = "too_short"
|
|||
|
|
BandSweet LengthBand = "sweet"
|
|||
|
|
BandLong LengthBand = "long"
|
|||
|
|
BandOverSoft LengthBand = "over_soft"
|
|||
|
|
BandOverHard LengthBand = "over_hard"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func RuneLen(text string) int {
|
|||
|
|
return len([]rune(strings.TrimSpace(text)))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ClampPublish(text string) string {
|
|||
|
|
text = strings.TrimSpace(text)
|
|||
|
|
runes := []rune(text)
|
|||
|
|
if len(runes) > MaxPublishRunes {
|
|||
|
|
return string(runes[:MaxPublishRunes])
|
|||
|
|
}
|
|||
|
|
return text
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func PublishBand(text string) LengthBand {
|
|||
|
|
n := RuneLen(text)
|
|||
|
|
switch {
|
|||
|
|
case n == 0:
|
|||
|
|
return BandEmpty
|
|||
|
|
case n < ViralSweetMin:
|
|||
|
|
return BandTooShort
|
|||
|
|
case n <= ViralSweetMax:
|
|||
|
|
return BandSweet
|
|||
|
|
case n <= ViralSoftWarn:
|
|||
|
|
return BandLong
|
|||
|
|
case n <= MaxPublishRunes:
|
|||
|
|
return BandOverSoft
|
|||
|
|
default:
|
|||
|
|
return BandOverHard
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ValidatePublish(text string) error {
|
|||
|
|
if strings.TrimSpace(text) == "" {
|
|||
|
|
return fmt.Errorf("貼文內文不可為空")
|
|||
|
|
}
|
|||
|
|
n := RuneLen(text)
|
|||
|
|
if n > MaxPublishRunes {
|
|||
|
|
return fmt.Errorf("貼文超過 Threads 上限 %d 字(目前 %d 字)", MaxPublishRunes, n)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func ValidateReply(text string) error {
|
|||
|
|
if strings.TrimSpace(text) == "" {
|
|||
|
|
return fmt.Errorf("留言內文不可為空")
|
|||
|
|
}
|
|||
|
|
n := RuneLen(text)
|
|||
|
|
if n > MaxReplyRunes {
|
|||
|
|
return fmt.Errorf("留言超過 Threads 上限 %d 字(目前 %d 字)", MaxReplyRunes, n)
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func PublishHint(text string) string {
|
|||
|
|
switch PublishBand(text) {
|
|||
|
|
case BandTooShort:
|
|||
|
|
return fmt.Sprintf("偏短(爆款常見 %d~%d 字)", ViralSweetMin, ViralSweetMax)
|
|||
|
|
case BandSweet:
|
|||
|
|
return fmt.Sprintf("長度適中(爆款常見 %d~%d 字)", ViralSweetMin, ViralSweetMax)
|
|||
|
|
case BandLong:
|
|||
|
|
return fmt.Sprintf("略長(超過 %d 字互動可能下降)", ViralSoftWarn)
|
|||
|
|
case BandOverSoft:
|
|||
|
|
return fmt.Sprintf("接近 Threads 主文上限 %d 字", MaxPublishRunes)
|
|||
|
|
case BandOverHard:
|
|||
|
|
return fmt.Sprintf("超過 Threads 上限 %d 字,請縮短", MaxPublishRunes)
|
|||
|
|
default:
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
}
|