From 2579cd7c94b59a3d7e3c89353947eb9e7ea0c907 Mon Sep 17 00:00:00 2001 From: eleksir Date: Sun, 8 Jun 2025 04:32:16 +0300 Subject: [PATCH] Initial commit --- Makefile | 72 ++++++++++++++++++++++++-- cmd/bunny-bot/main.go | 3 ++ data/config-example.json | 3 +- internal/moon/animation.go | 24 +++++---- internal/moon/casChatAPI.go | 2 + internal/moon/chatMemberUpdated.go | 6 +-- internal/moon/chatlog.go | 45 ++++++++++++++++ internal/moon/globals.go | 2 + internal/moon/leftChatMember.go | 2 + internal/moon/photo.go | 36 +++++++++++++ internal/moon/reaction.go | 53 +++++++++++++++++++ internal/moon/reactionCount.go | 18 +++++++ internal/moon/squash.go | 2 + internal/moon/telegram.go | 18 +++++++ internal/moon/textMessage.go | 82 +++++++++++------------------- internal/moon/types.go | 7 +++ 16 files changed, 306 insertions(+), 69 deletions(-) create mode 100644 internal/moon/chatlog.go create mode 100644 internal/moon/photo.go create mode 100644 internal/moon/reaction.go create mode 100644 internal/moon/reactionCount.go diff --git a/Makefile b/Makefile index a90fcb8..01f594a 100644 --- a/Makefile +++ b/Makefile @@ -2,18 +2,82 @@ GOOPTS=CGO_ENABLED=0 BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath -buildvcs=false +MYNAME=bunny-bot +BINARY=$MYNAME +RMCMD=rm -rf + +# На windows имя бинарника может зависеть не только от платформы, но и от выбранной цели, для linux-а суффикс .exe +# не нужен +ifeq ($(OS),Windows_NT) +ifdef GOOS +ifeq ($(GOOS),windows) +BINARY=${MYNAME}.exe +else # not ifeq ($(GOOS),windows) +BINARY=${MYNAME} +endif # ifeq ($(GOOS),windows) +else # not ifdef GOOS +BINARY=${MYNAME}.exe +endif # ifdef GOOS +ifeq ($(SHELL), sh.exe) +RMCMD=DEL /Q /F +endif +endif + +# Явно определяем символ новой строки, чтобы избежать неоднозначности на windows +define IFS + +endef + all: clean build + build: - ${GOOPTS} go build ${BUILDOPTS} -o bunny-bot ./cmd/bunny-bot +ifeq ($(OS),Windows_NT) +# Looks like on windows gnu make explicitly set SHELL to sh.exe, if it was not set. +ifeq ($(SHELL), sh.exe) +# # Vanilla cmd.exe / powershell. + SET "CGO_ENABLED=0" + go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +else ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +# # git-bash + CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +else # not ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +# # Some other shell. +# # TODO: handle it. + $(info "-- Dunno how to handle this ${SHELL}") +endif # ifeq (,$(findstring(Git/usr/bin/sh.exe, $(SHELL)))) +else # not ($(OS),Windows_NT) + CGO_ENABLED=0 go build ${BUILDOPTS} -o ${BINARY} ./cmd/${MYNAME} +endif # ifeq ($(OS),Windows_NT) + clean: - rm -rf bunny-bot +ifeq ($(OS),Windows_NT) +ifeq ($(SHELL),sh.exe) +# # Vanilla cmd.exe / powershell. + if exist ${BINARY} ${RMCMD} ${BINARY} +else # not ifeq ($(SHELL),sh.exe) + ${RMCMD} ./${BINARY} +endif # ifeq ($(SHELL),sh.exe) +else # not ifeq ($(OS),Windows_NT) + ${RMCMD} ./${BINARY} +endif + upgrade: - rm -rf vendor - go get -u -t -tool ./... +ifeq ($(OS),Windows_NT) +ifeq ($(SHELL),sh.exe) +# # Vanilla cmd.exe / powershell. + if exist vendor DEL /F /S /Q vendor >nul +else # not ifeq ($(SHELL),sh.exe) +# # git-bash + $(RM) -r vendor +endif # ifeq ($(SHELL),sh.exe) +else # not ifeq ($(OS),Windows_NT) + $(RM) -r vendor +endif + go get -d -u ./... go mod tidy go mod vendor diff --git a/cmd/bunny-bot/main.go b/cmd/bunny-bot/main.go index 9be9c53..a80103b 100644 --- a/cmd/bunny-bot/main.go +++ b/cmd/bunny-bot/main.go @@ -13,6 +13,9 @@ import ( "go.uber.org/zap/zapcore" ) +// TODO: use https://dev.to/gholami1313/saving-log-messages-to-a-custom-log-file-in-golang-ce5 for +// +// chat messages. func main() { var err error diff --git a/data/config-example.json b/data/config-example.json index ca945df..7c15697 100644 --- a/data/config-example.json +++ b/data/config-example.json @@ -2,7 +2,8 @@ "name": "Bunny", "token": "get_it_from_botfather", "loglevel": "debug", - "logfile": "bunny-bot.log", + "logfile": "bunny-bot.log", + "logchats": false, "csign": ".", "datadir": "data" } diff --git a/internal/moon/animation.go b/internal/moon/animation.go index 7102a0d..7fccd47 100644 --- a/internal/moon/animation.go +++ b/internal/moon/animation.go @@ -3,14 +3,18 @@ package moon import "github.com/NicoNex/echotron/v3" func animation(msg *echotron.Update) { - Log.Infof( - "User %s %s (username = %s, id = %d) sent animation to chat %s %s (%d)", - msg.Message.From.FirstName, - msg.Message.From.LastName, - msg.Message.From.Username, - msg.Message.From.ID, - msg.Message.Chat.Type, - msg.Message.Chat.Title, - msg.Message.Chat.ID, - ) + if Cfg.LogChats { + Log.Infof( + "User %s %s (username = %s, id = %d) sent animation to chat %s %s (%d)", + msg.Message.From.FirstName, + msg.Message.From.LastName, + msg.Message.From.Username, + msg.Message.From.ID, + msg.Message.Chat.Type, + msg.Message.Chat.Title, + msg.Message.Chat.ID, + ) + } } + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/casChatAPI.go b/internal/moon/casChatAPI.go index badd3f7..aeccb6c 100644 --- a/internal/moon/casChatAPI.go +++ b/internal/moon/casChatAPI.go @@ -93,3 +93,5 @@ func CasCheckID(id int64) (bool, error) { return result, fmt.Errorf("unable to parse response from api.cas.chat: %s", string(body)) //nolint: err113 } + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/chatMemberUpdated.go b/internal/moon/chatMemberUpdated.go index 500b8c5..fee883a 100644 --- a/internal/moon/chatMemberUpdated.go +++ b/internal/moon/chatMemberUpdated.go @@ -34,9 +34,9 @@ func newChatMember(msg *echotron.Update) { msg.ChatMember.Chat.ID, ) - NewMembers[msg.Message.Chat.ID] = cache.New(1 * time.Minute) - AppearedMembers[msg.Message.Chat.ID] = cache.New(1 * time.Minute) - SquashedMembers[msg.Message.Chat.ID] = cache.New(60 * time.Minute) + NewMembers[msg.ChatMember.Chat.ID] = cache.New(1 * time.Minute) + AppearedMembers[msg.ChatMember.Chat.ID] = cache.New(1 * time.Minute) + SquashedMembers[msg.ChatMember.Chat.ID] = cache.New(60 * time.Minute) Log.Debugf( "Add %s %s (%d) to list of known chats", diff --git a/internal/moon/chatlog.go b/internal/moon/chatlog.go new file mode 100644 index 0000000..7fa5d27 --- /dev/null +++ b/internal/moon/chatlog.go @@ -0,0 +1,45 @@ +package moon + +import ( + "fmt" + "log" + "os" +) + +func ChatLog(file, text string) { + var ( + isOpened = false + ) + + for _, l := range chatlogs { + if l.Name == file { + isOpened = true + break + } + } + + if !isOpened { + // open log file + logFile, err := os.OpenFile(file, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0644) + + if err != nil { + fmt.Printf("Unable to open log file %s: %s", file, err) + } + + l := Chatlog{Name: file, File: logFile} + + chatlogs = append(chatlogs, l) + } + + for _, l := range chatlogs { + if l.Name == file { + log.SetOutput(l.File) + // optional: log date-time, filename, and line number + log.SetFlags(log.Lshortfile | log.LstdFlags) + log.Println(text) + break + } + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/globals.go b/internal/moon/globals.go index 5a61e57..bcab8de 100644 --- a/internal/moon/globals.go +++ b/internal/moon/globals.go @@ -28,6 +28,8 @@ var ( AppearedMembers map[int64]*cache.Cache ChatList = make([]int64, 0) + + chatlogs []Chatlog ) /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/leftChatMember.go b/internal/moon/leftChatMember.go index 6179549..b696ee4 100644 --- a/internal/moon/leftChatMember.go +++ b/internal/moon/leftChatMember.go @@ -19,6 +19,8 @@ func leftChatMember(msg *echotron.Update) { msg.Message.Chat.ID, ) + // Save people that left chat to pebbledb. To preserve their state. + // TODO: Remove items on chatMemberUpdated event. if err := Cfg.SaveKeyValue( "LeftMembers", fmt.Sprintf("%d", msg.Message.Chat.ID), diff --git a/internal/moon/photo.go b/internal/moon/photo.go new file mode 100644 index 0000000..07403e3 --- /dev/null +++ b/internal/moon/photo.go @@ -0,0 +1,36 @@ +package moon + +import "github.com/NicoNex/echotron/v3" + +func photo(msg *echotron.Update) { + if Cfg.LogChats { + if msg.Message.Text != "" { + Log.Infof( + "User %s %s (username = %s, id = %d) sent %d photos to %s %s (%d) with text: %s", + msg.Message.From.FirstName, + msg.Message.From.LastName, + msg.Message.From.Username, + msg.Message.From.ID, + len(msg.Message.Photo), + msg.Message.Chat.Type, + msg.Message.Chat.Title, + msg.Message.Chat.ID, + msg.Message.Text, + ) + } else { + Log.Infof( + "User %s %s (username = %s, id = %d) sent %d photos to %s %s (%d)", + msg.Message.From.FirstName, + msg.Message.From.LastName, + msg.Message.From.Username, + msg.Message.From.ID, + len(msg.Message.Photo), + msg.Message.Chat.Type, + msg.Message.Chat.Title, + msg.Message.Chat.ID, + ) + } + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/reaction.go b/internal/moon/reaction.go new file mode 100644 index 0000000..5e06ead --- /dev/null +++ b/internal/moon/reaction.go @@ -0,0 +1,53 @@ +package moon + +import "github.com/NicoNex/echotron/v3" + +func reaction(msg *echotron.Update) { + if Cfg.LogChats { + var reactionString string + + for _, item := range msg.MessageReaction.NewReaction { + if item.Type == "Emoji" { + reactionString += item.Emoji + } else if item.Type == "CustomEmoji" { + reactionString += item.CustomEmoji + } + } + + if reactionString != "" { + Log.Infof( + "User %s %s (username = %s, id = %d) set reaction %s to chat %s %s (%d)", + msg.MessageReaction.User.FirstName, + msg.MessageReaction.User.LastName, + msg.MessageReaction.User.Username, + msg.MessageReaction.User.ID, + reactionString, + msg.MessageReaction.Chat.Type, + msg.MessageReaction.Chat.Title, + msg.MessageReaction.Chat.ID, + ) + } else { + for _, item := range msg.MessageReaction.OldReaction { + if item.Type == "Emoji" { + reactionString += item.Emoji + } else if item.Type == "CustomEmoji" { + reactionString += item.CustomEmoji + } + } + + Log.Infof( + "User %s %s (username = %s, id = %d) removed reaction %s to chat %s %s (%d)", + msg.MessageReaction.User.FirstName, + msg.MessageReaction.User.LastName, + msg.MessageReaction.User.Username, + msg.MessageReaction.User.ID, + reactionString, + msg.MessageReaction.Chat.Type, + msg.MessageReaction.Chat.Title, + msg.MessageReaction.Chat.ID, + ) + } + } +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/reactionCount.go b/internal/moon/reactionCount.go new file mode 100644 index 0000000..bde40dc --- /dev/null +++ b/internal/moon/reactionCount.go @@ -0,0 +1,18 @@ +package moon + +import ( + "github.com/NicoNex/echotron/v3" + "github.com/davecgh/go-spew/spew" +) + +func reactionCount(msg *echotron.Update) { + Log.Infof( + "Reaction count event in %s %s (%d): %", + msg.MessageReaction.Chat.Type, + msg.MessageReaction.Chat.Title, + msg.MessageReaction.Chat.ID, + spew.Sdump(msg), + ) +} + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/squash.go b/internal/moon/squash.go index 4fbe0fb..6857e7a 100644 --- a/internal/moon/squash.go +++ b/internal/moon/squash.go @@ -91,3 +91,5 @@ func squash(chatid int64, chattype, chattitle string, userid int64, firstname, l } } } + +/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */ diff --git a/internal/moon/telegram.go b/internal/moon/telegram.go index aecfa73..6aef4d0 100644 --- a/internal/moon/telegram.go +++ b/internal/moon/telegram.go @@ -72,6 +72,24 @@ func telegramMsgParser(msg *echotron.Update) { return } + if msg.MessageReaction != nil { + reaction(msg) + + return + } + + if msg.MessageReactionCount != nil { + reactionCount(msg) + + return + } + + if msg.Message != nil && msg.Message.Photo != nil { + photo(msg) + + return + } + Log.Debug(spew.Sdump(msg)) } diff --git a/internal/moon/textMessage.go b/internal/moon/textMessage.go index caa68ef..f7dd751 100644 --- a/internal/moon/textMessage.go +++ b/internal/moon/textMessage.go @@ -1,62 +1,42 @@ package moon import ( - "fmt" - "time" - "github.com/NicoNex/echotron/v3" ) func textMessage(msg *echotron.Update) { - if msg.Message.ReplyToMessage == nil { - // Just log it, it seems later here should be ban/mute command set. - Log.Infof( - "User %s %s (username = %s, id = %d) in %s %s (%d) says: %s", - msg.Message.From.FirstName, - msg.Message.From.LastName, - msg.Message.From.Username, - msg.Message.From.ID, - msg.Message.Chat.Type, - msg.Message.Chat.Title, - msg.Message.Chat.ID, - msg.Message.Text, - ) - } else { - // Just log it. - Log.Infof( - "User %s %s (username = %s, id = %d) in %s %s (%d) replies to message %d by %s %s (username = %s, id = %d): %s", - msg.Message.From.FirstName, - msg.Message.From.LastName, - msg.Message.From.Username, - msg.Message.From.ID, - msg.Message.Chat.Type, - msg.Message.Chat.Title, - msg.Message.Chat.ID, - msg.Message.ReplyToMessage.ID, - msg.Message.ReplyToMessage.From.FirstName, - msg.Message.ReplyToMessage.From.LastName, - msg.Message.ReplyToMessage.From.Username, - msg.Message.ReplyToMessage.From.ID, - msg.Message.Text, - ) - } - - // Update database of new members :) - // Try to get timestamp when this member joined to chat - if joinTimestampString := Cfg.GetValue( - "NewMembers", - fmt.Sprintf("%d", msg.Message.Chat.ID), - fmt.Sprintf("%d", msg.Message.From.ID), - ); joinTimestampString == "" { - if err := Cfg.SaveKeyValue( - "NewMembers", - fmt.Sprintf("%d", msg.Message.Chat.ID), - fmt.Sprintf("%d", msg.Message.From.ID), - fmt.Sprintf("%d", time.Now().Unix()), - ); err != nil { - Log.Error(err) + if Cfg.LogChats { + if msg.Message.ReplyToMessage == nil { + // Just log it, it seems later here should be ban/mute command set. + Log.Infof( + "User %s %s (username = %s, id = %d) in %s %s (%d) says: %s", + msg.Message.From.FirstName, + msg.Message.From.LastName, + msg.Message.From.Username, + msg.Message.From.ID, + msg.Message.Chat.Type, + msg.Message.Chat.Title, + msg.Message.Chat.ID, + msg.Message.Text, + ) } else { - Log.Infof("Info about new userid %d saved to new users db", msg.Message.From.ID) + // Just log it. + Log.Infof( + "User %s %s (username = %s, id = %d) in %s %s (%d) replies to message %d by %s %s (username = %s, id = %d): %s", + msg.Message.From.FirstName, + msg.Message.From.LastName, + msg.Message.From.Username, + msg.Message.From.ID, + msg.Message.Chat.Type, + msg.Message.Chat.Title, + msg.Message.Chat.ID, + msg.Message.ReplyToMessage.ID, + msg.Message.ReplyToMessage.From.FirstName, + msg.Message.ReplyToMessage.From.LastName, + msg.Message.ReplyToMessage.From.Username, + msg.Message.ReplyToMessage.From.ID, + msg.Message.Text, + ) } } } diff --git a/internal/moon/types.go b/internal/moon/types.go index a85f6ec..83f9722 100644 --- a/internal/moon/types.go +++ b/internal/moon/types.go @@ -1,6 +1,7 @@ package moon import ( + "os" "time" ) @@ -10,6 +11,7 @@ type MyConfig struct { Token string `json:"token,omitempty"` LogLevel string `json:"loglevel,omitempty"` LogFile string `json:"logfile,omitempty"` + LogChats bool `json:"logchats,omitempty"` CSign string `json:"csign,omitempty"` DataDir string `json:"datadir,omitempty"` } @@ -30,4 +32,9 @@ type CasTrue struct { } `json:"result"` } +type Chatlog struct { + Name string + File *os.File +} + /* vim: set ft=go noet ai ts=4 sw=4 sts=4: */