Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e45660d
Add basic blockquote formatting
PaulSonOfLars Nov 20, 2023
0289bee
Improve multiline blockquote support
PaulSonOfLars Dec 31, 2023
51ad059
Merge pull request #4 from PaulSonOfLars/paul/add-blockquote-formatting
PaulSonOfLars Jan 1, 2024
0099e0e
Fix OOB error and improve tests
PaulSonOfLars Jan 6, 2024
918e614
Improve OOB checks
PaulSonOfLars Jan 6, 2024
3cf1fcf
Improve handling of nested spoiler tags
PaulSonOfLars Feb 29, 2024
9220a93
Clean up and add support for the new expandable_blockquote resources
PaulSonOfLars May 28, 2024
e6e12f2
clean up some missing todos and improve some parsing components
PaulSonOfLars May 28, 2024
6b9dd57
Merge pull request #5 from PaulSonOfLars/paul/expandable-blockquotes
PaulSonOfLars Jun 2, 2024
03e44ab
improve blockquote detection
PaulSonOfLars Jun 5, 2024
a6fdd24
Merge pull request #6 from PaulSonOfLars/paul/improve-blockquotes
PaulSonOfLars Jun 5, 2024
794f8b1
Add fancy new button style syntax
PaulSonOfLars Feb 12, 2026
f834e12
Update deps
PaulSonOfLars Feb 14, 2026
69ffd28
Add button style validation logic
PaulSonOfLars Feb 14, 2026
04d460c
add new tg timestamp parts
PaulSonOfLars Mar 1, 2026
4dc93d7
cleanup tglink logic to fix backwards breaking change
PaulSonOfLars Mar 8, 2026
f7e1e71
lint
PaulSonOfLars Mar 8, 2026
71e85f5
update go version + run go fix
PaulSonOfLars Mar 14, 2026
fab2e51
fix CI
PaulSonOfLars Mar 14, 2026
c54c17c
fix lint
PaulSonOfLars Mar 14, 2026
3e47bfb
Bot API 9.5 - tg timestamps
PaulSonOfLars Mar 14, 2026
61634cb
Improve codeblock logic (#10)
PaulSonOfLars Mar 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@v5
with:
go-version: '1.19'
go-version: 'stable'

- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Get dependencies
run: go mod download
Expand All @@ -33,15 +33,17 @@ jobs:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v5
with:
go-version: '1.19'
go-version: 'stable'

- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v8
with:
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
version: latest
# skip cache to avoid flakes (and avoid using gh-action storage)
skip-cache: true
skip-save-cache: true
16 changes: 3 additions & 13 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ func validStart(pos int, input []rune) bool {
}

// If the previous char is alphanumeric, it is an invalid start char.
return !(unicode.IsLetter(input[pos-1]) || unicode.IsDigit(input[pos-1]))
return !unicode.IsLetter(input[pos-1]) && !unicode.IsDigit(input[pos-1])
}

func validEnd(pos int, input []rune) bool {
// First char is not a valid end char.
// First char is not a valid end char; we do NOT allow empty entities.
// If the end char has a space before it, its not valid either.
if pos == 0 || unicode.IsSpace(input[pos-1]) {
return false
Expand All @@ -34,17 +34,7 @@ func validEnd(pos int, input []rune) bool {
}

// If the next char is alphanumeric, it is an invalid end char.
return !(unicode.IsLetter(input[pos+1]) || unicode.IsDigit(input[pos+1]))
}

func contains(r rune, rr []rune) bool {
for _, x := range rr {
if r == x {
return true
}
}

return false
return !unicode.IsLetter(input[pos+1]) && !unicode.IsDigit(input[pos+1])
}

var link = regexp.MustCompile(`a href="(.*)"`)
Expand Down
45 changes: 33 additions & 12 deletions commonV2.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@ import (
"strings"
)

func hasPrefix(s string, tgPrefixes []string) bool {
rest, ok := strings.CutPrefix(s, "tg://")
if !ok {
return false
}

for _, prefix := range tgPrefixes {
if strings.HasPrefix(rest, prefix) {
return true
}
}
return false
}

// finds the middle '](' section of in a link markdown
func findLinkMidSectionIdx(in []rune, emoji bool) int {
func findLinkMidSectionIdx(in []rune, tgSpecial bool) int {
var textEnd int
var offset int
for offset < len(in) {
Expand All @@ -15,8 +29,8 @@ func findLinkMidSectionIdx(in []rune, emoji bool) int {
}
textEnd = offset + idx
if !IsEscaped(in, textEnd) {
isEmoji := strings.HasPrefix(string(in[textEnd+2:]), "tg://emoji?id=")
if (isEmoji && emoji) || (!isEmoji && !emoji) {
prefixed := hasPrefix(string(in[textEnd+2:]), []string{"emoji?", "time?"})
if (prefixed && tgSpecial) || (!prefixed && !tgSpecial) {
return textEnd
}
}
Expand Down Expand Up @@ -44,8 +58,8 @@ func findLinkEndSectionIdx(in []rune) int {
}

// finds the middle and closing sections of in a link markdown
func findLinkSectionsIdx(in []rune, isEmojiLink bool) (int, int) {
textEnd := findLinkMidSectionIdx(in, isEmojiLink)
func findLinkSectionsIdx(in []rune, tgSpecial bool) (int, int) {
textEnd := findLinkMidSectionIdx(in, tgSpecial)
if textEnd < 0 {
return -1, -1
}
Expand All @@ -60,7 +74,7 @@ func findLinkSectionsIdx(in []rune, isEmojiLink bool) (int, int) {
// Now, we iterate over the text in between the mid and end sections to see if any other mid sections exist.
// If yes, we choose those instead - it would be invalid in a URL anyway.
for textEnd < offsetLinkEnd {
newTextEnd := findLinkMidSectionIdx(in[textEnd+1:offsetLinkEnd], isEmojiLink)
newTextEnd := findLinkMidSectionIdx(in[textEnd+1:offsetLinkEnd], tgSpecial)
if newTextEnd == -1 {
break
}
Expand All @@ -70,9 +84,9 @@ func findLinkSectionsIdx(in []rune, isEmojiLink bool) (int, int) {
return textEnd, offsetLinkEnd
}

func getLinkContents(in []rune, emoji bool) (bool, []rune, string, int) {
func getLinkContents(in []rune, tgSpecial bool) (bool, []rune, string, int) {
// find ]( and then )
textEndIdx, urlEndIdx := findLinkSectionsIdx(in, emoji)
textEndIdx, urlEndIdx := findLinkSectionsIdx(in, tgSpecial)
if textEndIdx < 0 || urlEndIdx < 0 {
return false, nil, "", 0
}
Expand All @@ -82,6 +96,13 @@ func getLinkContents(in []rune, emoji bool) (bool, []rune, string, int) {
return true, text, content, urlEndIdx + 1
}

func isCodeBlockNewlineEnd(in []rune, offset int, item string) bool {
if item != "```" {
return false
}
return offset == 0 || in[offset-1] == '\n'
}

func getValidEnd(in []rune, s string) int {
offset := 0
for offset < len(in) {
Expand All @@ -92,7 +113,7 @@ func getValidEnd(in []rune, s string) int {

end := offset + idx
// validEnd check has double logic to account for multi char strings
if validEnd(end, in) && validEnd(end+len(s)-1, in) && !IsEscaped(in, end) {
if (validEnd(end, in) || isCodeBlockNewlineEnd(in, end, s)) && validEnd(end+len(s)-1, in) && !IsEscaped(in, end) {
idx = stringIndex(in[end+1:], s)
for idx == 0 {
end++
Expand Down Expand Up @@ -148,7 +169,7 @@ func isClosingTag(in []rune, pos int) bool {
return false
}

func getClosingTag(in []rune, tag string) (int, int) {
func getClosingTag(in []rune, openingTag string, closingTag string) (int, int) {
offset := 0
subtags := 0
for offset < len(in) {
Expand All @@ -164,9 +185,9 @@ func getClosingTag(in []rune, tag string) (int, int) {
}

closingTagIdx := openingTagIdx + 2 + c
if string(in[openingTagIdx+1:closingTagIdx]) == tag { // found a nested tag, this is annoying
if string(in[openingTagIdx+1:closingTagIdx]) == openingTag { // found a nested tag, this is annoying
subtags++
} else if isClosingTag(in, openingTagIdx) && string(in[openingTagIdx+2:closingTagIdx]) == tag {
} else if isClosingTag(in, openingTagIdx) && string(in[openingTagIdx+2:closingTagIdx]) == closingTag {
if subtags == 0 {
return openingTagIdx, closingTagIdx
}
Expand Down
14 changes: 14 additions & 0 deletions common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package tg_md2html_test

import "github.com/PaulSonOfLars/gotg_md2html"

func testConverter() *tg_md2html.ConverterV2 {
return tg_md2html.NewV2(map[string]string{
"url": "buttonurl",
"text": "buttontext",
}, map[string]string{
"primary": "primary",
"success": "success",
"danger": "danger",
})
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module github.com/PaulSonOfLars/gotg_md2html

go 1.19
go 1.24

require github.com/stretchr/testify v1.8.4
require github.com/stretchr/testify v1.11.1

require (
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
10 changes: 5 additions & 5 deletions md2html.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package tg_md2html

import (
"html"
"maps"
"slices"
"strings"
)

Expand Down Expand Up @@ -102,7 +104,7 @@ func (cv *Converter) md2html(input []rune, buttons bool) (string, []Button) {
v[char] = append(v[char], pos-offset)
containedMDChars = append(containedMDChars, char)
case '\\':
if len(input) <= pos+1 || !contains(input[pos+1], allMdChars) {
if len(input) <= pos+1 || !slices.Contains(allMdChars, input[pos+1]) {
continue
}
escaped = true
Expand Down Expand Up @@ -192,9 +194,7 @@ func (cv *Converter) md2html(input []rune, buttons bool) (string, []Button) {
}
i = cnt // set i to copy

for x, y := range bkp {
v[x] = y
}
maps.Copy(v, bkp)

case '[':
nameOpen, rest := posArr[0], posArr[1:]
Expand Down Expand Up @@ -405,7 +405,7 @@ func (cv *Converter) stripHTML(r []rune) string {
func EscapeMarkdown(r []rune, toEscape []rune) string {
out := strings.Builder{}
for i, x := range r {
if contains(x, toEscape) {
if slices.Contains(toEscape, x) {
if i == 0 || i == len(r)-1 || validEnd(i, r) || validStart(i, r) {
out.WriteRune('\\')
}
Expand Down
Loading