mirror of
https://github.com/ManyakRus/starter.git
synced 2025-11-23 22:45:11 +02:00
1023 lines
28 KiB
Go
1023 lines
28 KiB
Go
// модуль для использования Телеграмм Клиента (или бота)
|
|
package telegram_client
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"github.com/ManyakRus/starter/log"
|
|
"github.com/gotd/contrib/storage"
|
|
"github.com/gotd/td/telegram/message/peer"
|
|
"go.etcd.io/bbolt"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ManyakRus/starter/contextmain"
|
|
"github.com/ManyakRus/starter/micro"
|
|
"github.com/ManyakRus/starter/stopapp"
|
|
|
|
"github.com/gotd/td/clock"
|
|
"github.com/gotd/td/telegram/message"
|
|
"github.com/gotd/td/tg"
|
|
"github.com/gotd/td/tgerr"
|
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
|
|
"github.com/go-faster/errors"
|
|
"github.com/gotd/contrib/bg"
|
|
"github.com/gotd/td/session"
|
|
"github.com/gotd/td/telegram"
|
|
"github.com/gotd/td/telegram/auth"
|
|
"github.com/gotd/td/telegram/updates"
|
|
|
|
pebbledb "github.com/cockroachdb/pebble"
|
|
boltstor "github.com/gotd/contrib/bbolt"
|
|
"github.com/gotd/contrib/pebble"
|
|
lj "gopkg.in/natefinch/lumberjack.v2"
|
|
)
|
|
|
|
// filenameSession - имя файла сохранения сессии мессенджера Телеграм
|
|
var filenameSession string
|
|
|
|
// Client - клиент соединения мессенджера Телеграм
|
|
var Client *telegram.Client
|
|
|
|
// UserSelf - собственный юзер в Телеграм
|
|
var UserSelf *tg.User
|
|
|
|
// lastSendTime - время последней отправки сообщения и мьютекс
|
|
var lastSendTime = lastSendTimeMutex{}
|
|
|
|
//// log - глобальный логгер приложения
|
|
//var log = logger.GetLog()
|
|
|
|
// stopTelegramFunc - функция остановки соединения с мессенджером Телеграм
|
|
var stopTelegramFunc bg.StopFunc
|
|
|
|
// MAX_MESSAGE_LEN - максимальная длина сообщения
|
|
const MAX_MESSAGE_LEN = 4096
|
|
|
|
// MaxSendMessageCountIn1Second - максимальное количество сообщений в 1 секунду
|
|
var MaxSendMessageCountIn1Second float32 = 0.13 //0.13 =4 сообщения в секунду
|
|
|
|
// lastSendTimeMutex - структура хранения времени последней отправки и мьютекс
|
|
type lastSendTimeMutex struct {
|
|
time time.Time
|
|
sync.Mutex
|
|
}
|
|
|
|
// noSignUp can be embedded to prevent signing up.
|
|
type noSignUp struct{}
|
|
|
|
// Settings хранит все нужные переменные окружения
|
|
var Settings SettingsINI
|
|
|
|
// SettingsINI - структура для хранения всех нужных переменных окружения
|
|
// TELEGRAM_APP_ID, TELEGRAM_APP_HASH - первоначально получить по ссылке: https://my.telegram.org/apps
|
|
// TELEGRAM_PHONE_FROM - номер телефона с которого отправляются сообщения
|
|
type SettingsINI struct {
|
|
TELEGRAM_APP_ID int
|
|
TELEGRAM_APP_HASH string
|
|
TELEGRAM_PHONE_FROM string
|
|
TELEGRAM_PHONE_SEND_TEST string
|
|
}
|
|
|
|
// PeerDB - локальная база данных cockroachdb, для хранения кеша PeerID отправителей
|
|
var PeerDB *pebble.PeerStorage
|
|
|
|
// Func_OnNewMessage - функция из внешнего модуля, для приёма новых сообщений
|
|
var Func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error
|
|
|
|
// Contacts - список контактов в Telegram
|
|
var Contacts *tg.ContactsContacts
|
|
|
|
// MessageTelegram - сообщение из Telegram сокращённо
|
|
type MessageTelegram struct {
|
|
Text string
|
|
FromID int64
|
|
ChatID int64
|
|
IsFromMe bool
|
|
MediaType string
|
|
//NameTo string
|
|
IsGroup bool
|
|
ID int
|
|
TimeSent time.Time
|
|
}
|
|
|
|
// String - возвращает строку из структуры
|
|
func (m MessageTelegram) String() string {
|
|
Otvet := ""
|
|
|
|
Otvet = Otvet + fmt.Sprint("Text: ", m.Text, "\n")
|
|
Otvet = Otvet + fmt.Sprint("MediaType: ", m.MediaType, "\n")
|
|
Otvet = Otvet + fmt.Sprint("FromID: ", m.FromID, "\n")
|
|
Otvet = Otvet + fmt.Sprint("IsFromMe: ", m.IsFromMe, "\n")
|
|
Otvet = Otvet + fmt.Sprint("IsGroup: ", m.IsGroup, "\n")
|
|
Otvet = Otvet + fmt.Sprint("ID: ", m.ID, "\n")
|
|
Otvet = Otvet + fmt.Sprint("TimeSent: ", m.TimeSent, "\n")
|
|
|
|
return Otvet
|
|
}
|
|
|
|
// SendMessage - отправка сообщения в мессенджер Телеграм
|
|
// возвращает:
|
|
// id = id отправленного сообщения в telegram
|
|
// err = error
|
|
func SendMessage(phone_send_to string, text string) (int, error) {
|
|
var id int
|
|
|
|
if Client == nil {
|
|
CreateTelegramClient(nil)
|
|
}
|
|
|
|
if text == "" {
|
|
err := fmt.Errorf("SendMessage() text id empty ! ")
|
|
log.Error(err)
|
|
return 0, err
|
|
}
|
|
|
|
if phone_send_to == "" {
|
|
err := fmt.Errorf("SendMessage() phone_send_to id empty ! ")
|
|
log.Error(err)
|
|
return 0, err
|
|
}
|
|
|
|
TimeLimit()
|
|
log.Debug("phone_send_to: ", phone_send_to, ", text: "+text)
|
|
|
|
text = micro.SubstringLeft(text, MAX_MESSAGE_LEN)
|
|
|
|
//
|
|
api := Client.API()
|
|
|
|
//
|
|
ctxMain := context.Background()
|
|
ctx, cancel := context.WithTimeout(ctxMain, 60*time.Second)
|
|
defer cancel()
|
|
|
|
//
|
|
sender := message.NewSender(api)
|
|
target := sender.Resolve(phone_send_to)
|
|
target.NoForwards()
|
|
|
|
//отправка сообщения
|
|
UpdatesClass, err := target.Text(ctx, text)
|
|
if err != nil {
|
|
log.Error("Text() error: ", err)
|
|
return 0, err
|
|
}
|
|
|
|
//
|
|
if UpdatesClass != nil {
|
|
id = findIdFromUpdatesClass(UpdatesClass)
|
|
}
|
|
|
|
return id, err
|
|
}
|
|
|
|
// SendStyledText - отправка сообщения в мессенджер Телеграм, все слова должны быть оформлены в StyledTextOption
|
|
// возвращает:
|
|
// id = id отправленного сообщения в telegram
|
|
// err = error
|
|
func SendStyledText(phone_send_to string, texts ...message.StyledTextOption) (int, error) {
|
|
var id int
|
|
|
|
if Client == nil {
|
|
CreateTelegramClient(nil)
|
|
}
|
|
|
|
if len(texts) == 0 {
|
|
err := fmt.Errorf("SendMessage() text id empty ! ")
|
|
log.Error(err)
|
|
return 0, err
|
|
}
|
|
|
|
if phone_send_to == "" {
|
|
err := fmt.Errorf("SendMessage() phone_send_to id empty ! ")
|
|
log.Error(err)
|
|
return 0, err
|
|
}
|
|
|
|
TimeLimit()
|
|
//text := texts.
|
|
//log.Debug("phone_send_to: ", phone_send_to, ", text: "+ text)
|
|
|
|
//text = micro.SubstringLeft(text, MAX_MESSAGE_LEN)
|
|
|
|
//
|
|
api := Client.API()
|
|
|
|
//
|
|
ctxMain := context.Background()
|
|
ctx, cancel := context.WithTimeout(ctxMain, 60*time.Second)
|
|
defer cancel()
|
|
|
|
//
|
|
sender := message.NewSender(api)
|
|
target := sender.Resolve(phone_send_to)
|
|
target.NoForwards()
|
|
|
|
//отправка сообщения
|
|
UpdatesClass, err := target.StyledText(ctx, texts...)
|
|
if err != nil {
|
|
log.Error("Text() error: ", err)
|
|
return 0, err
|
|
}
|
|
|
|
//
|
|
if UpdatesClass != nil {
|
|
id = findIdFromUpdatesClass(UpdatesClass)
|
|
}
|
|
|
|
return id, err
|
|
}
|
|
|
|
// AddContact - добавляет новый контакт в список контактов Телеграм
|
|
func AddContact(ctx context.Context, phone_send_to string) error {
|
|
var err error
|
|
|
|
if phone_send_to == "" {
|
|
text1 := "phone_send_to='' !"
|
|
err := errors.New(text1)
|
|
log.Error(text1)
|
|
return err
|
|
}
|
|
|
|
TimeLimit()
|
|
|
|
api := Client.API()
|
|
|
|
//var contacts []tg.InputPhoneContact
|
|
contact := tg.InputPhoneContact{}
|
|
contact.Phone = phone_send_to
|
|
contact.FirstName = phone_send_to
|
|
|
|
contacts := make([]tg.InputPhoneContact, 1)
|
|
contacts = append(contacts, contact)
|
|
|
|
ContactsImportedContacts, err := api.ContactsImportContacts(ctx, contacts)
|
|
if ContactsImportedContacts == nil {
|
|
text1 := "ContactsImportedContacts == nil. Не удалось добавить контакт !"
|
|
err = errors.New(text1)
|
|
log.Error(text1)
|
|
} else if ContactsImportedContacts.Imported == nil {
|
|
text1 := "ContactsImportedContacts.Imported =nil. Не удалось добавить контакт !"
|
|
err = errors.New(text1)
|
|
log.Error(text1)
|
|
} else if len(ContactsImportedContacts.Imported) == 0 {
|
|
text1 := "ContactsImportedContacts.Imported len=0. Не удалось добавить контакт !"
|
|
err = errors.New(text1)
|
|
log.Error(text1)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// SignUp - обязательная функция клиента Телеграм
|
|
func (noSignUp) SignUp(context.Context) (auth.UserInfo, error) {
|
|
return auth.UserInfo{}, errors.New("not implemented")
|
|
}
|
|
|
|
// AcceptTermsOfService - обязательная функция клиента Телеграм
|
|
func (noSignUp) AcceptTermsOfService(ctx context.Context, tos tg.HelpTermsOfService) error {
|
|
if ctx == nil {
|
|
text1 := "telegramclient.AcceptTermsOfService() error: Context=nil"
|
|
return errors.New(text1)
|
|
}
|
|
|
|
return &auth.SignUpRequired{TermsOfService: tos}
|
|
}
|
|
|
|
// termAuth implements authentication via terminal.
|
|
type termAuth struct {
|
|
noSignUp
|
|
|
|
phone string
|
|
}
|
|
|
|
// Phone - обязательная функция клиента Телеграм
|
|
func (a termAuth) Phone(_ context.Context) (string, error) {
|
|
return a.phone, nil
|
|
}
|
|
|
|
// Password - обязательная функция клиента Телеграм
|
|
// ввод пароля с терминала
|
|
func (a termAuth) Password(ctx context.Context) (string, error) {
|
|
if ctx == nil {
|
|
return "", nil
|
|
}
|
|
|
|
fmt.Print("Enter 2FA password: ")
|
|
bytePwd, err := terminal.ReadPassword(0)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(string(bytePwd)), nil
|
|
}
|
|
|
|
// Code - обязательная функция клиента Телеграм
|
|
// ввод кода CODE с терминала
|
|
func (a termAuth) Code(ctx context.Context, _ *tg.AuthSentCode) (string, error) {
|
|
if ctx == nil {
|
|
return "", nil
|
|
}
|
|
|
|
//Stdin, _ := io.Pipe() //нужен т.к. не работает в тест
|
|
|
|
//r, _ := io.Pipe()
|
|
//scanner := bufio.NewScanner(r)
|
|
//msg := "Enter code from telegram: "
|
|
//fmt.Fprintln(os.Stdout, msg)
|
|
//
|
|
//scanner.Scan()
|
|
//if err := scanner.Err(); err != nil {
|
|
// log.Fatal(err)
|
|
//}
|
|
//code := scanner.Text()
|
|
//if len(code) == 0 {
|
|
// log.Fatal("empty input")
|
|
//}
|
|
|
|
fmt.Print("Enter code: ")
|
|
code, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
//code, err := bufio.NewReader(os.Stdin).ReadString('\n')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(code), nil
|
|
}
|
|
|
|
// memorySession implements in-memory session storage.
|
|
// Goroutine-safe.
|
|
type memorySession struct {
|
|
mux sync.RWMutex
|
|
data []byte
|
|
}
|
|
|
|
// LoadSession loads session from memory.
|
|
func (s *memorySession) LoadSession(context.Context) ([]byte, error) {
|
|
if s == nil {
|
|
return nil, session.ErrNotFound
|
|
}
|
|
|
|
s.mux.RLock()
|
|
defer s.mux.RUnlock()
|
|
|
|
// read the whole file at once
|
|
cpy, err := os.ReadFile(filenameSession)
|
|
if err != nil {
|
|
cpy = nil
|
|
log.Error(err)
|
|
return nil, nil
|
|
//return nil, session.ErrNotFound
|
|
}
|
|
|
|
return cpy, nil
|
|
}
|
|
|
|
// StoreSession stores session to memory.
|
|
func (s *memorySession) StoreSession(ctx context.Context, data []byte) error {
|
|
if ctx == nil {
|
|
text1 := "telegramclient.StoreSession() error: Context=nil"
|
|
return errors.New(text1)
|
|
}
|
|
|
|
s.mux.Lock()
|
|
//s.data = data
|
|
|
|
// write the whole body at once
|
|
err := os.WriteFile(filenameSession, data, 0644)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
s.mux.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// CreateTelegramClient создание клиента Телеграм, без подключения
|
|
// лучше использовать Connect() - с подключением
|
|
func CreateTelegramClient(func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error) {
|
|
err := CreateTelegramClient_err(func_OnNewMessage)
|
|
if err != nil {
|
|
log.Panic("CreateTelegramClient() error: ", err)
|
|
} else {
|
|
log.Info("CreateTelegramClient() ok, phone from: ", Settings.TELEGRAM_PHONE_FROM)
|
|
}
|
|
return
|
|
}
|
|
|
|
// CreateTelegramClient_err создание клиента Телеграм, без подключения
|
|
// лучше использовать Connect_err() - с подключением
|
|
func CreateTelegramClient_err(func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error) error {
|
|
|
|
//загрузим настройки, если они пустые
|
|
if Settings.TELEGRAM_APP_ID == 0 {
|
|
FillSettings()
|
|
}
|
|
|
|
//заполним глобальную функцию получения нового сообщения
|
|
Func_OnNewMessage = func_OnNewMessage
|
|
|
|
//
|
|
programDir := micro.ProgramDir()
|
|
|
|
//создадим логгер zap
|
|
sessionDir := programDir
|
|
LogDir := filepath.Join(sessionDir, "log")
|
|
logFilePath := filepath.Join(LogDir, "log.jsonl")
|
|
ok, err := micro.FileExists(LogDir)
|
|
if ok == false {
|
|
err = fmt.Errorf("FileExists() error: need create folder: %v", LogDir)
|
|
log.Error(err)
|
|
err := micro.CreateFolder(LogDir, 0600) //rw------- т.к. файл секретный
|
|
if err != nil {
|
|
log.Error("CreateFolder() error: ", err)
|
|
return err
|
|
}
|
|
//return err
|
|
}
|
|
|
|
if err != nil {
|
|
err = fmt.Errorf("FileExists() error: %w", err)
|
|
log.Debug(err)
|
|
return err
|
|
}
|
|
|
|
// Log to file, so we don't interfere with prompts and messages to user.
|
|
logWriter := zapcore.AddSync(&lj.Logger{
|
|
Filename: logFilePath,
|
|
MaxBackups: 3,
|
|
MaxSize: 1, // megabytes
|
|
MaxAge: 7, // days
|
|
})
|
|
logCore := zapcore.NewCore(
|
|
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()),
|
|
logWriter,
|
|
zap.DebugLevel,
|
|
)
|
|
lg := zap.New(logCore)
|
|
|
|
//настройка хранения текущей сессии в файле
|
|
sessionStorage := &telegram.FileSessionStorage{
|
|
Path: filepath.Join(programDir, "session.txt"),
|
|
}
|
|
|
|
// Peer storage, for resolve caching and short updates handling.
|
|
db, err := pebbledb.Open(filepath.Join(programDir, "peers.pebble.db"), &pebbledb.Options{})
|
|
if err != nil {
|
|
return errors.Wrap(err, "create pebble storage")
|
|
}
|
|
PeerDB = pebble.NewPeerStorage(db)
|
|
|
|
// Setting up client.
|
|
//
|
|
// Dispatcher is used to register handlers for events.
|
|
dispatcher := tg.NewUpdateDispatcher()
|
|
// Setting up update handler that will fill peer storage before
|
|
// calling dispatcher handlers.
|
|
updateHandler := storage.UpdateHook(dispatcher, PeerDB)
|
|
|
|
// Setting up persistent storage for qts/pts to be able to
|
|
// recover after restart.
|
|
boltdb, err := bbolt.Open(filepath.Join(sessionDir, "updates.bolt.db"), 0666, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "create bolt storage")
|
|
}
|
|
|
|
updatesRecovery := updates.New(updates.Config{
|
|
Handler: updateHandler, // using previous handler with PeerDB
|
|
Logger: lg.Named("updates.recovery"),
|
|
Storage: boltstor.NewStateStorage(boltdb),
|
|
})
|
|
|
|
//// Handler of FLOOD_WAIT that will automatically retry request.
|
|
//waiter := floodwait.NewWaiter().WithCallback(func(ctx context.Context, wait floodwait.FloodWait) {
|
|
// // Notifying about flood wait.
|
|
// lg.Warn("Flood wait", zap.Duration("wait", wait.Duration))
|
|
// fmt.Println("Got FLOOD_WAIT. Will retry after", wait.Duration)
|
|
//})
|
|
|
|
// Filling client options.
|
|
options := telegram.Options{
|
|
Logger: lg, // Passing logger for observability.
|
|
SessionStorage: sessionStorage, // Setting up session sessionStorage to store auth data.
|
|
UpdateHandler: updatesRecovery, // Setting up handler for updates from server.
|
|
//Middlewares: []telegram.Middleware{
|
|
// // Setting up FLOOD_WAIT handler to automatically wait and retry request.
|
|
// waiter,
|
|
// // Setting up general rate limits to less likely get flood wait errors.
|
|
// ratelimit.New(rate.Every(time.Millisecond*100), 5),
|
|
//},
|
|
}
|
|
|
|
Client = telegram.NewClient(Settings.TELEGRAM_APP_ID, Settings.TELEGRAM_APP_HASH, options)
|
|
|
|
if func_OnNewMessage != nil {
|
|
dispatcher.OnNewMessage(OnNewMessage)
|
|
}
|
|
|
|
api := Client.API()
|
|
|
|
// Setting up resolver cache that will use peer storage.
|
|
//не знаю зачем, удалить
|
|
resolver := storage.NewResolverCache(peer.Plain(api), PeerDB)
|
|
_ = resolver
|
|
|
|
return err
|
|
}
|
|
|
|
// OnNewMessage - функция для получения новых сообщений, и перенаправления во внешний Func_OnNewMessage()
|
|
// чтоб отправить сообщение надо:
|
|
// api := telegram_client.Client.API()
|
|
// sender := message.NewSender(api)
|
|
// InputPeerClass1 := Peer1.AsInputPeer()
|
|
// RequestBuilder1 := sender.To(InputPeerClass1)
|
|
// UpdateClass1, err := RequestBuilder1.Text(ctx, TextMess)
|
|
func OnNewMessage(ctx context.Context, entities tg.Entities, UpdateNewMessage1 *tg.UpdateNewMessage) error {
|
|
var err error
|
|
|
|
msg, ok := UpdateNewMessage1.Message.(*tg.Message)
|
|
if !ok {
|
|
err = fmt.Errorf("OnNewMessage() UpdateNewMessage1.Message.(*tg.Message) error")
|
|
return err
|
|
}
|
|
|
|
// Use PeerID to find peer because *Short updates does not contain any entities, so it necessary to
|
|
// store some entities.
|
|
//
|
|
// Storage can be filled using PeerCollector (i.e. fetching all dialogs first).
|
|
Peer1, err := storage.FindPeer(ctx, PeerDB, msg.GetPeerID())
|
|
if err != nil {
|
|
err = fmt.Errorf("OnNewMessage() FindPeer() error: %w", err)
|
|
return err
|
|
}
|
|
|
|
err = Func_OnNewMessage(ctx, entities, UpdateNewMessage1, Peer1)
|
|
|
|
return err
|
|
}
|
|
|
|
// OnNewMessage_Test - пример функции для получения новых сообщений
|
|
func OnNewMessage_Test(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage) error {
|
|
var err error
|
|
|
|
m, ok := u.Message.(*tg.Message)
|
|
if !ok || m.Out {
|
|
// Outgoing message, not interesting.
|
|
return nil
|
|
}
|
|
|
|
// тестовый пример эхо
|
|
// Helper for sending messages.
|
|
api := Client.API()
|
|
sender := message.NewSender(api)
|
|
|
|
// Sending reply.
|
|
_, err = sender.Reply(entities, u).Text(ctx, m.Message)
|
|
|
|
return err
|
|
}
|
|
|
|
// TimeLimit пауза для ограничения количество сообщений в секунду
|
|
func TimeLimit() {
|
|
//if MaxSendMessageCountIn1Second == 0 {
|
|
// return
|
|
//}
|
|
|
|
lastSendTime.Lock()
|
|
defer lastSendTime.Unlock()
|
|
|
|
if lastSendTime.time.IsZero() {
|
|
lastSendTime.time = time.Now()
|
|
return
|
|
}
|
|
|
|
t := time.Now()
|
|
ms := int(t.Sub(lastSendTime.time).Milliseconds())
|
|
msNeedWait := int(1000 / MaxSendMessageCountIn1Second)
|
|
if ms < msNeedWait {
|
|
micro.Sleep(msNeedWait - ms)
|
|
}
|
|
|
|
lastSendTime.time = time.Now()
|
|
}
|
|
|
|
// Connect подключение к серверу Телеграм, паника при ошибке
|
|
func Connect(func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error) {
|
|
err := Connect_err(func_OnNewMessage)
|
|
LogInfo_Connected(err)
|
|
}
|
|
|
|
// LogInfo_Connected - выводит сообщение в Лог, или паника при ошибке
|
|
func LogInfo_Connected(err error) {
|
|
if err != nil {
|
|
TextError := fmt.Sprint("Telegram connection: ", Settings.TELEGRAM_PHONE_FROM, ", error: ", err)
|
|
log.Panic(TextError)
|
|
} else {
|
|
log.Info("Telegram connected: ", Settings.TELEGRAM_PHONE_FROM)
|
|
}
|
|
|
|
}
|
|
|
|
// Connect_err подключение к серверу Телеграм
|
|
func Connect_err(func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error) error {
|
|
|
|
//создаём клиент Telegram
|
|
CreateTelegramClient(func_OnNewMessage)
|
|
|
|
//
|
|
ctxMain := context.Background()
|
|
//ctxMain := contextmain.GetContext()
|
|
ctx, cancel := context.WithTimeout(ctxMain, 60*time.Second) //60
|
|
defer cancel()
|
|
|
|
bg.WithContext(ctx)
|
|
var err error
|
|
//Option := bg.WithContext(ctx)
|
|
stopTelegramFunc, err = bg.Connect(Client)
|
|
if err != nil {
|
|
return err
|
|
//log.Fatalln("Can not connect to Telegram ! Error: ", err)
|
|
}
|
|
|
|
micro.Sleep(100) //не успевает
|
|
//for i := 1; i <= 5; i++ {
|
|
// err = Client.Ping(ctx)
|
|
// if err != nil {
|
|
// micro.Sleep(1000)
|
|
// }
|
|
//}
|
|
|
|
//fmt.Println("Client: ", Client)
|
|
|
|
//
|
|
flow := auth.NewFlow(
|
|
termAuth{phone: Settings.TELEGRAM_PHONE_FROM},
|
|
auth.SendCodeOptions{},
|
|
)
|
|
|
|
if err := Client.Auth().IfNecessary(ctx, flow); err != nil {
|
|
return err
|
|
}
|
|
|
|
//заполним UserSelf
|
|
UserSelf, err = Client.Self(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// findIdFromUpdatesClass - возвращает id сообщения из ответа Телеграм сервера
|
|
func findIdFromUpdatesClass(UpdatesClass tg.UpdatesClass) int {
|
|
var id int
|
|
|
|
switch v := UpdatesClass.(type) {
|
|
case *tg.UpdatesTooLong: // updatesTooLong#e317af7e
|
|
case *tg.UpdateShortMessage: // updateShortMessage#313bc7f8
|
|
case *tg.UpdateShortChatMessage: // updateShortChatMessage#4d6deea5
|
|
case *tg.UpdateShort: // updateShort#78d4dec1
|
|
case *tg.UpdatesCombined: // updatesCombined#725b04c3
|
|
case *tg.Updates: // updates#74ae4240
|
|
UpdatesClass1 := UpdatesClass.(*tg.Updates)
|
|
for _, row1 := range UpdatesClass1.Updates {
|
|
switch row1.(type) {
|
|
case *tg.UpdateMessageID:
|
|
{
|
|
rowV := row1.(*tg.UpdateMessageID)
|
|
id = rowV.ID
|
|
}
|
|
case *tg.UpdateNewMessage:
|
|
{
|
|
rowV := row1.(*tg.UpdateNewMessage)
|
|
MessageV := rowV.Message.(*tg.Message)
|
|
//is_sent = MessageV.Out
|
|
if id == 0 {
|
|
id = MessageV.ID
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
case *tg.UpdateShortSentMessage: // updateShortSentMessage#9015e101
|
|
UpdatesClass1 := UpdatesClass.(*tg.UpdateShortSentMessage)
|
|
id = UpdatesClass1.ID
|
|
default:
|
|
log.Fatalln("Wrong type: ", v)
|
|
}
|
|
|
|
return id
|
|
}
|
|
|
|
// FindMessageByID - находит сообщение на сервере Телеграм по id
|
|
func FindMessageByID(ctx context.Context, id int) (*tg.Message, error) {
|
|
var Otvet *tg.Message
|
|
|
|
if id == 0 {
|
|
text1 := "telegramclient.FindMessageByID() id=0 !"
|
|
err := errors.New(text1)
|
|
return Otvet, err
|
|
}
|
|
|
|
api := Client.API()
|
|
|
|
var IMC []tg.InputMessageClass
|
|
IMC = append(IMC, &tg.InputMessageID{ID: id})
|
|
|
|
MMC, err := api.MessagesGetMessages(ctx, IMC)
|
|
if err != nil {
|
|
return Otvet, err
|
|
}
|
|
|
|
if MMC == nil {
|
|
return Otvet, err
|
|
}
|
|
|
|
MMCV := MMC.(*tg.MessagesMessages)
|
|
Messages := MMCV.Messages
|
|
for _, v := range Messages {
|
|
Otvet = v.(*tg.Message)
|
|
//Otvet.MediaUnread
|
|
}
|
|
|
|
return Otvet, err
|
|
}
|
|
|
|
// WaitStop - ожидает отмену глобального контекста
|
|
func WaitStop() {
|
|
//stopapp.GetWaitGroup_Main().Add(1)
|
|
select {
|
|
case <-contextmain.GetContext().Done():
|
|
log.Warn("Context app is canceled.")
|
|
}
|
|
|
|
//
|
|
stopapp.WaitTotalMessagesSendingNow("telegram")
|
|
|
|
//
|
|
CloseConnection()
|
|
|
|
//
|
|
stopapp.GetWaitGroup_Main().Done()
|
|
}
|
|
|
|
// CloseConnection - остановка работы клиента Телеграм
|
|
func CloseConnection() {
|
|
var err error
|
|
|
|
if stopTelegramFunc != nil {
|
|
err := stopTelegramFunc()
|
|
if err != nil {
|
|
log.Error("error: ", err)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
log.Error("Telegram client CloseConnection() error: ", err)
|
|
} else {
|
|
log.Info("Telegram client connection closed")
|
|
}
|
|
|
|
}
|
|
|
|
// FloodWait sleeps required duration and returns true if err is FLOOD_WAIT
|
|
// or false and context or original error otherwise.
|
|
func FloodWait(ctx context.Context, err error) bool {
|
|
otvet := false
|
|
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
sec, ok := AsFloodWait(err)
|
|
if ok {
|
|
otvet = true
|
|
log.Debug("isFlood sec: ", sec)
|
|
|
|
var duration time.Duration
|
|
duration = time.Second * time.Duration(sec)
|
|
timer := clock.System.Timer(duration)
|
|
defer clock.StopTimer(timer)
|
|
|
|
select {
|
|
case <-timer.C():
|
|
return otvet
|
|
case <-ctx.Done():
|
|
return otvet
|
|
}
|
|
} else {
|
|
log.Warn("AsFloodWait() ok =false")
|
|
}
|
|
|
|
return otvet
|
|
}
|
|
|
|
// AsFloodWait returns wait duration and true boolean if err is
|
|
// the "FLOOD_WAIT" error.
|
|
//
|
|
// Client should wait for that duration before issuing new requests with
|
|
// same method.
|
|
func AsFloodWait(err error) (d int, ok bool) {
|
|
rpcErr, ok := tgerr.AsType(err, tgerr.ErrFloodWait)
|
|
if ok {
|
|
return rpcErr.Argument, true
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// StartTelegram - подключается к телеграмму, запускает остановку приложения.
|
|
// func_OnNewMessage - функция для приёма новых сообщений
|
|
func StartTelegram(func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error) {
|
|
var err error
|
|
|
|
ctx := contextmain.GetContext()
|
|
WaitGroup := stopapp.GetWaitGroup_Main()
|
|
err = Start_ctx(&ctx, WaitGroup, func_OnNewMessage)
|
|
LogInfo_Connected(err)
|
|
|
|
}
|
|
|
|
// Start_ctx - необходимые процедуры для подключения к серверу Telegram
|
|
// Свой контекст и WaitGroup нужны для остановки работы сервиса Graceful shutdown
|
|
// Для тех кто пользуется этим репозиторием для старта и останова сервиса можно просто StartTelegram()
|
|
func Start_ctx(ctx *context.Context, WaitGroup *sync.WaitGroup, func_OnNewMessage func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage, Peer1 storage.Peer) error) error {
|
|
var err error
|
|
|
|
//запомним к себе контекст
|
|
contextmain.Ctx = ctx
|
|
if ctx == nil {
|
|
contextmain.GetContext()
|
|
}
|
|
|
|
//запомним к себе WaitGroup
|
|
stopapp.SetWaitGroup_Main(WaitGroup)
|
|
if WaitGroup == nil {
|
|
stopapp.StartWaitStop()
|
|
}
|
|
|
|
//
|
|
CreateTelegramClient(func_OnNewMessage)
|
|
|
|
err = Connect_err(func_OnNewMessage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stopapp.GetWaitGroup_Main().Add(1)
|
|
go WaitStop()
|
|
|
|
return err
|
|
}
|
|
|
|
// FillSettings загружает переменные окружения в структуру из переменных окружения
|
|
func FillSettings() {
|
|
Settings = SettingsINI{}
|
|
Settings.TELEGRAM_APP_ID, _ = strconv.Atoi(os.Getenv("TELEGRAM_APP_ID"))
|
|
Settings.TELEGRAM_APP_HASH = os.Getenv("TELEGRAM_APP_HASH")
|
|
Settings.TELEGRAM_PHONE_FROM = os.Getenv("TELEGRAM_PHONE_FROM")
|
|
Settings.TELEGRAM_PHONE_SEND_TEST = os.Getenv("TELEGRAM_PHONE_SEND_TEST")
|
|
|
|
if Settings.TELEGRAM_APP_ID == 0 {
|
|
log.Panicln("Need fill TELEGRAM_APP_ID ! in os.ENV ")
|
|
}
|
|
|
|
if Settings.TELEGRAM_APP_HASH == "" {
|
|
log.Panicln("Need fill TELEGRAM_APP_HASH ! in os.ENV ")
|
|
}
|
|
|
|
if Settings.TELEGRAM_PHONE_FROM == "" {
|
|
log.Panicln("Need fill TELEGRAM_PHONE_FROM ! in os.ENV ")
|
|
}
|
|
|
|
if Settings.TELEGRAM_PHONE_SEND_TEST == "" && micro.IsTestApp() == true {
|
|
log.Info("Need fill TELEGRAM_PHONE_SEND_TEST ! in os.ENV ")
|
|
}
|
|
|
|
}
|
|
|
|
// FillMessageTelegramFromMessage - заполнение струткру MessageTelegram из сообщения от Telegram
|
|
func FillMessageTelegramFromMessage(m *tg.Message) MessageTelegram {
|
|
Otvet := MessageTelegram{}
|
|
|
|
//не подключен
|
|
if Client == nil {
|
|
return Otvet
|
|
}
|
|
|
|
////не подключен
|
|
//if stopTelegramFunc == nil {
|
|
// return Otvet
|
|
//}
|
|
|
|
//не подключен
|
|
if UserSelf == nil {
|
|
return Otvet
|
|
}
|
|
|
|
//
|
|
//ctxMain := contextmain.GetContext()
|
|
//ctx, cancel_func := context.WithTimeout(ctxMain, 60*time.Second) //60
|
|
//defer cancel_func()
|
|
IsGroup := false
|
|
|
|
Otvet.Text = m.Message
|
|
Otvet.ID = m.ID
|
|
Otvet.MediaType = m.TypeName()
|
|
TimeInt := m.GetDate()
|
|
Otvet.TimeSent = time.UnixMilli(int64(TimeInt * 1000))
|
|
var ChatID int64
|
|
|
|
if m.PeerID != nil && micro.IsNilInterface(m.PeerID) == false {
|
|
switch v := m.PeerID.(type) {
|
|
case *tg.PeerUser:
|
|
ChatID = v.UserID
|
|
case *tg.PeerChat:
|
|
{
|
|
ChatID = v.ChatID
|
|
IsGroup = true
|
|
}
|
|
case *tg.PeerChannel:
|
|
{
|
|
ChatID = v.ChannelID
|
|
IsGroup = true
|
|
}
|
|
default:
|
|
{
|
|
IsGroup = true
|
|
}
|
|
}
|
|
}
|
|
Otvet.ChatID = ChatID
|
|
|
|
MyID := UserSelf.ID
|
|
var SenderID int64
|
|
|
|
IsFromMe := false
|
|
if m.FromID != nil && micro.IsNilInterface(m.FromID) == false {
|
|
switch v := m.FromID.(type) {
|
|
case *tg.PeerUser:
|
|
{
|
|
SenderID = v.UserID
|
|
}
|
|
//case *tg.PeerChat: // peerChat#36c6019a
|
|
//case *tg.PeerChannel: // peerChannel#a2a5371e
|
|
default:
|
|
}
|
|
} else {
|
|
if m.PeerID != nil && micro.IsNilInterface(m.PeerID) == false {
|
|
switch v := m.PeerID.(type) {
|
|
case *tg.PeerUser:
|
|
{
|
|
if v.UserID == MyID {
|
|
IsFromMe = true
|
|
SenderID = MyID
|
|
} else {
|
|
SenderID = v.UserID
|
|
}
|
|
}
|
|
//case *tg.PeerChat: // peerChat#36c6019a
|
|
//case *tg.PeerChannel: // peerChannel#a2a5371e
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
Otvet.IsGroup = IsGroup //m.GroupedID != 0
|
|
|
|
//if MyID == SenderID {
|
|
// IsFromMe = true
|
|
//}
|
|
Otvet.IsFromMe = IsFromMe
|
|
Otvet.FromID = SenderID
|
|
|
|
return Otvet
|
|
}
|
|
|
|
// GetContactsAll - обновляет список контактов
|
|
func GetContactsAll() {
|
|
ctxMain := contextmain.GetContext()
|
|
ctx, cancel_func := context.WithTimeout(ctxMain, time.Second*60)
|
|
defer cancel_func()
|
|
IContacts, err := Client.API().ContactsGetContacts(ctx, 0)
|
|
if err != nil {
|
|
log.Error("GetContactsAll() error: ", err)
|
|
return
|
|
}
|
|
ok := false
|
|
Contacts, ok = IContacts.(*tg.ContactsContacts)
|
|
if ok == false {
|
|
log.Error("IContacts() error: ", err)
|
|
return
|
|
}
|
|
log.Debug("Contacts len: ", len(Contacts.Contacts))
|
|
}
|