client.go 5.1 KB

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