diff --git a/cmd/butler/main.go b/cmd/butler/main.go index 9bf488c..cddf030 100644 --- a/cmd/butler/main.go +++ b/cmd/butler/main.go @@ -5,6 +5,9 @@ import ( "os" "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger/telegram" + "git.3crabs.ru/VLADIMIR/butler/internal/modules/storage" + "git.3crabs.ru/VLADIMIR/butler/internal/services/bot" + "git.3crabs.ru/VLADIMIR/butler/internal/services/bot/bot_all" "git.3crabs.ru/VLADIMIR/butler/internal/services/listener" ) @@ -14,7 +17,13 @@ func main() { if err != nil { panic(err) } - listenerService := listener.NewListener(messengerTelegram) + var storage storage.IStorage // TODO: init + listenerService := listener.NewListener( + messengerTelegram, + []bot.IBot{ + bot_all.NewBotAll(messengerTelegram, storage), + }, + ) ctx := context.Background() listenerService.Run(ctx) } diff --git a/go.mod b/go.mod index 3ff7a91..0213a0c 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,12 @@ module git.3crabs.ru/VLADIMIR/butler go 1.20 -require github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect +require ( + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 + go.uber.org/mock v0.2.0 +) + +require ( + github.com/samber/lo v1.38.1 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect +) diff --git a/go.sum b/go.sum index db8e45c..64463b0 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,8 @@ github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= +github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= +go.uber.org/mock v0.2.0 h1:TaP3xedm7JaAgScZO7tlvlKrqT0p7I6OsdGB5YNSMDU= +go.uber.org/mock v0.2.0/go.mod h1:J0y0rp9L3xiff1+ZBfKxlC1fz2+aO16tw0tsDOixfuM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= diff --git a/internal/modules/messenger/interface.go b/internal/modules/messenger/interface.go index 99b3dc1..5bb7915 100644 --- a/internal/modules/messenger/interface.go +++ b/internal/modules/messenger/interface.go @@ -1,5 +1,7 @@ package messenger +//go:generate mockgen -source=$GOFILE -destination=mocks/$GOFILE -package=mocks + import "context" type Message struct { diff --git a/internal/modules/messenger/mocks/interface.go b/internal/modules/messenger/mocks/interface.go new file mode 100644 index 0000000..d75176c --- /dev/null +++ b/internal/modules/messenger/mocks/interface.go @@ -0,0 +1,65 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interface.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + messenger "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger" + gomock "go.uber.org/mock/gomock" +) + +// MockIMessenger is a mock of IMessenger interface. +type MockIMessenger struct { + ctrl *gomock.Controller + recorder *MockIMessengerMockRecorder +} + +// MockIMessengerMockRecorder is the mock recorder for MockIMessenger. +type MockIMessengerMockRecorder struct { + mock *MockIMessenger +} + +// NewMockIMessenger creates a new mock instance. +func NewMockIMessenger(ctrl *gomock.Controller) *MockIMessenger { + mock := &MockIMessenger{ctrl: ctrl} + mock.recorder = &MockIMessengerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIMessenger) EXPECT() *MockIMessengerMockRecorder { + return m.recorder +} + +// GetMessage mocks base method. +func (m *MockIMessenger) GetMessage(ctx context.Context) (*messenger.Message, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessage", ctx) + ret0, _ := ret[0].(*messenger.Message) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMessage indicates an expected call of GetMessage. +func (mr *MockIMessengerMockRecorder) GetMessage(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessage", reflect.TypeOf((*MockIMessenger)(nil).GetMessage), ctx) +} + +// SendMessage mocks base method. +func (m *MockIMessenger) SendMessage(ctx context.Context, msg *messenger.Message) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMessage", ctx, msg) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMessage indicates an expected call of SendMessage. +func (mr *MockIMessengerMockRecorder) SendMessage(ctx, msg interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockIMessenger)(nil).SendMessage), ctx, msg) +} diff --git a/internal/modules/storage/interface.go b/internal/modules/storage/interface.go new file mode 100644 index 0000000..d794025 --- /dev/null +++ b/internal/modules/storage/interface.go @@ -0,0 +1,15 @@ +package storage + +import "context" + +//go:generate mockgen -source=$GOFILE -destination=mocks/$GOFILE -package=mocks + +type User struct { + ChatID string + UserID string +} + +type IStorage interface { + UpsertUser(ctx context.Context, user User) error + GetAllUsersByChatID(ctx context.Context, chatID string) ([]User, error) +} diff --git a/internal/modules/storage/mocks/interface.go b/internal/modules/storage/mocks/interface.go new file mode 100644 index 0000000..2400c28 --- /dev/null +++ b/internal/modules/storage/mocks/interface.go @@ -0,0 +1,65 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interface.go + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + storage "git.3crabs.ru/VLADIMIR/butler/internal/modules/storage" + gomock "go.uber.org/mock/gomock" +) + +// MockIStorage is a mock of IStorage interface. +type MockIStorage struct { + ctrl *gomock.Controller + recorder *MockIStorageMockRecorder +} + +// MockIStorageMockRecorder is the mock recorder for MockIStorage. +type MockIStorageMockRecorder struct { + mock *MockIStorage +} + +// NewMockIStorage creates a new mock instance. +func NewMockIStorage(ctrl *gomock.Controller) *MockIStorage { + mock := &MockIStorage{ctrl: ctrl} + mock.recorder = &MockIStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIStorage) EXPECT() *MockIStorageMockRecorder { + return m.recorder +} + +// GetAllUsersByChatID mocks base method. +func (m *MockIStorage) GetAllUsersByChatID(ctx context.Context, chatID string) ([]storage.User, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllUsersByChatID", ctx, chatID) + ret0, _ := ret[0].([]storage.User) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAllUsersByChatID indicates an expected call of GetAllUsersByChatID. +func (mr *MockIStorageMockRecorder) GetAllUsersByChatID(ctx, chatID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllUsersByChatID", reflect.TypeOf((*MockIStorage)(nil).GetAllUsersByChatID), ctx, chatID) +} + +// UpsertUser mocks base method. +func (m *MockIStorage) UpsertUser(ctx context.Context, user storage.User) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpsertUser", ctx, user) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpsertUser indicates an expected call of UpsertUser. +func (mr *MockIStorageMockRecorder) UpsertUser(ctx, user interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpsertUser", reflect.TypeOf((*MockIStorage)(nil).UpsertUser), ctx, user) +} diff --git a/internal/services/bot/bot_all/bot.go b/internal/services/bot/bot_all/bot.go new file mode 100644 index 0000000..c041f63 --- /dev/null +++ b/internal/services/bot/bot_all/bot.go @@ -0,0 +1,53 @@ +package bot_all + +import ( + "context" + "strings" + + "github.com/samber/lo" + + "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger" + "git.3crabs.ru/VLADIMIR/butler/internal/modules/storage" + "git.3crabs.ru/VLADIMIR/butler/internal/services/bot" +) + +type botAll struct { + messenger messenger.IMessenger + storage storage.IStorage +} + +func NewBotAll( + messenger messenger.IMessenger, + storage storage.IStorage, +) bot.IBot { + return &botAll{ + messenger: messenger, + storage: storage, + } +} + +func (bot *botAll) Process(ctx context.Context, msg messenger.Message) error { + if err := bot.storage.UpsertUser(ctx, storage.User{ChatID: msg.ChatID, UserID: msg.UserID}); err != nil { + return err + } + if !strings.Contains(msg.Text, "@all") { + return nil + } + users, err := bot.storage.GetAllUsersByChatID(ctx, msg.ChatID) + if err != nil { + return err + } + usernames := lo.Map(users, func(item storage.User, _ int) string { + return item.UserID + }) + if len(usernames) > 0 { + bot.messenger.SendMessage( + ctx, + &messenger.Message{ + ChatID: msg.ChatID, + Text: strings.Join(usernames, " "), + }, + ) + } + return nil +} diff --git a/internal/services/bot/bot_all/bot_test.go b/internal/services/bot/bot_all/bot_test.go new file mode 100644 index 0000000..163c42b --- /dev/null +++ b/internal/services/bot/bot_all/bot_test.go @@ -0,0 +1,62 @@ +package bot_all + +import ( + "context" + "testing" + + "go.uber.org/mock/gomock" + + "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger" + messenger_mocks "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger/mocks" + "git.3crabs.ru/VLADIMIR/butler/internal/modules/storage" + storage_mocks "git.3crabs.ru/VLADIMIR/butler/internal/modules/storage/mocks" +) + +func Test_botAll_Process(t *testing.T) { + t.Parallel() + tests := []struct { + name string + messenger func(ctrl *gomock.Controller) messenger.IMessenger + storage func(ctrl *gomock.Controller) storage.IStorage + msg messenger.Message + wantErr bool + }{ + { + name: "hello message", + messenger: func(ctrl *gomock.Controller) messenger.IMessenger { + m := messenger_mocks.NewMockIMessenger(ctrl) + m.EXPECT().SendMessage(gomock.Any(), gomock.Any()).Times(0) + return m + }, + storage: func(ctrl *gomock.Controller) storage.IStorage { + m := storage_mocks.NewMockIStorage(ctrl) + m.EXPECT().UpsertUser(gomock.Any(), storage.User{ChatID: "123", UserID: "username"}).Times(1) + m.EXPECT().GetAllUsersByChatID(gomock.Any(), gomock.Any()).Times(0) + return m + }, + msg: messenger.Message{ + ChatID: "123", + UserID: "username", + Text: "hello", + }, + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ctx := context.Background() + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + bot := &botAll{ + messenger: tt.messenger(ctrl), + storage: tt.storage(ctrl), + } + if err := bot.Process(ctx, tt.msg); (err != nil) != tt.wantErr { + t.Errorf("botAll.Process() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/services/bot/interface.go b/internal/services/bot/interface.go new file mode 100644 index 0000000..56443cb --- /dev/null +++ b/internal/services/bot/interface.go @@ -0,0 +1,11 @@ +package bot + +import ( + "context" + + "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger" +) + +type IBot interface { + Process(ctx context.Context, msg messenger.Message) error +} diff --git a/internal/services/listener/listener.go b/internal/services/listener/listener.go index 5c92694..45f9433 100644 --- a/internal/services/listener/listener.go +++ b/internal/services/listener/listener.go @@ -2,21 +2,25 @@ package listener import ( "context" - "fmt" + "log" "git.3crabs.ru/VLADIMIR/butler/internal/modules/messenger" "git.3crabs.ru/VLADIMIR/butler/internal/services" + "git.3crabs.ru/VLADIMIR/butler/internal/services/bot" ) type listenerService struct { messenger messenger.IMessenger + bots []bot.IBot } func NewListener( messenger messenger.IMessenger, + bots []bot.IBot, ) services.IService { return &listenerService{ messenger: messenger, + bots: bots, } } @@ -29,8 +33,10 @@ func (s *listenerService) Run(ctx context.Context) error { } return err } - if err := s.messenger.SendMessage(ctx, msg); err != nil { - fmt.Println(err) + for b := range s.bots { + if err := b.Process(ctx, msg); err != nil { + log.Println(err) + } } } }