aboutsummaryrefslogtreecommitdiff
path: root/internal/integration
diff options
context:
space:
mode:
Diffstat (limited to 'internal/integration')
-rw-r--r--internal/integration/integration.go23
-rw-r--r--internal/integration/telegrambot/client.go174
-rw-r--r--internal/integration/telegrambot/telegrambot.go56
3 files changed, 217 insertions, 36 deletions
diff --git a/internal/integration/integration.go b/internal/integration/integration.go
index fbb3f3dc..eb9f842e 100644
--- a/internal/integration/integration.go
+++ b/internal/integration/integration.go
@@ -172,7 +172,7 @@ func SendEntry(entry *model.Entry, integration *model.Integration) {
// PushEntries pushes a list of entries to activated third-party providers during feed refreshes.
func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *model.Integration) {
if userIntegrations.MatrixBotEnabled {
- logger.Debug("[Integration] Sending %d entries for User #%d to Matrix", len(entries), userIntegrations.UserID)
+ logger.Debug("[Integration] Sending %d entries for user #%d to Matrix", len(entries), userIntegrations.UserID)
err := matrixbot.PushEntries(feed, entries, userIntegrations.MatrixBotURL, userIntegrations.MatrixBotUser, userIntegrations.MatrixBotPassword, userIntegrations.MatrixBotChatID)
if err != nil {
@@ -193,16 +193,23 @@ func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *mode
if userIntegrations.TelegramBotEnabled || userIntegrations.AppriseEnabled {
for _, entry := range entries {
if userIntegrations.TelegramBotEnabled {
- logger.Debug("[Integration] Sending Entry %q for User #%d to Telegram", entry.URL, userIntegrations.UserID)
-
- err := telegrambot.PushEntry(entry, userIntegrations.TelegramBotToken, userIntegrations.TelegramBotChatID)
- if err != nil {
- logger.Error("[Integration] push entry to telegram bot failed: %v", err)
+ logger.Debug("[Integration] Sending entry %q for user #%d to Telegram", entry.URL, userIntegrations.UserID)
+
+ if err := telegrambot.PushEntry(
+ feed,
+ entry,
+ userIntegrations.TelegramBotToken,
+ userIntegrations.TelegramBotChatID,
+ userIntegrations.TelegramBotTopicID,
+ userIntegrations.TelegramBotDisableWebPagePreview,
+ userIntegrations.TelegramBotDisableNotification,
+ ); err != nil {
+ logger.Error("[Integration] %v", err)
}
}
if userIntegrations.AppriseEnabled {
- logger.Debug("[Integration] Sending Entry %q for User #%d to apprise", entry.URL, userIntegrations.UserID)
+ logger.Debug("[Integration] Sending entry %q for user #%d to Apprise", entry.URL, userIntegrations.UserID)
appriseServiceURLs := userIntegrations.AppriseURL
if feed.AppriseServiceURLs != "" {
@@ -215,7 +222,7 @@ func PushEntries(feed *model.Feed, entries model.Entries, userIntegrations *mode
)
if err := client.SendNotification(entry); err != nil {
- logger.Error("[Integration] push entry to apprise failed: %v", err)
+ logger.Error("[Integration] %v", err)
}
}
}
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
}