mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-08-21 16:22:36 +08:00
fix(telegraph): parse page URLs from message entities (#232)
This commit is contained in:
@@ -5,6 +5,6 @@ import "regexp"
|
|||||||
var (
|
var (
|
||||||
TgMessageLinkRegexString = `https?://(?:t|telegram)\.me/(?:c/\d+|[A-Za-z0-9_]+)/\d+(?:/\d+)?(?:\?[^\s#]*[A-Za-z0-9_])?\b`
|
TgMessageLinkRegexString = `https?://(?:t|telegram)\.me/(?:c/\d+|[A-Za-z0-9_]+)/\d+(?:/\d+)?(?:\?[^\s#]*[A-Za-z0-9_])?\b`
|
||||||
TgMessageLinkRegexp = regexp.MustCompile(TgMessageLinkRegexString)
|
TgMessageLinkRegexp = regexp.MustCompile(TgMessageLinkRegexString)
|
||||||
TelegraphUrlRegexString = `https://telegra.ph/.*`
|
TelegraphUrlRegexString = `https://telegra\.ph/[^\s]+`
|
||||||
TelegraphUrlRegexp = regexp.MustCompile(TelegraphUrlRegexString)
|
TelegraphUrlRegexp = regexp.MustCompile(TelegraphUrlRegexString)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package shortcut
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -179,21 +180,19 @@ type TelegraphResult struct {
|
|||||||
// return replied message, image urls, telegraph path(unescaped), error
|
// return replied message, image urls, telegraph path(unescaped), error
|
||||||
func GetTphPicsFromMessageWithReply(ctx *ext.Context, update *ext.Update) (*types.Message, *TelegraphResult, error) {
|
func GetTphPicsFromMessageWithReply(ctx *ext.Context, update *ext.Update) (*types.Message, *TelegraphResult, error) {
|
||||||
logger := log.FromContext(ctx)
|
logger := log.FromContext(ctx)
|
||||||
tphurl := re.TelegraphUrlRegexp.FindString(tgutil.ExtractMessageEntityUrlsText(update.EffectiveMessage.Message))
|
tphurl := findTelegraphURL(update.EffectiveMessage.Message)
|
||||||
if tphurl == "" {
|
if tphurl == "" {
|
||||||
logger.Warnf("No telegraph url found but called handleTelegraph")
|
logger.Warnf("No telegraph url found but called handleTelegraph")
|
||||||
return nil, nil, dispatcher.ContinueGroups
|
return nil, nil, dispatcher.ContinueGroups
|
||||||
}
|
}
|
||||||
pagepath := strings.Split(tphurl, "/")[len(strings.Split(tphurl, "/"))-1]
|
pagepath, err := parseTelegraphPagePath(tphurl)
|
||||||
tphdir, err := url.PathUnescape(pagepath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to unescape telegraph path: %s", err)
|
logger.Errorf("Failed to parse telegraph path: %s", err)
|
||||||
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgCommonErrorParseTelegraphPathFailed, map[string]any{
|
ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgCommonErrorParseTelegraphPathFailed, map[string]any{
|
||||||
"Error": err.Error(),
|
"Error": err.Error(),
|
||||||
})), nil)
|
})), nil)
|
||||||
return nil, nil, dispatcher.EndGroups
|
return nil, nil, dispatcher.EndGroups
|
||||||
}
|
}
|
||||||
tphdir = strings.TrimSpace(tphdir)
|
|
||||||
msg, err := ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgCommonInfoFetchingTelegraphPage, nil)), nil)
|
msg, err := ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgCommonInfoFetchingTelegraphPage, nil)), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to reply to update: %s", err)
|
logger.Errorf("Failed to reply to update: %s", err)
|
||||||
@@ -244,7 +243,57 @@ func GetTphPicsFromMessageWithReply(ctx *ext.Context, update *ext.Update) (*type
|
|||||||
}
|
}
|
||||||
return msg, &TelegraphResult{
|
return msg, &TelegraphResult{
|
||||||
Pics: imgs,
|
Pics: imgs,
|
||||||
TphDir: tphdir,
|
TphDir: pagepath,
|
||||||
Page: page,
|
Page: page,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findTelegraphURL(msg *tg.Message) string {
|
||||||
|
if msg == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var firstMatch string
|
||||||
|
findValid := func(text string) string {
|
||||||
|
for _, tphurl := range re.TelegraphUrlRegexp.FindAllString(text, -1) {
|
||||||
|
if firstMatch == "" {
|
||||||
|
firstMatch = tphurl
|
||||||
|
}
|
||||||
|
if _, err := parseTelegraphPagePath(tphurl); err == nil {
|
||||||
|
return tphurl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, entityURL := range tgutil.ExtractMessageEntityUrls(msg) {
|
||||||
|
if tphurl := findValid(entityURL); tphurl != "" {
|
||||||
|
return tphurl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tphurl := findValid(msg.GetMessage()); tphurl != "" {
|
||||||
|
return tphurl
|
||||||
|
}
|
||||||
|
return firstMatch
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTelegraphPagePath(pageURL string) (string, error) {
|
||||||
|
u, err := url.Parse(pageURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("invalid telegraph URL: %w", err)
|
||||||
|
}
|
||||||
|
if u.Scheme != "https" || !strings.EqualFold(u.Hostname(), "telegra.ph") {
|
||||||
|
return "", fmt.Errorf("invalid telegraph URL host: %s", u.Host)
|
||||||
|
}
|
||||||
|
pagepath := strings.Trim(u.EscapedPath(), "/")
|
||||||
|
if pagepath == "" || strings.Contains(pagepath, "/") {
|
||||||
|
return "", fmt.Errorf("invalid telegraph URL path: %s", u.Path)
|
||||||
|
}
|
||||||
|
pagepath, err = url.PathUnescape(pagepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to unescape telegraph path: %w", err)
|
||||||
|
}
|
||||||
|
pagepath = strings.TrimSpace(pagepath)
|
||||||
|
if pagepath == "" || strings.Contains(pagepath, "/") {
|
||||||
|
return "", fmt.Errorf("invalid telegraph URL path: %s", u.Path)
|
||||||
|
}
|
||||||
|
return pagepath, nil
|
||||||
|
}
|
||||||
|
|||||||
163
client/bot/handlers/utils/shortcut/message_telegraph_test.go
Normal file
163
client/bot/handlers/utils/shortcut/message_telegraph_test.go
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
package shortcut
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gotd/td/tg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFindTelegraphURL(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
msg *tg.Message
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single URL entity",
|
||||||
|
msg: &tg.Message{
|
||||||
|
Message: "https://telegra.ph/Example-01-02",
|
||||||
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityURL{Offset: 0, Length: 32},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://telegra.ph/Example-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Telegraph URL before another URL",
|
||||||
|
msg: &tg.Message{
|
||||||
|
Message: "https://telegra.ph/Example-01-02 https://example.com/",
|
||||||
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityURL{Offset: 0, Length: 32},
|
||||||
|
&tg.MessageEntityURL{Offset: 33, Length: 20},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://telegra.ph/Example-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hidden Telegraph URL",
|
||||||
|
msg: &tg.Message{
|
||||||
|
Message: "article",
|
||||||
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityTextURL{
|
||||||
|
Offset: 0,
|
||||||
|
Length: 7,
|
||||||
|
URL: "https://telegra.ph/Hidden-01-02",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://telegra.ph/Hidden-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL entity after non-BMP character",
|
||||||
|
msg: &tg.Message{
|
||||||
|
Message: "😀 https://telegra.ph/Emoji-01-02",
|
||||||
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityURL{Offset: 3, Length: 30},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://telegra.ph/Emoji-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid Telegraph URL after invalid candidate",
|
||||||
|
msg: &tg.Message{
|
||||||
|
Message: "https://telegra.ph/nested/Bad https://telegra.ph/Valid-01-02",
|
||||||
|
Entities: []tg.MessageEntityClass{
|
||||||
|
&tg.MessageEntityURL{Offset: 0, Length: 29},
|
||||||
|
&tg.MessageEntityURL{Offset: 30, Length: 30},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://telegra.ph/Valid-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plain message fallback",
|
||||||
|
msg: &tg.Message{Message: "read https://telegra.ph/Plain-01-02 now"},
|
||||||
|
want: "https://telegra.ph/Plain-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil message",
|
||||||
|
msg: nil,
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := findTelegraphURL(tt.msg); got != tt.want {
|
||||||
|
t.Fatalf("findTelegraphURL() = %q, want %q", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseTelegraphPagePath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
pageURL string
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "plain path",
|
||||||
|
pageURL: "https://telegra.ph/Example-01-02",
|
||||||
|
want: "Example-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "escaped path with query and fragment",
|
||||||
|
pageURL: "https://telegra.ph/%E6%B5%8B%E8%AF%95-01-02?source=telegram#top",
|
||||||
|
want: "测试-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trailing slash",
|
||||||
|
pageURL: "https://telegra.ph/Example-01-02/",
|
||||||
|
want: "Example-01-02",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "root URL",
|
||||||
|
pageURL: "https://telegra.ph/",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong host",
|
||||||
|
pageURL: "https://example.com/Example-01-02",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrong scheme",
|
||||||
|
pageURL: "http://telegra.ph/Example-01-02",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid percent escape",
|
||||||
|
pageURL: "https://telegra.ph/Invalid-%zz",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nested path",
|
||||||
|
pageURL: "https://telegra.ph/nested/Example-01-02",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "encoded slash",
|
||||||
|
pageURL: "https://telegra.ph/nested%2FExample-01-02",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := parseTelegraphPagePath(tt.pageURL)
|
||||||
|
if tt.wantErr {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("parseTelegraphPagePath(%q) returned no error", tt.pageURL)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseTelegraphPagePath(%q) failed: %v", tt.pageURL, err)
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Fatalf("parseTelegraphPagePath(%q) = %q, want %q", tt.pageURL, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user