From ad1c3e55bae2bd97f801eb7ef04a1bf471d55617 Mon Sep 17 00:00:00 2001
From: ShukeBta <272197458+ShukeBta@users.noreply.github.com>
Date: Sat, 27 Jun 2026 01:27:34 +0800
Subject: [PATCH] split telegram notification formatting
---
internal/service/notify_telegram.go | 287 ---------------------
internal/service/notify_telegram_body.go | 175 +++++++++++++
internal/service/notify_telegram_data.go | 170 ++++++++++++
internal/service/notify_telegram_fields.go | 164 ------------
internal/service/notify_telegram_media.go | 123 +++++++++
5 files changed, 468 insertions(+), 451 deletions(-)
create mode 100644 internal/service/notify_telegram_body.go
create mode 100644 internal/service/notify_telegram_data.go
create mode 100644 internal/service/notify_telegram_media.go
diff --git a/internal/service/notify_telegram.go b/internal/service/notify_telegram.go
index 764a842..933da6a 100644
--- a/internal/service/notify_telegram.go
+++ b/internal/service/notify_telegram.go
@@ -4,7 +4,6 @@ package service
import (
"context"
"fmt"
- "regexp"
"strings"
"time"
"unicode/utf8"
@@ -140,289 +139,3 @@ func formatTelegramNotification(event NotifyEvent) string {
return strings.TrimSpace(sb.String())
}
-
-const (
- telegramMediaTemplateHeader = "🐈⬛🐈⬛ MediaStationGo 更新啦 🐈⬛🐈⬛"
- telegramMediaTemplateSeparator = "--------------------------------"
-)
-
-var telegramSeasonEpisodePattern = regexp.MustCompile(`(?i)S\d{1,2}E\d{1,3}(?:[\-.~_ ]?E?\d{1,3})?`)
-
-func formatTelegramMediaNotification(event NotifyEvent) string {
- tag := telegramMediaTag(event.Data)
- if tag == "" {
- return ""
- }
-
- title := telegramFirstValue(event.Data, "chinese_title", "title", "name", "media_title")
- if title == "" {
- title = telegramMessageFieldValue(event.Message, "任务", "订阅", "媒体", "资源")
- }
- originalTitle := telegramFirstValue(event.Data, "original_title", "original_name")
- originalLanguage := telegramLanguageName(telegramFirstValue(event.Data, "original_language", "language", "languages"))
- year := telegramFirstValue(event.Data, "year", "release_year")
- category := telegramMediaCategory(event.Data)
- seasonEpisode := telegramSeasonEpisodeValue(event)
- size := telegramSizeValue(event.Data)
- version := telegramVersionValue(event, seasonEpisode)
- rating := telegramFirstValue(event.Data, "rating", "score")
- genres := telegramGenresValue(telegramFirstValue(event.Data, "genres", "genre", "type"))
- overview := telegramFirstValue(event.Data, "overview", "summary", "description")
- links := telegramExternalLinks(event.Data)
-
- topFields := []string{}
- if title != "" {
- topFields = append(topFields, "📺 中文片名:"+escapeHTML(title))
- }
- if originalTitle != "" && !strings.EqualFold(originalTitle, title) {
- topFields = append(topFields, "🧿 原始片名:"+escapeHTML(originalTitle))
- }
- if originalLanguage != "" {
- topFields = append(topFields, "🌐 原始语言:"+escapeHTML(originalLanguage))
- }
- if year != "" && year != "0" {
- topFields = append(topFields, "📅 发行年份:"+escapeHTML(year))
- }
-
- mediaFields := []string{}
- if category != "" {
- mediaFields = append(mediaFields, "🐈⬛ 类别:"+escapeHTML(category))
- }
- if seasonEpisode != "" {
- mediaFields = append(mediaFields, "🫧 季集:"+escapeHTML(seasonEpisode))
- }
- if size != "" {
- mediaFields = append(mediaFields, "🔎 大小:"+escapeHTML(size))
- }
- if version != "" {
- mediaFields = append(mediaFields, "📁 版本:"+escapeHTML(version))
- }
-
- infoFields := []string{}
- if rating != "" && rating != "0" {
- infoFields = append(infoFields, "⭐️ 评分:"+escapeHTML(rating))
- }
- if genres != "" {
- infoFields = append(infoFields, "💎 类型:"+escapeHTML(genres))
- }
- if overview != "" {
- infoFields = append(infoFields, "🪬 简介:\n"+escapeHTML(overview))
- }
-
- if len(topFields) == 0 && len(mediaFields) == 0 && len(infoFields) == 0 && links == "" {
- return ""
- }
-
- lines := []string{telegramMediaTemplateHeader, telegramMediaTemplateSeparator, tag}
- lines = append(lines, topFields...)
- if len(mediaFields) > 0 {
- if len(topFields) > 0 {
- lines = append(lines, "")
- }
- lines = append(lines, mediaFields...)
- }
- if len(infoFields) > 0 {
- if len(topFields) > 0 || len(mediaFields) > 0 {
- lines = append(lines, "")
- }
- lines = append(lines, infoFields...)
- }
- if links != "" {
- lines = append(lines, "", telegramMediaTemplateSeparator, links)
- }
- return strings.TrimSpace(strings.Join(lines, "\n"))
-}
-
-func telegramShouldShowEventHeading(event NotifyEvent) bool {
- if telegramMediaTag(event.Data) != "" {
- return false
- }
- switch strings.TrimSpace(event.Type) {
- case EventSubscriptionHit, EventDownloadComplete:
- return false
- default:
- return true
- }
-}
-
-func telegramEventTag(event NotifyEvent) string {
- if tag := telegramMediaTag(event.Data); tag != "" {
- return tag
- }
- switch strings.TrimSpace(event.Type) {
- case EventSubscriptionHit:
- return "#订阅"
- case EventDownloadComplete:
- return "#下载完成"
- case EventScrapeFailed:
- return "#刮削失败"
- case EventSystemAlert:
- return "#系统提醒"
- case EventLibraryIngest:
- return "#入库"
- default:
- title := strings.TrimSpace(strings.TrimPrefix(event.Title, "MediaStationGo "))
- if title == "" {
- return "#MediaStationGo"
- }
- return "#" + escapeHTML(strings.ReplaceAll(title, " ", ""))
- }
-}
-
-func telegramMediaTag(data map[string]interface{}) string {
- for _, key := range []string{"media_category", "category", "media_type"} {
- value := strings.TrimSpace(telegramDataString(data, key))
- if value == "" {
- continue
- }
- lower := strings.ToLower(value)
- switch {
- case strings.Contains(value, "电影") || lower == "movie":
- return "#电影"
- case strings.Contains(value, "剧") || lower == "tv" || lower == "series" || lower == "show":
- return "#剧集"
- case strings.Contains(value, "动漫") || strings.Contains(value, "动画") || lower == "anime":
- return "#动漫"
- case strings.Contains(value, "综艺") || lower == "variety":
- return "#综艺"
- case strings.Contains(value, "纪录") || lower == "documentary":
- return "#纪录片"
- default:
- return "#" + escapeHTML(strings.ReplaceAll(value, " ", ""))
- }
- }
- return ""
-}
-
-func telegramEventHeading(event NotifyEvent) string {
- title := strings.TrimSpace(event.Title)
- title = strings.TrimSpace(strings.TrimPrefix(title, "MediaStationGo "))
- if title == "" {
- title = "MediaStationGo 通知"
- }
- icon := "🔔"
- switch strings.TrimSpace(event.Type) {
- case EventSubscriptionHit:
- icon = "🎯"
- case EventDownloadComplete:
- icon = "✅"
- case EventScrapeFailed:
- icon = "⚠️"
- case EventSystemAlert:
- icon = "🚨"
- case EventLibraryIngest:
- icon = "📚"
- }
- return fmt.Sprintf("%s %s", icon, escapeHTML(title))
-}
-
-func formatTelegramBody(message string) string {
- lines := strings.Split(message, "\n")
- out := make([]string, 0, len(lines))
- for _, line := range lines {
- line = strings.TrimSpace(line)
- if line == "" {
- out = append(out, "")
- continue
- }
- if strings.HasPrefix(line, "- ") {
- out = append(out, "- "+escapeHTML(strings.TrimSpace(strings.TrimPrefix(line, "- "))))
- continue
- }
- if key, ok := trimTelegramEmptyField(line); ok {
- out = append(out, fmt.Sprintf("%s %s:", telegramFieldIcon(telegramFieldLabel(key)), escapeHTML(telegramFieldLabel(key))))
- continue
- }
- if key, value, ok := splitTelegramField(line); ok {
- out = append(out, formatTelegramField(key, value))
- continue
- }
- out = append(out, escapeHTML(line))
- }
- return strings.TrimSpace(strings.Join(out, "\n"))
-}
-
-func trimTelegramEmptyField(line string) (string, bool) {
- line = strings.TrimSpace(line)
- for _, suffix := range []string{":", ":"} {
- if strings.HasSuffix(line, suffix) {
- key := strings.TrimSpace(strings.TrimSuffix(line, suffix))
- if key != "" && len([]rune(key)) <= 16 {
- return key, true
- }
- }
- }
- return "", false
-}
-
-func splitTelegramField(line string) (string, string, bool) {
- idx := strings.Index(line, ":")
- sepLen := len(":")
- if idx < 0 {
- idx = strings.Index(line, ":")
- sepLen = len(":")
- }
- if idx <= 0 {
- return "", "", false
- }
- key := strings.TrimSpace(line[:idx])
- value := strings.TrimSpace(line[idx+sepLen:])
- if key == "" || value == "" || len([]rune(key)) > 16 {
- return "", "", false
- }
- return key, value, true
-}
-
-func formatTelegramField(key, value string) string {
- key = telegramFieldLabel(key)
- escapedValue := escapeHTML(strings.TrimSpace(value))
- if telegramCodeField(key) {
- escapedValue = "" + escapedValue + ""
- }
- return fmt.Sprintf("%s %s:%s", telegramFieldIcon(key), escapeHTML(key), escapedValue)
-}
-
-func telegramCodeField(key string) bool {
- key = strings.ToLower(strings.TrimSpace(key))
- return strings.Contains(key, "hash") ||
- strings.Contains(key, "路径") ||
- strings.Contains(key, "path") ||
- strings.Contains(key, "id")
-}
-
-func telegramFieldIcon(key string) string {
- switch strings.ToLower(strings.TrimSpace(key)) {
- case "中文片名", "标题", "任务", "媒体", "资源":
- return "📺"
- case "原始片名":
- return "🧿"
- case "原始语言", "语言":
- return "🌐"
- case "发行年份", "年份":
- return "📅"
- case "类别", "分类", "媒体类型":
- return "🐈⬛"
- case "季集", "集数":
- return "🫧"
- case "大小", "质量", "规格":
- return "🔎"
- case "版本", "保存路径":
- return "📁"
- case "评分":
- return "⭐️"
- case "类型":
- return "💎"
- case "简介":
- return "🪬"
- case "订阅":
- return "🎯"
- case "新增资源":
- return "✨"
- case "Hash":
- return "🧿"
- case "错误":
- return "⚠️"
- default:
- return "•"
- }
-}
diff --git a/internal/service/notify_telegram_body.go b/internal/service/notify_telegram_body.go
new file mode 100644
index 0000000..3945438
--- /dev/null
+++ b/internal/service/notify_telegram_body.go
@@ -0,0 +1,175 @@
+package service
+
+import (
+ "fmt"
+ "strings"
+)
+
+func telegramShouldShowEventHeading(event NotifyEvent) bool {
+ if telegramMediaTag(event.Data) != "" {
+ return false
+ }
+ switch strings.TrimSpace(event.Type) {
+ case EventSubscriptionHit, EventDownloadComplete:
+ return false
+ default:
+ return true
+ }
+}
+
+func telegramEventTag(event NotifyEvent) string {
+ if tag := telegramMediaTag(event.Data); tag != "" {
+ return tag
+ }
+ switch strings.TrimSpace(event.Type) {
+ case EventSubscriptionHit:
+ return "#订阅"
+ case EventDownloadComplete:
+ return "#下载完成"
+ case EventScrapeFailed:
+ return "#刮削失败"
+ case EventSystemAlert:
+ return "#系统提醒"
+ case EventLibraryIngest:
+ return "#入库"
+ default:
+ title := strings.TrimSpace(strings.TrimPrefix(event.Title, "MediaStationGo "))
+ if title == "" {
+ return "#MediaStationGo"
+ }
+ return "#" + escapeHTML(strings.ReplaceAll(title, " ", ""))
+ }
+}
+
+func telegramEventHeading(event NotifyEvent) string {
+ title := strings.TrimSpace(event.Title)
+ title = strings.TrimSpace(strings.TrimPrefix(title, "MediaStationGo "))
+ if title == "" {
+ title = "MediaStationGo 通知"
+ }
+ icon := "🔔"
+ switch strings.TrimSpace(event.Type) {
+ case EventSubscriptionHit:
+ icon = "🎯"
+ case EventDownloadComplete:
+ icon = "✅"
+ case EventScrapeFailed:
+ icon = "⚠️"
+ case EventSystemAlert:
+ icon = "🚨"
+ case EventLibraryIngest:
+ icon = "📚"
+ }
+ return fmt.Sprintf("%s %s", icon, escapeHTML(title))
+}
+
+func formatTelegramBody(message string) string {
+ lines := strings.Split(message, "\n")
+ out := make([]string, 0, len(lines))
+ for _, line := range lines {
+ line = strings.TrimSpace(line)
+ if line == "" {
+ out = append(out, "")
+ continue
+ }
+ if strings.HasPrefix(line, "- ") {
+ out = append(out, "- "+escapeHTML(strings.TrimSpace(strings.TrimPrefix(line, "- "))))
+ continue
+ }
+ if key, ok := trimTelegramEmptyField(line); ok {
+ out = append(out, fmt.Sprintf("%s %s:", telegramFieldIcon(telegramFieldLabel(key)), escapeHTML(telegramFieldLabel(key))))
+ continue
+ }
+ if key, value, ok := splitTelegramField(line); ok {
+ out = append(out, formatTelegramField(key, value))
+ continue
+ }
+ out = append(out, escapeHTML(line))
+ }
+ return strings.TrimSpace(strings.Join(out, "\n"))
+}
+
+func trimTelegramEmptyField(line string) (string, bool) {
+ line = strings.TrimSpace(line)
+ for _, suffix := range []string{":", ":"} {
+ if strings.HasSuffix(line, suffix) {
+ key := strings.TrimSpace(strings.TrimSuffix(line, suffix))
+ if key != "" && len([]rune(key)) <= 16 {
+ return key, true
+ }
+ }
+ }
+ return "", false
+}
+
+func splitTelegramField(line string) (string, string, bool) {
+ idx := strings.Index(line, ":")
+ sepLen := len(":")
+ if idx < 0 {
+ idx = strings.Index(line, ":")
+ sepLen = len(":")
+ }
+ if idx <= 0 {
+ return "", "", false
+ }
+ key := strings.TrimSpace(line[:idx])
+ value := strings.TrimSpace(line[idx+sepLen:])
+ if key == "" || value == "" || len([]rune(key)) > 16 {
+ return "", "", false
+ }
+ return key, value, true
+}
+
+func formatTelegramField(key, value string) string {
+ key = telegramFieldLabel(key)
+ escapedValue := escapeHTML(strings.TrimSpace(value))
+ if telegramCodeField(key) {
+ escapedValue = "" + escapedValue + ""
+ }
+ return fmt.Sprintf("%s %s:%s", telegramFieldIcon(key), escapeHTML(key), escapedValue)
+}
+
+func telegramCodeField(key string) bool {
+ key = strings.ToLower(strings.TrimSpace(key))
+ return strings.Contains(key, "hash") ||
+ strings.Contains(key, "路径") ||
+ strings.Contains(key, "path") ||
+ strings.Contains(key, "id")
+}
+
+func telegramFieldIcon(key string) string {
+ switch strings.ToLower(strings.TrimSpace(key)) {
+ case "中文片名", "标题", "任务", "媒体", "资源":
+ return "📺"
+ case "原始片名":
+ return "🧿"
+ case "原始语言", "语言":
+ return "🌐"
+ case "发行年份", "年份":
+ return "📅"
+ case "类别", "分类", "媒体类型":
+ return "🐈⬛"
+ case "季集", "集数":
+ return "🫧"
+ case "大小", "质量", "规格":
+ return "🔎"
+ case "版本", "保存路径":
+ return "📁"
+ case "评分":
+ return "⭐️"
+ case "类型":
+ return "💎"
+ case "简介":
+ return "🪬"
+ case "订阅":
+ return "🎯"
+ case "新增资源":
+ return "✨"
+ case "Hash":
+ return "🧿"
+ case "错误":
+ return "⚠️"
+ default:
+ return "•"
+ }
+}
diff --git a/internal/service/notify_telegram_data.go b/internal/service/notify_telegram_data.go
new file mode 100644
index 0000000..adf432c
--- /dev/null
+++ b/internal/service/notify_telegram_data.go
@@ -0,0 +1,170 @@
+package service
+
+import (
+ "fmt"
+ "net/url"
+ "sort"
+ "strings"
+)
+
+type telegramDataField struct {
+ key string
+ value string
+}
+
+func telegramDisplayData(data map[string]interface{}) []telegramDataField {
+ if len(data) == 0 {
+ return nil
+ }
+ keys := make([]string, 0, len(data))
+ for key := range data {
+ if telegramHiddenDataKey(key) {
+ continue
+ }
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ fields := make([]telegramDataField, 0, len(keys))
+ for _, key := range keys {
+ value := strings.TrimSpace(fmt.Sprint(data[key]))
+ if value == "" || value == "" {
+ continue
+ }
+ fields = append(fields, telegramDataField{key: key, value: value})
+ }
+ return fields
+}
+
+func telegramHiddenDataKey(key string) bool {
+ switch strings.ToLower(strings.TrimSpace(key)) {
+ case "photo_url", "poster_url", "poster", "image_url", "backdrop_url",
+ "tmdb_url", "imdb_url", "douban_url", "detail_url", "external_url",
+ "resource_title", "torrent_title", "release_title":
+ return true
+ default:
+ return false
+ }
+}
+
+func telegramFieldLabel(key string) string {
+ switch strings.ToLower(strings.TrimSpace(key)) {
+ case "title", "name":
+ return "标题"
+ case "original_title":
+ return "原始片名"
+ case "original_language":
+ return "原始语言"
+ case "year", "release_year":
+ return "发行年份"
+ case "save_path":
+ return "保存路径"
+ case "hash":
+ return "Hash"
+ case "media_type":
+ return "媒体类型"
+ case "media_category":
+ return "类别"
+ case "season_episode":
+ return "季集"
+ case "size", "bitrate":
+ return "大小"
+ case "version", "release_group":
+ return "版本"
+ case "rating":
+ return "评分"
+ case "genres":
+ return "类型"
+ case "overview":
+ return "简介"
+ case "subscription":
+ return "订阅"
+ case "queued":
+ return "新增资源"
+ default:
+ return strings.TrimSpace(key)
+ }
+}
+
+func telegramExternalLinks(data map[string]interface{}) string {
+ if len(data) == 0 {
+ return ""
+ }
+ links := []string{}
+ for _, item := range []struct {
+ key string
+ name string
+ }{
+ {key: "tmdb_url", name: "TMDB"},
+ {key: "imdb_url", name: "IMDB"},
+ {key: "douban_url", name: "豆瓣"},
+ } {
+ value := telegramDataString(data, item.key)
+ if isTelegramRemotePhotoURL(value) {
+ links = append(links, fmt.Sprintf(`%s`, escapeHTML(value), escapeHTML(item.name)))
+ }
+ }
+ if len(links) == 0 {
+ return ""
+ }
+ return "🔗 外链:" + strings.Join(links, " / ")
+}
+
+func telegramEventPhotoURL(event NotifyEvent) string {
+ for _, key := range []string{"photo_url", "poster_url", "poster", "image_url", "backdrop_url"} {
+ value := telegramDataString(event.Data, key)
+ if isTelegramRemotePhotoURL(value) {
+ return value
+ }
+ }
+ return ""
+}
+
+func telegramDataString(data map[string]interface{}, key string) string {
+ if len(data) == 0 {
+ return ""
+ }
+ for k, value := range data {
+ if strings.EqualFold(strings.TrimSpace(k), key) {
+ return telegramValueString(value)
+ }
+ }
+ return ""
+}
+
+func telegramValueString(value interface{}) string {
+ switch v := value.(type) {
+ case nil:
+ return ""
+ case string:
+ return strings.TrimSpace(v)
+ case []string:
+ return strings.TrimSpace(strings.Join(v, ","))
+ case []interface{}:
+ out := make([]string, 0, len(v))
+ for _, item := range v {
+ if s := telegramValueString(item); s != "" {
+ out = append(out, s)
+ }
+ }
+ return strings.Join(out, ",")
+ case float32:
+ return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
+ case float64:
+ return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
+ default:
+ text := strings.TrimSpace(fmt.Sprint(value))
+ if text == "" {
+ return ""
+ }
+ return text
+ }
+}
+
+func isTelegramRemotePhotoURL(raw string) bool {
+ raw = strings.TrimSpace(raw)
+ if raw == "" {
+ return false
+ }
+ u, err := url.Parse(raw)
+ return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
+}
diff --git a/internal/service/notify_telegram_fields.go b/internal/service/notify_telegram_fields.go
index 43a760d..c7da82c 100644
--- a/internal/service/notify_telegram_fields.go
+++ b/internal/service/notify_telegram_fields.go
@@ -2,8 +2,6 @@ package service
import (
"fmt"
- "net/url"
- "sort"
"strconv"
"strings"
)
@@ -226,165 +224,3 @@ func telegramGenresValue(raw string) string {
}
return strings.Join(out, "、")
}
-
-type telegramDataField struct {
- key string
- value string
-}
-
-func telegramDisplayData(data map[string]interface{}) []telegramDataField {
- if len(data) == 0 {
- return nil
- }
- keys := make([]string, 0, len(data))
- for key := range data {
- if telegramHiddenDataKey(key) {
- continue
- }
- keys = append(keys, key)
- }
- sort.Strings(keys)
- fields := make([]telegramDataField, 0, len(keys))
- for _, key := range keys {
- value := strings.TrimSpace(fmt.Sprint(data[key]))
- if value == "" || value == "" {
- continue
- }
- fields = append(fields, telegramDataField{key: key, value: value})
- }
- return fields
-}
-
-func telegramHiddenDataKey(key string) bool {
- switch strings.ToLower(strings.TrimSpace(key)) {
- case "photo_url", "poster_url", "poster", "image_url", "backdrop_url",
- "tmdb_url", "imdb_url", "douban_url", "detail_url", "external_url",
- "resource_title", "torrent_title", "release_title":
- return true
- default:
- return false
- }
-}
-
-func telegramFieldLabel(key string) string {
- switch strings.ToLower(strings.TrimSpace(key)) {
- case "title", "name":
- return "标题"
- case "original_title":
- return "原始片名"
- case "original_language":
- return "原始语言"
- case "year", "release_year":
- return "发行年份"
- case "save_path":
- return "保存路径"
- case "hash":
- return "Hash"
- case "media_type":
- return "媒体类型"
- case "media_category":
- return "类别"
- case "season_episode":
- return "季集"
- case "size", "bitrate":
- return "大小"
- case "version", "release_group":
- return "版本"
- case "rating":
- return "评分"
- case "genres":
- return "类型"
- case "overview":
- return "简介"
- case "subscription":
- return "订阅"
- case "queued":
- return "新增资源"
- default:
- return strings.TrimSpace(key)
- }
-}
-
-func telegramExternalLinks(data map[string]interface{}) string {
- if len(data) == 0 {
- return ""
- }
- links := []string{}
- for _, item := range []struct {
- key string
- name string
- }{
- {key: "tmdb_url", name: "TMDB"},
- {key: "imdb_url", name: "IMDB"},
- {key: "douban_url", name: "豆瓣"},
- } {
- value := telegramDataString(data, item.key)
- if isTelegramRemotePhotoURL(value) {
- links = append(links, fmt.Sprintf(`%s`, escapeHTML(value), escapeHTML(item.name)))
- }
- }
- if len(links) == 0 {
- return ""
- }
- return "🔗 外链:" + strings.Join(links, " / ")
-}
-
-func telegramEventPhotoURL(event NotifyEvent) string {
- for _, key := range []string{"photo_url", "poster_url", "poster", "image_url", "backdrop_url"} {
- value := telegramDataString(event.Data, key)
- if isTelegramRemotePhotoURL(value) {
- return value
- }
- }
- return ""
-}
-
-func telegramDataString(data map[string]interface{}, key string) string {
- if len(data) == 0 {
- return ""
- }
- for k, value := range data {
- if strings.EqualFold(strings.TrimSpace(k), key) {
- return telegramValueString(value)
- }
- }
- return ""
-}
-
-func telegramValueString(value interface{}) string {
- switch v := value.(type) {
- case nil:
- return ""
- case string:
- return strings.TrimSpace(v)
- case []string:
- return strings.TrimSpace(strings.Join(v, ","))
- case []interface{}:
- out := make([]string, 0, len(v))
- for _, item := range v {
- if s := telegramValueString(item); s != "" {
- out = append(out, s)
- }
- }
- return strings.Join(out, ",")
- case float32:
- return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
- case float64:
- return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
- default:
- text := strings.TrimSpace(fmt.Sprint(value))
- if text == "" {
- return ""
- }
- return text
- }
-}
-
-func isTelegramRemotePhotoURL(raw string) bool {
- raw = strings.TrimSpace(raw)
- if raw == "" {
- return false
- }
- u, err := url.Parse(raw)
- return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
-}
diff --git a/internal/service/notify_telegram_media.go b/internal/service/notify_telegram_media.go
new file mode 100644
index 0000000..a0be82e
--- /dev/null
+++ b/internal/service/notify_telegram_media.go
@@ -0,0 +1,123 @@
+package service
+
+import (
+ "regexp"
+ "strings"
+)
+
+const (
+ telegramMediaTemplateHeader = "🐈⬛🐈⬛ MediaStationGo 更新啦 🐈⬛🐈⬛"
+ telegramMediaTemplateSeparator = "--------------------------------"
+)
+
+var telegramSeasonEpisodePattern = regexp.MustCompile(`(?i)S\d{1,2}E\d{1,3}(?:[\-.~_ ]?E?\d{1,3})?`)
+
+func formatTelegramMediaNotification(event NotifyEvent) string {
+ tag := telegramMediaTag(event.Data)
+ if tag == "" {
+ return ""
+ }
+
+ title := telegramFirstValue(event.Data, "chinese_title", "title", "name", "media_title")
+ if title == "" {
+ title = telegramMessageFieldValue(event.Message, "任务", "订阅", "媒体", "资源")
+ }
+ originalTitle := telegramFirstValue(event.Data, "original_title", "original_name")
+ originalLanguage := telegramLanguageName(telegramFirstValue(event.Data, "original_language", "language", "languages"))
+ year := telegramFirstValue(event.Data, "year", "release_year")
+ category := telegramMediaCategory(event.Data)
+ seasonEpisode := telegramSeasonEpisodeValue(event)
+ size := telegramSizeValue(event.Data)
+ version := telegramVersionValue(event, seasonEpisode)
+ rating := telegramFirstValue(event.Data, "rating", "score")
+ genres := telegramGenresValue(telegramFirstValue(event.Data, "genres", "genre", "type"))
+ overview := telegramFirstValue(event.Data, "overview", "summary", "description")
+ links := telegramExternalLinks(event.Data)
+
+ topFields := []string{}
+ if title != "" {
+ topFields = append(topFields, "📺 中文片名:"+escapeHTML(title))
+ }
+ if originalTitle != "" && !strings.EqualFold(originalTitle, title) {
+ topFields = append(topFields, "🧿 原始片名:"+escapeHTML(originalTitle))
+ }
+ if originalLanguage != "" {
+ topFields = append(topFields, "🌐 原始语言:"+escapeHTML(originalLanguage))
+ }
+ if year != "" && year != "0" {
+ topFields = append(topFields, "📅 发行年份:"+escapeHTML(year))
+ }
+
+ mediaFields := []string{}
+ if category != "" {
+ mediaFields = append(mediaFields, "🐈⬛ 类别:"+escapeHTML(category))
+ }
+ if seasonEpisode != "" {
+ mediaFields = append(mediaFields, "🫧 季集:"+escapeHTML(seasonEpisode))
+ }
+ if size != "" {
+ mediaFields = append(mediaFields, "🔎 大小:"+escapeHTML(size))
+ }
+ if version != "" {
+ mediaFields = append(mediaFields, "📁 版本:"+escapeHTML(version))
+ }
+
+ infoFields := []string{}
+ if rating != "" && rating != "0" {
+ infoFields = append(infoFields, "⭐️ 评分:"+escapeHTML(rating))
+ }
+ if genres != "" {
+ infoFields = append(infoFields, "💎 类型:"+escapeHTML(genres))
+ }
+ if overview != "" {
+ infoFields = append(infoFields, "🪬 简介:\n"+escapeHTML(overview))
+ }
+
+ if len(topFields) == 0 && len(mediaFields) == 0 && len(infoFields) == 0 && links == "" {
+ return ""
+ }
+
+ lines := []string{telegramMediaTemplateHeader, telegramMediaTemplateSeparator, tag}
+ lines = append(lines, topFields...)
+ if len(mediaFields) > 0 {
+ if len(topFields) > 0 {
+ lines = append(lines, "")
+ }
+ lines = append(lines, mediaFields...)
+ }
+ if len(infoFields) > 0 {
+ if len(topFields) > 0 || len(mediaFields) > 0 {
+ lines = append(lines, "")
+ }
+ lines = append(lines, infoFields...)
+ }
+ if links != "" {
+ lines = append(lines, "", telegramMediaTemplateSeparator, links)
+ }
+ return strings.TrimSpace(strings.Join(lines, "\n"))
+}
+
+func telegramMediaTag(data map[string]interface{}) string {
+ for _, key := range []string{"media_category", "category", "media_type"} {
+ value := strings.TrimSpace(telegramDataString(data, key))
+ if value == "" {
+ continue
+ }
+ lower := strings.ToLower(value)
+ switch {
+ case strings.Contains(value, "电影") || lower == "movie":
+ return "#电影"
+ case strings.Contains(value, "剧") || lower == "tv" || lower == "series" || lower == "show":
+ return "#剧集"
+ case strings.Contains(value, "动漫") || strings.Contains(value, "动画") || lower == "anime":
+ return "#动漫"
+ case strings.Contains(value, "综艺") || lower == "variety":
+ return "#综艺"
+ case strings.Contains(value, "纪录") || lower == "documentary":
+ return "#纪录片"
+ default:
+ return "#" + escapeHTML(strings.ReplaceAll(value, " ", ""))
+ }
+ }
+ return ""
+}