mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-15 00:56:08 +00:00
eec030f86f
* feat(notifications): event bus architecture with Telegram and SMTP subscribers
- Event bus core with buffered channel, fan-out, panic recovery
- Telegram subscriber with HTML formatting and rate limiting
- Email subscriber with SMTP/TLS/STARTTLS support and stage diagnostics
- 5 event types: outbound.down/up, xray.crash, cpu.high, login.attempt
- CPU threshold checks per subscriber (tgCpu for TG, smtpCpu for Email)
- SystemMetricData struct for raw metric values in events
- i18n keys for en-US, ru-RU, and English defaults for other locales
* fix
* fix(notifications): repair crash/CPU alerts, harden secrets, add node alerts
Bug fixes:
- Xray crash notifications were permanently suppressed after the first crash:
XrayStateTracker latched state="down" with no reset and no recovery event,
so only the first crash per process lifetime ever notified. Removed the
tracker; the existing 1/min rate limiter already dedupes crash-loop spam.
- Email CPU alerts could never fire unless Telegram was also enabled, because
the CPU job was registered only inside the tgbot block. Register it whenever
either Telegram or SMTP wants cpu.high (new cpuAlarmWanted gate) and relax
the cadence to @every 1m (cpu.Percent already samples over a full minute).
- SMTP password (and, pre-existing, all other secrets) were shipped to the
browser in plaintext: GetAllSettingView was dead code and /setting/all
returned the raw model. Wire getAllSetting -> GetAllSettingView, redact
smtpPassword with a hasSmtpPassword presence flag, and preserve it on blank
save. Closes the leak for tgBotToken/ldapPassword/2FA token too.
Polish:
- email Send: use nil SMTP auth when no credentials (Go refuses PlainAuth over
the unencrypted "none" transport).
- Remove unused EventClientDepleted; fix inaccurate bus.go doc comments; drop
stale tgBotLoginNotify from the frontend schema; gofmt alignment.
Feature - node online/offline alerts:
- Emit node.down/node.up from the heartbeat job on a real status transition
(with a startup-spam guard), reusing NodeHealthData. Formatted by both the
Telegram and email subscribers and selectable in the settings UI.
Regenerated frontend types (hasSmtpPassword). New i18n keys added to en-US;
other locales fall back to English (bundle default) until translated.
* fix(settings): use antd Space orientation instead of deprecated direction
Ant Design 6 deprecated Space's `direction` prop in favor of `orientation`,
which logged a console warning from the Telegram/Email notification tabs. Brings
these two tabs in line with the rest of the codebase, which already uses
`orientation`.
* i18n(notifications): translate the notification feature into all locales
The notifications PR shipped ~99 new strings (SMTP settings, event labels,
Telegram/email message templates) as English placeholders in every non-English
locale. Translate them — plus the node-alert keys added during this review —
into all 12 locales: Arabic, Spanish, Persian, Indonesian, Japanese,
Portuguese-BR, Russian, Turkish, Ukrainian, Vietnamese, and Simplified/
Traditional Chinese.
Go-template placeholders ({{ .Tag }}, {{ .Name }}, etc.) are preserved exactly;
tgbot message values carry no leading status emoji (the bot/email code adds
those, so an emoji in the value would duplicate it); product/protocol names
(SMTP, STARTTLS, TLS, CPU, Xray, Telegram) are kept as-is.
---------
Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
490 lines
16 KiB
Go
490 lines
16 KiB
Go
package tgbot
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/config"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/eventbus"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/web/service"
|
|
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
|
|
|
"github.com/mymmrac/telego"
|
|
tu "github.com/mymmrac/telego/telegoutil"
|
|
)
|
|
|
|
// SendReport sends a periodic report to admin chats.
|
|
func (t *Tgbot) SendReport() {
|
|
runTime, err := t.settingService.GetTgbotRuntime()
|
|
if err == nil && len(runTime) > 0 {
|
|
msg := ""
|
|
msg += t.I18nBot("tgbot.messages.report", "RunTime=="+runTime)
|
|
msg += t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
t.SendMsgToTgbotAdmins(msg)
|
|
}
|
|
|
|
info := t.sendServerUsage()
|
|
t.SendMsgToTgbotAdmins(info)
|
|
|
|
t.sendExhaustedToAdmins()
|
|
t.notifyExhausted()
|
|
|
|
backupEnable, err := t.settingService.GetTgBotBackup()
|
|
if err == nil && backupEnable {
|
|
t.SendBackupToAdmins()
|
|
}
|
|
}
|
|
|
|
// SendBackupToAdmins sends a database backup to admin chats.
|
|
func (t *Tgbot) SendBackupToAdmins() {
|
|
if !t.IsRunning() {
|
|
return
|
|
}
|
|
for i, adminId := range adminIds {
|
|
t.sendBackup(int64(adminId))
|
|
// Add delay between sends to avoid Telegram rate limits
|
|
if i < len(adminIds)-1 {
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
}
|
|
}
|
|
|
|
// sendExhaustedToAdmins sends notifications about exhausted clients to admins.
|
|
func (t *Tgbot) sendExhaustedToAdmins() {
|
|
if !t.IsRunning() {
|
|
return
|
|
}
|
|
for _, adminId := range adminIds {
|
|
t.getExhausted(int64(adminId))
|
|
}
|
|
}
|
|
|
|
// getServerUsage retrieves and formats server usage information.
|
|
func (t *Tgbot) getServerUsage(chatId int64, messageID ...int) string {
|
|
info := t.prepareServerUsageInfo()
|
|
|
|
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("usage_refresh"))))
|
|
|
|
if len(messageID) > 0 {
|
|
t.editMessageTgBot(chatId, messageID[0], info, keyboard)
|
|
} else {
|
|
t.SendMsgToTgbot(chatId, info, keyboard)
|
|
}
|
|
|
|
return info
|
|
}
|
|
|
|
// Send server usage without an inline keyboard
|
|
func (t *Tgbot) sendServerUsage() string {
|
|
info := t.prepareServerUsageInfo()
|
|
return info
|
|
}
|
|
|
|
// prepareServerUsageInfo prepares the server usage information string.
|
|
func (t *Tgbot) prepareServerUsageInfo() string {
|
|
// Check if we have cached data first
|
|
if cachedStats, found := t.getCachedServerStats(); found {
|
|
return cachedStats
|
|
}
|
|
|
|
info, ipv4, ipv6 := "", "", ""
|
|
|
|
// get latest status of server with caching
|
|
if cachedStatus, found := t.getCachedStatus(); found {
|
|
t.lastStatus = cachedStatus
|
|
} else {
|
|
t.lastStatus = t.serverService.GetStatus(t.lastStatus)
|
|
t.setCachedStatus(t.lastStatus)
|
|
}
|
|
onlines := service.XrayProcess().GetOnlineClients()
|
|
|
|
info += t.I18nBot("tgbot.messages.hostname", "Hostname=="+hostname)
|
|
info += t.I18nBot("tgbot.messages.version", "Version=="+config.GetVersion())
|
|
info += t.I18nBot("tgbot.messages.xrayVersion", "XrayVersion=="+fmt.Sprint(t.lastStatus.Xray.Version))
|
|
|
|
// get ip address
|
|
netInterfaces, err := net.Interfaces()
|
|
if err != nil {
|
|
logger.Error("net.Interfaces failed, err: ", err.Error())
|
|
info += t.I18nBot("tgbot.messages.ip", "IP=="+t.I18nBot("tgbot.unknown"))
|
|
info += "\r\n"
|
|
} else {
|
|
for i := range netInterfaces {
|
|
if (netInterfaces[i].Flags & net.FlagUp) != 0 {
|
|
addrs, _ := netInterfaces[i].Addrs()
|
|
|
|
for _, address := range addrs {
|
|
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
|
if ipnet.IP.To4() != nil {
|
|
ipv4 += ipnet.IP.String() + " "
|
|
} else if ipnet.IP.To16() != nil && !ipnet.IP.IsLinkLocalUnicast() {
|
|
ipv6 += ipnet.IP.String() + " "
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
info += t.I18nBot("tgbot.messages.ipv4", "IPv4=="+ipv4)
|
|
info += t.I18nBot("tgbot.messages.ipv6", "IPv6=="+ipv6)
|
|
}
|
|
|
|
info += t.I18nBot("tgbot.messages.serverUpTime", "UpTime=="+strconv.FormatUint(t.lastStatus.Uptime/86400, 10), "Unit=="+t.I18nBot("tgbot.days"))
|
|
info += t.I18nBot("tgbot.messages.serverLoad", "Load1=="+strconv.FormatFloat(t.lastStatus.Loads[0], 'f', 2, 64), "Load2=="+strconv.FormatFloat(t.lastStatus.Loads[1], 'f', 2, 64), "Load3=="+strconv.FormatFloat(t.lastStatus.Loads[2], 'f', 2, 64))
|
|
info += t.I18nBot("tgbot.messages.serverMemory", "Current=="+common.FormatTraffic(int64(t.lastStatus.Mem.Current)), "Total=="+common.FormatTraffic(int64(t.lastStatus.Mem.Total)))
|
|
info += t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(len(onlines)))
|
|
info += t.I18nBot("tgbot.messages.tcpCount", "Count=="+strconv.Itoa(t.lastStatus.TcpCount))
|
|
info += t.I18nBot("tgbot.messages.udpCount", "Count=="+strconv.Itoa(t.lastStatus.UdpCount))
|
|
info += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent+t.lastStatus.NetTraffic.Recv)), "Upload=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Sent)), "Download=="+common.FormatTraffic(int64(t.lastStatus.NetTraffic.Recv)))
|
|
info += t.I18nBot("tgbot.messages.xrayStatus", "State=="+fmt.Sprint(t.lastStatus.Xray.State))
|
|
|
|
// Cache the complete server stats
|
|
t.setCachedServerStats(info)
|
|
|
|
return info
|
|
}
|
|
|
|
// UserLoginNotify publishes a login event to the event bus.
|
|
func (t *Tgbot) UserLoginNotify(attempt LoginAttempt) {
|
|
if attempt.Username == "" || attempt.IP == "" || attempt.Time == "" {
|
|
logger.Warning("UserLoginNotify failed, invalid info!")
|
|
return
|
|
}
|
|
|
|
if EventBus == nil {
|
|
return
|
|
}
|
|
|
|
status := "fail"
|
|
if attempt.Status == LoginSuccess {
|
|
status = "success"
|
|
}
|
|
|
|
EventBus.Publish(eventbus.Event{
|
|
Type: eventbus.EventLoginAttempt,
|
|
Source: attempt.IP,
|
|
Data: &eventbus.LoginEventData{
|
|
Username: attempt.Username,
|
|
IP: attempt.IP,
|
|
Time: attempt.Time,
|
|
Status: status,
|
|
Reason: attempt.Reason,
|
|
},
|
|
})
|
|
}
|
|
|
|
// getExhausted retrieves and sends information about exhausted clients.
|
|
func (t *Tgbot) getExhausted(chatId int64) {
|
|
trDiff := int64(0)
|
|
exDiff := int64(0)
|
|
now := time.Now().Unix() * 1000
|
|
var exhaustedInbounds []model.Inbound
|
|
var exhaustedClients []xray.ClientTraffic
|
|
var disabledInbounds []model.Inbound
|
|
var disabledClients []xray.ClientTraffic
|
|
|
|
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
|
if err == nil && TrafficThreshold > 0 {
|
|
trDiff = int64(TrafficThreshold) * 1073741824
|
|
}
|
|
ExpireThreshold, err := t.settingService.GetExpireDiff()
|
|
if err == nil && ExpireThreshold > 0 {
|
|
exDiff = int64(ExpireThreshold) * 86400000
|
|
}
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
if err != nil {
|
|
logger.Warning("Unable to load Inbounds", err)
|
|
}
|
|
|
|
for _, inbound := range inbounds {
|
|
if inbound.Enable {
|
|
if (inbound.ExpiryTime > 0 && (inbound.ExpiryTime-now < exDiff)) ||
|
|
(inbound.Total > 0 && (inbound.Total-(inbound.Up+inbound.Down) < trDiff)) {
|
|
exhaustedInbounds = append(exhaustedInbounds, *inbound)
|
|
}
|
|
if len(inbound.ClientStats) > 0 {
|
|
for _, client := range inbound.ClientStats {
|
|
if client.Enable {
|
|
if (client.ExpiryTime > 0 && (client.ExpiryTime-now < exDiff)) ||
|
|
(client.Total > 0 && (client.Total-(client.Up+client.Down) < trDiff)) {
|
|
exhaustedClients = append(exhaustedClients, client)
|
|
}
|
|
} else {
|
|
disabledClients = append(disabledClients, client)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
disabledInbounds = append(disabledInbounds, *inbound)
|
|
}
|
|
}
|
|
|
|
// Inbounds
|
|
output := ""
|
|
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.inbounds"))
|
|
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledInbounds)))
|
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedInbounds)))
|
|
|
|
if len(exhaustedInbounds) > 0 {
|
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.inbounds"))
|
|
|
|
for _, inbound := range exhaustedInbounds {
|
|
output += t.I18nBot("tgbot.messages.inbound", "Remark=="+inbound.Remark)
|
|
output += t.I18nBot("tgbot.messages.port", "Port=="+strconv.Itoa(inbound.Port))
|
|
output += t.I18nBot("tgbot.messages.traffic", "Total=="+common.FormatTraffic((inbound.Up+inbound.Down)), "Upload=="+common.FormatTraffic(inbound.Up), "Download=="+common.FormatTraffic(inbound.Down))
|
|
if inbound.ExpiryTime == 0 {
|
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+t.I18nBot("tgbot.unlimited"))
|
|
} else {
|
|
output += t.I18nBot("tgbot.messages.expire", "Time=="+time.Unix((inbound.ExpiryTime/1000), 0).Format("2006-01-02 15:04:05"))
|
|
}
|
|
output += "\r\n"
|
|
}
|
|
}
|
|
|
|
// Clients
|
|
exhaustedCC := len(exhaustedClients)
|
|
output += t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients"))
|
|
output += t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients)))
|
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(exhaustedCC))
|
|
|
|
if exhaustedCC > 0 {
|
|
output += t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+t.I18nBot("tgbot.clients"))
|
|
var buttons []telego.InlineKeyboardButton
|
|
for _, traffic := range exhaustedClients {
|
|
output += t.clientInfoMsg(&traffic, true, false, false, true, true, false)
|
|
output += "\r\n"
|
|
buttons = append(buttons, tu.InlineKeyboardButton(traffic.Email).WithCallbackData(t.encodeQuery("client_get_usage "+traffic.Email)))
|
|
}
|
|
cols := 0
|
|
if exhaustedCC < 11 {
|
|
cols = 1
|
|
} else {
|
|
cols = 2
|
|
}
|
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
keyboard := tu.InlineKeyboardGrid(tu.InlineKeyboardCols(cols, buttons...))
|
|
t.SendMsgToTgbot(chatId, output, keyboard)
|
|
} else {
|
|
output += t.I18nBot("tgbot.messages.refreshedOn", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
t.SendMsgToTgbot(chatId, output)
|
|
}
|
|
}
|
|
|
|
// notifyExhausted sends notifications for exhausted clients.
|
|
func (t *Tgbot) notifyExhausted() {
|
|
trDiff := int64(0)
|
|
exDiff := int64(0)
|
|
now := time.Now().Unix() * 1000
|
|
|
|
TrafficThreshold, err := t.settingService.GetTrafficDiff()
|
|
if err == nil && TrafficThreshold > 0 {
|
|
trDiff = int64(TrafficThreshold) * 1073741824
|
|
}
|
|
ExpireThreshold, err := t.settingService.GetExpireDiff()
|
|
if err == nil && ExpireThreshold > 0 {
|
|
exDiff = int64(ExpireThreshold) * 86400000
|
|
}
|
|
inbounds, err := t.inboundService.GetAllInbounds()
|
|
if err != nil {
|
|
logger.Warning("Unable to load Inbounds", err)
|
|
}
|
|
|
|
var chatIDsDone []int64
|
|
for _, inbound := range inbounds {
|
|
if inbound.Enable {
|
|
if len(inbound.ClientStats) > 0 {
|
|
clients, err := t.inboundService.GetClients(inbound)
|
|
if err == nil {
|
|
for _, client := range clients {
|
|
if client.TgID != 0 {
|
|
chatID := client.TgID
|
|
if !int64Contains(chatIDsDone, chatID) && !checkAdmin(chatID) {
|
|
var disabledClients []xray.ClientTraffic
|
|
var exhaustedClients []xray.ClientTraffic
|
|
traffics, err := t.inboundService.GetClientTrafficTgBot(client.TgID)
|
|
if err == nil && len(traffics) > 0 {
|
|
var output strings.Builder
|
|
output.WriteString(t.I18nBot("tgbot.messages.exhaustedCount", "Type=="+t.I18nBot("tgbot.clients")))
|
|
for _, traffic := range traffics {
|
|
if traffic.Enable {
|
|
if (traffic.ExpiryTime > 0 && (traffic.ExpiryTime-now < exDiff)) ||
|
|
(traffic.Total > 0 && (traffic.Total-(traffic.Up+traffic.Down) < trDiff)) {
|
|
exhaustedClients = append(exhaustedClients, *traffic)
|
|
}
|
|
} else {
|
|
disabledClients = append(disabledClients, *traffic)
|
|
}
|
|
}
|
|
if len(exhaustedClients) > 0 {
|
|
output.WriteString(t.I18nBot("tgbot.messages.disabled", "Disabled=="+strconv.Itoa(len(disabledClients))))
|
|
if len(disabledClients) > 0 {
|
|
output.WriteString(t.I18nBot("tgbot.clients"))
|
|
output.WriteString(":\r\n")
|
|
for _, traffic := range disabledClients {
|
|
output.WriteString(" ")
|
|
output.WriteString(traffic.Email)
|
|
}
|
|
output.WriteString("\r\n")
|
|
}
|
|
output.WriteString("\r\n")
|
|
output.WriteString(t.I18nBot("tgbot.messages.depleteSoon", "Deplete=="+strconv.Itoa(len(exhaustedClients))))
|
|
for _, traffic := range exhaustedClients {
|
|
output.WriteString(t.clientInfoMsg(&traffic, true, false, false, true, true, false))
|
|
output.WriteString("\r\n")
|
|
}
|
|
t.SendMsgToTgbot(chatID, output.String())
|
|
}
|
|
chatIDsDone = append(chatIDsDone, chatID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// onlineClients retrieves and sends information about online clients.
|
|
func (t *Tgbot) onlineClients(chatId int64, messageID ...int) {
|
|
if !service.XrayProcess().IsRunning() {
|
|
return
|
|
}
|
|
|
|
onlines := service.XrayProcess().GetOnlineClients()
|
|
onlinesCount := len(onlines)
|
|
output := t.I18nBot("tgbot.messages.onlinesCount", "Count=="+fmt.Sprint(onlinesCount))
|
|
keyboard := tu.InlineKeyboard(tu.InlineKeyboardRow(
|
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.refresh")).WithCallbackData(t.encodeQuery("onlines_refresh"))))
|
|
|
|
if onlinesCount > 0 {
|
|
var buttons []telego.InlineKeyboardButton
|
|
for _, online := range onlines {
|
|
buttons = append(buttons, tu.InlineKeyboardButton(online).WithCallbackData(t.encodeQuery("client_get_usage "+online)))
|
|
}
|
|
cols := 0
|
|
if onlinesCount < 21 {
|
|
cols = 2
|
|
} else if onlinesCount < 61 {
|
|
cols = 3
|
|
} else {
|
|
cols = 4
|
|
}
|
|
keyboard.InlineKeyboard = append(keyboard.InlineKeyboard, tu.InlineKeyboardCols(cols, buttons...)...)
|
|
}
|
|
|
|
if len(messageID) > 0 {
|
|
t.editMessageTgBot(chatId, messageID[0], output, keyboard)
|
|
} else {
|
|
t.SendMsgToTgbot(chatId, output, keyboard)
|
|
}
|
|
}
|
|
|
|
// sendBackup sends a backup of the database and configuration files.
|
|
func (t *Tgbot) sendBackup(chatId int64) {
|
|
output := t.I18nBot("tgbot.messages.backupTime", "Time=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
t.SendMsgToTgbot(chatId, output)
|
|
|
|
// Send database backup (SQLite file, or a pg_dump archive on PostgreSQL)
|
|
dbData, err := t.serverService.GetDb()
|
|
if err == nil {
|
|
dbFilename := "x-ui.db"
|
|
if database.IsPostgres() {
|
|
dbFilename = "x-ui.dump"
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
document := tu.Document(
|
|
tu.ID(chatId),
|
|
tu.FileFromBytes(dbData, dbFilename),
|
|
)
|
|
_, err = bot.SendDocument(ctx, document)
|
|
cancel()
|
|
if err != nil {
|
|
logger.Error("Error in uploading backup: ", err)
|
|
}
|
|
} else {
|
|
logger.Error("Error in getting db backup: ", err)
|
|
}
|
|
|
|
// Small delay between file sends
|
|
time.Sleep(500 * time.Millisecond)
|
|
|
|
// Send config.json backup
|
|
file, err := os.Open(xray.GetConfigPath())
|
|
if err == nil {
|
|
defer file.Close()
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
document := tu.Document(
|
|
tu.ID(chatId),
|
|
tu.File(file),
|
|
)
|
|
_, err = bot.SendDocument(ctx, document)
|
|
if err != nil {
|
|
logger.Error("Error in uploading config.json: ", err)
|
|
}
|
|
} else {
|
|
logger.Error("Error in opening config.json file for backup: ", err)
|
|
}
|
|
}
|
|
|
|
// sendBanLogs sends the ban logs to the specified chat.
|
|
func (t *Tgbot) sendBanLogs(chatId int64, dt bool) {
|
|
if dt {
|
|
output := t.I18nBot("tgbot.messages.datetime", "DateTime=="+time.Now().Format("2006-01-02 15:04:05"))
|
|
t.SendMsgToTgbot(chatId, output)
|
|
}
|
|
|
|
file, err := os.Open(xray.GetIPLimitBannedPrevLogPath())
|
|
if err == nil {
|
|
// Check if the file is non-empty before attempting to upload
|
|
fileInfo, _ := file.Stat()
|
|
if fileInfo.Size() > 0 {
|
|
document := tu.Document(
|
|
tu.ID(chatId),
|
|
tu.File(file),
|
|
)
|
|
_, err = bot.SendDocument(context.Background(), document)
|
|
if err != nil {
|
|
logger.Error("Error in uploading IPLimitBannedPrevLog: ", err)
|
|
}
|
|
} else {
|
|
logger.Warning("IPLimitBannedPrevLog file is empty, not uploading.")
|
|
}
|
|
file.Close()
|
|
} else {
|
|
logger.Error("Error in opening IPLimitBannedPrevLog file for backup: ", err)
|
|
}
|
|
|
|
file, err = os.Open(xray.GetIPLimitBannedLogPath())
|
|
if err == nil {
|
|
// Check if the file is non-empty before attempting to upload
|
|
fileInfo, _ := file.Stat()
|
|
if fileInfo.Size() > 0 {
|
|
document := tu.Document(
|
|
tu.ID(chatId),
|
|
tu.File(file),
|
|
)
|
|
_, err = bot.SendDocument(context.Background(), document)
|
|
if err != nil {
|
|
logger.Error("Error in uploading IPLimitBannedLog: ", err)
|
|
}
|
|
} else {
|
|
logger.Warning("IPLimitBannedLog file is empty, not uploading.")
|
|
}
|
|
file.Close()
|
|
} else {
|
|
logger.Error("Error in opening IPLimitBannedLog file for backup: ", err)
|
|
}
|
|
}
|