Initial codex telegram bot source
This commit is contained in:
183
internal/telegram/render.go
Normal file
183
internal/telegram/render.go
Normal file
@@ -0,0 +1,183 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user