- Introduced Weixin integration support with new configuration options for WeChat iLink Bot. - Updated existing adapters (DingTalk, Discord, Feishu, Telegram) to include sender_id and app_id in message metadata for improved context handling. - Enhanced dispatcher logic to accommodate the new Weixin adapter and ensure proper initialization and shutdown processes. - Improved message handling across integrations to support typing indicators, providing a more interactive user experience.
226 lines
5.5 KiB
Go
226 lines
5.5 KiB
Go
package weixin
|
|
|
|
import (
|
|
"regexp"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
// FormatWeixinText converts standard Markdown to plain text suitable for
|
|
// WeChat iLink Bot's text_item. WeChat renders only plain text with clickable
|
|
// URLs and [text](url) style links. All other Markdown/HTML is stripped and
|
|
// gracefully degraded to readable plain text.
|
|
func FormatWeixinText(md string) string {
|
|
md = strings.ReplaceAll(md, "\r\n", "\n")
|
|
|
|
var out strings.Builder
|
|
lines := strings.Split(md, "\n")
|
|
|
|
inCodeBlock := false
|
|
var codeLines []string
|
|
|
|
inTable := false
|
|
var tableRows [][]string
|
|
|
|
for i := 0; i < len(lines); i++ {
|
|
line := lines[i]
|
|
|
|
if strings.HasPrefix(line, "```") {
|
|
if !inCodeBlock {
|
|
inCodeBlock = true
|
|
codeLines = nil
|
|
} else {
|
|
inCodeBlock = false
|
|
for _, cl := range codeLines {
|
|
out.WriteString(" " + cl + "\n")
|
|
}
|
|
}
|
|
continue
|
|
}
|
|
if inCodeBlock {
|
|
codeLines = append(codeLines, line)
|
|
continue
|
|
}
|
|
|
|
if wxIsTableRow(line) {
|
|
if !inTable {
|
|
inTable = true
|
|
tableRows = nil
|
|
}
|
|
if wxIsTableSep(line) {
|
|
continue
|
|
}
|
|
tableRows = append(tableRows, wxParseTableRow(line))
|
|
continue
|
|
}
|
|
if inTable {
|
|
wxFlushTable(&out, tableRows)
|
|
inTable = false
|
|
tableRows = nil
|
|
}
|
|
|
|
if line == "---" || line == "***" || line == "___" {
|
|
out.WriteString("——————\n")
|
|
continue
|
|
}
|
|
|
|
if m := wxReHeading.FindStringSubmatch(line); m != nil {
|
|
out.WriteString("【" + wxFormatInline(m[2]) + "】\n")
|
|
continue
|
|
}
|
|
|
|
if m := wxReBlockquote.FindStringSubmatch(line); m != nil {
|
|
out.WriteString("│ " + wxFormatInline(m[1]) + "\n")
|
|
continue
|
|
}
|
|
|
|
if m := wxReUnorderedList.FindStringSubmatch(line); m != nil {
|
|
out.WriteString("• " + wxFormatInline(m[1]) + "\n")
|
|
continue
|
|
}
|
|
|
|
if m := wxReOrderedList.FindStringSubmatch(line); m != nil {
|
|
out.WriteString(m[1] + ". " + wxFormatInline(m[2]) + "\n")
|
|
continue
|
|
}
|
|
|
|
if m := wxReImage.FindStringSubmatch(line); m != nil {
|
|
alt := m[1]
|
|
url := m[2]
|
|
if alt != "" {
|
|
out.WriteString("[" + alt + "](" + url + ")\n")
|
|
} else {
|
|
out.WriteString(url + "\n")
|
|
}
|
|
continue
|
|
}
|
|
|
|
out.WriteString(wxFormatInline(line) + "\n")
|
|
}
|
|
|
|
if inCodeBlock && len(codeLines) > 0 {
|
|
for _, cl := range codeLines {
|
|
out.WriteString(" " + cl + "\n")
|
|
}
|
|
}
|
|
if inTable {
|
|
wxFlushTable(&out, tableRows)
|
|
}
|
|
|
|
return strings.TrimRight(out.String(), "\n")
|
|
}
|
|
|
|
var (
|
|
wxReHeading = regexp.MustCompile(`^(#{1,6})\s+(.+)$`)
|
|
wxReBlockquote = regexp.MustCompile(`^>\s*(.*)$`)
|
|
wxReUnorderedList = regexp.MustCompile(`^[\s]*[-*+]\s+(.+)$`)
|
|
wxReOrderedList = regexp.MustCompile(`^[\s]*(\d+)[.)]\s+(.+)$`)
|
|
wxReImage = regexp.MustCompile(`^!\[([^\]]*)\]\(([^)]+)\)$`)
|
|
wxReTableRow = regexp.MustCompile(`^\|.*\|$`)
|
|
wxReTableSep = regexp.MustCompile(`^\|[\s\-:|]+\|$`)
|
|
|
|
wxReBoldItalic = regexp.MustCompile(`\*\*\*(.+?)\*\*\*`)
|
|
wxReBold = regexp.MustCompile(`\*\*(.+?)\*\*`)
|
|
wxReBoldAlt = regexp.MustCompile(`__(.+?)__`)
|
|
wxReItalic = regexp.MustCompile(`(?:^|[^*])\*([^*]+?)\*(?:[^*]|$)`)
|
|
wxReStrikethrough = regexp.MustCompile(`~~(.+?)~~`)
|
|
wxReCode = regexp.MustCompile("`([^`]+)`")
|
|
wxReLink = regexp.MustCompile(`\[([^\]]+)\]\(([^)]+)\)`)
|
|
|
|
wxReHTMLTag = regexp.MustCompile(`<[^>]+>`)
|
|
)
|
|
|
|
// wxFormatInline strips inline Markdown/HTML formatting, keeping links in
|
|
// [text](url) form which WeChat renders as clickable.
|
|
func wxFormatInline(s string) string {
|
|
s = wxReLink.ReplaceAllString(s, "[$1]($2)")
|
|
s = wxReBoldItalic.ReplaceAllString(s, "$1")
|
|
s = wxReBold.ReplaceAllString(s, "$1")
|
|
s = wxReBoldAlt.ReplaceAllString(s, "$1")
|
|
s = wxReStrikethrough.ReplaceAllString(s, "$1")
|
|
s = wxReCode.ReplaceAllString(s, "$1")
|
|
s = wxReItalic.ReplaceAllStringFunc(s, func(match string) string {
|
|
m := wxReItalic.FindStringSubmatch(match)
|
|
if len(m) < 2 {
|
|
return match
|
|
}
|
|
prefix := ""
|
|
suffix := ""
|
|
if len(match) > 0 && match[0] != '*' {
|
|
prefix = string(match[0])
|
|
}
|
|
if len(match) > 0 && match[len(match)-1] != '*' {
|
|
suffix = string(match[len(match)-1])
|
|
}
|
|
return prefix + m[1] + suffix
|
|
})
|
|
s = wxReHTMLTag.ReplaceAllString(s, "")
|
|
return s
|
|
}
|
|
|
|
func wxIsTableRow(line string) bool {
|
|
return wxReTableRow.MatchString(strings.TrimSpace(line))
|
|
}
|
|
|
|
func wxIsTableSep(line string) bool {
|
|
return wxReTableSep.MatchString(strings.TrimSpace(line))
|
|
}
|
|
|
|
func wxParseTableRow(line string) []string {
|
|
line = strings.TrimSpace(line)
|
|
line = strings.TrimPrefix(line, "|")
|
|
line = strings.TrimSuffix(line, "|")
|
|
cells := strings.Split(line, "|")
|
|
for i := range cells {
|
|
cells[i] = strings.TrimSpace(cells[i])
|
|
}
|
|
return cells
|
|
}
|
|
|
|
func wxFlushTable(out *strings.Builder, rows [][]string) {
|
|
if len(rows) == 0 {
|
|
return
|
|
}
|
|
colWidths := make([]int, len(rows[0]))
|
|
for _, row := range rows {
|
|
for i, cell := range row {
|
|
if i < len(colWidths) && utf8.RuneCountInString(cell) > colWidths[i] {
|
|
colWidths[i] = utf8.RuneCountInString(cell)
|
|
}
|
|
}
|
|
}
|
|
for ri, row := range rows {
|
|
for ci, cell := range row {
|
|
if ci > 0 {
|
|
out.WriteString(" | ")
|
|
}
|
|
w := 0
|
|
if ci < len(colWidths) {
|
|
w = colWidths[ci]
|
|
}
|
|
out.WriteString(wxPadRight(cell, w))
|
|
}
|
|
out.WriteString("\n")
|
|
if ri == 0 && len(rows) > 1 {
|
|
for ci := range row {
|
|
if ci > 0 {
|
|
out.WriteString("-+-")
|
|
}
|
|
w := 0
|
|
if ci < len(colWidths) {
|
|
w = colWidths[ci]
|
|
}
|
|
out.WriteString(strings.Repeat("-", w))
|
|
}
|
|
out.WriteString("\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
func wxPadRight(s string, width int) string {
|
|
runes := utf8.RuneCountInString(s)
|
|
if runes >= width {
|
|
return s
|
|
}
|
|
return s + strings.Repeat(" ", width-runes)
|
|
}
|