mirror of
https://github.com/Neur0toxine/bash.im-telegram-bot.git
synced 2024-11-24 06:06:07 +03:00
Initial commit
This commit is contained in:
commit
6c234a8084
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.exe
|
||||
.env
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Neur0toxine
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
4
README.md
Normal file
4
README.md
Normal file
@ -0,0 +1,4 @@
|
||||
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
|
123
bashim.go
Normal file
123
bashim.go
Normal file
@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
229
main.go
Normal file
229
main.go
Normal file
@ -0,0 +1,229 @@
|
||||
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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user