fix: refine Telegram links and mailbox selector
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
This commit is contained in:
@@ -473,6 +473,7 @@ func sanitizeTelegramAttachmentName(value string) string {
|
||||
var (
|
||||
telegramOTPKeywordRe = regexp.MustCompile(`(?i)(验证码|校验码|动态码|登录码|安全码|一次性密码|otp|verification[ -]?code|security[ -]?code|login[ -]?code|passcode|one[ -]?time[ -]?(?:password|code))`)
|
||||
telegramOTPCandidateRe = regexp.MustCompile(`(?i)[a-z0-9]{4,10}`)
|
||||
telegramURLRe = regexp.MustCompile(`(?i)https?://[^\s<>"']+`)
|
||||
)
|
||||
|
||||
func detectTelegramOTP(subject, body string) string {
|
||||
@@ -506,6 +507,9 @@ func detectTelegramOTP(subject, body string) string {
|
||||
if !hasDigit || telegramOTPKeywordRe.MatchString(value) {
|
||||
continue
|
||||
}
|
||||
if isTelegramOTPNonCode(value) {
|
||||
continue
|
||||
}
|
||||
best := 0
|
||||
for _, keyword := range keywords {
|
||||
distance := match[0] - keyword[1]
|
||||
@@ -557,6 +561,20 @@ func detectTelegramOTP(subject, body string) string {
|
||||
return items[0].value
|
||||
}
|
||||
|
||||
func isTelegramOTPNonCode(value string) bool {
|
||||
if len(value) == 4 {
|
||||
if year, err := strconv.Atoi(value); err == nil && year >= 1900 && year <= 2099 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if len(value) == 8 {
|
||||
if _, err := time.Parse("20060102", value); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isTelegramOTPAlphaNumeric(r rune) bool {
|
||||
return r <= unicode.MaxASCII && (unicode.IsLetter(r) || unicode.IsDigit(r))
|
||||
}
|
||||
@@ -723,7 +741,7 @@ func formatTelegramMailMessage(payload telegramMailPayload) telegramFormattedMes
|
||||
}
|
||||
prefix := strings.Join(lines, "\n") + "\n\n<b>" + label + "</b>\n<blockquote>"
|
||||
suffix := "</blockquote>"
|
||||
body = escapeTelegramWithinBudget(body, telegramMessageBudget-utf8.RuneCountInString(prefix)-utf8.RuneCountInString(suffix))
|
||||
body = formatTelegramBodyHTML(body, telegramMessageBudget-utf8.RuneCountInString(prefix)-utf8.RuneCountInString(suffix))
|
||||
lines = []string{prefix + body + suffix}
|
||||
}
|
||||
htmlText := strings.Join(lines, "\n")
|
||||
@@ -731,6 +749,73 @@ func formatTelegramMailMessage(payload telegramMailPayload) telegramFormattedMes
|
||||
return telegramFormattedMessage{HTML: htmlText, PlainText: plain, OTP: payload.OTP}
|
||||
}
|
||||
|
||||
func formatTelegramBodyHTML(value string, budget int) string {
|
||||
if budget <= 3 {
|
||||
return ""
|
||||
}
|
||||
var out strings.Builder
|
||||
used := 0
|
||||
truncated := false
|
||||
appendEscaped := func(text string) bool {
|
||||
for _, r := range text {
|
||||
escaped := html.EscapeString(string(r))
|
||||
length := utf8.RuneCountInString(escaped)
|
||||
if used+length > budget-3 {
|
||||
return false
|
||||
}
|
||||
out.WriteString(escaped)
|
||||
used += length
|
||||
}
|
||||
return true
|
||||
}
|
||||
last := 0
|
||||
for _, match := range telegramURLRe.FindAllStringIndex(value, -1) {
|
||||
if !appendEscaped(value[last:match[0]]) {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
rawURL, trailing := trimTelegramURL(value[match[0]:match[1]])
|
||||
parsed, err := url.Parse(rawURL)
|
||||
if err != nil || parsed.Host == "" || (parsed.Scheme != "http" && parsed.Scheme != "https") {
|
||||
if !appendEscaped(value[match[0]:match[1]]) {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
last = match[1]
|
||||
continue
|
||||
}
|
||||
display := rawURL
|
||||
if utf8.RuneCountInString(display) > 72 {
|
||||
display = "🔗 " + parsed.Hostname() + " 链接"
|
||||
}
|
||||
anchor := `<a href="` + html.EscapeString(rawURL) + `">` + html.EscapeString(display) + `</a>`
|
||||
length := utf8.RuneCountInString(anchor)
|
||||
if used+length > budget-3 {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
out.WriteString(anchor)
|
||||
used += length
|
||||
if !appendEscaped(trailing) {
|
||||
truncated = true
|
||||
break
|
||||
}
|
||||
last = match[1]
|
||||
}
|
||||
if !truncated && last < len(value) && !appendEscaped(value[last:]) {
|
||||
truncated = true
|
||||
}
|
||||
if truncated {
|
||||
out.WriteString("...")
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func trimTelegramURL(value string) (string, string) {
|
||||
trimmed := strings.TrimRight(value, ".,;:!?)]},。;:!?)》】")
|
||||
return trimmed, value[len(trimmed):]
|
||||
}
|
||||
|
||||
func (a *App) sendTelegramMessage(ctx context.Context, token, chatID, text string) error {
|
||||
_, err := a.sendTelegramPayload(ctx, token, map[string]any{
|
||||
"chat_id": chatID,
|
||||
|
||||
@@ -223,6 +223,42 @@ func TestTelegramOTPDetectionAndMessageBudget(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramForwardedGateOTPAndLinks(t *testing.T) {
|
||||
body := `---------- Forwarded message ---------
|
||||
Date: 2026年8月6日周四 17:59
|
||||
Subject: 登录验证码 (https://www.gate.com)
|
||||
|
||||
Gate 检测到您的账号正试图从此 IP 获得登录验证码:
|
||||
IP: 87.83.105.229
|
||||
如为您本人登录,请输入如下验证码完成操作:
|
||||
311665
|
||||
如非本人操作,请点击此处禁用账户 <https://data.gate.com/track/click?token=abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789>`
|
||||
if otp := detectTelegramOTP("Fwd: 登录验证码 (https://www.gate.com)", body); otp != "311665" {
|
||||
t.Fatalf("forwarded Gate OTP not detected: %q", otp)
|
||||
}
|
||||
if otp := detectTelegramOTP("登录验证码", "日期 2026-08-06,验证码将在稍后发送"); otp != "" {
|
||||
t.Fatalf("year was incorrectly detected as OTP: %q", otp)
|
||||
}
|
||||
message := formatTelegramMailMessage(telegramMailPayload{
|
||||
From: "no-reply@alert.gate.com", Recipient: "admin@example.com", Subject: "登录验证码",
|
||||
ReceivedAt: time.Now().UTC().Format(time.RFC3339Nano), Body: body, BodyMode: "full", OTP: "311665",
|
||||
})
|
||||
if !strings.Contains(message.HTML, `<a href="https://www.gate.com">https://www.gate.com</a>`) {
|
||||
t.Fatalf("normal URL was not linkified: %s", message.HTML)
|
||||
}
|
||||
if !strings.Contains(message.HTML, `>🔗 data.gate.com 链接</a>`) {
|
||||
t.Fatalf("long tracking URL was not shortened: %s", message.HTML)
|
||||
}
|
||||
if strings.Contains(message.HTML, "<a href=") || utf8.RuneCountInString(message.HTML) > telegramMessageBudget {
|
||||
t.Fatalf("generated Telegram HTML is invalid or too long: %s", message.HTML)
|
||||
}
|
||||
markup := telegramCopyMarkup(message.OTP)
|
||||
buttons, ok := markup["inline_keyboard"].([][]map[string]any)
|
||||
if !ok || len(buttons) != 1 || len(buttons[0]) != 1 || buttons[0][0]["text"] != "复制验证码" {
|
||||
t.Fatalf("copy OTP button missing: %#v", markup)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramPseudoHTMLAndBodyCharset(t *testing.T) {
|
||||
pseudo := `<html><head><style>.hidden{display:none}</style></head><body><p>验证码:778899</p><div>欢迎登录</div></body></html>`
|
||||
text := telegramMessageBody(storedMessage{BodyText: pseudo})
|
||||
|
||||
@@ -3292,7 +3292,7 @@ function MailboxSwitcher({ collapsed, mailboxes, loading, selectedMailboxId, sel
|
||||
align="start"
|
||||
className={cn(
|
||||
"max-w-[calc(100vw-32px)] p-1",
|
||||
collapsed ? "w-[204px]" : "w-[21rem] min-w-[var(--radix-dropdown-menu-trigger-width)]"
|
||||
collapsed ? "w-[204px]" : "w-[var(--radix-dropdown-menu-trigger-width)] min-w-0"
|
||||
)}
|
||||
>
|
||||
{mailboxes.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user