package rewrite import ( "bytes" "compress/flate" "compress/gzip" "fmt" "io" "log" "net/http" "regexp" "strings" "git.gulenok.ru/greenhaze/gaslight/internal/cheating" "git.gulenok.ru/greenhaze/gaslight/internal/latex" "git.gulenok.ru/greenhaze/gaslight/internal/openrouter" ) var questionTestDivRe = regexp.MustCompile(`(?is)]*\bid\s*=\s*["'][^"']*\bQuestionTest\b[^"']*["'][^>]*>`) type HTMLHook func(req *http.Request, html []byte) ([]byte, error) type Processor struct { hook HTMLHook } func NewProcessor(hook HTMLHook) *Processor { return &Processor{hook: hook} } func DefaultQuestionTestHook(_ *http.Request, html []byte) ([]byte, error) { switch cheating.DetermineQuestionType(string(html)) { case cheating.FullTextQuestion: { aiResponce, err := openrouter.AskOpenRouter(fmt.Sprintf("РЕШИ ЗАДАНИЕ И ДАЙ МАКСИМАЛЬНО КРАТКИЙ ОТВЕТ: %s", string(html))) if err != nil { log.Printf("openrouter failed %s", err) return html, nil } return []byte(strings.ReplaceAll(string(html), "generated", latex.ConvertLaTeXToASCII(aiResponce))), nil } case cheating.QuestionWithPicture: { } } return []byte(strings.ReplaceAll(string(html), "generated", "pwned")), nil } func IsHTMLResponse(contentType string) bool { contentType = strings.ToLower(contentType) return strings.Contains(contentType, "text/html") || strings.Contains(contentType, "application/xhtml+xml") } func (p *Processor) RewriteIfNeeded(req *http.Request, contentEncoding string, body []byte) ([]byte, error) { decoded, err := decodeBody(contentEncoding, body) if err != nil { return nil, err } if !questionTestDivRe.Match(decoded) { return body, nil } modifiedDecoded, err := p.hook(req, decoded) if err != nil { return nil, err } return encodeBody(contentEncoding, modifiedDecoded) } func decodeBody(encoding string, body []byte) ([]byte, error) { switch strings.ToLower(strings.TrimSpace(encoding)) { case "", "identity": return body, nil case "gzip": r, err := gzip.NewReader(bytes.NewReader(body)) if err != nil { return nil, err } defer r.Close() return io.ReadAll(r) case "deflate": r := flate.NewReader(bytes.NewReader(body)) defer r.Close() return io.ReadAll(r) default: return nil, fmt.Errorf("unsupported content-encoding: %s", encoding) } } func encodeBody(encoding string, decoded []byte) ([]byte, error) { switch strings.ToLower(strings.TrimSpace(encoding)) { case "", "identity": return decoded, nil case "gzip": var buf bytes.Buffer w := gzip.NewWriter(&buf) if _, err := w.Write(decoded); err != nil { _ = w.Close() return nil, err } if err := w.Close(); err != nil { return nil, err } return buf.Bytes(), nil case "deflate": var buf bytes.Buffer w, err := flate.NewWriter(&buf, flate.DefaultCompression) if err != nil { return nil, err } if _, err := w.Write(decoded); err != nil { _ = w.Close() return nil, err } if err := w.Close(); err != nil { return nil, err } return buf.Bytes(), nil default: return nil, fmt.Errorf("unsupported content-encoding: %s", encoding) } }