diff --git a/api.go b/api.go new file mode 100644 index 0000000..9a2babc --- /dev/null +++ b/api.go @@ -0,0 +1,68 @@ +package laniakea + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" +) + +type ApiResponse struct { + Ok bool `json:"ok"` + Description string `json:"description,omitempty"` + Result any `json:"result,omitempty"` + ErrorCode int `json:"error_code,omitempty"` +} + +// request is a low-level call to api. +func (b *Bot) request(methodName string, params any) (map[string]any, error) { + var buf bytes.Buffer + err := json.NewEncoder(&buf).Encode(params) + if err != nil { + return nil, err + } + + if b.debug && b.requestLogger != nil { + b.requestLogger.Debugln(strings.ReplaceAll(fmt.Sprintf( + "POST https://api.telegram.org/bot%s/%s %s", + "", + methodName, + buf.String(), + ), "\n", "")) + } + r, err := http.Post(fmt.Sprintf("https://api.telegram.org/bot%s/%s", b.token, methodName), "application/json", &buf) + if err != nil { + return nil, err + } + defer r.Body.Close() + data, err := io.ReadAll(r.Body) + if err != nil { + return nil, err + } + b.requestLogger.Debugln(fmt.Sprintf("RES %s %s", methodName, string(data))) + + response := new(ApiResponse) + err = json.Unmarshal(data, &response) + if err != nil { + return nil, err + } + + if !response.Ok { + return nil, fmt.Errorf("[%d] %s", response.ErrorCode, response.Description) + } + if res, ok := response.Result.(bool); ok { + return map[string]any{ + "data": res, + }, nil + } else if res, ok := response.Result.([]any); ok { + return map[string]any{ + "data": res, + }, nil + } else if res, ok := response.Result.(map[string]any); ok { + return res, nil + } + return map[string]any{}, errors.New("can't parse response") +} diff --git a/bot.go b/bot.go index 9d1cea9..91b92b9 100644 --- a/bot.go +++ b/bot.go @@ -1,11 +1,9 @@ package laniakea import ( - "bytes" "encoding/json" "fmt" - "io" - "net/http" + "log" "os" "sort" "strings" @@ -36,6 +34,7 @@ type Bot struct { plugins []*Plugin middlewares []*Middleware prefixes []string + runners []Runner dbContext *DatabaseContext @@ -67,29 +66,18 @@ func LoadSettingsFromEnv() *BotSettings { } } -type MsgContext struct { - Bot *Bot - Msg *Message - Update *Update - From *User - CallbackMsgId int - FromID int - Prefix string - Text string - Args []string +func LoadPrefixesFromEnv() []string { + prefixesS, exists := os.LookupEnv("PREFIXES") + if !exists { + return []string{"!"} + } + return strings.Split(prefixesS, ";") } - -type DatabaseContext struct { - PostgresSQL *sqlx.DB - MongoDB *mongo.Client - Redis *redis.Client -} - func NewBot(settings *BotSettings) *Bot { updateQueue := CreateQueue[*Update](256) bot := &Bot{ updateOffset: 0, plugins: make([]*Plugin, 0), debug: settings.Debug, errorTemplate: "%s", - prefixes: settings.Prefixes, updateTypes: make([]string, 0), + prefixes: settings.Prefixes, updateTypes: make([]string, 0), runners: make([]Runner, 0), updateQueue: updateQueue, token: settings.Token, } @@ -97,10 +85,10 @@ func NewBot(settings *BotSettings) *Bot { if len(settings.ErrorTemplate) > 0 { bot.errorTemplate = settings.ErrorTemplate } - if len(settings.LoggerBasePath) == 0 { settings.LoggerBasePath = "./" } + level := slog.FATAL if settings.Debug { level = slog.DEBUG @@ -134,14 +122,22 @@ func NewBot(settings *BotSettings) *Bot { } func (b *Bot) Close() { - b.logger.Close() - b.requestLogger.Close() + err := b.logger.Close() + if err != nil { + log.Println(err) + } + err = b.requestLogger.Close() + if err != nil { + log.Println(err) + } } -func (b *Bot) InitDatabaseContext(ctx *DatabaseContext) *Bot { - b.dbContext = ctx - return b +type DatabaseContext struct { + PostgresSQL *sqlx.DB + MongoDB *mongo.Client + Redis *redis.Client } + func (b *Bot) AddDatabaseLogger(writer func(db *DatabaseContext) slog.LoggerWriter) *Bot { w := writer(b.dbContext) b.logger.AddWriter(w) @@ -151,6 +147,10 @@ func (b *Bot) AddDatabaseLogger(writer func(db *DatabaseContext) slog.LoggerWrit return b } +func (b *Bot) DatabaseContext(ctx *DatabaseContext) *Bot { + b.dbContext = ctx + return b +} func (b *Bot) UpdateTypes(t ...string) *Bot { b.updateTypes = make([]string, 0) b.updateTypes = append(b.updateTypes, t...) @@ -164,18 +164,11 @@ func (b *Bot) AddPrefixes(prefixes ...string) *Bot { b.prefixes = append(b.prefixes, prefixes...) return b } -func LoadPrefixesFromEnv() []string { - prefixesS, exists := os.LookupEnv("PREFIXES") - if !exists { - return []string{"!"} - } - return strings.Split(prefixesS, ";") -} func (b *Bot) ErrorTemplate(s string) *Bot { b.errorTemplate = s return b } -func (b *Bot) Debugln(debug bool) *Bot { +func (b *Bot) Debug(debug bool) *Bot { b.debug = debug return b } @@ -202,6 +195,17 @@ func (b *Bot) AddMiddleware(middleware ...*Middleware) *Bot { } return b } +func (b *Bot) AddRunner(runner Runner) *Bot { + b.runners = append(b.runners, runner) + b.logger.Debugln(fmt.Sprintf("runner with name \"%s\" registered", runner.Name)) + return b +} +func (b *Bot) Logger() *slog.Logger { + return b.logger +} +func (b *Bot) GetDBContext() *DatabaseContext { + return b.dbContext +} func (b *Bot) Run() { if len(b.prefixes) == 0 { @@ -214,15 +218,16 @@ func (b *Bot) Run() { return } - b.logger.Infoln("Bot running. Press CTRL+C to exit.") + b.logger.Infoln("Executing runners...") + b.ExecRunners() + b.logger.Infoln("Bot running. Press CTRL+C to exit.") go func() { for { _, err := b.Updates() if err != nil { b.logger.Errorln(err) } - time.Sleep(time.Millisecond * 10) } }() @@ -239,8 +244,7 @@ func (b *Bot) Run() { continue } ctx := &MsgContext{ - Bot: b, - Update: u, + Bot: b, Update: u, } for _, middleware := range b.middlewares { middleware.Execute(ctx, b.dbContext) @@ -263,25 +267,26 @@ func (b *Bot) Run() { // {"callback_query":{"chat_instance":"6202057960757700762","data":"aboba","from":{"first_name":"scuroneko","id":314834933,"is_bot":false,"language_code":"ru","username":"scuroneko"},"id":"1352205741990111553","message":{"chat":{"first_name":"scuroneko","id":314834933,"type":"private","username":"scuroneko"},"date":1734338107,"from":{"first_name":"Kurumi","id":7718900880,"is_bot":true,"username":"kurumi_game_bot"},"message_id":19,"reply_markup":{"inline_keyboard":[[{"callback_data":"aboba","text":"Test"},{"callback_data":"another","text":"Another"}]]},"text":"Aboba"}},"update_id":350979488} func (b *Bot) handleMessage(update *Update, ctx *MsgContext) { - var text string if update.Message == nil { return } + + var text string if len(update.Message.Text) > 0 { text = update.Message.Text } else { text = update.Message.Caption } - ctx.FromID = update.Message.From.ID - ctx.From = update.Message.From - ctx.Msg = update.Message text = strings.TrimSpace(text) prefix, hasPrefix := b.checkPrefixes(text) if !hasPrefix { return } ctx.Prefix = prefix + ctx.FromID = update.Message.From.ID + ctx.From = update.Message.From + ctx.Msg = update.Message text = strings.TrimSpace(text[len(prefix):]) @@ -296,6 +301,7 @@ func (b *Bot) handleMessage(update *Update, ctx *MsgContext) { ctx.Args = strings.Split(ctx.Text, " ") go plugin.Execute(cmd, ctx, b.dbContext) + return } } } @@ -320,7 +326,7 @@ func (b *Bot) handleCallback(update *Update, ctx *MsgContext) { continue } go plugin.ExecutePayload(data.Command, ctx, b.dbContext) - break + return } } @@ -332,219 +338,3 @@ func (b *Bot) checkPrefixes(text string) (string, bool) { } return "", false } - -type AnswerMessage struct { - MessageID int - Text string - IsMedia bool - Keyboard *InlineKeyboard - ctx *MsgContext -} - -func (ctx *MsgContext) edit(messageId int, text string, keyboard *InlineKeyboard) *AnswerMessage { - params := &EditMessageTextP{ - MessageID: messageId, - ChatID: ctx.Msg.Chat.ID, - Text: text, - ParseMode: ParseMD, - } - if keyboard != nil { - params.ReplyMarkup = keyboard.Get() - } - msg, err := ctx.Bot.EditMessageText(params) - if err != nil { - ctx.Bot.logger.Errorln(err) - return nil - } - return &AnswerMessage{ - MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: false, - } -} -func (m *AnswerMessage) Edit(text string) *AnswerMessage { - return m.ctx.edit(m.MessageID, text, nil) -} -func (ctx *MsgContext) EditCallback(text string, keyboard *InlineKeyboard) *AnswerMessage { - if ctx.CallbackMsgId == 0 { - ctx.Bot.logger.Errorln("Can't edit non-callback update message") - return nil - } - - return ctx.edit(ctx.CallbackMsgId, text, keyboard) -} -func (ctx *MsgContext) EditCallbackf(format string, keyboard *InlineKeyboard, args ...any) *AnswerMessage { - return ctx.EditCallback(fmt.Sprintf(format, args...), keyboard) -} - -func (ctx *MsgContext) editPhotoText(messageId int, text string, kb *InlineKeyboard) *AnswerMessage { - params := &EditMessageCaptionP{ - ChatID: ctx.Msg.Chat.ID, - MessageID: messageId, - Caption: text, - ParseMode: ParseMD, - } - if kb != nil { - params.ReplyMarkup = kb.Get() - } - msg, err := ctx.Bot.EditMessageCaption(params) - if err != nil { - ctx.Bot.logger.Errorln(err) - } - return &AnswerMessage{ - MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true, - } -} -func (m *AnswerMessage) EditCaption(text string) *AnswerMessage { - return m.ctx.editPhotoText(m.MessageID, text, nil) -} -func (m *AnswerMessage) EditCaptionKeyboard(text string, kb *InlineKeyboard) *AnswerMessage { - return m.ctx.editPhotoText(m.MessageID, text, kb) -} - -func (ctx *MsgContext) answer(text string, keyboard *InlineKeyboard) *AnswerMessage { - params := &SendMessageP{ - ChatID: ctx.Msg.Chat.ID, - Text: text, - ParseMode: ParseMD, - } - if keyboard != nil { - params.ReplyMarkup = keyboard.Get() - } - - msg, err := ctx.Bot.SendMessage(params) - if err != nil { - ctx.Bot.logger.Errorln(err) - return nil - } - return &AnswerMessage{ - MessageID: msg.MessageID, ctx: ctx, IsMedia: false, Text: text, - } -} -func (ctx *MsgContext) Answer(text string) *AnswerMessage { - return ctx.answer(text, nil) -} -func (ctx *MsgContext) Answerf(template string, args ...any) *AnswerMessage { - return ctx.answer(fmt.Sprintf(template, args...), nil) -} -func (ctx *MsgContext) Keyboard(text string, kb *InlineKeyboard) *AnswerMessage { - return ctx.answer(text, kb) -} - -func (ctx *MsgContext) answerPhoto(photoId, text string, kb *InlineKeyboard) *AnswerMessage { - params := &SendPhotoP{ - ChatID: ctx.Msg.Chat.ID, - Caption: text, - Photo: photoId, - ParseMode: ParseMD, - } - if kb != nil { - params.ReplyMarkup = kb.Get() - } - msg, err := ctx.Bot.SendPhoto(params) - if err != nil { - ctx.Bot.logger.Errorln(err) - } - return &AnswerMessage{ - MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true, - } -} -func (ctx *MsgContext) AnswerPhoto(photoId, text string) *AnswerMessage { - return ctx.answerPhoto(photoId, text, nil) -} -func (ctx *MsgContext) AnswerPhotoKeyboard(photoId, text string, kb *InlineKeyboard) *AnswerMessage { - return ctx.answerPhoto(photoId, text, kb) -} - -func (ctx *MsgContext) delete(messageId int) { - _, err := ctx.Bot.DeleteMessage(&DeleteMessageP{ - ChatID: ctx.Msg.Chat.ID, - MessageID: messageId, - }) - if err != nil { - ctx.Bot.logger.Errorln(err) - } -} -func (m *AnswerMessage) Delete() { - m.ctx.delete(m.MessageID) -} -func (ctx *MsgContext) CallbackDelete() { - ctx.delete(ctx.CallbackMsgId) -} - -func (ctx *MsgContext) Error(err error) { - _, sendErr := ctx.Bot.SendMessage(&SendMessageP{ - ChatID: ctx.Msg.Chat.ID, - Text: fmt.Sprintf(ctx.Bot.errorTemplate, EscapeMarkdown(err.Error())), - }) - ctx.Bot.logger.Errorln(err) - - if sendErr != nil { - ctx.Bot.logger.Errorln(sendErr) - } -} - -func (b *Bot) Logger() *slog.Logger { - return b.logger -} - -type ApiResponse struct { - Ok bool `json:"ok"` - Result map[string]any `json:"result,omitempty"` - Description string `json:"description,omitempty"` - ErrorCode int `json:"error_code,omitempty"` -} - -type ApiResponseA struct { - Ok bool `json:"ok"` - Result []any `json:"result,omitempty"` - Description string `json:"description,omitempty"` - ErrorCode int `json:"error_code,omitempty"` -} - -// request is a low-level call to api. -func (b *Bot) request(methodName string, params any) (map[string]interface{}, error) { - var buf bytes.Buffer - err := json.NewEncoder(&buf).Encode(params) - if err != nil { - return nil, err - } - - if b.debug && b.requestLogger != nil { - b.requestLogger.Debugln(strings.ReplaceAll(fmt.Sprintf( - "POST https://api.telegram.org/bot%s/%s %s", - "", - methodName, - buf.String(), - ), "\n", "")) - } - r, err := http.Post(fmt.Sprintf("https://api.telegram.org/bot%s/%s", b.token, methodName), "application/json", &buf) - if err != nil { - return nil, err - } - defer r.Body.Close() - data, err := io.ReadAll(r.Body) - if err != nil { - return nil, err - } - b.requestLogger.Debugln(fmt.Sprintf("RES %s %s", methodName, string(data))) - response := new(ApiResponse) - - var result map[string]any - - err = json.Unmarshal(data, &response) - if err != nil { - responseArray := new(ApiResponseA) - err = json.Unmarshal(data, responseArray) - if err != nil { - return nil, err - } - result = map[string]interface{}{ - "data": responseArray.Result, - } - } else { - result = response.Result - } - if !response.Ok { - return nil, fmt.Errorf("[%d] %s", response.ErrorCode, response.Description) - } - return result, err -} diff --git a/go.mod b/go.mod index 519aafc..fc1aad0 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - golang.org/x/crypto v0.33.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.22.0 // indirect diff --git a/methods.go b/methods.go index cc82b21..f80cfa7 100644 --- a/methods.go +++ b/methods.go @@ -18,29 +18,21 @@ func (b *Bot) Updates() ([]*Update, error) { return nil, err } res := make([]*Update, 0) - for _, u := range data["data"].([]any) { - updateObj := new(Update) - data, err := json.Marshal(u) + err = AnyToStruct(data["data"], &res) + if err != nil { + return res, err + } + + for _, u := range res { + b.updateOffset = u.UpdateID + 1 + err = b.updateQueue.Enqueue(u) if err != nil { return res, err } - err = json.Unmarshal(data, updateObj) - if err != nil { - return res, err - } - //err = MapToStruct(u.(map[string]any), updateObj) - //if err != nil { - // return res, err - //} - b.updateOffset = updateObj.UpdateID + 1 - err = b.updateQueue.Enqueue(updateObj) - if err != nil { - return res, err - } - res = append(res, updateObj) + res = append(res, u) if b.debug && b.requestLogger != nil { - j, err := MapToJson(u.(map[string]interface{})) + j, err := json.Marshal(u) if err != nil { b.logger.Error(err) } diff --git a/msg_context.go b/msg_context.go new file mode 100644 index 0000000..441ee6a --- /dev/null +++ b/msg_context.go @@ -0,0 +1,164 @@ +package laniakea + +import "fmt" + +type MsgContext struct { + Bot *Bot + Msg *Message + Update *Update + From *User + CallbackMsgId int + FromID int + Prefix string + Text string + Args []string +} + +type AnswerMessage struct { + MessageID int + Text string + IsMedia bool + Keyboard *InlineKeyboard + ctx *MsgContext +} + +func (ctx *MsgContext) edit(messageId int, text string, keyboard *InlineKeyboard) *AnswerMessage { + params := &EditMessageTextP{ + MessageID: messageId, + ChatID: ctx.Msg.Chat.ID, + Text: text, + ParseMode: ParseMD, + } + if keyboard != nil { + params.ReplyMarkup = keyboard.Get() + } + msg, err := ctx.Bot.EditMessageText(params) + if err != nil { + ctx.Bot.logger.Errorln(err) + return nil + } + return &AnswerMessage{ + MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: false, + } +} +func (m *AnswerMessage) Edit(text string) *AnswerMessage { + return m.ctx.edit(m.MessageID, text, nil) +} +func (ctx *MsgContext) EditCallback(text string, keyboard *InlineKeyboard) *AnswerMessage { + if ctx.CallbackMsgId == 0 { + ctx.Bot.logger.Errorln("Can't edit non-callback update message") + return nil + } + + return ctx.edit(ctx.CallbackMsgId, text, keyboard) +} +func (ctx *MsgContext) EditCallbackf(format string, keyboard *InlineKeyboard, args ...any) *AnswerMessage { + return ctx.EditCallback(fmt.Sprintf(format, args...), keyboard) +} + +func (ctx *MsgContext) editPhotoText(messageId int, text string, kb *InlineKeyboard) *AnswerMessage { + params := &EditMessageCaptionP{ + ChatID: ctx.Msg.Chat.ID, + MessageID: messageId, + Caption: text, + ParseMode: ParseMD, + } + if kb != nil { + params.ReplyMarkup = kb.Get() + } + msg, err := ctx.Bot.EditMessageCaption(params) + if err != nil { + ctx.Bot.logger.Errorln(err) + } + return &AnswerMessage{ + MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true, + } +} +func (m *AnswerMessage) EditCaption(text string) *AnswerMessage { + return m.ctx.editPhotoText(m.MessageID, text, nil) +} +func (m *AnswerMessage) EditCaptionKeyboard(text string, kb *InlineKeyboard) *AnswerMessage { + return m.ctx.editPhotoText(m.MessageID, text, kb) +} + +func (ctx *MsgContext) answer(text string, keyboard *InlineKeyboard) *AnswerMessage { + params := &SendMessageP{ + ChatID: ctx.Msg.Chat.ID, + Text: text, + ParseMode: ParseMD, + } + if keyboard != nil { + params.ReplyMarkup = keyboard.Get() + } + + msg, err := ctx.Bot.SendMessage(params) + if err != nil { + ctx.Bot.logger.Errorln(err) + return nil + } + return &AnswerMessage{ + MessageID: msg.MessageID, ctx: ctx, IsMedia: false, Text: text, + } +} +func (ctx *MsgContext) Answer(text string) *AnswerMessage { + return ctx.answer(text, nil) +} +func (ctx *MsgContext) Answerf(template string, args ...any) *AnswerMessage { + return ctx.answer(fmt.Sprintf(template, args...), nil) +} +func (ctx *MsgContext) Keyboard(text string, kb *InlineKeyboard) *AnswerMessage { + return ctx.answer(text, kb) +} + +func (ctx *MsgContext) answerPhoto(photoId, text string, kb *InlineKeyboard) *AnswerMessage { + params := &SendPhotoP{ + ChatID: ctx.Msg.Chat.ID, + Caption: text, + Photo: photoId, + ParseMode: ParseMD, + } + if kb != nil { + params.ReplyMarkup = kb.Get() + } + msg, err := ctx.Bot.SendPhoto(params) + if err != nil { + ctx.Bot.logger.Errorln(err) + } + return &AnswerMessage{ + MessageID: msg.MessageID, ctx: ctx, Text: text, IsMedia: true, + } +} +func (ctx *MsgContext) AnswerPhoto(photoId, text string) *AnswerMessage { + return ctx.answerPhoto(photoId, text, nil) +} +func (ctx *MsgContext) AnswerPhotoKeyboard(photoId, text string, kb *InlineKeyboard) *AnswerMessage { + return ctx.answerPhoto(photoId, text, kb) +} + +func (ctx *MsgContext) delete(messageId int) { + _, err := ctx.Bot.DeleteMessage(&DeleteMessageP{ + ChatID: ctx.Msg.Chat.ID, + MessageID: messageId, + }) + if err != nil { + ctx.Bot.logger.Errorln(err) + } +} +func (m *AnswerMessage) Delete() { + m.ctx.delete(m.MessageID) +} +func (ctx *MsgContext) CallbackDelete() { + ctx.delete(ctx.CallbackMsgId) +} + +func (ctx *MsgContext) Error(err error) { + _, sendErr := ctx.Bot.SendMessage(&SendMessageP{ + ChatID: ctx.Msg.Chat.ID, + Text: fmt.Sprintf(ctx.Bot.errorTemplate, EscapeMarkdown(err.Error())), + }) + ctx.Bot.logger.Errorln(err) + + if sendErr != nil { + ctx.Bot.logger.Errorln(sendErr) + } +} diff --git a/runners.go b/runners.go new file mode 100644 index 0000000..75daaa2 --- /dev/null +++ b/runners.go @@ -0,0 +1,80 @@ +package laniakea + +import ( + "time" +) + +type RunnerFn func(*Bot) error +type RunnerBuilder struct { + name string + onetime bool + async bool + timeout time.Duration + fn RunnerFn +} +type Runner struct { + Name string + Onetime bool + Async bool + Timeout time.Duration + Fn RunnerFn +} + +func NewRunner(name string, fn RunnerFn) *RunnerBuilder { + return &RunnerBuilder{ + name: name, fn: fn, async: true, + } +} +func (b *RunnerBuilder) Onetime(onetime bool) *RunnerBuilder { + b.onetime = onetime + return b +} +func (b *RunnerBuilder) Async(async bool) *RunnerBuilder { + b.async = async + return b +} +func (b *RunnerBuilder) Timeout(timeout time.Duration) *RunnerBuilder { + b.timeout = timeout + return b +} +func (b *RunnerBuilder) Build() Runner { + return Runner{ + Name: b.name, Onetime: b.onetime, Async: b.async, Fn: b.fn, Timeout: b.timeout, + } +} + +func (b *Bot) ExecRunners() { + for _, runner := range b.runners { + if !runner.Onetime && !runner.Async { + b.logger.Warnf("Runner %s not onetime, but sync\n", runner.Name) + continue + } + if !runner.Onetime && runner.Async && runner.Timeout == (time.Second*0) { + b.logger.Warnf("Background runner \"%s\" should have timeout", runner.Name) + } + + if runner.Async && runner.Onetime { + go func() { + err := runner.Fn(b) + if err != nil { + b.logger.Warnf("Runner %s failed: %s\n", runner.Name, err) + } + }() + } else if !runner.Async && runner.Onetime { + err := runner.Fn(b) + if err != nil { + b.logger.Warnf("Runner %s failed: %s\n", runner.Name, err) + } + } else if !runner.Onetime { + go func() { + for { + err := runner.Fn(b) + if err != nil { + b.logger.Warnf("Runner %s failed: %s\n", runner.Name, err) + } + time.Sleep(runner.Timeout) + } + }() + } + } +} diff --git a/utils.go b/utils.go index 6725da0..b071e13 100644 --- a/utils.go +++ b/utils.go @@ -6,7 +6,8 @@ import ( "strings" ) -func MapToStruct(m map[string]interface{}, s interface{}) error { +// MapToStruct unsafe function +func MapToStruct(m map[string]any, s any) error { data, err := json.Marshal(m) if err != nil { return err @@ -15,7 +16,27 @@ func MapToStruct(m map[string]interface{}, s interface{}) error { return err } -func MapToJson(m map[string]interface{}) (string, error) { +// SliceToStruct unsafe function +func SliceToStruct(sl []any, s any) error { + data, err := json.Marshal(sl) + if err != nil { + return err + } + err = json.Unmarshal(data, s) + return err +} + +// AnyToStruct unsafe function +func AnyToStruct(src, dest any) error { + data, err := json.Marshal(src) + if err != nil { + return err + } + err = json.Unmarshal(data, dest) + return err +} + +func MapToJson(m map[string]any) (string, error) { data, err := json.Marshal(m) return string(data), err }