Add Telegram integration

This commit is contained in:
三三 2021-09-08 11:04:22 +08:00 committed by GitHub
parent 93596c1218
commit 34dd358eb0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 173 additions and 4 deletions

View File

@ -546,4 +546,13 @@ var migrations = []func(tx *sql.Tx) error{
`)
return err
},
func(tx *sql.Tx) (err error) {
sql := `
ALTER TABLE integrations ADD COLUMN telegram_bot_enabled bool default 'f';
ALTER TABLE integrations ADD COLUMN telegram_bot_token text default '';
ALTER TABLE integrations ADD COLUMN telegram_bot_chat_id text default '';
`
_, err = tx.Exec(sql)
return err
},
}

2
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/PuerkitoBio/goquery v1.7.1
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/golang/gddo v0.0.0-20200831202555-721e228c7686 // indirect
github.com/gorilla/mux v1.8.0
github.com/lib/pq v1.10.2
@ -15,6 +16,7 @@ require (
github.com/rylans/getlang v0.0.0-20200505200108-4c3188ff8a2d
github.com/stretchr/testify v1.6.1 // indirect
github.com/tdewolff/minify/v2 v2.9.21
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d

4
go.sum
View File

@ -61,6 +61,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.6.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
@ -227,6 +229,8 @@ github.com/tdewolff/parse/v2 v2.5.19 h1:Kjaj3KQOx/4elIxlBSglus4E2oMfdROphvbq2b+O
github.com/tdewolff/parse/v2 v2.5.19/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=

View File

@ -10,6 +10,7 @@ import (
"miniflux.app/integration/nunuxkeeper"
"miniflux.app/integration/pinboard"
"miniflux.app/integration/pocket"
"miniflux.app/integration/telegrambot"
"miniflux.app/integration/wallabag"
"miniflux.app/logger"
"miniflux.app/model"
@ -70,3 +71,15 @@ func SendEntry(entry *model.Entry, integration *model.Integration) {
}
}
}
// PushEntry pushes new entry to the activated providers.
// This function should be wrapped in a goroutine to avoid block of program execution.
func PushEntry(entry *model.Entry, integration *model.Integration) {
if integration.TelegramBotEnabled {
logger.Debug("[Integration] Sending Entry #%d for User #%d to telegram", entry.ID, integration.UserID)
err := telegrambot.PushEntry(entry, integration.TelegramBotToken, integration.TelegramBotChatID)
if err != nil {
logger.Error("[Integration] push entry to telegram bot failed: %v", err)
}
}
}

View File

@ -0,0 +1,2 @@
// Package telegrambot provides a simple entry-to-telegram push
package telegrambot

View File

@ -0,0 +1,41 @@
package telegrambot
import (
"bytes"
"fmt"
"html/template"
"strconv"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"miniflux.app/model"
)
// PushEntry pushes entry to telegram chat using integration settings provided
func PushEntry(entry *model.Entry, botToken, chatID string) error {
bot, err := tgbotapi.NewBotAPI(botToken)
if err != nil {
return fmt.Errorf("telegrambot: create bot failed: %w", err)
}
t, err := template.New("message").Parse("{{ .Title }}\n<a href=\"{{ .URL }}\">{{ .URL }}</a>")
if err != nil {
return fmt.Errorf("telegrambot: parse template failed: %w", err)
}
var result bytes.Buffer
err = t.Execute(&result, entry)
if err != nil {
return fmt.Errorf("telegrambot: execute template failed: %w", err)
}
chatId, _ := strconv.ParseInt(chatID, 10, 64)
msg := tgbotapi.NewMessage(chatId, result.String())
msg.ParseMode = tgbotapi.ModeHTML
msg.DisableWebPagePreview = false
if _, err := bot.Send(msg); err != nil {
return fmt.Errorf("telegrambot: send message failed: %w", err)
}
return nil
}

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Artikel in Nunux Keeper speichern",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API-Endpunkt",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API-Schlüssel",
"form.integration.telegram_bot_activate": "Pushen Sie neue Artikel in den Telegram-Chat",
"form.integration.telegram_bot_token": "Bot token",
"form.integration.telegram_chat_id": "Chat ID",
"form.api_key.label.description": "API-Schlüsselbezeichnung",
"form.submit.loading": "Lade...",
"form.submit.saving": "Speichern...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Αποθήκευση άρθρων στο Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Τελικό σημείο Nunux Keeper API",
"form.integration.nunux_keeper_api_key": "Κλειδί API Nunux Keeper",
"form.integration.telegram_bot_activate": "Προωθήστε νέα άρθρα στη συνομιλία Telegram",
"form.integration.telegram_bot_token": "Διακριτικό bot",
"form.integration.telegram_chat_id": "Αναγνωριστικό συνομιλίας",
"form.api_key.label.description": "Ετικέτα κλειδιού API",
"form.submit.loading": "Φόρτωση...",
"form.submit.saving": "Αποθήκευση...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Save articles to Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API Endpoint",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API key",
"form.integration.telegram_bot_activate": "Push new articles to Telegram chat",
"form.integration.telegram_bot_token": "Bot token",
"form.integration.telegram_chat_id": "Chat ID",
"form.api_key.label.description": "API Key Label",
"form.submit.loading": "Loading...",
"form.submit.saving": "Saving...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Guardar artículos a Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Extremo de API de Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Clave de API de Nunux Keeper",
"form.integration.telegram_bot_activate": "Envíe nuevos artículos al chat de Telegram",
"form.integration.telegram_bot_token": "Token de bot",
"form.integration.telegram_chat_id": "ID de chat",
"form.api_key.label.description": "Etiqueta de clave API",
"form.submit.loading": "Cargando...",
"form.submit.saving": "Guardando...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Sauvegarder les articles vers Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "URL de l'API de Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Clé d'API de Nunux Keeper",
"form.integration.telegram_bot_activate": "Envoyer les nouveaux articles vers Telegram",
"form.integration.telegram_bot_token": "Jeton de sécurité de l'API du Bot Telegram",
"form.integration.telegram_chat_id": "Identifiant de discussion",
"form.api_key.label.description": "Libellé de la clé d'API",
"form.submit.loading": "Chargement...",
"form.submit.saving": "Sauvegarde en cours...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Salva gli articoli su Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Endpoint dell'API di Nunux Keeper",
"form.integration.nunux_keeper_api_key": "API key dell'account Nunux Keeper",
"form.integration.telegram_bot_activate": "Invia nuovi articoli alla chat di Telegram",
"form.integration.telegram_bot_token": "Token bot",
"form.integration.telegram_chat_id": "ID chat",
"form.api_key.label.description": "Etichetta chiave API",
"form.submit.loading": "Caricamento in corso...",
"form.submit.saving": "Salvataggio in corso...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Nunux Keeper に記事を保存する",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper の API Endpoint",
"form.integration.nunux_keeper_api_key": "Nunux Keeper の API key",
"form.integration.telegram_bot_activate": "新しい記事をTelegramチャットにプッシュする",
"form.integration.telegram_bot_token": "ボットトークン",
"form.integration.telegram_chat_id": "チャットID",
"form.api_key.label.description": "APIキーラベル",
"form.submit.loading": "読み込み中…",
"form.submit.saving": "保存中…",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Opslaan naar Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper URL",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API-sleutel",
"form.integration.telegram_bot_activate": "Push nieuwe artikelen naar Telegram-chat",
"form.integration.telegram_bot_token": "Bot token",
"form.integration.telegram_chat_id": "Chat ID",
"form.api_key.label.description": "API-sleutellabel",
"form.submit.loading": "Laden...",
"form.submit.saving": "Opslaag...",

View File

@ -327,6 +327,9 @@
"form.integration.nunux_keeper_activate": "Zapisz artykuly do Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper URL",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API key",
"form.integration.telegram_bot_activate": "Przesyłaj nowe artykuły do czatu Telegram",
"form.integration.telegram_bot_token": "Token bota",
"form.integration.telegram_chat_id": "Identyfikator czatu",
"form.api_key.label.description": "Etykieta klucza API",
"form.submit.loading": "Ładowanie...",
"form.submit.saving": "Zapisywanie...",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Salvar itens no Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Endpoint de API do Nunux Keeper",
"form.integration.nunux_keeper_api_key": "Chave de API do Nunux Keeper",
"form.integration.telegram_bot_activate": "Envie novos artigos para o chat do Telegram",
"form.integration.telegram_bot_token": "Token de bot",
"form.integration.telegram_chat_id": "ID de bate-papo",
"form.api_key.label.description": "Etiqueta da chave de API",
"form.submit.loading": "Carregando...",
"form.submit.saving": "Salvando...",

View File

@ -327,6 +327,9 @@
"form.integration.nunux_keeper_activate": "Сохранять статьи в Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Конечная точка Nunux Keeper API",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API Key",
"form.integration.telegram_bot_activate": "Публикуйте новые статьи в Telegram-чате",
"form.integration.telegram_bot_token": "Токен бота",
"form.integration.telegram_chat_id": "ID чата",
"form.api_key.label.description": "Описание API-ключа",
"form.submit.loading": "Загрузка…",
"form.submit.saving": "Сохранение…",

View File

@ -325,6 +325,9 @@
"form.integration.nunux_keeper_activate": "Makaleleri Nunux Keeper'a kaydet",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API Uç Noktası",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API anahtarı",
"form.integration.telegram_bot_activate": "Yeni makaleleri Telegram sohbetine gönderin",
"form.integration.telegram_bot_token": "Bot jetonu",
"form.integration.telegram_chat_id": "Sohbet kimliği",
"form.api_key.label.description": "API Anahtar Etiketi",
"form.submit.loading": "Yükleniyor...",
"form.submit.saving": "Kaydediliyor...",

View File

@ -323,7 +323,10 @@
"form.integration.nunux_keeper_activate": "保存文章到 Nunux Keeper",
"form.integration.nunux_keeper_endpoint": "Nunux Keeper API 端点",
"form.integration.nunux_keeper_api_key": "Nunux Keeper API 密钥",
"form.api_key.label.description": "API 密钥标签",
"form.integration.telegram_bot_activate": "将新文章推送到 Telegram",
"form.integration.telegram_bot_token": "机器人令牌",
"form.integration.telegram_chat_id": "聊天ID",
"form.api_key.label.description": "API密钥标签",
"form.submit.loading": "载入中…",
"form.submit.saving": "保存中…",
"time_elapsed.not_yet": "未来",

View File

@ -29,4 +29,7 @@ type Integration struct {
PocketEnabled bool
PocketAccessToken string
PocketConsumerKey string
TelegramBotEnabled bool
TelegramBotToken string
TelegramBotChatID string
}

View File

@ -14,6 +14,8 @@ import (
"time"
"unicode/utf8"
"miniflux.app/integration"
"miniflux.app/config"
"miniflux.app/http/client"
"miniflux.app/logger"
@ -80,6 +82,18 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed) {
// The sanitizer should always run at the end of the process to make sure unsafe HTML is filtered.
entry.Content = sanitizer.Sanitize(entry.URL, entry.Content)
if entryIsNew {
intg, err := store.Integration(feed.UserID)
if err != nil {
logger.Error("[Processor] Get integrations for user %d failed: %v; the refresh process will go on, but no integrations will run this time.", feed.UserID, err)
} else if intg != nil {
localEntry := entry
go func() {
integration.PushEntry(localEntry, intg)
}()
}
}
updateEntryReadingTime(store, feed, entry, entryIsNew)
filteredEntries = append(filteredEntries, entry)
}

View File

@ -68,7 +68,10 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) {
nunux_keeper_api_key,
pocket_enabled,
pocket_access_token,
pocket_consumer_key
pocket_consumer_key,
telegram_bot_enabled,
telegram_bot_token,
telegram_bot_chat_id
FROM
integrations
WHERE
@ -99,6 +102,9 @@ func (s *Storage) Integration(userID int64) (*model.Integration, error) {
&integration.PocketEnabled,
&integration.PocketAccessToken,
&integration.PocketConsumerKey,
&integration.TelegramBotEnabled,
&integration.TelegramBotToken,
&integration.TelegramBotChatID,
)
switch {
case err == sql.ErrNoRows:
@ -137,9 +143,12 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error {
nunux_keeper_api_key=$19,
pocket_enabled=$20,
pocket_access_token=$21,
pocket_consumer_key=$22
pocket_consumer_key=$22,
telegram_bot_enabled=$23,
telegram_bot_token=$24,
telegram_bot_chat_id=$25
WHERE
user_id=$23
user_id=$26
`
_, err := s.db.Exec(
query,
@ -165,6 +174,9 @@ func (s *Storage) UpdateIntegration(integration *model.Integration) error {
integration.PocketEnabled,
integration.PocketAccessToken,
integration.PocketConsumerKey,
integration.TelegramBotEnabled,
integration.TelegramBotToken,
integration.TelegramBotChatID,
integration.UserID,
)

View File

@ -136,6 +136,24 @@
</div>
</div>
<h3>Telegram Bot</h3>
<div class="form-section">
<label>
<input type="checkbox" name="telegram_bot_enabled" value="1" {{ if .form.TelegramBotEnabled }}checked{{ end }}> {{ t "form.integration.telegram_bot_activate" }}
</label>
<label for="form-telegram-bot-token">{{ t "form.integration.telegram_bot_token" }}</label>
<input type="text" name="telegram_bot_token" id="form-telegram-bot-token" value="{{ .form.TelegramBotToken }}" placeholder="bot123456:Abcdefg" spellcheck="false">
<label for="form-telegram-chat-id">{{ t "form.integration.telegram_chat_id" }}</label>
<input type="text" name="telegram_bot_chat_id" id="form-telegram-chat-id" value="{{ .form.TelegramBotChatID }}" spellcheck="false">
<div class="buttons">
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
</div>
</div>
</form>
<h3>{{ t "page.integration.bookmarklet" }}</h3>

View File

@ -34,6 +34,9 @@ type IntegrationForm struct {
PocketEnabled bool
PocketAccessToken string
PocketConsumerKey string
TelegramBotEnabled bool
TelegramBotToken string
TelegramBotChatID string
}
// Merge copy form values to the model.
@ -59,6 +62,9 @@ func (i IntegrationForm) Merge(integration *model.Integration) {
integration.PocketEnabled = i.PocketEnabled
integration.PocketAccessToken = i.PocketAccessToken
integration.PocketConsumerKey = i.PocketConsumerKey
integration.TelegramBotEnabled = i.TelegramBotEnabled
integration.TelegramBotToken = i.TelegramBotToken
integration.TelegramBotChatID = i.TelegramBotChatID
}
// NewIntegrationForm returns a new AuthForm.
@ -86,5 +92,8 @@ func NewIntegrationForm(r *http.Request) *IntegrationForm {
PocketEnabled: r.FormValue("pocket_enabled") == "1",
PocketAccessToken: r.FormValue("pocket_access_token"),
PocketConsumerKey: r.FormValue("pocket_consumer_key"),
TelegramBotEnabled: r.FormValue("telegram_bot_enabled") == "1",
TelegramBotToken: r.FormValue("telegram_bot_token"),
TelegramBotChatID: r.FormValue("telegram_bot_chat_id"),
}
}

View File

@ -50,6 +50,9 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) {
PocketEnabled: integration.PocketEnabled,
PocketAccessToken: integration.PocketAccessToken,
PocketConsumerKey: integration.PocketConsumerKey,
TelegramBotEnabled: integration.TelegramBotEnabled,
TelegramBotToken: integration.TelegramBotToken,
TelegramBotChatID: integration.TelegramBotChatID,
}
sess := session.New(h.store, request.SessionID(r))