184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
package telegram
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"html"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const TelegramMessageLimit = 4096
|
|
const TelegramHTMLMessageLimit = 3900
|
|
|
|
func EscapeHTML(text string) string {
|
|
return html.EscapeString(text)
|
|
}
|
|
|
|
func SummaryDetailsHTML(summary, details string) string {
|
|
summary = strings.TrimSpace(summary)
|
|
details = strings.TrimSpace(details)
|
|
if details == "" {
|
|
return EscapeHTML(summary)
|
|
}
|
|
if summary == "" {
|
|
return ExpandableQuoteHTML(details)
|
|
}
|
|
return EscapeHTML(summary) + "\n" + ExpandableQuoteHTML(details)
|
|
}
|
|
|
|
func ExpandableQuoteHTML(text string) string {
|
|
text = strings.TrimSpace(text)
|
|
if text == "" {
|
|
return ""
|
|
}
|
|
return "<blockquote expandable>" + EscapeHTML(text) + "</blockquote>"
|
|
}
|
|
|
|
func SummaryDetailsHTMLLimited(summary, details string, limit int) string {
|
|
if limit <= 0 {
|
|
limit = TelegramHTMLMessageLimit
|
|
}
|
|
summary = strings.TrimSpace(summary)
|
|
details = strings.TrimSpace(details)
|
|
out := SummaryDetailsHTML(summary, details)
|
|
if len([]rune(out)) <= limit || details == "" {
|
|
return out
|
|
}
|
|
|
|
suffix := "\n...[truncated]"
|
|
runes := []rune(details)
|
|
for len(runes) > 0 {
|
|
candidateLen := len(runes) - max(1, (len([]rune(out))-limit)/2)
|
|
if candidateLen < 0 {
|
|
candidateLen = 0
|
|
}
|
|
if candidateLen > len(runes) {
|
|
candidateLen = len(runes)
|
|
}
|
|
candidate := strings.TrimSpace(string(runes[:candidateLen])) + suffix
|
|
out = SummaryDetailsHTML(summary, candidate)
|
|
if len([]rune(out)) <= limit || candidateLen == 0 {
|
|
return out
|
|
}
|
|
runes = runes[:candidateLen]
|
|
}
|
|
return SummaryDetailsHTML(summary, suffix)
|
|
}
|
|
|
|
func ChunkText(text string, max int) []string {
|
|
if max <= 0 {
|
|
max = TelegramMessageLimit
|
|
}
|
|
runes := []rune(text)
|
|
if len(runes) == 0 {
|
|
return nil
|
|
}
|
|
var chunks []string
|
|
for len(runes) > max {
|
|
cut := max
|
|
for i := max; i > max/2; i-- {
|
|
if runes[i-1] == '\n' {
|
|
cut = i
|
|
break
|
|
}
|
|
}
|
|
chunks = append(chunks, string(runes[:cut]))
|
|
runes = runes[cut:]
|
|
}
|
|
if len(runes) > 0 {
|
|
chunks = append(chunks, string(runes))
|
|
}
|
|
return chunks
|
|
}
|
|
|
|
func ApprovalCallbackData(id int64, decision string) string {
|
|
return fmt.Sprintf("approval:%d:%s", id, decision)
|
|
}
|
|
|
|
func ParseApprovalCallbackData(data string) (int64, string, bool) {
|
|
parts := strings.Split(data, ":")
|
|
if len(parts) != 3 || parts[0] != "approval" {
|
|
return 0, "", false
|
|
}
|
|
id, err := strconv.ParseInt(parts[1], 10, 64)
|
|
if err != nil || id <= 0 {
|
|
return 0, "", false
|
|
}
|
|
switch parts[2] {
|
|
case "accept", "acceptForSession", "decline", "cancel", "details":
|
|
return id, parts[2], true
|
|
default:
|
|
return 0, "", false
|
|
}
|
|
}
|
|
|
|
func WorkspaceCallbackData(id int64) string {
|
|
return fmt.Sprintf("workspace:%d", id)
|
|
}
|
|
|
|
func ParseWorkspaceCallbackData(data string) (int64, bool) {
|
|
if !strings.HasPrefix(data, "workspace:") {
|
|
return 0, false
|
|
}
|
|
id, err := strconv.ParseInt(strings.TrimPrefix(data, "workspace:"), 10, 64)
|
|
return id, err == nil && id > 0
|
|
}
|
|
|
|
func ResumeThreadCallbackData(id int64) string {
|
|
return fmt.Sprintf("resume:thread:%d", id)
|
|
}
|
|
|
|
func ParseResumeThreadCallbackData(data string) (int64, bool) {
|
|
if !strings.HasPrefix(data, "resume:thread:") {
|
|
return 0, false
|
|
}
|
|
id, err := strconv.ParseInt(strings.TrimPrefix(data, "resume:thread:"), 10, 64)
|
|
return id, err == nil && id > 0
|
|
}
|
|
|
|
func ResumePageCallbackData(page int) string {
|
|
return fmt.Sprintf("resume:page:%d", page)
|
|
}
|
|
|
|
func ParseResumePageCallbackData(data string) (int, bool) {
|
|
if !strings.HasPrefix(data, "resume:page:") {
|
|
return 0, false
|
|
}
|
|
page, err := strconv.Atoi(strings.TrimPrefix(data, "resume:page:"))
|
|
return page, err == nil && page >= 0
|
|
}
|
|
|
|
func ModelCallbackData(modelID string) (string, bool) {
|
|
encoded := base64.RawURLEncoding.EncodeToString([]byte(modelID))
|
|
data := "model:" + encoded
|
|
return data, len([]rune(data)) <= 64
|
|
}
|
|
|
|
func ParseModelCallbackData(data string) (string, bool) {
|
|
if !strings.HasPrefix(data, "model:") {
|
|
return "", false
|
|
}
|
|
decoded, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(data, "model:"))
|
|
if err != nil || len(decoded) == 0 {
|
|
return "", false
|
|
}
|
|
return string(decoded), true
|
|
}
|
|
|
|
func EffortCallbackData(effort string) string {
|
|
encoded := base64.RawURLEncoding.EncodeToString([]byte(effort))
|
|
return "effort:" + encoded
|
|
}
|
|
|
|
func ParseEffortCallbackData(data string) (string, bool) {
|
|
if !strings.HasPrefix(data, "effort:") {
|
|
return "", false
|
|
}
|
|
decoded, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(data, "effort:"))
|
|
if err != nil || len(decoded) == 0 {
|
|
return "", false
|
|
}
|
|
return string(decoded), true
|
|
}
|