leobot

Simple Telegram Logging Bot
git clone git://bsandro.tech/leobot
Log | Files | Refs | README | LICENSE

bot.go (18016B)


      1 // Package tgbotapi has functions and types used for interacting with
      2 // the Telegram Bot API.
      3 package tgbotapi
      4 
      5 import (
      6 	"encoding/json"
      7 	"errors"
      8 	"fmt"
      9 	"io"
     10 	"io/ioutil"
     11 	"mime/multipart"
     12 	"net/http"
     13 	"net/url"
     14 	"strings"
     15 	"time"
     16 )
     17 
     18 // HTTPClient is the type needed for the bot to perform HTTP requests.
     19 type HTTPClient interface {
     20 	Do(req *http.Request) (*http.Response, error)
     21 }
     22 
     23 // BotAPI allows you to interact with the Telegram Bot API.
     24 type BotAPI struct {
     25 	Token  string `json:"token"`
     26 	Debug  bool   `json:"debug"`
     27 	Buffer int    `json:"buffer"`
     28 
     29 	Self            User       `json:"-"`
     30 	Client          HTTPClient `json:"-"`
     31 	shutdownChannel chan interface{}
     32 
     33 	apiEndpoint string
     34 }
     35 
     36 // NewBotAPI creates a new BotAPI instance.
     37 //
     38 // It requires a token, provided by @BotFather on Telegram.
     39 func NewBotAPI(token string) (*BotAPI, error) {
     40 	return NewBotAPIWithClient(token, APIEndpoint, &http.Client{})
     41 }
     42 
     43 // NewBotAPIWithAPIEndpoint creates a new BotAPI instance
     44 // and allows you to pass API endpoint.
     45 //
     46 // It requires a token, provided by @BotFather on Telegram and API endpoint.
     47 func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) {
     48 	return NewBotAPIWithClient(token, apiEndpoint, &http.Client{})
     49 }
     50 
     51 // NewBotAPIWithClient creates a new BotAPI instance
     52 // and allows you to pass a http.Client.
     53 //
     54 // It requires a token, provided by @BotFather on Telegram and API endpoint.
     55 func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) {
     56 	bot := &BotAPI{
     57 		Token:           token,
     58 		Client:          client,
     59 		Buffer:          100,
     60 		shutdownChannel: make(chan interface{}),
     61 
     62 		apiEndpoint: apiEndpoint,
     63 	}
     64 
     65 	self, err := bot.GetMe()
     66 	if err != nil {
     67 		return nil, err
     68 	}
     69 
     70 	bot.Self = self
     71 
     72 	return bot, nil
     73 }
     74 
     75 // SetAPIEndpoint changes the Telegram Bot API endpoint used by the instance.
     76 func (bot *BotAPI) SetAPIEndpoint(apiEndpoint string) {
     77 	bot.apiEndpoint = apiEndpoint
     78 }
     79 
     80 func buildParams(in Params) url.Values {
     81 	if in == nil {
     82 		return url.Values{}
     83 	}
     84 
     85 	out := url.Values{}
     86 
     87 	for key, value := range in {
     88 		out.Set(key, value)
     89 	}
     90 
     91 	return out
     92 }
     93 
     94 // MakeRequest makes a request to a specific endpoint with our token.
     95 func (bot *BotAPI) MakeRequest(endpoint string, params Params) (*APIResponse, error) {
     96 	if bot.Debug {
     97 		log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
     98 	}
     99 
    100 	method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
    101 
    102 	values := buildParams(params)
    103 
    104 	req, err := http.NewRequest("POST", method, strings.NewReader(values.Encode()))
    105 	if err != nil {
    106 		return &APIResponse{}, err
    107 	}
    108 	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    109 
    110 	resp, err := bot.Client.Do(req)
    111 	if err != nil {
    112 		return nil, err
    113 	}
    114 	defer resp.Body.Close()
    115 
    116 	var apiResp APIResponse
    117 	bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
    118 	if err != nil {
    119 		return &apiResp, err
    120 	}
    121 
    122 	if bot.Debug {
    123 		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
    124 	}
    125 
    126 	if !apiResp.Ok {
    127 		var parameters ResponseParameters
    128 
    129 		if apiResp.Parameters != nil {
    130 			parameters = *apiResp.Parameters
    131 		}
    132 
    133 		return &apiResp, &Error{
    134 			Code:               apiResp.ErrorCode,
    135 			Message:            apiResp.Description,
    136 			ResponseParameters: parameters,
    137 		}
    138 	}
    139 
    140 	return &apiResp, nil
    141 }
    142 
    143 // decodeAPIResponse decode response and return slice of bytes if debug enabled.
    144 // If debug disabled, just decode http.Response.Body stream to APIResponse struct
    145 // for efficient memory usage
    146 func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) ([]byte, error) {
    147 	if !bot.Debug {
    148 		dec := json.NewDecoder(responseBody)
    149 		err := dec.Decode(resp)
    150 		return nil, err
    151 	}
    152 
    153 	// if debug, read response body
    154 	data, err := ioutil.ReadAll(responseBody)
    155 	if err != nil {
    156 		return nil, err
    157 	}
    158 
    159 	err = json.Unmarshal(data, resp)
    160 	if err != nil {
    161 		return nil, err
    162 	}
    163 
    164 	return data, nil
    165 }
    166 
    167 // UploadFiles makes a request to the API with files.
    168 func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) {
    169 	r, w := io.Pipe()
    170 	m := multipart.NewWriter(w)
    171 
    172 	// This code modified from the very helpful @HirbodBehnam
    173 	// https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473
    174 	go func() {
    175 		defer w.Close()
    176 		defer m.Close()
    177 
    178 		for field, value := range params {
    179 			if err := m.WriteField(field, value); err != nil {
    180 				w.CloseWithError(err)
    181 				return
    182 			}
    183 		}
    184 
    185 		for _, file := range files {
    186 			if file.Data.NeedsUpload() {
    187 				name, reader, err := file.Data.UploadData()
    188 				if err != nil {
    189 					w.CloseWithError(err)
    190 					return
    191 				}
    192 
    193 				part, err := m.CreateFormFile(file.Name, name)
    194 				if err != nil {
    195 					w.CloseWithError(err)
    196 					return
    197 				}
    198 
    199 				if _, err := io.Copy(part, reader); err != nil {
    200 					w.CloseWithError(err)
    201 					return
    202 				}
    203 
    204 				if closer, ok := reader.(io.ReadCloser); ok {
    205 					if err = closer.Close(); err != nil {
    206 						w.CloseWithError(err)
    207 						return
    208 					}
    209 				}
    210 			} else {
    211 				value := file.Data.SendData()
    212 
    213 				if err := m.WriteField(file.Name, value); err != nil {
    214 					w.CloseWithError(err)
    215 					return
    216 				}
    217 			}
    218 		}
    219 	}()
    220 
    221 	if bot.Debug {
    222 		log.Printf("Endpoint: %s, params: %v, with %d files\n", endpoint, params, len(files))
    223 	}
    224 
    225 	method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
    226 
    227 	req, err := http.NewRequest("POST", method, r)
    228 	if err != nil {
    229 		return nil, err
    230 	}
    231 
    232 	req.Header.Set("Content-Type", m.FormDataContentType())
    233 
    234 	resp, err := bot.Client.Do(req)
    235 	if err != nil {
    236 		return nil, err
    237 	}
    238 	defer resp.Body.Close()
    239 
    240 	var apiResp APIResponse
    241 	bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
    242 	if err != nil {
    243 		return &apiResp, err
    244 	}
    245 
    246 	if bot.Debug {
    247 		log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
    248 	}
    249 
    250 	if !apiResp.Ok {
    251 		var parameters ResponseParameters
    252 
    253 		if apiResp.Parameters != nil {
    254 			parameters = *apiResp.Parameters
    255 		}
    256 
    257 		return &apiResp, &Error{
    258 			Message:            apiResp.Description,
    259 			ResponseParameters: parameters,
    260 		}
    261 	}
    262 
    263 	return &apiResp, nil
    264 }
    265 
    266 // GetFileDirectURL returns direct URL to file
    267 //
    268 // It requires the FileID.
    269 func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
    270 	file, err := bot.GetFile(FileConfig{fileID})
    271 
    272 	if err != nil {
    273 		return "", err
    274 	}
    275 
    276 	return file.Link(bot.Token), nil
    277 }
    278 
    279 // GetMe fetches the currently authenticated bot.
    280 //
    281 // This method is called upon creation to validate the token,
    282 // and so you may get this data from BotAPI.Self without the need for
    283 // another request.
    284 func (bot *BotAPI) GetMe() (User, error) {
    285 	resp, err := bot.MakeRequest("getMe", nil)
    286 	if err != nil {
    287 		return User{}, err
    288 	}
    289 
    290 	var user User
    291 	err = json.Unmarshal(resp.Result, &user)
    292 
    293 	return user, err
    294 }
    295 
    296 // IsMessageToMe returns true if message directed to this bot.
    297 //
    298 // It requires the Message.
    299 func (bot *BotAPI) IsMessageToMe(message Message) bool {
    300 	return strings.Contains(message.Text, "@"+bot.Self.UserName)
    301 }
    302 
    303 func hasFilesNeedingUpload(files []RequestFile) bool {
    304 	for _, file := range files {
    305 		if file.Data.NeedsUpload() {
    306 			return true
    307 		}
    308 	}
    309 
    310 	return false
    311 }
    312 
    313 // Request sends a Chattable to Telegram, and returns the APIResponse.
    314 func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) {
    315 	params, err := c.params()
    316 	if err != nil {
    317 		return nil, err
    318 	}
    319 
    320 	if t, ok := c.(Fileable); ok {
    321 		files := t.files()
    322 
    323 		// If we have files that need to be uploaded, we should delegate the
    324 		// request to UploadFile.
    325 		if hasFilesNeedingUpload(files) {
    326 			return bot.UploadFiles(t.method(), params, files)
    327 		}
    328 
    329 		// However, if there are no files to be uploaded, there's likely things
    330 		// that need to be turned into params instead.
    331 		for _, file := range files {
    332 			params[file.Name] = file.Data.SendData()
    333 		}
    334 	}
    335 
    336 	return bot.MakeRequest(c.method(), params)
    337 }
    338 
    339 // Send will send a Chattable item to Telegram and provides the
    340 // returned Message.
    341 func (bot *BotAPI) Send(c Chattable) (Message, error) {
    342 	resp, err := bot.Request(c)
    343 	if err != nil {
    344 		return Message{}, err
    345 	}
    346 
    347 	var message Message
    348 	err = json.Unmarshal(resp.Result, &message)
    349 
    350 	return message, err
    351 }
    352 
    353 // SendMediaGroup sends a media group and returns the resulting messages.
    354 func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
    355 	resp, err := bot.Request(config)
    356 	if err != nil {
    357 		return nil, err
    358 	}
    359 
    360 	var messages []Message
    361 	err = json.Unmarshal(resp.Result, &messages)
    362 
    363 	return messages, err
    364 }
    365 
    366 // GetUserProfilePhotos gets a user's profile photos.
    367 //
    368 // It requires UserID.
    369 // Offset and Limit are optional.
    370 func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
    371 	resp, err := bot.Request(config)
    372 	if err != nil {
    373 		return UserProfilePhotos{}, err
    374 	}
    375 
    376 	var profilePhotos UserProfilePhotos
    377 	err = json.Unmarshal(resp.Result, &profilePhotos)
    378 
    379 	return profilePhotos, err
    380 }
    381 
    382 // GetFile returns a File which can download a file from Telegram.
    383 //
    384 // Requires FileID.
    385 func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
    386 	resp, err := bot.Request(config)
    387 	if err != nil {
    388 		return File{}, err
    389 	}
    390 
    391 	var file File
    392 	err = json.Unmarshal(resp.Result, &file)
    393 
    394 	return file, err
    395 }
    396 
    397 // GetUpdates fetches updates.
    398 // If a WebHook is set, this will not return any data!
    399 //
    400 // Offset, Limit, Timeout, and AllowedUpdates are optional.
    401 // To avoid stale items, set Offset to one higher than the previous item.
    402 // Set Timeout to a large number to reduce requests, so you can get updates
    403 // instantly instead of having to wait between requests.
    404 func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
    405 	resp, err := bot.Request(config)
    406 	if err != nil {
    407 		return []Update{}, err
    408 	}
    409 
    410 	var updates []Update
    411 	err = json.Unmarshal(resp.Result, &updates)
    412 
    413 	return updates, err
    414 }
    415 
    416 // GetWebhookInfo allows you to fetch information about a webhook and if
    417 // one currently is set, along with pending update count and error messages.
    418 func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
    419 	resp, err := bot.MakeRequest("getWebhookInfo", nil)
    420 	if err != nil {
    421 		return WebhookInfo{}, err
    422 	}
    423 
    424 	var info WebhookInfo
    425 	err = json.Unmarshal(resp.Result, &info)
    426 
    427 	return info, err
    428 }
    429 
    430 // GetUpdatesChan starts and returns a channel for getting updates.
    431 func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
    432 	ch := make(chan Update, bot.Buffer)
    433 
    434 	go func() {
    435 		for {
    436 			select {
    437 			case <-bot.shutdownChannel:
    438 				close(ch)
    439 				return
    440 			default:
    441 			}
    442 
    443 			updates, err := bot.GetUpdates(config)
    444 			if err != nil {
    445 				log.Println(err)
    446 				log.Println("Failed to get updates, retrying in 3 seconds...")
    447 				time.Sleep(time.Second * 3)
    448 
    449 				continue
    450 			}
    451 
    452 			for _, update := range updates {
    453 				if update.UpdateID >= config.Offset {
    454 					config.Offset = update.UpdateID + 1
    455 					ch <- update
    456 				}
    457 			}
    458 		}
    459 	}()
    460 
    461 	return ch
    462 }
    463 
    464 // StopReceivingUpdates stops the go routine which receives updates
    465 func (bot *BotAPI) StopReceivingUpdates() {
    466 	if bot.Debug {
    467 		log.Println("Stopping the update receiver routine...")
    468 	}
    469 	close(bot.shutdownChannel)
    470 }
    471 
    472 // ListenForWebhook registers a http handler for a webhook.
    473 func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
    474 	ch := make(chan Update, bot.Buffer)
    475 
    476 	http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
    477 		update, err := bot.HandleUpdate(r)
    478 		if err != nil {
    479 			errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
    480 			w.WriteHeader(http.StatusBadRequest)
    481 			w.Header().Set("Content-Type", "application/json")
    482 			_, _ = w.Write(errMsg)
    483 			return
    484 		}
    485 
    486 		ch <- *update
    487 	})
    488 
    489 	return ch
    490 }
    491 
    492 // ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook.
    493 func (bot *BotAPI) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel {
    494 	ch := make(chan Update, bot.Buffer)
    495 
    496 	func(w http.ResponseWriter, r *http.Request) {
    497 		update, err := bot.HandleUpdate(r)
    498 		if err != nil {
    499 			errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
    500 			w.WriteHeader(http.StatusBadRequest)
    501 			w.Header().Set("Content-Type", "application/json")
    502 			_, _ = w.Write(errMsg)
    503 			return
    504 		}
    505 
    506 		ch <- *update
    507 		close(ch)
    508 	}(w, r)
    509 
    510 	return ch
    511 }
    512 
    513 // HandleUpdate parses and returns update received via webhook
    514 func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
    515 	if r.Method != http.MethodPost {
    516 		err := errors.New("wrong HTTP method required POST")
    517 		return nil, err
    518 	}
    519 
    520 	var update Update
    521 	err := json.NewDecoder(r.Body).Decode(&update)
    522 	if err != nil {
    523 		return nil, err
    524 	}
    525 
    526 	return &update, nil
    527 }
    528 
    529 // WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
    530 //
    531 // It doesn't support uploading files.
    532 //
    533 // See https://core.telegram.org/bots/api#making-requests-when-getting-updates
    534 // for details.
    535 func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error {
    536 	params, err := c.params()
    537 	if err != nil {
    538 		return err
    539 	}
    540 
    541 	if t, ok := c.(Fileable); ok {
    542 		if hasFilesNeedingUpload(t.files()) {
    543 			return errors.New("unable to use http response to upload files")
    544 		}
    545 	}
    546 
    547 	values := buildParams(params)
    548 	values.Set("method", c.method())
    549 
    550 	w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
    551 	_, err = w.Write([]byte(values.Encode()))
    552 	return err
    553 }
    554 
    555 // GetChat gets information about a chat.
    556 func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
    557 	resp, err := bot.Request(config)
    558 	if err != nil {
    559 		return Chat{}, err
    560 	}
    561 
    562 	var chat Chat
    563 	err = json.Unmarshal(resp.Result, &chat)
    564 
    565 	return chat, err
    566 }
    567 
    568 // GetChatAdministrators gets a list of administrators in the chat.
    569 //
    570 // If none have been appointed, only the creator will be returned.
    571 // Bots are not shown, even if they are an administrator.
    572 func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
    573 	resp, err := bot.Request(config)
    574 	if err != nil {
    575 		return []ChatMember{}, err
    576 	}
    577 
    578 	var members []ChatMember
    579 	err = json.Unmarshal(resp.Result, &members)
    580 
    581 	return members, err
    582 }
    583 
    584 // GetChatMembersCount gets the number of users in a chat.
    585 func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
    586 	resp, err := bot.Request(config)
    587 	if err != nil {
    588 		return -1, err
    589 	}
    590 
    591 	var count int
    592 	err = json.Unmarshal(resp.Result, &count)
    593 
    594 	return count, err
    595 }
    596 
    597 // GetChatMember gets a specific chat member.
    598 func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
    599 	resp, err := bot.Request(config)
    600 	if err != nil {
    601 		return ChatMember{}, err
    602 	}
    603 
    604 	var member ChatMember
    605 	err = json.Unmarshal(resp.Result, &member)
    606 
    607 	return member, err
    608 }
    609 
    610 // GetGameHighScores allows you to get the high scores for a game.
    611 func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
    612 	resp, err := bot.Request(config)
    613 	if err != nil {
    614 		return []GameHighScore{}, err
    615 	}
    616 
    617 	var highScores []GameHighScore
    618 	err = json.Unmarshal(resp.Result, &highScores)
    619 
    620 	return highScores, err
    621 }
    622 
    623 // GetInviteLink get InviteLink for a chat
    624 func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
    625 	resp, err := bot.Request(config)
    626 	if err != nil {
    627 		return "", err
    628 	}
    629 
    630 	var inviteLink string
    631 	err = json.Unmarshal(resp.Result, &inviteLink)
    632 
    633 	return inviteLink, err
    634 }
    635 
    636 // GetStickerSet returns a StickerSet.
    637 func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
    638 	resp, err := bot.Request(config)
    639 	if err != nil {
    640 		return StickerSet{}, err
    641 	}
    642 
    643 	var stickers StickerSet
    644 	err = json.Unmarshal(resp.Result, &stickers)
    645 
    646 	return stickers, err
    647 }
    648 
    649 // StopPoll stops a poll and returns the result.
    650 func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
    651 	resp, err := bot.Request(config)
    652 	if err != nil {
    653 		return Poll{}, err
    654 	}
    655 
    656 	var poll Poll
    657 	err = json.Unmarshal(resp.Result, &poll)
    658 
    659 	return poll, err
    660 }
    661 
    662 // GetMyCommands gets the currently registered commands.
    663 func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) {
    664 	return bot.GetMyCommandsWithConfig(GetMyCommandsConfig{})
    665 }
    666 
    667 // GetMyCommandsWithConfig gets the currently registered commands with a config.
    668 func (bot *BotAPI) GetMyCommandsWithConfig(config GetMyCommandsConfig) ([]BotCommand, error) {
    669 	resp, err := bot.Request(config)
    670 	if err != nil {
    671 		return nil, err
    672 	}
    673 
    674 	var commands []BotCommand
    675 	err = json.Unmarshal(resp.Result, &commands)
    676 
    677 	return commands, err
    678 }
    679 
    680 // CopyMessage copy messages of any kind. The method is analogous to the method
    681 // forwardMessage, but the copied message doesn't have a link to the original
    682 // message. Returns the MessageID of the sent message on success.
    683 func (bot *BotAPI) CopyMessage(config CopyMessageConfig) (MessageID, error) {
    684 	params, err := config.params()
    685 	if err != nil {
    686 		return MessageID{}, err
    687 	}
    688 
    689 	resp, err := bot.MakeRequest(config.method(), params)
    690 	if err != nil {
    691 		return MessageID{}, err
    692 	}
    693 
    694 	var messageID MessageID
    695 	err = json.Unmarshal(resp.Result, &messageID)
    696 
    697 	return messageID, err
    698 }
    699 
    700 // EscapeText takes an input text and escape Telegram markup symbols.
    701 // In this way we can send a text without being afraid of having to escape the characters manually.
    702 // Note that you don't have to include the formatting style in the input text, or it will be escaped too.
    703 // If there is an error, an empty string will be returned.
    704 //
    705 // parseMode is the text formatting mode (ModeMarkdown, ModeMarkdownV2 or ModeHTML)
    706 // text is the input string that will be escaped
    707 func EscapeText(parseMode string, text string) string {
    708 	var replacer *strings.Replacer
    709 
    710 	if parseMode == ModeHTML {
    711 		replacer = strings.NewReplacer("<", "&lt;", ">", "&gt;", "&", "&amp;")
    712 	} else if parseMode == ModeMarkdown {
    713 		replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[")
    714 	} else if parseMode == ModeMarkdownV2 {
    715 		replacer = strings.NewReplacer(
    716 			"_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(",
    717 			"\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>",
    718 			"#", "\\#", "+", "\\+", "-", "\\-", "=", "\\=", "|",
    719 			"\\|", "{", "\\{", "}", "\\}", ".", "\\.", "!", "\\!",
    720 		)
    721 	} else {
    722 		return ""
    723 	}
    724 
    725 	return replacer.Replace(text)
    726 }