mirror of
https://github.com/Neur0toxine/bash.im-telegram-bot.git
synced 2024-11-23 21:56:08 +03:00
Refactor
This commit is contained in:
parent
6c234a8084
commit
37d6d7baf7
7
.env.dist
Normal file
7
.env.dist
Normal file
@ -0,0 +1,7 @@
|
||||
TG_BOT_TOKEN=token # Telegram Bot API Token
|
||||
POLL_TIMEOUT=30 # Poll timeout (in seconds). Default: 30
|
||||
WEBHOOK="https://www.google.com:{PORT}/{TOKEN}" # Webhook URL (don't provide if you want to use polling). {PORT} and {TOKEN} will be replaced automatically.
|
||||
WEBHOOK_PORT=8000 # Webhook port. Ignored if webhook URL is not provided. Default: 8000
|
||||
CERT="cert.pem" # Certificate file name. Provide if you want to use SSL
|
||||
CERT_KEY="key.pem" # Certificate key. Should be provided with CERT
|
||||
DEBUG=false # Debug mode. This value will be passed to API library, which will log all requests in debug mode.
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,2 +1,4 @@
|
||||
*.exe
|
||||
.env
|
||||
.env
|
||||
bin/*
|
||||
bin
|
23
Makefile
Normal file
23
Makefile
Normal file
@ -0,0 +1,23 @@
|
||||
.DEFAULT_GOAL := build
|
||||
|
||||
GO=$(shell which go)
|
||||
PROJECT_DIR=$(shell pwd)
|
||||
GOPATH=$(PROJECT_DIR)
|
||||
SRC=$(PROJECT_DIR)/src
|
||||
BIN=$(PROJECT_DIR)/bin/bash_im_bot
|
||||
|
||||
build: fmt deps
|
||||
@echo "- Building"
|
||||
@cd $(SRC) && $(GO) build -o $(BIN)
|
||||
@echo Built "$(BIN)"
|
||||
|
||||
run:
|
||||
@$(BIN)
|
||||
|
||||
deps:
|
||||
@echo "- Installing dependencies"
|
||||
@$(GO) mod tidy
|
||||
|
||||
fmt:
|
||||
@echo "- Running 'go fmt'"
|
||||
@$(GO) fmt $(SRC)
|
@ -1,4 +1,7 @@
|
||||
This bot will show latest quotes from bash.im. Also it can (but not yet) work inline.
|
||||
- [x] Ability to fetch latest quotes from bash.im
|
||||
- [ ] Ability to send quote to dialog via inline mode
|
||||
- [ ] Integrated search and autocomplete for inline mode
|
||||
- [x] Ability to send quote to dialog via inline mode
|
||||
- [x] Integrated search and autocomplete for inline mode
|
||||
- [ ] Use [goquery](https://github.com/PuerkitoBio/goquery) instead of regular expressions
|
||||
- [ ] Automated version increment
|
||||
- [ ] Setup CI/CD
|
123
bashim.go
123
bashim.go
@ -1,123 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const BASH_URL = "https://bash.im"
|
||||
|
||||
type BashQuote struct {
|
||||
ID int
|
||||
Created string
|
||||
Rating string
|
||||
Permalink string
|
||||
Text string
|
||||
}
|
||||
|
||||
func GetBashQuote(id int) (BashQuote, error) {
|
||||
var quote BashQuote
|
||||
|
||||
if resp, err := http.Get(BASH_URL + "/quote/" + strconv.Itoa(id)); err == nil {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
if bodyData, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||
body := string(bodyData)
|
||||
items := getQuotesList(body)
|
||||
|
||||
if len(items) == 1 {
|
||||
return items[0], nil
|
||||
} else {
|
||||
return quote, errors.New("Can't find quote")
|
||||
}
|
||||
} else {
|
||||
return quote, err
|
||||
}
|
||||
} else {
|
||||
return quote, errors.New("Incorrect status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
} else {
|
||||
return quote, err
|
||||
}
|
||||
}
|
||||
|
||||
func GetLatestQuotes() ([]BashQuote, error) {
|
||||
var quotes []BashQuote
|
||||
|
||||
if resp, err := http.Get(BASH_URL); err == nil {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
if bodyData, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||
body := string(bodyData)
|
||||
items := getQuotesList(body)
|
||||
|
||||
if len(items) > 1 {
|
||||
return items, nil
|
||||
} else {
|
||||
return quotes, errors.New("Error while trying to extract quotes")
|
||||
}
|
||||
} else {
|
||||
return quotes, err
|
||||
}
|
||||
} else {
|
||||
return quotes, errors.New("Incorrect status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
} else {
|
||||
return quotes, err
|
||||
}
|
||||
}
|
||||
|
||||
func getQuotesList(response string) []BashQuote {
|
||||
re := regexp.MustCompile(`(?im)[.\s\w\W]+?\<article\sclass\="quote\"[.\s\w\W]+?<\/article\>`)
|
||||
matches := re.FindAllString(response, -1)
|
||||
items := make([]BashQuote, len(matches))
|
||||
|
||||
for index, match := range matches {
|
||||
id, created, rating, permalink, text, err := getQuoteData(match)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
items[index] = BashQuote{
|
||||
ID: id,
|
||||
Created: created,
|
||||
Rating: rating,
|
||||
Permalink: permalink,
|
||||
Text: text,
|
||||
}
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func getQuoteData(response string) (id int, created string, rating string, permalink string, text string, err error) {
|
||||
re := regexp.MustCompile(`(?im)data\-quote\=\"(?P<id>\d+)\"[.\s\w\W]+?quote__header_permalink.+href\=\"(?P<permalink>\/.+\d)\"[.\s\w\W]+?quote__header_date\"\>[.\s\w\W]+?(?P<date>.+)[.\s\w\W]+?quote__body\"\>\s+?(?P<text>.+)[.\s\w\W]+?quote__total.+\>(?P<rating>\d+)`)
|
||||
matches := re.FindStringSubmatch(response)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return 0, "", "", "", "", errors.New("No data found")
|
||||
} else {
|
||||
matches = matches[1:]
|
||||
}
|
||||
|
||||
id, err = strconv.Atoi(matches[0])
|
||||
|
||||
if err != nil {
|
||||
return 0, "", "", "", "", err
|
||||
}
|
||||
|
||||
created = strings.TrimSpace(matches[2])
|
||||
rating = strings.TrimSpace(matches[4])
|
||||
permalink = BASH_URL + matches[1]
|
||||
text = strings.ReplaceAll(strings.TrimSpace(matches[3]), "<br>", "\n")
|
||||
err = nil
|
||||
|
||||
return
|
||||
}
|
9
go.mod
Normal file
9
go.mod
Normal file
@ -0,0 +1,9 @@
|
||||
module github.com/Neur0toxine/bash.im-telegram-bot
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
)
|
6
go.sum
Normal file
6
go.sum
Normal file
@ -0,0 +1,6 @@
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
229
main.go
229
main.go
@ -1,229 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
type updateFunc func(tgbotapi.Update, *tgbotapi.BotAPI)
|
||||
|
||||
const envToken = "TG_BOT_TOKEN"
|
||||
const envPollTimeout = "POLL_TIMEOUT"
|
||||
const envWebhook = "WEBHOOK"
|
||||
const envWebhookPort = "WEBHOOK_PORT"
|
||||
const envCert = "CERT"
|
||||
const envKey = "CERT_KEY"
|
||||
const envDebug = "DEBUG"
|
||||
|
||||
const defaultPollTimeout = 30
|
||||
const defaultWebhookPort = 8000
|
||||
|
||||
func main() {
|
||||
var (
|
||||
pollTimeout, webhookPort int
|
||||
err error
|
||||
)
|
||||
|
||||
err = godotenv.Load()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("WARNING: Error while loading `.env` file `%s`", err.Error())
|
||||
}
|
||||
|
||||
token := os.Getenv(envToken)
|
||||
webhookUrl := os.Getenv(envWebhook)
|
||||
webhookPortStr := os.Getenv(envWebhookPort)
|
||||
certFile := os.Getenv(envCert)
|
||||
certKey := os.Getenv(envKey)
|
||||
pollTimeoutStr := os.Getenv(envPollTimeout)
|
||||
debug, err := strconv.ParseBool(os.Getenv(envDebug))
|
||||
|
||||
if err != nil {
|
||||
debug = false
|
||||
}
|
||||
|
||||
if pollTimeout, err = strconv.Atoi(pollTimeoutStr); err != nil {
|
||||
log.Printf("Using default poll timeout - %d seconds...", defaultPollTimeout)
|
||||
pollTimeout = defaultPollTimeout
|
||||
}
|
||||
|
||||
if webhookPort, err = strconv.Atoi(webhookPortStr); err != nil && webhookUrl != "" {
|
||||
log.Printf("Using default webhook port %d ...", defaultWebhookPort)
|
||||
webhookPort = defaultWebhookPort
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
log.Fatalf(
|
||||
"`%s` is not found in environment - specify it in `.env` file or pass it while launching",
|
||||
envToken,
|
||||
)
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(token)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
bot.Debug = debug
|
||||
|
||||
log.Printf("Authorized on account: @%s (id: %d)", bot.Self.UserName, bot.Self.ID)
|
||||
log.Printf("Debug mode: %t", debug)
|
||||
|
||||
if webhookUrl == "" {
|
||||
initWithPolling(bot, pollTimeout, processUpdate)
|
||||
} else {
|
||||
initWithWebhook(bot, webhookUrl, webhookPort, certFile, certKey, processUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
func processUpdate(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
||||
if update.InlineQuery != nil {
|
||||
results := make([]interface{}, 1)
|
||||
results[0] = tgbotapi.NewInlineQueryResultArticleMarkdown(
|
||||
update.InlineQuery.ID,
|
||||
"",
|
||||
"",
|
||||
)
|
||||
|
||||
bot.AnswerInlineQuery(
|
||||
tgbotapi.InlineConfig{
|
||||
InlineQueryID: update.InlineQuery.ID,
|
||||
Results: results,
|
||||
CacheTime: 30,
|
||||
IsPersonal: false,
|
||||
NextOffset: "",
|
||||
SwitchPMText: "",
|
||||
SwitchPMParameter: "",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
msgs := make([]tgbotapi.MessageConfig, 1)
|
||||
msgs[0] = tgbotapi.NewMessage(update.Message.Chat.ID, "")
|
||||
msgs[0].ParseMode = "markdown"
|
||||
|
||||
if update.Message.IsCommand() {
|
||||
switch update.Message.Command() {
|
||||
case "latest":
|
||||
items, err := GetLatestQuotes()
|
||||
|
||||
if err != nil {
|
||||
msgs[len(msgs)-1].Text = "Не удалось получить последние цитаты :("
|
||||
} else {
|
||||
for _, item := range items {
|
||||
text := fmt.Sprintf(
|
||||
"*Цитата:* [#%d](%s) \n"+
|
||||
"*Создано:* %s \n"+
|
||||
"*Рейтинг:* %s \n"+
|
||||
"*Текст:*\n %s \n\n",
|
||||
item.ID,
|
||||
item.Permalink,
|
||||
item.Created,
|
||||
item.Rating,
|
||||
item.Text,
|
||||
)
|
||||
|
||||
if len(msgs[len(msgs)-1].Text+text) > 4096 {
|
||||
msgs = append(msgs, tgbotapi.NewMessage(update.Message.Chat.ID, text))
|
||||
msgs[len(msgs)-1].ParseMode = "markdown"
|
||||
} else {
|
||||
msgs[len(msgs)-1].Text += text
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
msgs[len(msgs)-1].Text = "Как насчёт последних цитат? Используйте /latest"
|
||||
}
|
||||
} else {
|
||||
text := fmt.Sprintf("Что вы пытаетесь здесь найти? Тут ничего нет...\n"+
|
||||
"Бот работает не так. Зайдите в любой чат, вызовите бота вот так: @%s <id>, где ID - "+
|
||||
"это идентификатор цитаты на bash.im. И бот перешлёт её!", bot.Self.UserName)
|
||||
msgs[len(msgs)-1] = tgbotapi.NewMessage(update.Message.Chat.ID, text)
|
||||
msgs[len(msgs)-1].ReplyToMessageID = update.Message.MessageID
|
||||
}
|
||||
|
||||
for _, msg := range msgs {
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
log.Printf("Error while trying to send message to chat `%d`: %s", update.Message.Chat.ID, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initWithPolling(bot *tgbotapi.BotAPI, updateTimeout int, updateCallback updateFunc) {
|
||||
var (
|
||||
updates tgbotapi.UpdatesChannel
|
||||
err error
|
||||
)
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = updateTimeout
|
||||
|
||||
if updates, err = bot.GetUpdatesChan(u); err != nil {
|
||||
log.Fatalf("Error while trying to get updates: %s", err.Error())
|
||||
}
|
||||
|
||||
for update := range updates {
|
||||
updateCallback(update, bot)
|
||||
}
|
||||
}
|
||||
|
||||
func initWithWebhook(
|
||||
bot *tgbotapi.BotAPI,
|
||||
webhookUrl string,
|
||||
webhookPort int,
|
||||
certFile string,
|
||||
certKey string,
|
||||
updateCallback updateFunc,
|
||||
) {
|
||||
var err error
|
||||
|
||||
if webhookPort == 0 {
|
||||
webhookPort = 80
|
||||
}
|
||||
|
||||
if webhookUrl == "" {
|
||||
log.Fatalf("Empty webhook URL provided (env %s)", envWebhook)
|
||||
}
|
||||
|
||||
webhookLink := fmt.Sprintf("%s:%d/%s", webhookUrl, webhookPort, bot.Token)
|
||||
|
||||
if certFile != "" && certKey != "" {
|
||||
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert(webhookLink, "cert.pem"))
|
||||
} else {
|
||||
_, err = bot.SetWebhook(tgbotapi.NewWebhook(webhookLink))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
info, err := bot.GetWebhookInfo()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if info.LastErrorDate != 0 {
|
||||
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
|
||||
}
|
||||
|
||||
updates := bot.ListenForWebhook("/" + bot.Token)
|
||||
serverUrl := fmt.Sprintf("0.0.0.0:%d", webhookPort)
|
||||
|
||||
if certFile != "" && certKey != "" {
|
||||
go http.ListenAndServeTLS(serverUrl, certFile, certKey, nil)
|
||||
} else {
|
||||
go http.ListenAndServe(serverUrl, nil)
|
||||
}
|
||||
|
||||
for update := range updates {
|
||||
updateCallback(update, bot)
|
||||
}
|
||||
}
|
130
src/bashim.go
Normal file
130
src/bashim.go
Normal file
@ -0,0 +1,130 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const BASH_URL = "https://bash.im"
|
||||
|
||||
type BashQuote struct {
|
||||
ID int
|
||||
Created string
|
||||
Rating string
|
||||
Permalink string
|
||||
Text string
|
||||
}
|
||||
|
||||
var (
|
||||
replaceBrRe = regexp.MustCompile(`(?im)\<[\s+]?br[\s+\/]{0,2}?\>`)
|
||||
getQuotesListRe = regexp.MustCompile(`(?im)[.\s\w\W]+?\<article\sclass\="quote\"[.\s\w\W]+?<\/article\>`)
|
||||
getQuoteDataRe = regexp.MustCompile(`(?im)data\-quote\=\"(?P<id>\d+)\"[.\s\w\W]+?quote__header_permalink.+href\=\"(?P<permalink>\/.+\d)\"[.\s\w\W]+?quote__header_date\"\>[.\s\w\W]+?(?P<date>.+)[.\s\w\W]+?quote__body\"\>\s+?(?P<text>.+)[.\s\w\W]+?quote__total.+\>(?P<rating>\d+)`)
|
||||
)
|
||||
|
||||
func getQuotesList(response string, maxItems int) []BashQuote {
|
||||
var items []BashQuote
|
||||
matches := getQuotesListRe.FindAllString(response, -1)
|
||||
|
||||
if maxItems != 0 && len(matches) > maxItems {
|
||||
matches = matches[:maxItems]
|
||||
}
|
||||
|
||||
for _, match := range matches {
|
||||
id, created, rating, permalink, text, err := getQuoteData(match)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if id == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
items = append(items, BashQuote{
|
||||
ID: id,
|
||||
Created: created,
|
||||
Rating: rating,
|
||||
Permalink: permalink,
|
||||
Text: text,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func getQuoteData(response string) (id int, created string, rating string, permalink string, text string, err error) {
|
||||
matches := getQuoteDataRe.FindStringSubmatch(response)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return 0, "", "", "", "", errors.New("No data found")
|
||||
} else {
|
||||
matches = matches[1:]
|
||||
}
|
||||
|
||||
id, err = strconv.Atoi(matches[0])
|
||||
|
||||
if err != nil {
|
||||
return 0, "", "", "", "", err
|
||||
}
|
||||
|
||||
created = strings.ReplaceAll(strings.TrimSpace(matches[2]), " ", " ")
|
||||
rating = strings.TrimSpace(matches[4])
|
||||
permalink = BASH_URL + matches[1]
|
||||
text = html.UnescapeString(replaceBrRe.ReplaceAllString(strings.TrimSpace(matches[3]), "\n"))
|
||||
err = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func GetLatestQuotes() ([]BashQuote, error) {
|
||||
return extractQuotes("/", 25)
|
||||
}
|
||||
|
||||
func GetQuote(id int) (BashQuote, error) {
|
||||
quotes, err := extractQuotes(fmt.Sprintf("/quote/%d", id), 1)
|
||||
|
||||
if err != nil {
|
||||
return BashQuote{}, err
|
||||
} else if len(quotes) == 0 {
|
||||
return BashQuote{}, errors.New("Error while trying to extract quote")
|
||||
} else {
|
||||
return quotes[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func SearchQuotes(search string, maxResults int) ([]BashQuote, error) {
|
||||
return extractQuotes(fmt.Sprintf("/search?text=%s", url.QueryEscape(search)), maxResults)
|
||||
}
|
||||
|
||||
func extractQuotes(url string, maxItems int) ([]BashQuote, error) {
|
||||
var (
|
||||
quotes []BashQuote
|
||||
link = fmt.Sprintf("%s%s", BASH_URL, url)
|
||||
)
|
||||
|
||||
if resp, err := http.Get(link); err == nil {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == 200 {
|
||||
if bodyData, err := ioutil.ReadAll(resp.Body); err == nil {
|
||||
body := string(bodyData)
|
||||
items := getQuotesList(body, maxItems)
|
||||
|
||||
return items, nil
|
||||
} else {
|
||||
return quotes, err
|
||||
}
|
||||
} else {
|
||||
return quotes, errors.New("Incorrect status code: " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
} else {
|
||||
return quotes, err
|
||||
}
|
||||
}
|
233
src/bot.go
Normal file
233
src/bot.go
Normal file
@ -0,0 +1,233 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"unicode/utf16"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
type updateFunc func(tgbotapi.Update, *tgbotapi.BotAPI)
|
||||
|
||||
func processUpdate(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
||||
if update.InlineQuery != nil {
|
||||
var (
|
||||
results []interface{}
|
||||
bashQuotes []BashQuote
|
||||
quote BashQuote
|
||||
err error
|
||||
)
|
||||
|
||||
if update.InlineQuery.Query == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if quoteId, errConv := strconv.Atoi(update.InlineQuery.Query); errConv == nil {
|
||||
quote, err = GetQuote(quoteId)
|
||||
bashQuotes = append(bashQuotes, quote)
|
||||
} else {
|
||||
log.Print(errConv)
|
||||
bashQuotes, err = SearchQuotes(update.InlineQuery.Query, 3)
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
for _, quote := range bashQuotes {
|
||||
utfEncodedString := utf16.Encode([]rune(quote.Text))
|
||||
runeString := utf16.Decode(utfEncodedString[:50])
|
||||
|
||||
title := fmt.Sprintf(
|
||||
"[#%d]: %s\n",
|
||||
quote.ID,
|
||||
string(runeString)+"...\n",
|
||||
)
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"*Цитата:* [#%d](%s), %s \n"+
|
||||
"*Рейтинг:* %s \n"+
|
||||
"%s \n\n",
|
||||
quote.ID,
|
||||
quote.Permalink,
|
||||
quote.Created,
|
||||
quote.Rating,
|
||||
quote.Text,
|
||||
)
|
||||
|
||||
results = append(results, tgbotapi.NewInlineQueryResultArticleMarkdown(
|
||||
strconv.Itoa(rand.Int()),
|
||||
title,
|
||||
text,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
errMsg := "Не удалось произвести поиск"
|
||||
results = append(results, tgbotapi.NewInlineQueryResultArticleMarkdown(
|
||||
strconv.Itoa(rand.Int()),
|
||||
errMsg,
|
||||
errMsg,
|
||||
))
|
||||
}
|
||||
|
||||
if len(results) == 0 {
|
||||
errMsg := "Ничего не найдено..."
|
||||
results = append(results, tgbotapi.NewInlineQueryResultArticleMarkdown(
|
||||
strconv.Itoa(rand.Int()),
|
||||
errMsg,
|
||||
errMsg,
|
||||
))
|
||||
}
|
||||
|
||||
response, err := bot.AnswerInlineQuery(
|
||||
tgbotapi.InlineConfig{
|
||||
InlineQueryID: update.InlineQuery.ID,
|
||||
Results: results,
|
||||
CacheTime: 30,
|
||||
IsPersonal: false,
|
||||
NextOffset: "",
|
||||
SwitchPMText: "",
|
||||
SwitchPMParameter: "",
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
log.Printf("Error %d while trying to send inline update", response.ErrorCode)
|
||||
}
|
||||
} else {
|
||||
msgs := make([]tgbotapi.MessageConfig, 1)
|
||||
msgs[0] = NewMessage(update.Message.Chat.ID, 0, "", "markdown")
|
||||
|
||||
if update.Message.IsCommand() {
|
||||
switch update.Message.Command() {
|
||||
case "latest":
|
||||
SendMessages(bot, []tgbotapi.MessageConfig{
|
||||
NewMessage(update.Message.Chat.ID, update.Message.MessageID, "_Получаю свежие цитаты..._", "markdown"),
|
||||
})
|
||||
|
||||
items, err := GetLatestQuotes()
|
||||
|
||||
if err != nil {
|
||||
msgs[len(msgs)-1].Text = "Не удалось получить последние цитаты :("
|
||||
} else {
|
||||
for _, item := range items {
|
||||
text := fmt.Sprintf(
|
||||
"*Цитата:* [#%d](%s), %s \n"+
|
||||
"*Рейтинг:* %s \n"+
|
||||
"%s \n\n",
|
||||
item.ID,
|
||||
item.Permalink,
|
||||
item.Created,
|
||||
item.Rating,
|
||||
item.Text,
|
||||
)
|
||||
|
||||
if len(msgs[len(msgs)-1].Text+text) > 4096 {
|
||||
msgs = append(msgs, NewMessage(update.Message.Chat.ID, 0, text, "markdown"))
|
||||
} else {
|
||||
msgs[len(msgs)-1].Text += text
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
msgs[len(msgs)-1].Text = "Как насчёт последних цитат? Используйте /latest"
|
||||
}
|
||||
} else {
|
||||
text := fmt.Sprintf("Зайдите в любой чат, вызовите бота вот так:\n `@%s <id>`, где ID - "+
|
||||
"это идентификатор цитаты на bash.im. И бот перешлёт её!\n"+
|
||||
"Ещё вместо идентификатора можно указать текст, по которому бот попытается найти цитаты.", bot.Self.UserName)
|
||||
msgs[len(msgs)-1] = NewMessage(update.Message.Chat.ID, update.Message.MessageID, text, "markdown")
|
||||
}
|
||||
|
||||
SendMessages(bot, msgs)
|
||||
}
|
||||
}
|
||||
|
||||
func NewMessage(chatID int64, replyTo int, text string, parse string) tgbotapi.MessageConfig {
|
||||
msg := tgbotapi.NewMessage(chatID, text)
|
||||
msg.ParseMode = parse
|
||||
|
||||
if replyTo != 0 {
|
||||
msg.ReplyToMessageID = replyTo
|
||||
}
|
||||
|
||||
return msg
|
||||
}
|
||||
|
||||
func SendMessages(bot *tgbotapi.BotAPI, msgs []tgbotapi.MessageConfig) {
|
||||
for _, msg := range msgs {
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
log.Printf("Error while trying to send message to chat `%d`: %s", msg.BaseChat.ChatID, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initWithPolling(bot *tgbotapi.BotAPI, updateTimeout int, updateCallback updateFunc) {
|
||||
var (
|
||||
updates tgbotapi.UpdatesChannel
|
||||
err error
|
||||
)
|
||||
|
||||
u := tgbotapi.NewUpdate(0)
|
||||
u.Timeout = updateTimeout
|
||||
|
||||
if updates, err = bot.GetUpdatesChan(u); err != nil {
|
||||
log.Fatalf("Error while trying to get updates: %s", err.Error())
|
||||
}
|
||||
|
||||
for update := range updates {
|
||||
go updateCallback(update, bot)
|
||||
}
|
||||
}
|
||||
|
||||
func initWithWebhook(
|
||||
bot *tgbotapi.BotAPI,
|
||||
webhookUrl string,
|
||||
listenAddr string,
|
||||
certFile string,
|
||||
certKey string,
|
||||
updateCallback updateFunc,
|
||||
) {
|
||||
var err error
|
||||
|
||||
if webhookUrl == "" {
|
||||
log.Fatalf("Empty webhook URL provided (env %s)", envWebhook)
|
||||
}
|
||||
|
||||
if certFile != "" && certKey != "" {
|
||||
_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert(webhookUrl, certFile))
|
||||
} else {
|
||||
_, err = bot.SetWebhook(tgbotapi.NewWebhook(webhookUrl))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
info, err := bot.GetWebhookInfo()
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if info.LastErrorDate != 0 {
|
||||
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
|
||||
}
|
||||
|
||||
updates := bot.ListenForWebhook("/" + bot.Token)
|
||||
|
||||
if certFile != "" && certKey != "" {
|
||||
go http.ListenAndServeTLS(listenAddr, certFile, certKey, nil)
|
||||
} else {
|
||||
go http.ListenAndServe(listenAddr, nil)
|
||||
}
|
||||
|
||||
for update := range updates {
|
||||
go updateCallback(update, bot)
|
||||
}
|
||||
}
|
105
src/config.go
Normal file
105
src/config.go
Normal file
@ -0,0 +1,105 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
const envToken = "TG_BOT_TOKEN"
|
||||
const envPollTimeout = "POLL_TIMEOUT"
|
||||
const envListen = "LISTEN_IP"
|
||||
const envWebhook = "WEBHOOK"
|
||||
const envWebhookPort = "WEBHOOK_PORT"
|
||||
const envCert = "CERT"
|
||||
const envKey = "CERT_KEY"
|
||||
const envDebug = "DEBUG"
|
||||
|
||||
const defaultPollTimeout = 30
|
||||
const defaultWebhookPort = 8000
|
||||
|
||||
const ModePolling = "polling"
|
||||
const ModeWebhook = "webhook"
|
||||
|
||||
type BotConfig struct {
|
||||
Token string
|
||||
Mode string
|
||||
Debug bool
|
||||
PollingTimeout int
|
||||
WebhookURL string
|
||||
ListenAddr string
|
||||
CertificateFile string
|
||||
CertificateKey string
|
||||
}
|
||||
|
||||
func LoadConfig() (BotConfig, error) {
|
||||
var (
|
||||
cfg BotConfig
|
||||
pollTimeout, webhookPort int
|
||||
)
|
||||
|
||||
if err := godotenv.Load(); err != nil {
|
||||
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := godotenv.Load(filepath.Join(filepath.Dir(dir), ".env")); err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
}
|
||||
|
||||
token := os.Getenv(envToken)
|
||||
webhookUrl := os.Getenv(envWebhook)
|
||||
listenAddr := os.Getenv(envListen)
|
||||
webhookPortStr := os.Getenv(envWebhookPort)
|
||||
certFile := os.Getenv(envCert)
|
||||
certKey := os.Getenv(envKey)
|
||||
pollTimeoutStr := os.Getenv(envPollTimeout)
|
||||
debug, err := strconv.ParseBool(os.Getenv(envDebug))
|
||||
|
||||
if err != nil {
|
||||
debug = false
|
||||
}
|
||||
|
||||
if pollTimeout, err = strconv.Atoi(pollTimeoutStr); err != nil {
|
||||
pollTimeout = defaultPollTimeout
|
||||
}
|
||||
|
||||
if webhookPort, err = strconv.Atoi(webhookPortStr); err != nil && webhookUrl != "" {
|
||||
webhookPort = defaultWebhookPort
|
||||
}
|
||||
|
||||
webhookLink := strings.ReplaceAll(webhookUrl, "{PORT}", strconv.Itoa(webhookPort))
|
||||
webhookLink = strings.ReplaceAll(webhookLink, "{TOKEN}", token)
|
||||
|
||||
if token == "" {
|
||||
log.Fatalf(
|
||||
"`%s` is not found in environment - specify it in `.env` file or pass it while launching",
|
||||
envToken,
|
||||
)
|
||||
}
|
||||
|
||||
cfg = BotConfig{
|
||||
Token: token,
|
||||
Debug: debug,
|
||||
PollingTimeout: pollTimeout,
|
||||
WebhookURL: webhookLink,
|
||||
ListenAddr: listenAddr,
|
||||
CertificateFile: certFile,
|
||||
CertificateKey: certKey,
|
||||
}
|
||||
|
||||
if webhookUrl == "" {
|
||||
cfg.Mode = ModePolling
|
||||
} else {
|
||||
cfg.Mode = ModeWebhook
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
35
src/main.go
Normal file
35
src/main.go
Normal file
@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg, err := LoadConfig()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("WARNING: Error while loading `.env` file `%s`", err.Error())
|
||||
}
|
||||
|
||||
bot, err := tgbotapi.NewBotAPI(cfg.Token)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
bot.Debug = cfg.Debug
|
||||
|
||||
log.Printf("Authorized on account: @%s (id: %d)", bot.Self.UserName, bot.Self.ID)
|
||||
log.Printf("Debug mode: %t", cfg.Debug)
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
if cfg.WebhookURL == "" {
|
||||
initWithPolling(bot, cfg.PollingTimeout, processUpdate)
|
||||
} else {
|
||||
initWithWebhook(bot, cfg.WebhookURL, cfg.ListenAddr, cfg.CertificateFile, cfg.CertificateKey, processUpdate)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user