Compare commits
12 Commits
70594a62d2
...
master
Author | SHA1 | Date | |
---|---|---|---|
08456aa576 | |||
12fce31ed8 | |||
456982e82c | |||
4456ca69a2 | |||
8a3614419a | |||
4c88f0268d | |||
4bf5914d89 | |||
d2cf73b61d | |||
3d010de8e0 | |||
762642b2cf | |||
36055c61a8 | |||
90a86fc18b |
14
.drone.yml
14
.drone.yml
@ -7,6 +7,14 @@ platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
commands:
|
||||
@ -15,7 +23,13 @@ steps:
|
||||
- name: install
|
||||
commands:
|
||||
- make install
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
|
||||
- name: restart
|
||||
commands:
|
||||
- systemctl restart craft-bot
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
|
26
context.go
26
context.go
@ -2,9 +2,10 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/sirupsen/logrus"
|
||||
"os"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
@ -19,6 +20,7 @@ const (
|
||||
ctxKeyMessageCleaner
|
||||
ctxKeyDataBase
|
||||
ctxKeyPlayersBoard
|
||||
ctxKeyMessageStorage
|
||||
)
|
||||
|
||||
func createLogger(debug bool) *logrus.Logger {
|
||||
@ -42,16 +44,16 @@ func createLogger(debug bool) *logrus.Logger {
|
||||
}
|
||||
}
|
||||
|
||||
func createContext(config *Config, bot *tgbotapi.BotAPI, mc *messageCleaner, ch *commandHandler, db *dbLayer, pb *playersBoard) (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 = context.WithValue(ctx, ctxKeyLogger, createLogger(true))
|
||||
ctx = context.WithValue(ctx, ctxKeyBotApi, bot)
|
||||
ctx = context.WithValue(ctx, ctxKeyConfig, config)
|
||||
ctx = context.WithValue(ctx, ctxKeyMessageCleaner, mc)
|
||||
ctx = context.WithValue(ctx, ctxKeyCommandHandler, ch)
|
||||
ctx = context.WithValue(ctx, ctxKeyDataBase, db)
|
||||
ctx = context.WithValue(ctx, ctxKeyPlayersBoard, pb)
|
||||
ctx = context.WithValue(ctx, ctxKeyMessageStorage, ms)
|
||||
|
||||
return ctx, cancelFunc
|
||||
}
|
||||
@ -83,15 +85,6 @@ func getConfig(ctx context.Context) *Config {
|
||||
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 {
|
||||
v := ctx.Value(ctxKeyCommandHandler)
|
||||
if ch, ok := v.(*commandHandler); ok {
|
||||
@ -118,3 +111,12 @@ func getDPlayersBoard(ctx context.Context) *playersBoard {
|
||||
|
||||
panic("Failed to get players board from ctx")
|
||||
}
|
||||
|
||||
func getMessageStorage(ctx context.Context) *messageStorage {
|
||||
v := ctx.Value(ctxKeyMessageStorage)
|
||||
if ms, ok := v.(*messageStorage); ok {
|
||||
return ms
|
||||
}
|
||||
|
||||
panic("Failed to get message storage from ctx")
|
||||
}
|
||||
|
81
handlers.go
81
handlers.go
@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
type command interface {
|
||||
@ -42,19 +40,21 @@ type joinCommand struct {
|
||||
}
|
||||
|
||||
func (c *joinCommand) run(ctx context.Context) {
|
||||
botApi := getBotApi(ctx)
|
||||
cfg := getConfig(ctx)
|
||||
mc := getMessageCleaner(ctx)
|
||||
db := getDB(ctx)
|
||||
log := getLogger(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 {
|
||||
log.Error(err)
|
||||
} else {
|
||||
mc.add(ctx, m.MessageID, cfg.ChatID, time.Second*30)
|
||||
}
|
||||
|
||||
p, err := db.getPlayerByName(ctx, c.name)
|
||||
@ -90,19 +90,21 @@ type quitCommand struct {
|
||||
}
|
||||
|
||||
func (c *quitCommand) run(ctx context.Context) {
|
||||
botApi := getBotApi(ctx)
|
||||
cfg := getConfig(ctx)
|
||||
mc := getMessageCleaner(ctx)
|
||||
db := getDB(ctx)
|
||||
log := getLogger(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 {
|
||||
log.Error(err)
|
||||
} else {
|
||||
mc.add(ctx, m.MessageID, cfg.ChatID, time.Second*30)
|
||||
}
|
||||
|
||||
p, err := db.getPlayerByName(ctx, c.name)
|
||||
@ -114,13 +116,11 @@ func (c *quitCommand) run(ctx context.Context) {
|
||||
err = db.updatePlayerLastLogout(ctx, p.id, time.Now())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.increasePlayerOnlineDuration(ctx, p.id, time.Now().Sub(p.lastLogin))
|
||||
err = db.increasePlayerOnlineDuration(ctx, p.id, time.Since(p.lastLogin))
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
pb.getPlayerBoard(p.id).setOnline(false)
|
||||
@ -140,6 +140,19 @@ func (c *deathCommand) run(ctx context.Context) {
|
||||
db := getDB(ctx)
|
||||
log := getLogger(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)
|
||||
if err != nil {
|
||||
@ -150,7 +163,6 @@ func (c *deathCommand) run(ctx context.Context) {
|
||||
err = db.increasePlayerDeath(ctx, p.id, 1)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = pb.update(ctx, achievementDeathless, achievementBestFeeder)
|
||||
@ -165,20 +177,10 @@ type killCommand struct {
|
||||
}
|
||||
|
||||
func (c *killCommand) run(ctx context.Context) {
|
||||
botApi := getBotApi(ctx)
|
||||
cfg := getConfig(ctx)
|
||||
mc := getMessageCleaner(ctx)
|
||||
db := getDB(ctx)
|
||||
log := getLogger(ctx)
|
||||
pb := getDPlayersBoard(ctx)
|
||||
|
||||
mess := tgbotapi.NewMessage(cfg.ChatID, fmt.Sprintf("%s убил %s", c.name, c.entity))
|
||||
m, err := botApi.Send(mess)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
mc.add(ctx, m.MessageID, cfg.ChatID, time.Second*30)
|
||||
}
|
||||
ms := getMessageStorage(ctx)
|
||||
|
||||
p, err := db.getPlayerByName(ctx, c.name)
|
||||
if err != nil {
|
||||
@ -189,7 +191,21 @@ func (c *killCommand) run(ctx context.Context) {
|
||||
err = db.increasePlayerEntryKills(ctx, p.id, c.entity, 1)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
@ -217,7 +233,6 @@ func (c *changeLevelCommand) run(ctx context.Context) {
|
||||
err = db.updatePlayerLevel(ctx, p.id, c.newLevel)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = pb.update(ctx, achievementMaxLevel)
|
||||
|
30
main.go
30
main.go
@ -55,13 +55,11 @@ func main() {
|
||||
os.Exit(5)
|
||||
}
|
||||
|
||||
mc := newMessageCleaner(bot, &wg)
|
||||
|
||||
ch := newCommandHandler()
|
||||
|
||||
pb := newPlayersBoard()
|
||||
ms := NewMessageStorage()
|
||||
|
||||
ctx, cancelFunc := createContext(&cfg, bot, mc, ch, db, pb)
|
||||
ctx, cancelFunc := createContext(&cfg, bot, ch, db, pb, ms)
|
||||
|
||||
getLogger(ctx).Infof("Run...")
|
||||
|
||||
@ -71,24 +69,16 @@ func main() {
|
||||
os.Exit(5)
|
||||
}
|
||||
|
||||
runPlayerBoard(ctx, pb, &wg)
|
||||
runCommandHandler(ctx, ch, &wg)
|
||||
runMessageStorage(ctx, ms, &wg)
|
||||
runHttpServer(ctx, cfg.Http, &wg)
|
||||
runSignalHandler(ctx, cancelFunc)
|
||||
|
||||
getLogger(ctx).Infof("Running")
|
||||
|
||||
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) {
|
||||
wg.Add(1)
|
||||
|
||||
@ -99,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) {
|
||||
server := http.Server{
|
||||
Addr: addr,
|
||||
|
@ -1,39 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
type messageCleaner struct {
|
||||
botApi *tgbotapi.BotAPI
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
|
||||
func newMessageCleaner(bot *tgbotapi.BotAPI, wg *sync.WaitGroup) *messageCleaner {
|
||||
return &messageCleaner{
|
||||
botApi: bot,
|
||||
wg: wg,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *messageCleaner) add(ctx context.Context, messID int, chatID int64, d time.Duration) {
|
||||
m.wg.Add(1)
|
||||
|
||||
go func() {
|
||||
defer m.wg.Done()
|
||||
|
||||
select {
|
||||
case <-time.NewTimer(d).C:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
_, _ = m.botApi.DeleteMessage(tgbotapi.DeleteMessageConfig{
|
||||
MessageID: messID,
|
||||
ChatID: chatID,
|
||||
})
|
||||
}()
|
||||
}
|
131
messageStorage.go
Normal file
131
messageStorage.go
Normal 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
|
||||
}
|
@ -7,9 +7,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
"github.com/hako/durafmt"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type achievement uint8
|
||||
@ -53,11 +51,10 @@ var allAchievement = []achievement{
|
||||
}
|
||||
|
||||
type playerInfo struct {
|
||||
playerID string
|
||||
playerMessageID int
|
||||
isOnline bool
|
||||
achievements []achievement
|
||||
messageText string
|
||||
playerID string
|
||||
isOnline bool
|
||||
achievements []achievement
|
||||
messageText string
|
||||
}
|
||||
|
||||
func (p *playerInfo) setOnline(v bool) *playerInfo {
|
||||
@ -74,8 +71,6 @@ const (
|
||||
)
|
||||
|
||||
func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
|
||||
botApi := getBotApi(ctx)
|
||||
conf := getConfig(ctx)
|
||||
db := getDB(ctx)
|
||||
|
||||
lines := make([]string, 0, 10)
|
||||
@ -132,46 +127,21 @@ func (p *playerInfo) updatePlayerInfo(ctx context.Context) error {
|
||||
text := strings.Join(lines, "\n")
|
||||
|
||||
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 {
|
||||
return errors.Wrap(err, "Failed to send message")
|
||||
return err
|
||||
}
|
||||
|
||||
p.playerMessageID = m.MessageID
|
||||
p.messageText = text
|
||||
}
|
||||
|
||||
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) {
|
||||
for _, a := range p.achievements {
|
||||
if a == ach {
|
||||
@ -249,11 +219,13 @@ func (pb *playersBoard) getPlayerBoard(playerID string) *playerInfo {
|
||||
pb.mx.Lock()
|
||||
defer pb.mx.Unlock()
|
||||
|
||||
pb.players = append(pb.players, &playerInfo{
|
||||
pi := &playerInfo{
|
||||
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 {
|
||||
@ -347,19 +319,3 @@ func (pb *playersBoard) update(ctx context.Context, as ...achievement) error {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user