style: adopt golangci-lint v2 and resolve all findings

Add .golangci.yml (v2): the standard linters plus bodyclose, errorlint, noctx, misspell, rowserrcheck, sqlclosecheck, unconvert, usestdlibvars, with gofumpt + goimports formatters. Enable the std-error-handling exclusion preset for idiomatic Close/Remove/Setenv ignores; scope-exclude SA1019 (parser.ParseDir in tools/openapigen) and ST1005 (intentional capitalized user-facing error copy that tests assert verbatim). No inline nolint directives were introduced.

Resolve all 217 findings behavior-preserving: gofumpt/goimports formatting, explicit blank assignment on intentionally ignored errors, errors.Is/errors.As and %w wrapping, context-aware stdlib calls (CommandContext/QueryContext/NewRequestWithContext/Dialer), staticcheck simplifications, removed redundant conversions, http.StatusOK and http.MethodGet, inlined the go:fix intPtr helper, and deferred sql rows Close. Add a golangci CI job mirroring the existing Go jobs.
This commit is contained in:
MHSanaei
2026-06-27 15:42:22 +02:00
parent 7efa0d9ddd
commit fa1a19c03c
81 changed files with 410 additions and 286 deletions
+2 -2
View File
@@ -283,7 +283,7 @@ func (t *Tgbot) Start(i18nFS embed.FS) error {
logger.Warning("Failed to parse admin ID from Telegram bot chat ID:", err)
return err
}
parsedAdminIds = append(parsedAdminIds, int64(id))
parsedAdminIds = append(parsedAdminIds, id)
}
}
tgBotMutex.Lock()
@@ -472,7 +472,7 @@ func StopBot() {
userStateMgr.reset()
if handler != nil {
handler.Stop()
_ = handler.Stop()
}
if cancel != nil {
+9 -10
View File
@@ -74,13 +74,13 @@ func (t *Tgbot) BuildClientDraftMessage() string {
var b strings.Builder
b.WriteString("📝 *New client draft*\r\n")
b.WriteString(fmt.Sprintf("📧 Email: `%s`\r\n", client_Email))
b.WriteString(fmt.Sprintf("🔗 Attached: %s\r\n", attached))
b.WriteString(fmt.Sprintf("📊 Traffic: %s\r\n", traffic))
b.WriteString(fmt.Sprintf("📅 Expire: %s\r\n", expiry))
b.WriteString(fmt.Sprintf("🔢 IP limit: %s\r\n", ipLimit))
b.WriteString(fmt.Sprintf("👤 TG user: %s\r\n", tgID))
b.WriteString(fmt.Sprintf("💬 Comment: %s\r\n", comment))
fmt.Fprintf(&b, "📧 Email: `%s`\r\n", client_Email)
fmt.Fprintf(&b, "🔗 Attached: %s\r\n", attached)
fmt.Fprintf(&b, "📊 Traffic: %s\r\n", traffic)
fmt.Fprintf(&b, "📅 Expire: %s\r\n", expiry)
fmt.Fprintf(&b, "🔢 IP limit: %s\r\n", ipLimit)
fmt.Fprintf(&b, "👤 TG user: %s\r\n", tgID)
fmt.Fprintf(&b, "💬 Comment: %s\r\n", comment)
return b.String()
}
@@ -216,7 +216,6 @@ func (t *Tgbot) buildSubscriptionURLs(email string) (string, string, error) {
}
subJsonURL = fmt.Sprintf("%s%s", subJsonURI, client.SubID)
} else {
subJsonURL = fmt.Sprintf("%s://%s%s%s", scheme, host, subJsonPath, client.SubID)
}
@@ -258,7 +257,7 @@ func (t *Tgbot) sendClientIndividualLinks(chatId int64, email string) {
}
// Try to fetch raw subscription links. Prefer plain text response.
req, err := http.NewRequest("GET", subURL, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, subURL, nil)
if err != nil {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation")+"\r\n"+err.Error())
return
@@ -376,7 +375,7 @@ func (t *Tgbot) sendClientQRLinks(chatId int64, email string) {
// Also generate a few individual links' QRs (first up to 5)
subPageURL := subURL
req, err := http.NewRequest("GET", subPageURL, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, subPageURL, nil)
if err == nil {
req.Header.Set("Accept", "text/plain, */*;q=0.1")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+1 -5
View File
@@ -31,7 +31,7 @@ func (t *Tgbot) getInboundUsages() string {
clients, listErr := t.clientService.ListForInbound(nil, inbound.Id)
if listErr == nil {
info.WriteString(fmt.Sprintf("👥 Clients: %d\r\n", len(clients)))
fmt.Fprintf(&info, "👥 Clients: %d\r\n", len(clients))
}
if inbound.ExpiryTime == 0 {
@@ -126,11 +126,9 @@ func (t *Tgbot) getInboundClientsFor(inboundID int, action string) (*telego.Inli
for _, client := range clients {
buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery(action+" "+client.Email)))
}
} else {
return nil, errors.New(t.I18nBot("tgbot.answers.getClientsFailed"))
}
}
cols := 0
if len(buttons) < 6 {
@@ -252,11 +250,9 @@ func (t *Tgbot) getInboundClients(id int) (*telego.InlineKeyboardMarkup, error)
for _, client := range clients {
buttons = append(buttons, tu.InlineKeyboardButton(client.Email).WithCallbackData(t.encodeQuery("client_get_usage "+client.Email)))
}
} else {
return nil, errors.New(t.I18nBot("tgbot.answers.getClientsFailed"))
}
}
cols := 0
if len(buttons) < 6 {
+2 -2
View File
@@ -49,7 +49,7 @@ func (t *Tgbot) SendBackupToAdmins() {
return
}
for i, adminId := range adminIds {
t.sendBackup(int64(adminId))
t.sendBackup(adminId)
// Add delay between sends to avoid Telegram rate limits
if i < len(adminIds)-1 {
time.Sleep(1 * time.Second)
@@ -63,7 +63,7 @@ func (t *Tgbot) sendExhaustedToAdmins() {
return
}
for _, adminId := range adminIds {
t.getExhausted(int64(adminId))
t.getExhausted(adminId)
}
}
+8 -11
View File
@@ -141,7 +141,6 @@ func (t *Tgbot) OnReceive() {
userStateMgr.clear(message.Chat.ID)
t.addClient(message.Chat.ID, t.BuildClientDraftMessage())
}
} else {
if message.UsersShared != nil {
if checkAdmin(message.From.ID) {
@@ -167,7 +166,7 @@ func (t *Tgbot) OnReceive() {
return nil
}, th.AnyMessage())
h.Start()
_ = h.Start()
}()
}
@@ -205,7 +204,7 @@ func (t *Tgbot) answerCommand(message *telego.Message, chatId int64, isAdmin boo
if isAdmin {
t.searchClient(chatId, commandArgs[0])
} else {
t.getClientUsage(chatId, int64(message.From.ID), commandArgs[0])
t.getClientUsage(chatId, message.From.ID, commandArgs[0])
}
} else {
msg += t.I18nBot("tgbot.commands.usage")
@@ -595,12 +594,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
if traffic.ExpiryTime > 0 {
if traffic.ExpiryTime-time.Now().Unix()*1000 < 0 {
date = -int64(days * 24 * 60 * 60000)
date = -(days * 24 * 60 * 60000)
} else {
date = traffic.ExpiryTime + int64(days*24*60*60000)
date = traffic.ExpiryTime + days*24*60*60000
}
} else {
date = traffic.ExpiryTime - int64(days*24*60*60000)
date = traffic.ExpiryTime - days*24*60*60000
}
}
@@ -685,12 +684,12 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
var date int64
if client_ExpiryTime > 0 {
if client_ExpiryTime-time.Now().Unix()*1000 < 0 {
date = -int64(days * 24 * 60 * 60000)
date = -(days * 24 * 60 * 60000)
} else {
date = client_ExpiryTime + int64(days*24*60*60000)
date = client_ExpiryTime + days*24*60*60000
}
} else {
date = client_ExpiryTime - int64(days*24*60*60000)
date = client_ExpiryTime - days*24*60*60000
}
client_ExpiryTime = date
@@ -1111,7 +1110,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
}
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.chooseInbound"), inbounds)
}
}
}
@@ -1458,7 +1456,6 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
case "get_sorted_traffic_usage_report":
t.deleteMessageTgBot(chatId, callbackQuery.Message.GetMessageID())
emails, err := t.inboundService.GetAllEmails()
if err != nil {
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.errorOperation"), tu.ReplyKeyboardRemove())
return