add telegram bot
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.vakhrushev.me/av/remembos/internal/memos"
|
||||
"git.vakhrushev.me/av/remembos/internal/search"
|
||||
)
|
||||
|
||||
const (
|
||||
maxMessageLen = 4096
|
||||
maxCaptionLen = 1024
|
||||
)
|
||||
|
||||
// formatMemory returns (mainText, captionText) formatted as HTML for Telegram.
|
||||
// mainText is for sendMessage (up to 4096 chars), captionText for photo captions (up to 1024 chars).
|
||||
func formatMemory(mem *search.Memory, publicURL string) (mainText, captionText string) {
|
||||
var b strings.Builder
|
||||
|
||||
// Header: date and "ago" text
|
||||
b.WriteString(fmt.Sprintf("<b>%s</b>", formatDate(mem.Date)))
|
||||
if ago := agoText(mem.Date); ago != "" {
|
||||
b.WriteString(fmt.Sprintf(" <i>(%s)</i>", ago))
|
||||
}
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Content
|
||||
b.WriteString(escapeHTML(mem.Memo.Content))
|
||||
|
||||
// Tags
|
||||
if len(mem.Memo.Tags) > 0 {
|
||||
b.WriteString("\n\n")
|
||||
tags := make([]string, len(mem.Memo.Tags))
|
||||
for i, t := range mem.Memo.Tags {
|
||||
tags[i] = "#" + t
|
||||
}
|
||||
b.WriteString(strings.Join(tags, " "))
|
||||
}
|
||||
|
||||
// Link to original
|
||||
memoURL := fmt.Sprintf("%s/%s", publicURL, mem.Memo.Name)
|
||||
b.WriteString(fmt.Sprintf("\n\n<a href=\"%s\">Оригинал</a>", memoURL))
|
||||
|
||||
full := b.String()
|
||||
mainText = truncateHTML(full, maxMessageLen)
|
||||
captionText = truncateHTML(full, maxCaptionLen)
|
||||
return mainText, captionText
|
||||
}
|
||||
|
||||
// imageAttachments returns image attachments from the memo.
|
||||
func imageAttachments(memo *memos.Memo) []memos.Attachment {
|
||||
var images []memos.Attachment
|
||||
for _, att := range memo.Attachments {
|
||||
if att.IsImage() {
|
||||
images = append(images, att)
|
||||
}
|
||||
}
|
||||
return images
|
||||
}
|
||||
|
||||
// truncateHTML truncates text to maxLen, cutting at a word/line boundary and adding "...".
|
||||
func truncateHTML(text string, maxLen int) string {
|
||||
if len(text) <= maxLen {
|
||||
return text
|
||||
}
|
||||
|
||||
// Reserve space for "..."
|
||||
cut := maxLen - 3
|
||||
|
||||
// Find last newline or space before cut point
|
||||
idx := strings.LastIndexAny(text[:cut], "\n ")
|
||||
if idx <= 0 {
|
||||
idx = cut
|
||||
}
|
||||
|
||||
return text[:idx] + "..."
|
||||
}
|
||||
|
||||
// escapeHTML escapes special HTML characters for Telegram HTML parse mode.
|
||||
func escapeHTML(s string) string {
|
||||
s = strings.ReplaceAll(s, "&", "&")
|
||||
s = strings.ReplaceAll(s, "<", "<")
|
||||
s = strings.ReplaceAll(s, ">", ">")
|
||||
return s
|
||||
}
|
||||
|
||||
var months = [...]string{
|
||||
1: "января", 2: "февраля", 3: "марта",
|
||||
4: "апреля", 5: "мая", 6: "июня",
|
||||
7: "июля", 8: "августа", 9: "сентября",
|
||||
10: "октября", 11: "ноября", 12: "декабря",
|
||||
}
|
||||
|
||||
func formatDate(t time.Time) string {
|
||||
return fmt.Sprintf("%d %s %d", t.Day(), months[t.Month()], t.Year())
|
||||
}
|
||||
|
||||
func agoText(t time.Time) string {
|
||||
now := time.Now()
|
||||
years := now.Year() - t.Year()
|
||||
monthsDiff := int(now.Month()) - int(t.Month())
|
||||
if monthsDiff < 0 {
|
||||
years--
|
||||
monthsDiff += 12
|
||||
}
|
||||
|
||||
if years > 0 {
|
||||
return fmt.Sprintf("%s назад", pluralYears(years))
|
||||
}
|
||||
if monthsDiff > 0 {
|
||||
return fmt.Sprintf("%s назад", pluralMonths(monthsDiff))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func pluralYears(n int) string {
|
||||
mod10 := n % 10
|
||||
mod100 := n % 100
|
||||
switch {
|
||||
case mod100 >= 11 && mod100 <= 14:
|
||||
return fmt.Sprintf("%d лет", n)
|
||||
case mod10 == 1:
|
||||
return fmt.Sprintf("%d год", n)
|
||||
case mod10 >= 2 && mod10 <= 4:
|
||||
return fmt.Sprintf("%d года", n)
|
||||
default:
|
||||
return fmt.Sprintf("%d лет", n)
|
||||
}
|
||||
}
|
||||
|
||||
func pluralMonths(n int) string {
|
||||
mod10 := n % 10
|
||||
mod100 := n % 100
|
||||
switch {
|
||||
case mod100 >= 11 && mod100 <= 14:
|
||||
return fmt.Sprintf("%d месяцев", n)
|
||||
case mod10 == 1:
|
||||
return fmt.Sprintf("%d месяц", n)
|
||||
case mod10 >= 2 && mod10 <= 4:
|
||||
return fmt.Sprintf("%d месяца", n)
|
||||
default:
|
||||
return fmt.Sprintf("%d месяцев", n)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user