mirror of
https://github.com/miniflux/v2.git
synced 2025-06-27 16:36:00 +00:00
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
This commit is contained in:
parent
d33db40b39
commit
cb228e73ad
29 changed files with 601 additions and 316 deletions
174
internal/integration/telegrambot/client.go
Normal file
174
internal/integration/telegrambot/client.go
Normal file
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue