diff options
author | 2023-09-10 11:22:32 -0700 | |
---|---|---|
committer | 2023-09-10 12:25:39 -0700 | |
commit | cb228e73ada44c4641bf92d61c274a6d18dda69c (patch) | |
tree | 7f3d5cfb07f30cd29634fadf32645df74d4a2326 /internal/integration/telegrambot | |
parent | d33db40b39fd857b5f10d922090c128e14413c1b (diff) | |
download | v2-cb228e73ada44c4641bf92d61c274a6d18dda69c.tar.gz v2-cb228e73ada44c4641bf92d61c274a6d18dda69c.tar.zst v2-cb228e73ada44c4641bf92d61c274a6d18dda69c.zip |
Improve Telegram integration
- Remove dependency on `go-telegram-bot-api`
- Add new options: optional topic ID, disable page preview, disable notifications
- Add new button to go to article
Diffstat (limited to 'internal/integration/telegrambot')
-rw-r--r-- | internal/integration/telegrambot/client.go | 174 | ||||
-rw-r--r-- | internal/integration/telegrambot/telegrambot.go | 56 |
2 files changed, 202 insertions, 28 deletions
diff --git a/internal/integration/telegrambot/client.go b/internal/integration/telegrambot/client.go new file mode 100644 index 00000000..ba7cedd2 --- /dev/null +++ b/internal/integration/telegrambot/client.go @@ -0,0 +1,174 @@ +// SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package telegrambot // import "miniflux.app/v2/internal/integration/telegrambot" + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "miniflux.app/v2/internal/version" +) + +const ( + defaultClientTimeout = 10 * time.Second + telegramAPIEndpoint = "https://api.telegram.org" + + MarkdownFormatting = "Markdown" + MarkdownV2Formatting = "MarkdownV2" + HTMLFormatting = "HTML" +) + +type Client struct { + botToken string + chatID string +} + +func NewClient(botToken, chatID string) *Client { + return &Client{ + botToken: botToken, + chatID: chatID, + } +} + +// Specs: https://core.telegram.org/bots/api#getme +func (c *Client) GetMe() (*User, error) { + endpointURL, err := url.JoinPath(telegramAPIEndpoint, "/bot"+c.botToken, "/getMe") + if err != nil { + return nil, fmt.Errorf("telegram: unable to join base URL and path: %w", err) + } + + request, err := http.NewRequest(http.MethodGet, endpointURL, nil) + if err != nil { + return nil, fmt.Errorf("telegram: unable to create request: %v", err) + } + + request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", "Miniflux/"+version.Version) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + response, err := httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("telegram: unable to send request: %v", err) + } + defer response.Body.Close() + + var userResponse UserResponse + if err := json.NewDecoder(response.Body).Decode(&userResponse); err != nil { + return nil, fmt.Errorf("telegram: unable to decode user response: %w", err) + } + + if !userResponse.Ok { + return nil, fmt.Errorf("telegram: unable to send message: %s (error code is %d)", userResponse.Description, userResponse.ErrorCode) + } + + return &userResponse.Result, nil +} + +// Specs: https://core.telegram.org/bots/api#sendmessage +func (c *Client) SendMessage(message *MessageRequest) (*Message, error) { + endpointURL, err := url.JoinPath(telegramAPIEndpoint, "/bot"+c.botToken, "/sendMessage") + if err != nil { + return nil, fmt.Errorf("telegram: unable to join base URL and path: %w", err) + } + + requestBody, err := json.Marshal(message) + if err != nil { + return nil, fmt.Errorf("telegram: unable to encode request body: %v", err) + } + + request, err := http.NewRequest(http.MethodPost, endpointURL, bytes.NewReader(requestBody)) + if err != nil { + return nil, fmt.Errorf("telegram: unable to create request: %v", err) + } + + request.Header.Set("Content-Type", "application/json") + request.Header.Set("Accept", "application/json") + request.Header.Set("User-Agent", "Miniflux/"+version.Version) + + httpClient := &http.Client{Timeout: defaultClientTimeout} + response, err := httpClient.Do(request) + if err != nil { + return nil, fmt.Errorf("telegram: unable to send request: %v", err) + } + defer response.Body.Close() + + var messageResponse MessageResponse + if err := json.NewDecoder(response.Body).Decode(&messageResponse); err != nil { + return nil, fmt.Errorf("telegram: unable to decode discovery response: %w", err) + } + + if !messageResponse.Ok { + return nil, fmt.Errorf("telegram: unable to send message: %s (error code is %d)", messageResponse.Description, messageResponse.ErrorCode) + } + + return &messageResponse.Result, nil +} + +type InlineKeyboard struct { + InlineKeyboard []InlineKeyboardRow `json:"inline_keyboard"` +} + +type InlineKeyboardRow []*InlineKeyboardButton + +type InlineKeyboardButton struct { + Text string `json:"text"` + URL string `json:"url,omitempty"` +} + +type User struct { + ID int64 `json:"id"` + IsBot bool `json:"is_bot"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Username string `json:"username"` + LanguageCode string `json:"language_code"` + IsPremium bool `json:"is_premium"` + CanJoinGroups bool `json:"can_join_groups"` + CanReadAllGroupMessages bool `json:"can_read_all_group_messages"` + SupportsInlineQueries bool `json:"supports_inline_queries"` +} + +type Chat struct { + ID int64 `json:"id"` + Type string `json:"type"` + Title string `json:"title"` +} + +type Message struct { + MessageID int64 `json:"message_id"` + From User `json:"from"` + Chat Chat `json:"chat"` + MessageThreadID int64 `json:"message_thread_id"` + Date int64 `json:"date"` +} + +type BaseResponse struct { + Ok bool `json:"ok"` + ErrorCode int `json:"error_code"` + Description string `json:"description"` +} + +type UserResponse struct { + BaseResponse + Result User `json:"result"` +} + +type MessageRequest struct { + ChatID string `json:"chat_id"` + MessageThreadID int64 `json:"message_thread_id,omitempty"` + Text string `json:"text"` + ParseMode string `json:"parse_mode,omitempty"` + DisableWebPagePreview bool `json:"disable_web_page_preview"` + DisableNotification bool `json:"disable_notification"` + ReplyMarkup *InlineKeyboard `json:"reply_markup,omitempty"` +} + +type MessageResponse struct { + BaseResponse + Result Message `json:"result"` +} diff --git a/internal/integration/telegrambot/telegrambot.go b/internal/integration/telegrambot/telegrambot.go index a89092b5..b6097b2b 100644 --- a/internal/integration/telegrambot/telegrambot.go +++ b/internal/integration/telegrambot/telegrambot.go @@ -4,47 +4,47 @@ package telegrambot // import "miniflux.app/v2/internal/integration/telegrambot" import ( - "bytes" "fmt" - "html/template" - "strconv" - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" "miniflux.app/v2/internal/model" ) -// PushEntry pushes entry to telegram chat using integration settings provided -func PushEntry(entry *model.Entry, botToken, chatID string) error { - bot, err := tgbotapi.NewBotAPI(botToken) - if err != nil { - return fmt.Errorf("telegrambot: bot creation failed: %w", err) +func PushEntry(feed *model.Feed, entry *model.Entry, botToken, chatID string, topicID *int64, disableWebPagePreview, disableNotification bool) error { + textTemplate := `<b><a href=%q>%s</a></b> - <a href=%q>%s</a>` + formattedText := fmt.Sprintf( + textTemplate, + feed.SiteURL, + feed.Title, + entry.URL, + entry.Title, + ) + + message := &MessageRequest{ + ChatID: chatID, + Text: formattedText, + ParseMode: HTMLFormatting, + DisableWebPagePreview: disableWebPagePreview, + DisableNotification: disableNotification, } - tpl, err := template.New("message").Parse("{{ .Title }}\n<a href=\"{{ .URL }}\">{{ .URL }}</a>") - if err != nil { - return fmt.Errorf("telegrambot: template parsing failed: %w", err) + if topicID != nil { + message.MessageThreadID = *topicID } - var result bytes.Buffer - if err := tpl.Execute(&result, entry); err != nil { - return fmt.Errorf("telegrambot: template execution failed: %w", err) - } + var markupRow []*InlineKeyboardButton - chatIDInt, _ := strconv.ParseInt(chatID, 10, 64) - msg := tgbotapi.NewMessage(chatIDInt, result.String()) - msg.ParseMode = tgbotapi.ModeHTML - msg.DisableWebPagePreview = false + minifluxURLButton := InlineKeyboardButton{Text: "Go to article", URL: entry.URL} + markupRow = append(markupRow, &minifluxURLButton) if entry.CommentsURL != "" { - msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup( - tgbotapi.NewInlineKeyboardRow( - tgbotapi.NewInlineKeyboardButtonURL("Comments", entry.CommentsURL), - )) + commentButton := InlineKeyboardButton{Text: "Comments", URL: entry.CommentsURL} + markupRow = append(markupRow, &commentButton) } - if _, err := bot.Send(msg); err != nil { - return fmt.Errorf("telegrambot: sending message failed: %w", err) - } + message.ReplyMarkup = &InlineKeyboard{} + message.ReplyMarkup.InlineKeyboard = append(message.ReplyMarkup.InlineKeyboard, markupRow) - return nil + client := NewClient(botToken, chatID) + _, err := client.SendMessage(message) + return err } |