Merge pull request 'small refactoring message cleaning' (#3) from small-refactor into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #3
This commit is contained in:
dedal.qq 2021-06-25 22:30:25 +03:00
commit 08456aa576
7 changed files with 213 additions and 296 deletions

View File

@ -20,7 +20,7 @@ const (
ctxKeyMessageCleaner ctxKeyMessageCleaner
ctxKeyDataBase ctxKeyDataBase
ctxKeyPlayersBoard ctxKeyPlayersBoard
ctxKeyFragMessages ctxKeyMessageStorage
) )
func createLogger(debug bool) *logrus.Logger { func createLogger(debug bool) *logrus.Logger {
@ -44,17 +44,16 @@ func createLogger(debug bool) *logrus.Logger {
} }
} }
func createContext(config *Config, bot *tgbotapi.BotAPI, mc *messageCleaner, ch *commandHandler, db *dbLayer, pb *playersBoard, fms *fragMessages) (context.Context, func()) { func createContext(config *Config, bot *tgbotapi.BotAPI, ch *commandHandler, db *dbLayer, pb *playersBoard, ms *messageStorage) (context.Context, func()) {
ctx, cancelFunc := context.WithCancel(context.Background()) ctx, cancelFunc := context.WithCancel(context.Background())
ctx = context.WithValue(ctx, ctxKeyLogger, createLogger(true)) ctx = context.WithValue(ctx, ctxKeyLogger, createLogger(true))
ctx = context.WithValue(ctx, ctxKeyBotApi, bot) ctx = context.WithValue(ctx, ctxKeyBotApi, bot)
ctx = context.WithValue(ctx, ctxKeyConfig, config) ctx = context.WithValue(ctx, ctxKeyConfig, config)
ctx = context.WithValue(ctx, ctxKeyMessageCleaner, mc)
ctx = context.WithValue(ctx, ctxKeyCommandHandler, ch) ctx = context.WithValue(ctx, ctxKeyCommandHandler, ch)
ctx = context.WithValue(ctx, ctxKeyDataBase, db) ctx = context.WithValue(ctx, ctxKeyDataBase, db)
ctx = context.WithValue(ctx, ctxKeyPlayersBoard, pb) ctx = context.WithValue(ctx, ctxKeyPlayersBoard, pb)
ctx = context.WithValue(ctx, ctxKeyFragMessages, fms) ctx = context.WithValue(ctx, ctxKeyMessageStorage, ms)
return ctx, cancelFunc return ctx, cancelFunc
} }
@ -86,15 +85,6 @@ func getConfig(ctx context.Context) *Config {
panic("Failed to get config from ctx") panic("Failed to get config from ctx")
} }
func getMessageCleaner(ctx context.Context) *messageCleaner {
v := ctx.Value(ctxKeyMessageCleaner)
if mc, ok := v.(*messageCleaner); ok {
return mc
}
panic("Failed to get message cleaner from ctx")
}
func getCommandHandler(ctx context.Context) *commandHandler { func getCommandHandler(ctx context.Context) *commandHandler {
v := ctx.Value(ctxKeyCommandHandler) v := ctx.Value(ctxKeyCommandHandler)
if ch, ok := v.(*commandHandler); ok { if ch, ok := v.(*commandHandler); ok {
@ -122,11 +112,11 @@ func getDPlayersBoard(ctx context.Context) *playersBoard {
panic("Failed to get players board from ctx") panic("Failed to get players board from ctx")
} }
func getFragMessages(ctx context.Context) *fragMessages { func getMessageStorage(ctx context.Context) *messageStorage {
v := ctx.Value(ctxKeyFragMessages) v := ctx.Value(ctxKeyMessageStorage)
if fms, ok := v.(*fragMessages); ok { if ms, ok := v.(*messageStorage); ok {
return fms return ms
} }
panic("Failed to get frag messages from ctx") panic("Failed to get message storage from ctx")
} }

View File

@ -1,90 +0,0 @@
package main
import (
"context"
"fmt"
"sync"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
type fragMessage struct {
playerName string
entityName string
chatID int64
messageID int
fragCount int
latestFragTime time.Time
}
type fragMessages struct {
mx sync.Mutex
messages []*fragMessage
}
func (f *fragMessage) increaseFrag() *fragMessage {
f.fragCount += 1
f.latestFragTime = time.Now()
return f
}
func newFragMessages() *fragMessages {
return &fragMessages{
messages: make([]*fragMessage, 0),
}
}
func (fms *fragMessages) addFrag(ctx context.Context, playerName string, entityName string) error {
botApi := getBotApi(ctx)
cfg := getConfig(ctx)
mc := getMessageCleaner(ctx)
fms.mx.Lock()
defer fms.mx.Unlock()
for _, fm := range fms.messages {
if fm.playerName == playerName && fm.entityName == entityName {
mc.resetTimer(fm.messageID, fm.chatID, time.Second*30)
fm.increaseFrag()
mess := tgbotapi.NewEditMessageText(
fm.chatID,
fm.messageID,
fmt.Sprintf("%s убил %s ✗ *%d*", fm.playerName, fm.entityName, fm.fragCount),
)
mess.ParseMode = tgbotapi.ModeMarkdown
m, err := botApi.Send(mess)
if err != nil {
return err
}
if m.MessageID != fm.messageID {
m.MessageID = fm.messageID
getLogger(ctx).Info("message id was changed")
}
return nil
}
}
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s убил %s", playerName, entityName))
m, err := botApi.Send(mess)
if err != nil {
return err
}
mc.add(ctx, m.MessageID, cfg.ChatID, time.Second*30)
fm := &fragMessage{
playerName: playerName,
entityName: entityName,
chatID: cfg.ChatID,
messageID: m.MessageID,
fragCount: 1,
latestFragTime: time.Now(),
}
fms.messages = append(fms.messages, fm)
return nil
}

View File

@ -4,8 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"time" "time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
) )
type command interface { type command interface {
@ -42,19 +40,21 @@ type joinCommand struct {
} }
func (c *joinCommand) run(ctx context.Context) { func (c *joinCommand) run(ctx context.Context) {
botApi := getBotApi(ctx)
cfg := getConfig(ctx)
mc := getMessageCleaner(ctx)
db := getDB(ctx) db := getDB(ctx)
log := getLogger(ctx) log := getLogger(ctx)
pb := getDPlayersBoard(ctx) pb := getDPlayersBoard(ctx)
ms := getMessageStorage(ctx)
err := ms.apply(ctx, fmt.Sprintf("join:%s", c.name), time.Minute/2, func(d interface{}) (string, interface{}) {
if v, _ := d.(bool); v {
return fmt.Sprintf("%s подключился снова", c.name), true
}
return fmt.Sprintf("%s подключился", c.name), true
})
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s подключился", c.name))
m, err := botApi.Send(mess)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} else {
mc.add(ctx, m.MessageID, cfg.ChatID, time.Second*30)
} }
p, err := db.getPlayerByName(ctx, c.name) p, err := db.getPlayerByName(ctx, c.name)
@ -90,19 +90,21 @@ type quitCommand struct {
} }
func (c *quitCommand) run(ctx context.Context) { func (c *quitCommand) run(ctx context.Context) {
botApi := getBotApi(ctx)
cfg := getConfig(ctx)
mc := getMessageCleaner(ctx)
db := getDB(ctx) db := getDB(ctx)
log := getLogger(ctx) log := getLogger(ctx)
pb := getDPlayersBoard(ctx) pb := getDPlayersBoard(ctx)
ms := getMessageStorage(ctx)
err := ms.apply(ctx, fmt.Sprintf("quit:%s", c.name), time.Minute/2, func(d interface{}) (string, interface{}) {
if v, _ := d.(bool); v {
return fmt.Sprintf("%s отключился снова", c.name), true
}
return fmt.Sprintf("%s отключился", c.name), true
})
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s отключился", c.name))
m, err := botApi.Send(mess)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
} else {
mc.add(ctx, m.MessageID, cfg.ChatID, time.Second*30)
} }
p, err := db.getPlayerByName(ctx, c.name) p, err := db.getPlayerByName(ctx, c.name)
@ -138,6 +140,19 @@ func (c *deathCommand) run(ctx context.Context) {
db := getDB(ctx) db := getDB(ctx)
log := getLogger(ctx) log := getLogger(ctx)
pb := getDPlayersBoard(ctx) pb := getDPlayersBoard(ctx)
ms := getMessageStorage(ctx)
err := ms.apply(ctx, fmt.Sprintf("death:%s", c.name), time.Minute/2, func(d interface{}) (string, interface{}) {
if v, _ := d.(bool); v {
return fmt.Sprintf("%s умудрился опять помереть от %s", c.name, c.deathType), true
}
return fmt.Sprintf("%s пал смертью храбрых от %s", c.name, c.deathType), true
})
if err != nil {
log.Error(err)
}
p, err := db.getPlayerByName(ctx, c.name) p, err := db.getPlayerByName(ctx, c.name)
if err != nil { if err != nil {
@ -165,12 +180,7 @@ func (c *killCommand) run(ctx context.Context) {
db := getDB(ctx) db := getDB(ctx)
log := getLogger(ctx) log := getLogger(ctx)
pb := getDPlayersBoard(ctx) pb := getDPlayersBoard(ctx)
fms := getFragMessages(ctx) ms := getMessageStorage(ctx)
err := fms.addFrag(ctx, c.name, c.entity)
if err != nil {
log.Error(err)
}
p, err := db.getPlayerByName(ctx, c.name) p, err := db.getPlayerByName(ctx, c.name)
if err != nil { if err != nil {
@ -183,6 +193,21 @@ func (c *killCommand) run(ctx context.Context) {
log.Error(err) log.Error(err)
} }
f := func(d interface{}) (string, interface{}) {
if fragsNum, ok := d.(int); ok {
fragsNum++
return fmt.Sprintf("%s убил %s ✗ *%d*", p.name, c.entity, fragsNum), fragsNum
}
return fmt.Sprintf("%s убил %s", p.name, c.entity), 1
}
err = ms.apply(ctx, fmt.Sprintf("kill:%s:%s", p.id, c.entity), time.Minute/2, f)
if err != nil {
log.Error(err)
}
err = pb.update(ctx, achievementMaxFrags, achievementPeaceable) err = pb.update(ctx, achievementMaxFrags, achievementPeaceable)
if err != nil { if err != nil {
log.Error(err) log.Error(err)

30
main.go
View File

@ -55,15 +55,11 @@ func main() {
os.Exit(5) os.Exit(5)
} }
mc := newMessageCleaner(bot, &wg)
ch := newCommandHandler() ch := newCommandHandler()
pb := newPlayersBoard() pb := newPlayersBoard()
ms := NewMessageStorage()
fms := newFragMessages() ctx, cancelFunc := createContext(&cfg, bot, ch, db, pb, ms)
ctx, cancelFunc := createContext(&cfg, bot, mc, ch, db, pb, fms)
getLogger(ctx).Infof("Run...") getLogger(ctx).Infof("Run...")
@ -73,8 +69,8 @@ func main() {
os.Exit(5) os.Exit(5)
} }
runPlayerBoard(ctx, pb, &wg)
runCommandHandler(ctx, ch, &wg) runCommandHandler(ctx, ch, &wg)
runMessageStorage(ctx, ms, &wg)
runHttpServer(ctx, cfg.Http, &wg) runHttpServer(ctx, cfg.Http, &wg)
runSignalHandler(ctx, cancelFunc) runSignalHandler(ctx, cancelFunc)
@ -83,16 +79,6 @@ func main() {
wg.Wait() wg.Wait()
} }
func runPlayerBoard(ctx context.Context, pb *playersBoard, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
defer wg.Done()
pb.run(ctx)
}()
}
func runCommandHandler(ctx context.Context, ch *commandHandler, wg *sync.WaitGroup) { func runCommandHandler(ctx context.Context, ch *commandHandler, wg *sync.WaitGroup) {
wg.Add(1) wg.Add(1)
@ -103,6 +89,16 @@ func runCommandHandler(ctx context.Context, ch *commandHandler, wg *sync.WaitGro
}() }()
} }
func runMessageStorage(ctx context.Context, ms *messageStorage, wg *sync.WaitGroup) {
wg.Add(1)
go func() {
defer wg.Done()
ms.run(ctx)
}()
}
func runHttpServer(ctx context.Context, addr string, wg *sync.WaitGroup) { func runHttpServer(ctx context.Context, addr string, wg *sync.WaitGroup) {
server := http.Server{ server := http.Server{
Addr: addr, Addr: addr,

View File

@ -1,91 +0,0 @@
package main
import (
"context"
"sync"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
type messageToClean struct {
messID int
chatID int64
timer *time.Timer
}
type messageCleaner struct {
botApi *tgbotapi.BotAPI
wg *sync.WaitGroup
messages []*messageToClean
mx sync.Mutex
}
func newMessageCleaner(bot *tgbotapi.BotAPI, wg *sync.WaitGroup) *messageCleaner {
return &messageCleaner{
botApi: bot,
wg: wg,
messages: make([]*messageToClean, 0),
}
}
func (m *messageCleaner) add(ctx context.Context, messID int, chatID int64, d time.Duration) {
m.mx.Lock()
defer m.mx.Unlock()
for _, ms := range m.messages {
if ms.chatID == chatID && ms.messID == messID {
return
}
}
m.wg.Add(1)
go func() {
defer m.wg.Done()
ms := &messageToClean{
messID: messID,
chatID: chatID,
timer: time.NewTimer(d),
}
m.messages = append(m.messages, ms)
select {
case <-ms.timer.C:
case <-ctx.Done():
}
m.remove(messID, chatID)
_, _ = m.botApi.DeleteMessage(tgbotapi.DeleteMessageConfig{
MessageID: messID,
ChatID: chatID,
})
}()
}
func (m *messageCleaner) remove(messID int, chatID int64) {
m.mx.Lock()
defer m.mx.Unlock()
for i, ms := range m.messages {
if ms.chatID == chatID && ms.messID == messID {
m.messages = append(m.messages[:i], m.messages[i+1:]...)
return
}
}
}
func (m *messageCleaner) resetTimer(messID int, chatID int64, d time.Duration) {
m.mx.Lock()
defer m.mx.Unlock()
for _, ms := range m.messages {
if ms.chatID == chatID && ms.messID == messID {
ms.timer.Reset(d)
return
}
}
}

131
messageStorage.go Normal file
View File

@ -0,0 +1,131 @@
package main
import (
"context"
"sync"
"time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
)
type messageData struct {
key string
messID int
chatID int64
data interface{}
timer *time.Timer
}
type messageStorage struct {
m sync.Mutex
messages []*messageData
}
func NewMessageStorage() *messageStorage {
return &messageStorage{
messages: make([]*messageData, 0, 20),
}
}
func deleteMessageFromTelegram(ctx context.Context, messID int, chatID int64) {
botApi := getBotApi(ctx)
_, err := botApi.DeleteMessage(tgbotapi.DeleteMessageConfig{
MessageID: messID,
ChatID: chatID,
})
if err != nil {
getLogger(ctx).Error(err)
}
}
func (ms *messageStorage) run(ctx context.Context) {
<-ctx.Done()
ms.m.Lock()
defer ms.m.Unlock()
for _, m := range ms.messages {
deleteMessageFromTelegram(ctx, m.messID, m.chatID)
}
ms.messages = ms.messages[:]
}
func (ms *messageStorage) runTimeCleaner(ctx context.Context, md *messageData, d time.Duration) {
if d <= 0 {
md.timer = nil
return
}
t := time.NewTimer(d)
md.timer = t
go func() {
<-t.C
ms.m.Lock()
defer ms.m.Unlock()
if md.timer == t {
for i, m := range ms.messages {
if m == md {
deleteMessageFromTelegram(ctx, m.messID, m.chatID)
ms.messages = append(ms.messages[:i], ms.messages[i+1:]...)
return
}
}
}
}()
}
type messageChanger func(interface{}) (string, interface{})
func (ms *messageStorage) apply(ctx context.Context, key string, d time.Duration, mc messageChanger) error {
ms.m.Lock()
defer ms.m.Unlock()
botApi := getBotApi(ctx)
cfg := getConfig(ctx)
for _, md := range ms.messages {
if md.key == key {
text, data := mc(md.data)
mess := tgbotapi.NewEditMessageText(md.chatID, md.messID, text)
mess.ParseMode = tgbotapi.ModeMarkdown
_, err := botApi.Send(mess)
if err != nil {
return err
}
md.data = data
ms.runTimeCleaner(ctx, md, d)
return nil
}
}
text, data := mc(nil)
mess := tgbotapi.NewMessage(cfg.ChatID, text)
mess.ParseMode = tgbotapi.ModeMarkdown
m, err := botApi.Send(mess)
if err != nil {
return err
}
md := &messageData{
key: key,
messID: m.MessageID,
chatID: cfg.ChatID,
data: data,
}
ms.messages = append(ms.messages, md)
ms.runTimeCleaner(ctx, md, d)
return nil
}

View File

@ -7,9 +7,7 @@ import (
"sync" "sync"
"time" "time"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/hako/durafmt" "github.com/hako/durafmt"
"github.com/pkg/errors"
) )
type achievement uint8 type achievement uint8
@ -54,7 +52,6 @@ var allAchievement = []achievement{
type playerInfo struct { type playerInfo struct {
playerID string playerID string
playerMessageID int
isOnline bool isOnline bool
achievements []achievement achievements []achievement
messageText string messageText string
@ -74,8 +71,6 @@ const (
) )
func (p *playerInfo) updatePlayerInfo(ctx context.Context) error { func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
botApi := getBotApi(ctx)
conf := getConfig(ctx)
db := getDB(ctx) db := getDB(ctx)
lines := make([]string, 0, 10) lines := make([]string, 0, 10)
@ -132,46 +127,21 @@ func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
text := strings.Join(lines, "\n") text := strings.Join(lines, "\n")
if text != p.messageText { if text != p.messageText {
messStorage := getMessageStorage(ctx)
err := messStorage.apply(ctx, fmt.Sprintf("playerBoard:%s", p.playerID), 0, func(interface{}) (string, interface{}) {
return text, nil
})
var c tgbotapi.Chattable
if p.playerMessageID == 0 {
mess := tgbotapi.NewMessage(conf.ChatID, text)
mess.ParseMode = tgbotapi.ModeMarkdown
c = mess
} else {
mess := tgbotapi.NewEditMessageText(conf.ChatID, p.playerMessageID, text)
mess.ParseMode = tgbotapi.ModeMarkdown
c = mess
}
m, err := botApi.Send(c)
if err != nil { if err != nil {
return errors.Wrap(err, "Failed to send message") return err
} }
p.playerMessageID = m.MessageID
p.messageText = text p.messageText = text
} }
return nil return nil
} }
func (p *playerInfo) deletePlayerInfo(ctx context.Context) error {
botApi := getBotApi(ctx)
conf := getConfig(ctx)
mess := tgbotapi.NewDeleteMessage(conf.ChatID, p.playerMessageID)
_, err := botApi.Send(mess)
if err != nil {
return errors.Wrap(err, "Failed to send message")
}
return nil
}
func (p *playerInfo) setAchievement(ach achievement) { func (p *playerInfo) setAchievement(ach achievement) {
for _, a := range p.achievements { for _, a := range p.achievements {
if a == ach { if a == ach {
@ -249,11 +219,13 @@ func (pb *playersBoard) getPlayerBoard(playerID string) *playerInfo {
pb.mx.Lock() pb.mx.Lock()
defer pb.mx.Unlock() defer pb.mx.Unlock()
pb.players = append(pb.players, &playerInfo{ pi := &playerInfo{
playerID: playerID, playerID: playerID,
}) }
return pb.players[len(pb.players)-1] pb.players = append(pb.players, pi)
return pi
} }
func (pb *playersBoard) updateAchievements(ctx context.Context, ach achievement) error { func (pb *playersBoard) updateAchievements(ctx context.Context, ach achievement) error {
@ -347,19 +319,3 @@ func (pb *playersBoard) update(ctx context.Context, as ...achievement) error {
return nil return nil
} }
func (pb *playersBoard) run(ctx context.Context) {
log := getLogger(ctx)
<-ctx.Done()
pb.mx.Lock()
defer pb.mx.Unlock()
for _, p := range pb.players {
err := p.deletePlayerInfo(ctx)
if err != nil {
log.Error(err)
}
}
}