mirror of
https://github.com/jlelse/GoBlog
synced 2024-07-27 03:45:55 +00:00
Rework Telegram implementation
This commit is contained in:
parent
40d4a7952e
commit
d60e613bbd
@ -294,10 +294,9 @@ type configNtfy struct {
|
||||
}
|
||||
|
||||
type configTelegram struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
ChatID string `mapstructure:"chatId"`
|
||||
BotToken string `mapstructure:"botToken"`
|
||||
InstantViewHash string `mapstructure:"instantViewHash"`
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
ChatID string `mapstructure:"chatId"`
|
||||
BotToken string `mapstructure:"botToken"`
|
||||
}
|
||||
|
||||
type configMatrix struct {
|
||||
|
@ -255,7 +255,6 @@ blogs:
|
||||
enabled: true # Enable
|
||||
chatId: "@telegram" # Chat ID, usually channel username
|
||||
botToken: BOT-TOKEN # Telegram Bot Token
|
||||
instantViewHash: INSTANT-VIEW-HASH # Use custom TG IV template
|
||||
# Comments
|
||||
comments:
|
||||
enabled: true # Enable comments
|
||||
|
3
go.mod
3
go.mod
@ -26,7 +26,6 @@ require (
|
||||
github.com/go-ap/jsonld v0.0.0-20221030091449-f2a191312c73
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/go-fed/httpsig v1.1.0
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
@ -52,7 +51,7 @@ require (
|
||||
github.com/spf13/cast v1.6.0
|
||||
github.com/spf13/viper v1.18.2
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/tdewolff/minify/v2 v2.20.20
|
||||
github.com/tdewolff/minify/v2 v2.20.21
|
||||
github.com/tiptophelmet/cspolicy v0.1.1
|
||||
github.com/tkrajina/gpxgo v1.4.0
|
||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
|
||||
|
6
go.sum
6
go.sum
@ -85,8 +85,6 @@ github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
|
||||
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
@ -253,8 +251,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tdewolff/minify/v2 v2.20.20 h1:vhULb+VsW2twkplgsawAoUY957efb+EdiZ7zu5fUhhk=
|
||||
github.com/tdewolff/minify/v2 v2.20.20/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
|
||||
github.com/tdewolff/minify/v2 v2.20.21 h1:8MCHcxXAVO8B7X+v07mwMWBIEtQo65e1JzBqDgZOQpU=
|
||||
github.com/tdewolff/minify/v2 v2.20.21/go.mod h1:GYaLXFpIIwsX99apQHXfGdISUdlA98wmaoWxjT9C37k=
|
||||
github.com/tdewolff/parse/v2 v2.7.14 h1:100KJ+QAO3PpMb3uUjzEU/NpmCdbBYz6KPmCIAfWpR8=
|
||||
github.com/tdewolff/parse/v2 v2.7.14/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
|
206
telegram.go
206
telegram.go
@ -1,11 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
"github.com/carlmjohnson/requests"
|
||||
"go.goblog.app/app/pkgs/builderpool"
|
||||
)
|
||||
|
||||
@ -23,21 +24,26 @@ func (tg *configTelegram) enabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
telegramChatParam = "telegramchat"
|
||||
telegramMsgParam = "telegrammsg"
|
||||
)
|
||||
|
||||
func (a *goBlog) tgPost(p *post, silent bool) {
|
||||
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() && p.isPublicPublishedSectionPost() {
|
||||
tgChat := p.firstParameter("telegramchat")
|
||||
tgMsg := p.firstParameter("telegrammsg")
|
||||
tgChat := p.firstParameter(telegramChatParam)
|
||||
tgMsg := p.firstParameter(telegramMsgParam)
|
||||
if tgChat != "" && tgMsg != "" {
|
||||
// Already posted
|
||||
return
|
||||
}
|
||||
// Generate HTML
|
||||
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
|
||||
html := tg.generateHTML(p.RenderedTitle, a.shortPostURL(p))
|
||||
if html == "" {
|
||||
return
|
||||
}
|
||||
// Send message
|
||||
chatId, msgId, err := a.sendTelegram(tg, html, tgbotapi.ModeHTML, silent)
|
||||
chatId, msgId, err := a.sendTelegram(tg, html, "HTML", silent)
|
||||
if err != nil {
|
||||
a.error("Failed to send post to Telegram", "err", err)
|
||||
return
|
||||
@ -47,12 +53,10 @@ func (a *goBlog) tgPost(p *post, silent bool) {
|
||||
return
|
||||
}
|
||||
// Save chat and message id to post
|
||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{strconv.FormatInt(chatId, 10)})
|
||||
if err != nil {
|
||||
if err := a.db.replacePostParam(p.Path, telegramChatParam, []string{strconv.FormatInt(chatId, 10)}); err != nil {
|
||||
a.error("Failed to save Telegram chat id", "err", err)
|
||||
}
|
||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{strconv.Itoa(msgId)})
|
||||
if err != nil {
|
||||
if err := a.db.replacePostParam(p.Path, telegramMsgParam, []string{strconv.Itoa(msgId)}); err != nil {
|
||||
a.error("Failed to save Telegram message id", "err", err)
|
||||
}
|
||||
}
|
||||
@ -60,32 +64,19 @@ func (a *goBlog) tgPost(p *post, silent bool) {
|
||||
|
||||
func (a *goBlog) tgUpdate(p *post) {
|
||||
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() {
|
||||
tgChat := p.firstParameter("telegramchat")
|
||||
tgMsg := p.firstParameter("telegrammsg")
|
||||
tgChat := p.firstParameter(telegramChatParam)
|
||||
tgMsg := p.firstParameter(telegramMsgParam)
|
||||
if tgChat == "" || tgMsg == "" {
|
||||
// Not send to Telegram
|
||||
return
|
||||
}
|
||||
// Parse tgChat to int64
|
||||
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
||||
if err != nil {
|
||||
a.error("Failed to parse Telegram chat ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Parse tgMsg to int
|
||||
messageId, err := strconv.Atoi(tgMsg)
|
||||
if err != nil {
|
||||
a.error("Failed to parse Telegram message ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Generate HTML
|
||||
html := tg.generateHTML(p.RenderedTitle, a.fullPostURL(p), a.shortPostURL(p))
|
||||
html := tg.generateHTML(p.RenderedTitle, a.shortPostURL(p))
|
||||
if html == "" {
|
||||
return
|
||||
}
|
||||
// Send update
|
||||
err = a.updateTelegram(tg, chatId, messageId, html, "HTML")
|
||||
if err != nil {
|
||||
if err := a.updateTelegram(tg, tgChat, tgMsg, html, "HTML"); err != nil {
|
||||
a.error("Failed to send update to Telegram", "err", err)
|
||||
}
|
||||
}
|
||||
@ -93,143 +84,120 @@ func (a *goBlog) tgUpdate(p *post) {
|
||||
|
||||
func (a *goBlog) tgDelete(p *post) {
|
||||
if tg := a.getBlogFromPost(p).Telegram; tg.enabled() {
|
||||
tgChat := p.firstParameter("telegramchat")
|
||||
tgMsg := p.firstParameter("telegrammsg")
|
||||
tgChat := p.firstParameter(telegramChatParam)
|
||||
tgMsg := p.firstParameter(telegramMsgParam)
|
||||
if tgChat == "" || tgMsg == "" {
|
||||
// Not send to Telegram
|
||||
return
|
||||
}
|
||||
// Parse tgChat to int64
|
||||
chatId, err := strconv.ParseInt(tgChat, 10, 64)
|
||||
if err != nil {
|
||||
a.error("Failed to parse Telegram chat ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Parse tgMsg to int
|
||||
messageId, err := strconv.Atoi(tgMsg)
|
||||
if err != nil {
|
||||
a.error("Failed to parse Telegram message ID", "err", err)
|
||||
return
|
||||
}
|
||||
// Delete message
|
||||
err = a.deleteTelegram(tg, chatId, messageId)
|
||||
if err != nil {
|
||||
if err := a.deleteTelegram(tg, tgChat, tgMsg); err != nil {
|
||||
a.error("Failed to delete Telegram message", "err", err)
|
||||
}
|
||||
// Delete chat and message id from post
|
||||
err = a.db.replacePostParam(p.Path, "telegramchat", []string{})
|
||||
if err != nil {
|
||||
if err := a.db.replacePostParam(p.Path, telegramChatParam, []string{}); err != nil {
|
||||
a.error("Failed to remove Telegram chat id", "err", err)
|
||||
}
|
||||
err = a.db.replacePostParam(p.Path, "telegrammsg", []string{})
|
||||
if err != nil {
|
||||
if err := a.db.replacePostParam(p.Path, telegramMsgParam, []string{}); err != nil {
|
||||
a.error("Failed to remove Telegram message id", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tg *configTelegram) generateHTML(title, fullURL, shortURL string) (html string) {
|
||||
func (tg *configTelegram) generateHTML(title, shortURL string) string {
|
||||
if !tg.enabled() {
|
||||
return ""
|
||||
}
|
||||
message := builderpool.Get()
|
||||
defer builderpool.Put(message)
|
||||
tgReplacer := strings.NewReplacer("<", "<", ">", ">", "&", "&")
|
||||
if title != "" {
|
||||
message.WriteString(tgbotapi.EscapeText(tgbotapi.ModeHTML, title))
|
||||
tgReplacer.WriteString(message, title)
|
||||
message.WriteString("\n\n")
|
||||
}
|
||||
if tg.InstantViewHash != "" {
|
||||
message.WriteString("<a href=\"https://t.me/iv?rhash=" + tg.InstantViewHash + "&url=" + url.QueryEscape(fullURL) + "\">")
|
||||
message.WriteString(tgbotapi.EscapeText(tgbotapi.ModeHTML, shortURL))
|
||||
message.WriteString("</a>")
|
||||
} else {
|
||||
message.WriteString("<a href=\"" + shortURL + "\">")
|
||||
message.WriteString(tgbotapi.EscapeText(tgbotapi.ModeHTML, shortURL))
|
||||
message.WriteString("</a>")
|
||||
}
|
||||
html = message.String()
|
||||
return
|
||||
message.WriteString("<a href=\"" + shortURL + "\">")
|
||||
tgReplacer.WriteString(message, shortURL)
|
||||
message.WriteString("</a>")
|
||||
return message.String()
|
||||
}
|
||||
|
||||
type telegramMessageResult struct {
|
||||
OK bool `json:"ok"`
|
||||
Description string `json:"description"`
|
||||
Result struct {
|
||||
Chat struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"chat"`
|
||||
MessageID int `json:"message_id"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
func (a *goBlog) sendTelegram(tg *configTelegram, message, mode string, silent bool) (int64, int, error) {
|
||||
if !tg.enabled() {
|
||||
return 0, 0, nil
|
||||
}
|
||||
bot, err := tgbotapi.NewBotAPIWithClient(tg.BotToken, tgbotapi.APIEndpoint, a.httpClient)
|
||||
if err != nil {
|
||||
|
||||
telegramURL := "https://api.telegram.org/bot" + tg.BotToken + "/sendMessage"
|
||||
result := &telegramMessageResult{}
|
||||
if err := requests.URL(telegramURL).Client(a.httpClient).
|
||||
Param("chat_id", tg.ChatID).
|
||||
Param("text", message).
|
||||
Param("parse_mode", mode).
|
||||
Param("disable_notification", strconv.FormatBool(silent)).
|
||||
ToJSON(result).
|
||||
Fetch(context.Background()); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
msg := tgbotapi.MessageConfig{
|
||||
BaseChat: tgbotapi.BaseChat{
|
||||
ChannelUsername: tg.ChatID,
|
||||
DisableNotification: silent,
|
||||
},
|
||||
Text: message,
|
||||
ParseMode: mode,
|
||||
|
||||
if !result.OK {
|
||||
return 0, 0, fmt.Errorf("error from Telegram API: %s", result.Description)
|
||||
}
|
||||
res, err := bot.Send(msg)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return res.Chat.ID, res.MessageID, nil
|
||||
|
||||
return result.Result.Chat.ID, result.Result.MessageID, nil
|
||||
}
|
||||
|
||||
func (a *goBlog) updateTelegram(tg *configTelegram, chatId int64, messageId int, message, mode string) error {
|
||||
func (a *goBlog) updateTelegram(tg *configTelegram, chatID, messageID, message, mode string) error {
|
||||
if !tg.enabled() {
|
||||
return nil
|
||||
}
|
||||
bot, err := tgbotapi.NewBotAPIWithClient(tg.BotToken, tgbotapi.APIEndpoint, a.httpClient)
|
||||
if err != nil {
|
||||
|
||||
telegramURL := "https://api.telegram.org/bot" + tg.BotToken + "/editMessageText"
|
||||
result := &telegramMessageResult{}
|
||||
if err := requests.URL(telegramURL).Client(a.httpClient).
|
||||
Param("chat_id", chatID).
|
||||
Param("message_id", messageID).
|
||||
Param("text", message).
|
||||
Param("parse_mode", mode).
|
||||
ToJSON(result).
|
||||
Fetch(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if chat is still the configured one
|
||||
chat, err := bot.GetChat(tgbotapi.ChatInfoConfig{
|
||||
ChatConfig: tgbotapi.ChatConfig{
|
||||
SuperGroupUsername: tg.ChatID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
if !result.OK {
|
||||
return fmt.Errorf("error from Telegram API: %s", result.Description)
|
||||
}
|
||||
if chat.ID != chatId {
|
||||
return errors.New("chat id mismatch")
|
||||
}
|
||||
// Send update
|
||||
msg := tgbotapi.EditMessageTextConfig{
|
||||
BaseEdit: tgbotapi.BaseEdit{
|
||||
ChatID: chatId,
|
||||
MessageID: messageId,
|
||||
},
|
||||
Text: message,
|
||||
ParseMode: mode,
|
||||
}
|
||||
_, err = bot.Send(msg)
|
||||
return err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *goBlog) deleteTelegram(tg *configTelegram, chatId int64, messageId int) error {
|
||||
func (a *goBlog) deleteTelegram(tg *configTelegram, chatID, messageID string) error {
|
||||
if !tg.enabled() {
|
||||
return nil
|
||||
}
|
||||
bot, err := tgbotapi.NewBotAPIWithClient(tg.BotToken, tgbotapi.APIEndpoint, a.httpClient)
|
||||
if err != nil {
|
||||
|
||||
telegramURL := "https://api.telegram.org/bot" + tg.BotToken + "/deleteMessage"
|
||||
result := &telegramMessageResult{}
|
||||
if err := requests.URL(telegramURL).Client(a.httpClient).
|
||||
Param("chat_id", chatID).
|
||||
Param("message_id", messageID).
|
||||
ToJSON(result).
|
||||
Fetch(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
chat, err := bot.GetChat(tgbotapi.ChatInfoConfig{
|
||||
ChatConfig: tgbotapi.ChatConfig{
|
||||
SuperGroupUsername: tg.ChatID,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
if !result.OK {
|
||||
return fmt.Errorf("error from Telegram API: %s", result.Description)
|
||||
}
|
||||
if chat.ID != chatId {
|
||||
return errors.New("chat id mismatch")
|
||||
}
|
||||
msg := tgbotapi.DeleteMessageConfig{
|
||||
ChatID: chatId,
|
||||
MessageID: messageId,
|
||||
}
|
||||
_, err = bot.Send(msg)
|
||||
return err
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -28,18 +28,8 @@ func Test_configTelegram_generateHTML(t *testing.T) {
|
||||
BotToken: "abc",
|
||||
}
|
||||
|
||||
// Without Instant View
|
||||
|
||||
expected := "Title\n\n<a href=\"https://example.com/s/1\">https://example.com/s/1</a>"
|
||||
if got := tg.generateHTML("Title", "https://example.com/test", "https://example.com/s/1"); got != expected {
|
||||
t.Errorf("Wrong result, got: %v", got)
|
||||
}
|
||||
|
||||
// With Instant View
|
||||
|
||||
tg.InstantViewHash = "abc"
|
||||
expected = "Title\n\n<a href=\"https://t.me/iv?rhash=abc&url=https%3A%2F%2Fexample.com%2Ftest\">https://example.com/s/1</a>"
|
||||
if got := tg.generateHTML("Title", "https://example.com/test", "https://example.com/s/1"); got != expected {
|
||||
if got := tg.generateHTML("Title", "https://example.com/s/1"); got != expected {
|
||||
t.Errorf("Wrong result, got: %v", got)
|
||||
}
|
||||
}
|
||||
@ -48,11 +38,6 @@ func Test_configTelegram_send(t *testing.T) {
|
||||
fakeClient := newFakeHttpClient()
|
||||
|
||||
fakeClient.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() == "https://api.telegram.org/botbottoken/getMe" {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write([]byte(`{"ok":true,"result":{"id":123456789,"is_bot":true,"first_name":"Test","username":"testbot"}}`))
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write([]byte(`{"ok":true,"result":{"message_id":123,"from":{"id":123456789,"is_bot":true,"first_name":"Test","username":"testbot"},"chat":{"id":789,"first_name":"Test","username":"testbot"},"date":1564181818,"text":"Message"}}`))
|
||||
}))
|
||||
@ -74,8 +59,7 @@ func Test_configTelegram_send(t *testing.T) {
|
||||
assert.Equal(t, int64(789), chatId)
|
||||
|
||||
assert.NotNil(t, fakeClient.req)
|
||||
assert.Equal(t, http.MethodPost, fakeClient.req.Method)
|
||||
assert.Equal(t, "https://api.telegram.org/botbottoken/sendMessage", fakeClient.req.URL.String())
|
||||
assert.Contains(t, fakeClient.req.URL.String(), "https://api.telegram.org/botbottoken/sendMessage")
|
||||
|
||||
req := fakeClient.req
|
||||
assert.Equal(t, "chatid", req.FormValue("chat_id"))
|
||||
@ -99,11 +83,6 @@ func Test_telegram(t *testing.T) {
|
||||
t.Run("Send post to Telegram", func(t *testing.T) {
|
||||
fakeClient := newFakeHttpClient()
|
||||
fakeClient.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() == "https://api.telegram.org/botbottoken/getMe" {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write([]byte(`{"ok":true,"result":{"id":123456789,"is_bot":true,"first_name":"Test","username":"testbot"}}`))
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
_, _ = rw.Write([]byte(`{"ok":true,"result":{"message_id":123,"from":{"id":123456789,"is_bot":true,"first_name":"Test","username":"testbot"},"chat":{"id":123456789,"first_name":"Test","username":"testbot"},"date":1564181818,"text":"Message"}}`))
|
||||
}))
|
||||
@ -139,7 +118,7 @@ func Test_telegram(t *testing.T) {
|
||||
|
||||
app.pPostHooks[0](p)
|
||||
|
||||
assert.Equal(t, "https://api.telegram.org/botbottoken/sendMessage", fakeClient.req.URL.String())
|
||||
assert.Contains(t, fakeClient.req.URL.String(), "https://api.telegram.org/botbottoken/sendMessage")
|
||||
|
||||
req := fakeClient.req
|
||||
assert.Equal(t, "chatid", req.FormValue("chat_id"))
|
||||
|
Loading…
Reference in New Issue
Block a user