aboutsummaryrefslogtreecommitdiff
path: root/internal/integration/telegrambot
diff options
context:
space:
mode:
authorGravatar Frédéric Guillot <f@miniflux.net> 2023-09-10 11:22:32 -0700
committerGravatar Frédéric Guillot <f@miniflux.net> 2023-09-10 12:25:39 -0700
commitcb228e73ada44c4641bf92d61c274a6d18dda69c (patch)
tree7f3d5cfb07f30cd29634fadf32645df74d4a2326 /internal/integration/telegrambot
parentd33db40b39fd857b5f10d922090c128e14413c1b (diff)
downloadv2-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.go174
-rw-r--r--internal/integration/telegrambot/telegrambot.go56
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
}
name='id' value='7fdd494236a527057b4ba37b99094d6a733aef4f'/>
path: root/packages/integrations/lit/test (unfollow)
AgeCommit message (Expand)AuthorFilesLines
2022-09-30[@astrojs/image] fixes a bug in dev when <Image /> is used with no transforma...Gravatar Tony Sullivan 5-6/+30
2022-09-30Update seven-shrimps-hope.md (#4934)Gravatar Tony Sullivan 1-1/+1
2022-09-30[ci] update lockfile (#4927)Gravatar Fred K. Bot 1-280/+280
2022-09-30Move module declarations for Markdown and MDX so they're available everywhere...Gravatar Erika 4-35/+46
2022-09-29[ci] formatGravatar tony-sull 4-17/+29
2022-09-29[@astrojs/image] adding caching support for SSG builds (#4909)Gravatar Tony Sullivan 8-10/+240
2022-09-29[ci] release (#4903)astro@1.4.0@astrojs/vue@1.1.0@astrojs/vercel@2.1.0@astrojs/telemetry@1.0.1@astrojs/tailwind@2.0.2@astrojs/svelte@1.0.1@astrojs/rss@1.0.2@astrojs/preact@1.1.1@astrojs/node@1.1.0@astrojs/netlify@1.1.0@astrojs/mdx@0.11.3@astrojs/markdown-remark@1.1.3@astrojs/image@0.8.1@astrojs/deno@1.1.0@astrojs/cloudflare@2.1.0Gravatar Fred K. Bot 65-235/+512
2022-09-29[ci] update lockfile (#4899)Gravatar Fred K. Bot 1-735/+718
2022-09-29[ci] formatGravatar matthewp 1-1/+3
2022-09-29fix trailing slash mismatch in dev vs build in docs example (#4912)Gravatar Rishi Raj Jain 1-1/+1
2022-09-29[ci] formatGravatar bluwy 1-1/+1
2022-09-29Support Vue JSX (#4897)Gravatar Bjorn Lu 12-5/+329
2022-09-28[ci] formatGravatar matthewp 1-8/+4
2022-09-28Fix CSS ordering between imported and Astro styles (#4907)Gravatar Matthew Phillips 12-7/+218
2022-09-28[ci] formatGravatar matthewp 23-137/+127
2022-09-28Astro.cookies implementation (#4876)Gravatar Matthew Phillips 32-29/+943
2022-09-28Fix: let Squoosh default image quality internally (#4906)Gravatar Tony Sullivan 5-11/+20
2022-09-28Update README.md (#4898)Gravatar stijlmassi 1-2/+3
2022-09-28Fix test (#4904)Gravatar Bjorn Lu 2-1/+7
2022-09-28[ci] formatGravatar FredKSchott 2-4/+4
2022-09-28redesign basics template (#4879)Gravatar Fred K. Schott 3-88/+34
2022-09-28[ci] formatGravatar bluwy 1-2/+2
2022-09-28Remove shamefully-hoist (#4842)Gravatar Bjorn Lu 104-527/+768
2022-09-28[ci] formatGravatar matthewp 4-14/+16
2022-09-28Hoist hydration script out of slot templates (#4891)Gravatar Matthew Phillips 13-43/+165
2022-09-28Ensure head content rendered once with lazy layouts (#4892)Gravatar Matthew Phillips 9-3/+59
2022-09-27fixed typing (#4893)Gravatar tweenietomatoes 1-1/+1
2022-09-27[ci] release (#4846)create-astro@1.1.0astro@1.3.1@astrojs/webapi@1.1.0@astrojs/vercel@2.0.1@astrojs/mdx@0.11.2@astrojs/image@0.8.0Gravatar Fred K. Bot 60-185/+169
2022-09-27fix: post API routes in SSG should warn or error during dev mode (#4878)Gravatar Rishi Raj Jain 3-2/+17
2022-09-27docs: Fix links to Tailwind examples (#4883)Gravatar Deanmv 1-1/+1
2022-09-27Set SSR target webworker for Vercel edge (#4884)Gravatar Bjorn Lu 2-0/+6
2022-09-27[ci] update lockfile (#4885)Gravatar Fred K. Bot 1-86/+79
2022-09-26[ci] formatGravatar bholmesdev 3-23/+19
2022-09-26Fix: correctly transform `import.meta.env.*` in MDX (#4858)Gravatar Ben Holmes 12-233/+454
2022-09-26Change negative lookbehind to lookahead (#4866)Gravatar Rishi Raj Jain 1-1/+1
2022-09-26add double check on astro file return type to display more human readable err...Gravatar Steven Yung 6-2/+61
2022-09-26[ci] update lockfile (#4862)Gravatar Fred K. Bot 1-81/+81
2022-09-26fix: Script with innerHTML not working on Safari (#4861)Gravatar Rishi Raj Jain 3-3/+10
2022-09-26Prevent /undefined catch-all routes in dev (#4873)Gravatar Bjorn Lu 6-9/+66
2022-09-26fix: 🐛 BUG: class:list directive adding class attribute when undefined (#4...Gravatar Rishi Raj Jain 2-2/+9
2022-09-26docs: Standardize common integration READMEs (#4874)Gravatar Jake Strawn 7-6/+66
2022-09-26docs: Update references to support channel in Discord. (#4872)Gravatar Jake Strawn 12-12/+12
2022-09-26[ci] formatGravatar bluwy 1-1/+1
2022-09-26fix: "chunks" directory appears in build output, if custom modules are import...Gravatar Rishi Raj Jain 2-6/+34
2022-09-23[ci] formatGravatar matthewp 1-1/+1
2022-09-23Define toStringTag another way (#4855)Gravatar Matthew Phillips 2-4/+12
2022-09-23update SSR example to match recent change on Astro API Context (#4854)Gravatar Steven Yung 2-4/+6
2022-09-23[ci] update lockfile (#4852)Gravatar Fred K. Bot 1-373/+402