package parser
import (
"fmt"
"math/rand"
"regexp"
"strconv"
"strings"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
// ParseTemplate возвращает текст, список звуков и количество минут для таймаута (0 если нет)
func ParseTemplate(template string, username string, args string, aiResult string, getRandomUsername func() string) (result string, soundFiles []string, timeoutMinutes int, err error) {
// Замена простых переменных
result = strings.ReplaceAll(template, "", "@"+username)
result = strings.ReplaceAll(result, "", getRandomUsername())
result = strings.ReplaceAll(result, "", args)
result = strings.ReplaceAll(result, "", aiResult)
// Обработка , ,
result, soundFiles, timeoutMinutes = processTags(result)
// Рекурсивная обработка всех
result, err = processGroups(result)
if err != nil {
return "", nil, 0, err
}
result = strings.ReplaceAll(result, "\n", " ")
result = strings.ReplaceAll(result, "\r", " ")
result = regexp.MustCompile(`\s+`).ReplaceAllString(result, " ")
result = strings.TrimSpace(result)
return result, soundFiles, timeoutMinutes, nil
}
func getRandomViewer() string {
names := []string{"зрителя", "кого-то", "случайного пользователя", "незнакомца"}
return names[rand.Intn(len(names))]
}
// processTags обрабатывает теги , ,
func processTags(text string) (string, []string, int) {
//
randomRe := regexp.MustCompile(``)
text = randomRe.ReplaceAllStringFunc(text, func(m string) string {
sub := randomRe.FindStringSubmatch(m)
s, _ := strconv.Atoi(sub[1])
e, _ := strconv.Atoi(sub[2])
if s > e {
s, e = e, s
}
val := rand.Intn(e-s+1) + s
return strconv.Itoa(val)
})
//
songRe := regexp.MustCompile(`]+)\s*/>`)
soundFiles := make([]string, 0)
text = songRe.ReplaceAllStringFunc(text, func(m string) string {
fRe := regexp.MustCompile(`f="([^"]+)"`)
matches := fRe.FindStringSubmatch(m)
if len(matches) > 1 {
soundFiles = append(soundFiles, matches[1])
}
return ""
})
//
timeoutMinutes := 0
timeoutRe := regexp.MustCompile(``)
text = timeoutRe.ReplaceAllStringFunc(text, func(m string) string {
sub := timeoutRe.FindStringSubmatch(m)
if len(sub) > 1 {
minutes, _ := strconv.Atoi(sub[1])
if minutes > 0 {
timeoutMinutes = minutes
}
}
return ""
})
return text, soundFiles, timeoutMinutes
}
func processRandomAndSong(text string) (string, []string) {
// Обработка
randomRe := regexp.MustCompile(``)
text = randomRe.ReplaceAllStringFunc(text, func(m string) string {
sub := randomRe.FindStringSubmatch(m)
s, _ := strconv.Atoi(sub[1])
e, _ := strconv.Atoi(sub[2])
if s > e {
s, e = e, s
}
val := rand.Intn(e-s+1) + s
return strconv.Itoa(val)
})
// Обработка с любыми атрибутами
songRe := regexp.MustCompile(`]+)\s*/>`)
soundFiles := make([]string, 0)
text = songRe.ReplaceAllStringFunc(text, func(m string) string {
// Извлекаем значение атрибута f="..."
fRe := regexp.MustCompile(`f="([^"]+)"`)
matches := fRe.FindStringSubmatch(m)
if len(matches) > 1 {
soundFiles = append(soundFiles, matches[1])
}
return "" // заменяем тег на пустую строку
})
return text, soundFiles
}
// processGroups и остальные функции без изменений ...
func processGroups(text string) (string, error) {
var err error
for {
text, err = processOneGroup(text)
if err != nil {
return "", err
}
if !strings.Contains(text, "") {
break
}
}
return text, nil
}
func processOneGroup(text string) (string, error) {
start := strings.Index(text, "")
if start == -1 {
return text, nil
}
end := findMatchingClosingTag(text, start)
if end == -1 {
return "", fmt.Errorf("unclosed at position %d", start)
}
inner := text[start+7 : end]
sections := extractGSections(inner)
if len(sections) == 0 {
return "", fmt.Errorf("no sections inside at position %d", start)
}
chosen := sections[rand.Intn(len(sections))]
newText := text[:start] + chosen + text[end+8:]
return newText, nil
}
// findMatchingClosingTag ищет позицию закрывающего с учётом вложенности
func findMatchingClosingTag(text string, start int) int {
depth := 1
i := start + 7
for i < len(text) {
if strings.HasPrefix(text[i:], "") {
depth++
i += 7
} else if strings.HasPrefix(text[i:], "") {
depth--
if depth == 0 {
return i
}
i += 8
} else {
i++
}
}
return -1
}
// extractGSections извлекает содержимое всех ... на верхнем уровне (не внутри вложенных групп)
func extractGSections(s string) []string {
var result []string
i := 0
for i < len(s) {
if strings.HasPrefix(s[i:], "") {
startContent := i + 3
j := startContent
depth := 0
for j < len(s) {
if strings.HasPrefix(s[j:], "") {
depth++
j += 7
} else if strings.HasPrefix(s[j:], "") {
if depth > 0 {
depth--
}
j += 8
} else if strings.HasPrefix(s[j:], "") && depth == 0 {
content := s[startContent:j]
result = append(result, content)
i = j + 4
break
} else {
j++
}
}
if j >= len(s) {
break
}
} else if strings.HasPrefix(s[i:], "") {
// Пропускаем вложенную группу целиком
groupEnd := findMatchingClosingTag(s, i)
if groupEnd == -1 {
break
}
i = groupEnd + 8
} else {
i++
}
}
return result
}
// ValidateTemplate проверяет баланс тегов
func ValidateTemplate(template string) error {
balance := 0
for i := 0; i < len(template); i++ {
if strings.HasPrefix(template[i:], "") {
balance++
i += 6
} else if strings.HasPrefix(template[i:], "") {
balance--
i += 7
}
}
if balance != 0 {
return fmt.Errorf("unbalanced tags")
}
return nil
}