fix golint errors
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- bodyclose
|
||||||
|
- errcheck
|
||||||
|
- errorlint
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gofmt
|
||||||
|
- gosec
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- noctx
|
||||||
|
- prealloc
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
- wastedassign
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- style
|
||||||
|
- performance
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: blank-imports
|
||||||
|
- name: exported
|
||||||
|
disabled: true
|
||||||
+14
-8
@@ -63,7 +63,7 @@ func main() {
|
|||||||
client := memos.NewClient(cfg.Memos.URL, cfg.Memos.Token)
|
client := memos.NewClient(cfg.Memos.URL, cfg.Memos.Token)
|
||||||
|
|
||||||
// Search selector
|
// Search selector
|
||||||
selector := search.NewSelector(client, store, cfg.Search, loc, logger)
|
selector := search.NewSelector(client, store, &cfg.Search, loc, logger)
|
||||||
|
|
||||||
// Memory service
|
// Memory service
|
||||||
memorySvc := memory.NewService(selector, store, loc, logger)
|
memorySvc := memory.NewService(selector, store, loc, logger)
|
||||||
@@ -71,6 +71,18 @@ func main() {
|
|||||||
// Web handler
|
// Web handler
|
||||||
handler := web.NewHandler(memorySvc, cfg.Memos.URL, cfg.Memos.PublicURL, cfg.General.AllowLoadMore, logger)
|
handler := web.NewHandler(memorySvc, cfg.Memos.URL, cfg.Memos.PublicURL, cfg.General.AllowLoadMore, logger)
|
||||||
|
|
||||||
|
// Telegram bot
|
||||||
|
var tgBot *telegram.Bot
|
||||||
|
if cfg.Telegram.Enabled {
|
||||||
|
var err error
|
||||||
|
tgBot, err = telegram.NewBot(cfg.Telegram, memorySvc, client, cfg.Memos.URL, cfg.Memos.PublicURL, cfg.General.AllowLoadMore, loc, logger)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to create telegram bot", "error", err)
|
||||||
|
store.Close()
|
||||||
|
os.Exit(1) //nolint:gocritic // store.Close() called above; linter doesn't track manual cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// HTTP server
|
// HTTP server
|
||||||
srv := &http.Server{
|
srv := &http.Server{
|
||||||
Addr: cfg.Web.Listen,
|
Addr: cfg.Web.Listen,
|
||||||
@@ -84,13 +96,7 @@ func main() {
|
|||||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
// Telegram bot
|
if tgBot != nil {
|
||||||
if cfg.Telegram.Enabled {
|
|
||||||
tgBot, err := telegram.NewBot(cfg.Telegram, memorySvc, client, cfg.Memos.URL, cfg.Memos.PublicURL, cfg.General.AllowLoadMore, loc, logger)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("failed to create telegram bot", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
go tgBot.Run(ctx)
|
go tgBot.Run(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (c *Client) ListMemos(ctx context.Context, filter string, pageSize int, pag
|
|||||||
}
|
}
|
||||||
u.RawQuery = q.Encode()
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create request: %w", err)
|
return nil, fmt.Errorf("create request: %w", err)
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ func (c *Client) DownloadAttachment(ctx context.Context, att Attachment) ([]byte
|
|||||||
needsAuth = true
|
needsAuth = true
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create request: %w", err)
|
return nil, fmt.Errorf("create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func weightedSelect(candidates []candidate, preferOlder bool, maxYearsBack int)
|
|||||||
total += score
|
total += score
|
||||||
}
|
}
|
||||||
|
|
||||||
r := rand.Float64() * total
|
r := rand.Float64() * total //nolint:gosec // non-cryptographic use
|
||||||
var cumulative float64
|
var cumulative float64
|
||||||
for i, s := range scores {
|
for i, s := range scores {
|
||||||
cumulative += s
|
cumulative += s
|
||||||
|
|||||||
@@ -22,19 +22,16 @@ type Selector struct {
|
|||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSelector(client *memos.Client, store *storage.Storage, cfg config.SearchConfig, loc *time.Location, logger *slog.Logger) *Selector {
|
func NewSelector(client *memos.Client, store *storage.Storage, cfg *config.SearchConfig, loc *time.Location, logger *slog.Logger) *Selector {
|
||||||
return &Selector{
|
return &Selector{
|
||||||
client: client,
|
client: client,
|
||||||
store: store,
|
store: store,
|
||||||
cfg: cfg,
|
cfg: *cfg,
|
||||||
loc: loc,
|
loc: loc,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// tierFunc returns date ranges for a given tier.
|
|
||||||
type tierFunc func(today time.Time, maxYears int, loc *time.Location) []DateRange
|
|
||||||
|
|
||||||
// Select runs the full search algorithm and returns one Memory for the given day.
|
// Select runs the full search algorithm and returns one Memory for the given day.
|
||||||
func (s *Selector) Select(ctx context.Context, today time.Time) (*Memory, error) {
|
func (s *Selector) Select(ctx context.Context, today time.Time) (*Memory, error) {
|
||||||
weights := s.cfg.TierWeights.AsSlice()
|
weights := s.cfg.TierWeights.AsSlice()
|
||||||
@@ -248,7 +245,7 @@ func weightedTierOrder(weights [7]int) []int {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
r := rand.IntN(totalWeight)
|
r := rand.IntN(totalWeight) //nolint:gosec // non-cryptographic use
|
||||||
cumulative := 0
|
cumulative := 0
|
||||||
for i, e := range remaining {
|
for i, e := range remaining {
|
||||||
cumulative += e.weight
|
cumulative += e.weight
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (s *Storage) GetShowCounts(ctx context.Context, memoNames []string) (map[st
|
|||||||
args[i] = name
|
args[i] = name
|
||||||
}
|
}
|
||||||
|
|
||||||
query := fmt.Sprintf(
|
query := fmt.Sprintf( //nolint:gosec // placeholders are always "?"
|
||||||
`SELECT memo_name, COUNT(*) FROM show_history WHERE memo_name IN (%s) GROUP BY memo_name`,
|
`SELECT memo_name, COUNT(*) FROM show_history WHERE memo_name IN (%s) GROUP BY memo_name`,
|
||||||
strings.Join(placeholders, ","))
|
strings.Join(placeholders, ","))
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite" // SQLite driver
|
||||||
)
|
)
|
||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -93,8 +94,12 @@ func (b *Bot) nextSendTime() time.Time {
|
|||||||
hour := 9
|
hour := 9
|
||||||
minute := 0
|
minute := 0
|
||||||
if len(parts) == 2 {
|
if len(parts) == 2 {
|
||||||
fmt.Sscanf(parts[0], "%d", &hour)
|
if h, err := strconv.Atoi(parts[0]); err == nil {
|
||||||
fmt.Sscanf(parts[1], "%d", &minute)
|
hour = h
|
||||||
|
}
|
||||||
|
if m, err := strconv.Atoi(parts[1]); err == nil {
|
||||||
|
minute = m
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
target := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, b.loc)
|
target := time.Date(now.Year(), now.Month(), now.Day(), hour, minute, 0, 0, b.loc)
|
||||||
@@ -185,7 +190,7 @@ type imageFile struct {
|
|||||||
|
|
||||||
// downloadImages downloads image attachments, skipping failures.
|
// downloadImages downloads image attachments, skipping failures.
|
||||||
func (b *Bot) downloadImages(ctx context.Context, attachments []memos.Attachment) []imageFile {
|
func (b *Bot) downloadImages(ctx context.Context, attachments []memos.Attachment) []imageFile {
|
||||||
var files []imageFile
|
files := make([]imageFile, 0, len(attachments))
|
||||||
for _, att := range attachments {
|
for _, att := range attachments {
|
||||||
data, err := b.client.DownloadAttachment(ctx, att)
|
data, err := b.client.DownloadAttachment(ctx, att)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func formatMemory(mem *search.Memory, publicURL string) (mainText, captionText s
|
|||||||
|
|
||||||
// Link to original
|
// Link to original
|
||||||
memoURL := fmt.Sprintf("%s/%s", publicURL, mem.Memo.Name)
|
memoURL := fmt.Sprintf("%s/%s", publicURL, mem.Memo.Name)
|
||||||
b.WriteString(fmt.Sprintf("\n\n<a href=\"%s\">Оригинал</a>", memoURL))
|
b.WriteString("\n\n<a href=\"" + memoURL + "\">Оригинал</a>")
|
||||||
|
|
||||||
full := b.String()
|
full := b.String()
|
||||||
mainText = truncateHTML(full, maxMessageLen)
|
mainText = truncateHTML(full, maxMessageLen)
|
||||||
|
|||||||
@@ -80,21 +80,25 @@ func (h *Handler) handleMemory(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("failed to get memory", "error", err)
|
h.logger.Error("failed to get memory", "error", err)
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
templates.ExecuteTemplate(w, "error.html", errorData{
|
if err := templates.ExecuteTemplate(w, "error.html", errorData{
|
||||||
Message: "Не удалось загрузить воспоминание",
|
Message: "Не удалось загрузить воспоминание",
|
||||||
})
|
}); err != nil {
|
||||||
|
h.logger.Error("template render failed", "error", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if mem == nil {
|
if mem == nil {
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
templates.ExecuteTemplate(w, "error.html", errorData{
|
if err := templates.ExecuteTemplate(w, "error.html", errorData{
|
||||||
Message: "Нет заметок для воспоминания",
|
Message: "Нет заметок для воспоминания",
|
||||||
})
|
}); err != nil {
|
||||||
|
h.logger.Error("template render failed", "error", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var images []imageData
|
images := make([]imageData, 0, len(mem.Memo.Attachments))
|
||||||
for _, att := range mem.Memo.Attachments {
|
for _, att := range mem.Memo.Attachments {
|
||||||
if !att.IsImage() {
|
if !att.IsImage() {
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user