client.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package telegrambot // import "miniflux.app/v2/internal/integration/telegrambot"
  4. import (
  5. "bytes"
  6. "encoding/json"
  7. "fmt"
  8. "net/http"
  9. "net/url"
  10. "time"
  11. "miniflux.app/v2/internal/http/client"
  12. "miniflux.app/v2/internal/version"
  13. )
  14. const (
  15. defaultClientTimeout = 10 * time.Second
  16. telegramAPIEndpoint = "https://api.telegram.org"
  17. MarkdownFormatting = "Markdown"
  18. MarkdownV2Formatting = "MarkdownV2"
  19. HTMLFormatting = "HTML"
  20. )
  21. type Client struct {
  22. botToken string
  23. chatID string
  24. }
  25. func NewClient(botToken, chatID string) *Client {
  26. return &Client{
  27. botToken: botToken,
  28. chatID: chatID,
  29. }
  30. }
  31. // Specs: https://core.telegram.org/bots/api#getme
  32. func (c *Client) GetMe() (*User, error) {
  33. endpointURL, err := url.JoinPath(telegramAPIEndpoint, "/bot"+c.botToken, "/getMe")
  34. if err != nil {
  35. return nil, fmt.Errorf("telegram: unable to join base URL and path: %w", err)
  36. }
  37. request, err := http.NewRequest(http.MethodGet, endpointURL, nil)
  38. if err != nil {
  39. return nil, fmt.Errorf("telegram: unable to create request: %v", err)
  40. }
  41. request.Header.Set("Accept", "application/json")
  42. request.Header.Set("User-Agent", "Miniflux/"+version.Version)
  43. httpClient := client.NewClientWithOptions(client.Options{Timeout: defaultClientTimeout})
  44. response, err := httpClient.Do(request)
  45. if err != nil {
  46. return nil, fmt.Errorf("telegram: unable to send request: %v", err)
  47. }
  48. defer response.Body.Close()
  49. var userResponse UserResponse
  50. if err := json.NewDecoder(response.Body).Decode(&userResponse); err != nil {
  51. return nil, fmt.Errorf("telegram: unable to decode user response: %w", err)
  52. }
  53. if !userResponse.Ok {
  54. return nil, fmt.Errorf("telegram: unable to send message: %s (error code is %d)", userResponse.Description, userResponse.ErrorCode)
  55. }
  56. return &userResponse.Result, nil
  57. }
  58. // Specs: https://core.telegram.org/bots/api#sendmessage
  59. func (c *Client) SendMessage(message *MessageRequest) (*Message, error) {
  60. endpointURL, err := url.JoinPath(telegramAPIEndpoint, "/bot"+c.botToken, "/sendMessage")
  61. if err != nil {
  62. return nil, fmt.Errorf("telegram: unable to join base URL and path: %w", err)
  63. }
  64. requestBody, err := json.Marshal(message)
  65. if err != nil {
  66. return nil, fmt.Errorf("telegram: unable to encode request body: %v", err)
  67. }
  68. request, err := http.NewRequest(http.MethodPost, endpointURL, bytes.NewReader(requestBody))
  69. if err != nil {
  70. return nil, fmt.Errorf("telegram: unable to create request: %v", err)
  71. }
  72. request.Header.Set("Content-Type", "application/json")
  73. request.Header.Set("Accept", "application/json")
  74. request.Header.Set("User-Agent", "Miniflux/"+version.Version)
  75. httpClient := client.NewClientWithOptions(client.Options{Timeout: defaultClientTimeout})
  76. response, err := httpClient.Do(request)
  77. if err != nil {
  78. return nil, fmt.Errorf("telegram: unable to send request: %v", err)
  79. }
  80. defer response.Body.Close()
  81. var messageResponse MessageResponse
  82. if err := json.NewDecoder(response.Body).Decode(&messageResponse); err != nil {
  83. return nil, fmt.Errorf("telegram: unable to decode discovery response: %w", err)
  84. }
  85. if !messageResponse.Ok {
  86. return nil, fmt.Errorf("telegram: unable to send message: %s (error code is %d)", messageResponse.Description, messageResponse.ErrorCode)
  87. }
  88. return &messageResponse.Result, nil
  89. }
  90. type InlineKeyboard struct {
  91. InlineKeyboard []InlineKeyboardRow `json:"inline_keyboard"`
  92. }
  93. type InlineKeyboardRow []*InlineKeyboardButton
  94. type InlineKeyboardButton struct {
  95. Text string `json:"text"`
  96. URL string `json:"url,omitempty"`
  97. }
  98. type User struct {
  99. ID int64 `json:"id"`
  100. IsBot bool `json:"is_bot"`
  101. FirstName string `json:"first_name"`
  102. LastName string `json:"last_name"`
  103. Username string `json:"username"`
  104. LanguageCode string `json:"language_code"`
  105. IsPremium bool `json:"is_premium"`
  106. CanJoinGroups bool `json:"can_join_groups"`
  107. CanReadAllGroupMessages bool `json:"can_read_all_group_messages"`
  108. SupportsInlineQueries bool `json:"supports_inline_queries"`
  109. }
  110. type Chat struct {
  111. ID int64 `json:"id"`
  112. Type string `json:"type"`
  113. Title string `json:"title"`
  114. }
  115. type Message struct {
  116. MessageID int64 `json:"message_id"`
  117. From User `json:"from"`
  118. Chat Chat `json:"chat"`
  119. MessageThreadID int64 `json:"message_thread_id"`
  120. Date int64 `json:"date"`
  121. }
  122. type BaseResponse struct {
  123. Ok bool `json:"ok"`
  124. ErrorCode int `json:"error_code"`
  125. Description string `json:"description"`
  126. }
  127. type UserResponse struct {
  128. BaseResponse
  129. Result User `json:"result"`
  130. }
  131. type MessageRequest struct {
  132. ChatID string `json:"chat_id"`
  133. MessageThreadID int64 `json:"message_thread_id,omitempty"`
  134. Text string `json:"text"`
  135. ParseMode string `json:"parse_mode,omitempty"`
  136. DisableWebPagePreview bool `json:"disable_web_page_preview"`
  137. DisableNotification bool `json:"disable_notification"`
  138. ReplyMarkup *InlineKeyboard `json:"reply_markup,omitempty"`
  139. }
  140. type MessageResponse struct {
  141. BaseResponse
  142. Result Message `json:"result"`
  143. }