From ac17bbf962a894f99d6a2536b7dcc7ecfb1d4a0b Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 18 Jan 2026 04:18:56 +0000 Subject: [PATCH 1/2] Fix: Handle HTML responses from BibleAI API in /ask command --- pkg/app/ask.go | 8 +++-- pkg/app/ask_test.go | 44 +++++++++++++++++++++++- pkg/app/html_parser.go | 76 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 pkg/app/html_parser.go diff --git a/pkg/app/ask.go b/pkg/app/ask.go index 5310754..203cf46 100644 --- a/pkg/app/ask.go +++ b/pkg/app/ask.go @@ -2,6 +2,7 @@ package app import ( "fmt" + stdhtml "html" "log" "strings" @@ -50,16 +51,17 @@ func GetBibleAskWithContext(env def.SessionData, contextVerses []string) def.Ses } var sb strings.Builder - sb.WriteString(resp.Text) + sb.WriteString(ParseToTelegramHTML(resp.Text)) if len(resp.References) > 0 { - sb.WriteString("\n\n*References:*") + sb.WriteString("\n\nReferences:") for _, ref := range resp.References { - sb.WriteString(fmt.Sprintf("\n- %s", ref.Verse)) + sb.WriteString(fmt.Sprintf("\n• %s", stdhtml.EscapeString(ref.Verse))) } } env.Res.Message = sb.String() + env.Res.ParseMode = def.TELEGRAM_PARSE_MODE_HTML } return env } diff --git a/pkg/app/ask_test.go b/pkg/app/ask_test.go index 7d79e43..c88fd1f 100644 --- a/pkg/app/ask_test.go +++ b/pkg/app/ask_test.go @@ -1,6 +1,7 @@ package app import ( + "strings" "testing" "github.com/julwrites/BotPlatform/pkg/def" @@ -110,9 +111,50 @@ func TestGetBibleAsk(t *testing.T) { env = GetBibleAsk(env) - expected := "This is a mock response.\n\n*References:*\n- John 3:16" + expected := "This is a mock response.\n\nReferences:\n• John 3:16" if env.Res.Message != expected { t.Errorf("Expected admin response, got: %s", env.Res.Message) } }) + + t.Run("HTML Response Handling", func(t *testing.T) { + ResetAPIConfigCache() + SetAPIConfigOverride("https://mock", "key") + + // Mock SubmitQuery to return HTML + SubmitQuery = func(req QueryRequest, result interface{}) error { + if r, ok := result.(*OQueryResponse); ok { + *r = OQueryResponse{ + Text: "
God is Love
", + References: []SearchResult{ + {Verse: "1 John 4:8"}, + }, + } + } + return nil + } + + var env def.SessionData + env.Msg.Message = "Who is God?" + conf := utils.UserConfig{Version: "NIV"} + env = utils.SetUserConfig(env, utils.SerializeUserConfig(conf)) + + env = GetBibleAskWithContext(env, nil) + + // Check ParseMode + if env.Res.ParseMode != def.TELEGRAM_PARSE_MODE_HTML { + t.Errorf("Expected ParseMode to be HTML, got %v", env.Res.ParseMode) + } + + // Check Content + if !strings.Contains(env.Res.Message, "God is Love") { + t.Errorf("Expected message to contain parsed HTML, got: %s", env.Res.Message) + } + if strings.Contains(env.Res.Message, "") { + t.Errorf("Expected message to NOT contain
tag, got: %s", env.Res.Message) + } + if !strings.Contains(env.Res.Message, "References:") { + t.Errorf("Expected message to contain bold References header, got: %s", env.Res.Message) + } + }) } diff --git a/pkg/app/html_parser.go b/pkg/app/html_parser.go new file mode 100644 index 0000000..7a74f8a --- /dev/null +++ b/pkg/app/html_parser.go @@ -0,0 +1,76 @@ +package app + +import ( + "fmt" + "strings" + stdhtml "html" + + "golang.org/x/net/html" +) + +// ParseToTelegramHTML converts generic HTML to Telegram-supported HTML. +// It converts block elements like
to newlines, handles lists, and preserves
+// inline formatting like , , while stripping unsupported tags.
+func ParseToTelegramHTML(htmlStr string) string {
+ doc, err := html.Parse(strings.NewReader(htmlStr))
+ if err != nil {
+ // Fallback to original string if parsing fails
+ return htmlStr
+ }
+
+ return strings.TrimSpace(parseNodesForTelegram(doc))
+}
+
+func parseNodesForTelegram(node *html.Node) string {
+ var parts []string
+
+ for child := node.FirstChild; child != nil; child = child.NextSibling {
+ switch tag := child.Data; tag {
+ case "b", "strong":
+ parts = append(parts, fmt.Sprintf("%s", parseNodesForTelegram(child)))
+ case "i", "em":
+ parts = append(parts, fmt.Sprintf("%s", parseNodesForTelegram(child)))
+ case "u", "ins":
+ parts = append(parts, fmt.Sprintf("%s", parseNodesForTelegram(child)))
+ case "s", "strike", "del":
+ parts = append(parts, fmt.Sprintf("%s", parseNodesForTelegram(child)))
+ case "code":
+ parts = append(parts, fmt.Sprintf("%s", parseNodesForTelegram(child)))
+ case "pre":
+ parts = append(parts, fmt.Sprintf("%s
", parseNodesForTelegram(child)))
+ case "a":
+ href := ""
+ for _, attr := range child.Attr {
+ if attr.Key == "href" {
+ href = attr.Val
+ break
+ }
+ }
+ if href != "" {
+ parts = append(parts, fmt.Sprintf(`%s`, href, parseNodesForTelegram(child)))
+ } else {
+ parts = append(parts, parseNodesForTelegram(child))
+ }
+ case "p":
+ parts = append(parts, parseNodesForTelegram(child))
+ parts = append(parts, "\n\n")
+ case "br":
+ parts = append(parts, "\n")
+ case "ul", "ol":
+ parts = append(parts, parseNodesForTelegram(child))
+ case "li":
+ parts = append(parts, fmt.Sprintf("• %s\n", strings.TrimSpace(parseNodesForTelegram(child))))
+ case "h1", "h2", "h3", "h4", "h5", "h6":
+ parts = append(parts, fmt.Sprintf("%s\n", strings.TrimSpace(parseNodesForTelegram(child))))
+ default:
+ if child.Type == html.TextNode {
+ parts = append(parts, stdhtml.EscapeString(child.Data))
+ } else if child.Type == html.ElementNode {
+ // Recurse for unknown elements (like div, span) to preserve content
+ parts = append(parts, parseNodesForTelegram(child))
+ }
+ }
+ }
+
+ return strings.Join(parts, "")
+}
From c697186b4ddf6f26d909ee4a8d1d1d79209b5254 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Sun, 18 Jan 2026 04:28:46 +0000
Subject: [PATCH 2/2] Fix: Handle HTML responses from BibleAI API in /ask
command
---
pkg/app/bible_reference.go | 2 +-
pkg/app/bible_reference_test.go | 24 +++++------
pkg/app/html_parser.go | 2 +-
pkg/app/natural_language_test.go | 68 +++++++++++++++++---------------
pkg/app/passage.go | 7 ++--
5 files changed, 53 insertions(+), 50 deletions(-)
diff --git a/pkg/app/bible_reference.go b/pkg/app/bible_reference.go
index 7d26539..0db0975 100644
--- a/pkg/app/bible_reference.go
+++ b/pkg/app/bible_reference.go
@@ -329,7 +329,7 @@ func consumeReferenceSyntax(s string) (string, int) {
return "", 0
}
- return s[:lastDigit+1], lastDigit+1
+ return s[:lastDigit+1], lastDigit + 1
}
func hasDigit(s string) bool {
diff --git a/pkg/app/bible_reference_test.go b/pkg/app/bible_reference_test.go
index c588ad8..fdb6b46 100644
--- a/pkg/app/bible_reference_test.go
+++ b/pkg/app/bible_reference_test.go
@@ -28,20 +28,20 @@ func TestParseBibleReference(t *testing.T) {
{"Phlm 1", "Philemon 1", true},
// Fuzzy Matches (Typos)
- {"Gensis 1", "Genesis 1", true}, // Missing 'e', dist 1
- {"Genisis 1", "Genesis 1", true}, // 'i' instead of 'e', dist 1
- {"Mathew 5", "Matthew 5", true}, // Missing 't', dist 1
- {"Revalation 3", "Revelation 3", true},// 'a' instead of 'e', dist 1
- {"Philipians 4", "Philippians 4", true},// Missing 'p', dist 1
- {"1 Jhn 3", "1 John 3", true}, // Missing 'o', dist 1. "1 Jhn" vs "1 John".
+ {"Gensis 1", "Genesis 1", true}, // Missing 'e', dist 1
+ {"Genisis 1", "Genesis 1", true}, // 'i' instead of 'e', dist 1
+ {"Mathew 5", "Matthew 5", true}, // Missing 't', dist 1
+ {"Revalation 3", "Revelation 3", true}, // 'a' instead of 'e', dist 1
+ {"Philipians 4", "Philippians 4", true}, // Missing 'p', dist 1
+ {"1 Jhn 3", "1 John 3", true}, // Missing 'o', dist 1. "1 Jhn" vs "1 John".
// Thresholds / False Positives
- {"Genius 1", "", false}, // Dist to Genesis is > threshold? "Genius" (6) vs "Genesis" (7). Dist 3. Threshold 1. False.
- {"Mary 1", "", false}, // "Mary" (4). Threshold 0. "Mark" (4). Dist 1. No fuzzy allowed for len < 5.
- {"Mark 1", "Mark 1", true}, // Exact match.
- {"Luke 1", "Luke 1", true}, // Exact match.
- {"Luke", "Luke 1", true}, // Exact match.
- {"Luek 1", "", false}, // "Luek" (4). Threshold 0. No match.
+ {"Genius 1", "", false}, // Dist to Genesis is > threshold? "Genius" (6) vs "Genesis" (7). Dist 3. Threshold 1. False.
+ {"Mary 1", "", false}, // "Mary" (4). Threshold 0. "Mark" (4). Dist 1. No fuzzy allowed for len < 5.
+ {"Mark 1", "Mark 1", true}, // Exact match.
+ {"Luke 1", "Luke 1", true}, // Exact match.
+ {"Luke", "Luke 1", true}, // Exact match.
+ {"Luek 1", "", false}, // "Luek" (4). Threshold 0. No match.
// Invalid References
{"John is here", "", false},
diff --git a/pkg/app/html_parser.go b/pkg/app/html_parser.go
index 7a74f8a..862be89 100644
--- a/pkg/app/html_parser.go
+++ b/pkg/app/html_parser.go
@@ -2,8 +2,8 @@ package app
import (
"fmt"
- "strings"
stdhtml "html"
+ "strings"
"golang.org/x/net/html"
)
diff --git a/pkg/app/natural_language_test.go b/pkg/app/natural_language_test.go
index fcb97d7..32b0c54 100644
--- a/pkg/app/natural_language_test.go
+++ b/pkg/app/natural_language_test.go
@@ -24,78 +24,82 @@ func TestProcessNaturalLanguage(t *testing.T) {
{
name: "Passage: Reference",
message: "John 3:16",
- expectedCheck: func(msg string) bool { return strings.Contains(msg, "John 3") || strings.Contains(msg, "loved the world") },
+ expectedCheck: func(msg string) bool {
+ return strings.Contains(msg, "John 3") || strings.Contains(msg, "loved the world")
+ },
desc: "Should retrieve John 3:16 passage",
},
{
name: "Passage: Short Book",
message: "Jude",
- expectedCheck: func(msg string) bool { return strings.Contains(msg, "Jude") || strings.Contains(msg, "servant of Jesus Christ") },
+ expectedCheck: func(msg string) bool {
+ return strings.Contains(msg, "Jude") || strings.Contains(msg, "servant of Jesus Christ")
+ },
desc: "Should retrieve Jude passage",
},
{
- name: "Passage: Book only",
- message: "Genesis",
+ name: "Passage: Book only",
+ message: "Genesis",
expectedCheck: func(msg string) bool { return strings.Contains(msg, "Genesis 1") },
- desc: "Should retrieve Genesis 1",
+ desc: "Should retrieve Genesis 1",
},
// Search Scenarios
{
- name: "Search: One word",
- message: "Grace",
+ name: "Search: One word",
+ message: "Grace",
expectedCheck: func(msg string) bool { return strings.Contains(msg, "Found") || strings.Contains(msg, "No results") },
- desc: "Should perform search for Grace",
+ desc: "Should perform search for Grace",
},
{
- name: "Search: Short phrase",
- message: "Jesus wept",
+ name: "Search: Short phrase",
+ message: "Jesus wept",
expectedCheck: func(msg string) bool { return strings.Contains(msg, "Found") },
- desc: "Should perform search for Jesus wept",
+ desc: "Should perform search for Jesus wept",
},
{
- name: "Search: 3 words",
- message: "Love of God",
+ name: "Search: 3 words",
+ message: "Love of God",
expectedCheck: func(msg string) bool { return strings.Contains(msg, "Found") },
- desc: "Should perform search for Love of God",
+ desc: "Should perform search for Love of God",
},
// Ask Scenarios
{
- name: "Ask: Question",
- message: "What does the bible say about love?",
+ name: "Ask: Question",
+ message: "What does the bible say about love?",
expectedCheck: func(msg string) bool { return len(msg) == 0 },
- desc: "Should not ask the AI (Question)",
+ desc: "Should not ask the AI (Question)",
},
{
- name: "Ask: With Reference",
- message: "Explain John 3:16",
+ name: "Ask: With Reference",
+ message: "Explain John 3:16",
expectedCheck: func(msg string) bool { return !strings.Contains(msg, "Found") },
- desc: "Should ask the AI (With Reference)",
+ desc: "Should ask the AI (With Reference)",
},
{
- name: "Ask: Compare",
- message: "Compare Genesis 1 and John 1",
+ name: "Ask: Compare",
+ message: "Compare Genesis 1 and John 1",
expectedCheck: func(msg string) bool { return true },
- desc: "Should ask the AI (Compare)",
+ desc: "Should ask the AI (Compare)",
},
{
- name: "Ask: Short Question",
- message: "Who is Jesus?",
+ name: "Ask: Short Question",
+ message: "Who is Jesus?",
expectedCheck: func(msg string) bool { return len(msg) == 0 && !strings.Contains(msg, "Found") },
- desc: "Should not ask the AI (Short Question)",
+ desc: "Should not ask the AI (Short Question)",
},
{
- name: "Ask: Embedded Reference",
- message: "What does it say in Mark 5?",
+ name: "Ask: Embedded Reference",
+ message: "What does it say in Mark 5?",
expectedCheck: func(msg string) bool { return true },
- desc: "Should ask the AI (Embedded Reference)",
+ desc: "Should ask the AI (Embedded Reference)",
},
{
- name: "Ask: Book name in text",
- message: "I like Genesis",
+ name: "Ask: Book name in text",
+ message: "I like Genesis",
expectedCheck: func(msg string) bool { return !strings.Contains(msg, "Found") },
- desc: "Should ask the AI (Found reference Genesis)",
+ desc: "Should ask the AI (Found reference Genesis)",
},
}
diff --git a/pkg/app/passage.go b/pkg/app/passage.go
index 0af0998..223baf6 100644
--- a/pkg/app/passage.go
+++ b/pkg/app/passage.go
@@ -5,11 +5,11 @@ package app
import (
"fmt"
+ stdhtml "html"
"log"
"net/url"
"regexp"
"strings"
- stdhtml "html"
"golang.org/x/net/html"
@@ -43,7 +43,6 @@ func GetReference(doc *html.Node) string {
return utils.GetTextNode(refNode).Data
}
-
func isNextSiblingBr(node *html.Node) bool {
for next := node.NextSibling; next != nil; next = next.NextSibling {
if next.Type == html.TextNode {
@@ -234,7 +233,7 @@ func GetBiblePassage(env def.SessionData) def.SessionData {
// If indeed a reference, attempt to query
if len(ref) > 0 {
- log.Printf("%s", ref);
+ log.Printf("%s", ref)
// Attempt to retrieve from API
req := QueryRequest{
@@ -256,7 +255,7 @@ func GetBiblePassage(env def.SessionData) def.SessionData {
log.Printf("Error retrieving passage from API: %v. Falling back to deprecated method.", err)
return GetBiblePassageFallback(env)
- }
+ }
if len(resp.Verse) > 0 {
env.Res.Message = ParsePassageFromHtml(env.Msg.Message, resp.Verse, config.Version)