залил

This commit is contained in:
2026-04-15 08:00:15 +03:00
commit 5549b3545e
51 changed files with 8073 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
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/>", "@"+username)
result = strings.ReplaceAll(result, "<RANDOMUSER/>", getRandomUsername())
result = strings.ReplaceAll(result, "<ARG/>", args)
result = strings.ReplaceAll(result, "<AI/>", aiResult)
// Обработка <random>, <song>, <timeout>
result, soundFiles, timeoutMinutes = processTags(result)
// Рекурсивная обработка всех <group>
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 обрабатывает теги <random>, <song>, <timeout>
func processTags(text string) (string, []string, int) {
// <random>
randomRe := regexp.MustCompile(`<random\s+s=([-0-9]+)\s+e=([-0-9]+)\s*/>`)
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)
})
// <song>
songRe := regexp.MustCompile(`<song\s+([^>]+)\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 ""
})
// <timeout minutes="X"/>
timeoutMinutes := 0
timeoutRe := regexp.MustCompile(`<timeout\s+minutes="?([0-9]+)"?\s*/>`)
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) {
// Обработка <random>
randomRe := regexp.MustCompile(`<random\s+s=([-0-9]+)\s+e=([-0-9]+)\s*/>`)
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)
})
// Обработка <song> с любыми атрибутами
songRe := regexp.MustCompile(`<song\s+([^>]+)\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, "<group>") {
break
}
}
return text, nil
}
func processOneGroup(text string) (string, error) {
start := strings.Index(text, "<group>")
if start == -1 {
return text, nil
}
end := findMatchingClosingTag(text, start)
if end == -1 {
return "", fmt.Errorf("unclosed <group> at position %d", start)
}
inner := text[start+7 : end]
sections := extractGSections(inner)
if len(sections) == 0 {
return "", fmt.Errorf("no <g> sections inside <group> at position %d", start)
}
chosen := sections[rand.Intn(len(sections))]
newText := text[:start] + chosen + text[end+8:]
return newText, nil
}
// findMatchingClosingTag ищет позицию закрывающего </group> с учётом вложенности
func findMatchingClosingTag(text string, start int) int {
depth := 1
i := start + 7
for i < len(text) {
if strings.HasPrefix(text[i:], "<group>") {
depth++
i += 7
} else if strings.HasPrefix(text[i:], "</group>") {
depth--
if depth == 0 {
return i
}
i += 8
} else {
i++
}
}
return -1
}
// extractGSections извлекает содержимое всех <g>...</g> на верхнем уровне (не внутри вложенных групп)
func extractGSections(s string) []string {
var result []string
i := 0
for i < len(s) {
if strings.HasPrefix(s[i:], "<g>") {
startContent := i + 3
j := startContent
depth := 0
for j < len(s) {
if strings.HasPrefix(s[j:], "<group>") {
depth++
j += 7
} else if strings.HasPrefix(s[j:], "</group>") {
if depth > 0 {
depth--
}
j += 8
} else if strings.HasPrefix(s[j:], "</g>") && 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:], "<group>") {
// Пропускаем вложенную группу целиком
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:], "<group>") {
balance++
i += 6
} else if strings.HasPrefix(template[i:], "</group>") {
balance--
i += 7
}
}
if balance != 0 {
return fmt.Errorf("unbalanced <group> tags")
}
return nil
}