github.com/hungdoo/bot@v0.0.0-20240325145135-dd1f386f7b81/src/services/telecommands/service.go (about) 1 package telecommands 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" 10 "github.com/hungdoo/bot/src/common" 11 "github.com/hungdoo/bot/src/packages/cmdparser" 12 command "github.com/hungdoo/bot/src/packages/command/common" 13 "github.com/hungdoo/bot/src/packages/db" 14 "github.com/hungdoo/bot/src/packages/interfaces" 15 "github.com/hungdoo/bot/src/packages/log" 16 "github.com/hungdoo/bot/src/packages/telegram" 17 "github.com/hungdoo/bot/src/packages/utils" 18 lock "github.com/square/mongo-lock" 19 "github.com/urfave/cli" 20 ) 21 22 // @dev external facing controller 23 // Responsibility: listen to command from tele, executes & reports 24 type CommandService struct { 25 interfaces.IService 26 Factory CommandFactory 27 Parser *cli.App 28 LockCli *lock.Client 29 } 30 31 func (s *CommandService) RegisterCommands() { 32 s.Parser.Commands = []cli.Command{ 33 { 34 Name: "add", 35 Aliases: []string{"a"}, 36 Usage: "add a task to the list", 37 Subcommands: []cli.Command{ 38 { 39 Name: "call", 40 UsageText: "name, rpc, contract address, method (axy(address)(uint256), params(pr1;pr2), value index, margin(1%), usdcPrice;precision", 41 Action: func(c *cli.Context) error { 42 fmt.Fprintln(s.Parser.Writer, s.Factory.Add(command.ContractCall, c.Args())) 43 return nil 44 }, 45 }, 46 { 47 Name: "tomb", 48 UsageText: "name, rpc, contract, up, pkIdx, k", 49 Action: func(c *cli.Context) error { 50 fmt.Fprintln(s.Parser.Writer, s.Factory.Add(command.Tomb, c.Args())) 51 return nil 52 }, 53 }, 54 { 55 Name: "balance", 56 UsageText: "name, rpc, ...wallets", 57 Action: func(c *cli.Context) error { 58 fmt.Fprintln(s.Parser.Writer, s.Factory.Add(command.Balance, c.Args())) 59 return nil 60 }, 61 }, 62 { 63 Name: "bybit", 64 UsageText: "name", 65 Action: func(c *cli.Context) error { 66 fmt.Fprintln(s.Parser.Writer, s.Factory.Add(command.BybitIdo, c.Args())) 67 return nil 68 }, 69 }, 70 }, 71 }, 72 { 73 Name: "remove", 74 Aliases: []string{"rm"}, 75 Flags: []cli.Flag{ 76 cli.StringFlag{Name: "task,t", Required: true}, 77 }, 78 Usage: "rm a task", 79 Action: func(c *cli.Context) error { 80 task := c.String("task") 81 fmt.Fprintln(s.Parser.Writer, s.Factory.Remove(task)) 82 return nil 83 }, 84 }, 85 { 86 Name: "show", 87 Aliases: []string{"s"}, 88 Flags: []cli.Flag{ 89 cli.StringFlag{Name: "task,t", Required: true}, 90 }, 91 Usage: "show a task details", 92 Action: func(c *cli.Context) error { 93 task := c.String("task") 94 fmt.Fprintln(s.Parser.Writer, s.Factory.Show(task)) 95 return nil 96 }, 97 }, 98 { 99 Name: "execute", 100 Aliases: []string{"exec"}, 101 Usage: "exec a task", 102 Subcommands: []cli.Command{ 103 { 104 Name: "call", 105 Flags: []cli.Flag{ 106 cli.StringFlag{Name: "task,t", Required: true}, 107 }, 108 Action: func(c *cli.Context) error { 109 task := c.String("task") 110 res, err := s.Factory.Exec(command.ContractCall, task, "") 111 if err != nil { 112 fmt.Fprintln(s.Parser.Writer, err.Error()) 113 return err 114 } 115 fmt.Fprintln(s.Parser.Writer, res) 116 return nil 117 }, 118 }, 119 { 120 Name: "balance", 121 Flags: []cli.Flag{ 122 cli.StringFlag{Name: "task,t", Required: true}, 123 }, 124 Action: func(c *cli.Context) error { 125 task := c.String("task") 126 res, err := s.Factory.Exec(command.Balance, task, "") 127 if err != nil { 128 fmt.Fprintln(s.Parser.Writer, err.Error()) 129 return err 130 } 131 fmt.Fprintln(s.Parser.Writer, res) 132 return nil 133 }, 134 }, 135 { 136 Name: "tomb", 137 Flags: []cli.Flag{ 138 cli.StringFlag{Name: "task,t", Required: true}, 139 cli.StringFlag{Name: "subcommand,sc", Usage: "stats, clear, claim, cronjob, default", Value: "stats"}, 140 }, 141 Action: func(c *cli.Context) error { 142 task := c.String("task") 143 subcommand := c.String("subcommand") 144 res, err := s.Factory.Exec(command.Tomb, task, subcommand) 145 if err != nil { 146 fmt.Fprintln(s.Parser.Writer, err.Error()) 147 return err 148 } 149 fmt.Fprintln(s.Parser.Writer, res) 150 return nil 151 }, 152 }, 153 { 154 Name: "bybit", 155 Flags: []cli.Flag{ 156 cli.StringFlag{Name: "task,t", Required: true}, 157 cli.StringFlag{Name: "subcommand,sc", Usage: "latest, all, default", Value: "latest"}, 158 }, 159 Action: func(c *cli.Context) error { 160 task := c.String("task") 161 subcommand := c.String("subcommand") 162 res, err := s.Factory.Exec(command.BybitIdo, task, subcommand) 163 if err != nil { 164 fmt.Fprintln(s.Parser.Writer, err.Error()) 165 return err 166 } 167 fmt.Fprintln(s.Parser.Writer, res) 168 return nil 169 }, 170 }, 171 }, 172 }, 173 { 174 Name: "list", 175 Aliases: []string{"ls"}, 176 Flags: []cli.Flag{ 177 cli.BoolFlag{Name: "all,a", Usage: "show all"}, 178 }, 179 Usage: "list tasks", 180 Action: func(c *cli.Context) error { 181 showAll := c.Bool("all") 182 fmt.Fprintln(s.Parser.Writer, s.Factory.List(showAll)) 183 return nil 184 }, 185 }, 186 { 187 Name: "on", 188 Aliases: []string{"on"}, 189 Usage: "enable a task", 190 Flags: []cli.Flag{ 191 cli.StringFlag{Name: "task,t", Required: true}, 192 }, 193 Action: func(c *cli.Context) error { 194 task := c.String("task") 195 fmt.Fprintln(s.Parser.Writer, s.Factory.On(task)) 196 return nil 197 }, 198 }, 199 { 200 Name: "off", 201 Aliases: []string{"off"}, 202 Usage: "disable a task", 203 Flags: []cli.Flag{ 204 cli.StringFlag{Name: "task,t", Required: true}, 205 }, 206 Action: func(c *cli.Context) error { 207 task := c.String("task") 208 fmt.Fprintln(s.Parser.Writer, s.Factory.Off(task)) 209 return nil 210 }, 211 }, 212 { 213 Name: "setInterval", 214 Aliases: []string{"si"}, 215 Usage: "set new run interval for task in s", 216 Flags: []cli.Flag{ 217 cli.StringFlag{Name: "task,t", Required: true}, 218 cli.StringFlag{Name: "duration, d", Required: true, Usage: "30s, 1m, 2h"}, 219 }, 220 Action: func(c *cli.Context) error { 221 task := c.String("task") 222 duration, err := utils.ParseCustomDuration(c.String("duration")) 223 if err != nil { 224 fmt.Fprintln(s.Parser.Writer, err.Error()) 225 return err 226 } 227 fmt.Fprintln(s.Parser.Writer, s.Factory.SetInterval(task, duration)) 228 return nil 229 }, 230 }, 231 } 232 } 233 234 func (c *CommandService) process(message string) string { 235 // Refresh jobs 236 _, err := c.Factory.GetJobs() 237 if err != nil { 238 return fmt.Sprintf("GetJobs err: [%s]", err) 239 } 240 241 messages := strings.Split(strings.TrimSpace(message), " ") 242 if err = c.Parser.Run(append([]string{"tele"}, messages...)); err != nil { 243 return fmt.Sprintf("Parser run err: %s", err) 244 } 245 return cmdparser.GetOutput() 246 } 247 248 // Work all commands in intervals 249 func (c *CommandService) Work() { 250 for { 251 jobs, err := c.Factory.GetJobs() 252 if err != nil { 253 log.GeneralLogger.Printf("GetJobs err: [%s]", err) 254 } 255 var results []string 256 for _, j := range jobs { 257 // Create an exclusive lock on a resource. 258 lockId := "mutexLock" + j.GetName() 259 err = c.LockCli.XLock(context.Background(), j.GetName(), lockId, lock.LockDetails{TTL: 60 * 5}) // lock expires in 5min 260 if err != nil { 261 log.GeneralLogger.Printf("Job [%s] XLock err: [%s]", j.GetName(), err.Error()) 262 j.SetError(err.Error()) 263 continue 264 } 265 266 if !j.IsIdle() { 267 result, execErr := j.Execute(false, "") 268 log.GeneralLogger.Printf("[%s] execution: [%s]", j.GetName(), result) 269 j.SetExecutedTime(time.Now()) 270 if execErr != nil && execErr.Level >= common.Error { 271 log.GeneralLogger.Printf("Job [%s] exec failed: [%s]", j.GetName(), execErr.Error()) 272 if execErr.Level >= common.Critical { // should report asap 273 off := c.Factory.Off(j.GetName()) 274 results = append(results, fmt.Sprintf("%v failed with CRIT[%s]: off[%s]", j.GetName(), execErr.Error(), off)) 275 } else { 276 j.SetError(execErr.Error()) // log for ls cmd 277 // results = append(results, fmt.Sprintf("%v failed with [%s:%s]", j.GetName(), execErr.Level, execErr.Error())) 278 } 279 } 280 281 // record result & info error for logging with tele.List cmd, no realtime report 282 if result != "" { 283 j.SetDisplayMsg(result) 284 results = append(results, fmt.Sprintf("[%s]\n%s", j.GetName(), result)) 285 } 286 // exec seccessfully -> update db 287 UpdateCmd(j) 288 } 289 290 // Unlock the resource 291 _, err = c.LockCli.Unlock(context.Background(), lockId) 292 if err != nil { 293 log.GeneralLogger.Printf("XLock Unlock err: [%s]", err) 294 results = append(results, fmt.Sprintf("%v XLock Unlock failed with [%s]", j.GetName(), err)) 295 } 296 log.GeneralLogger.Printf("[%s] XLock Release successfully", j.GetName()) 297 } 298 299 if len(results) != 0 { 300 reportChatId, err := telegram.GetReportChatId() 301 if err != nil { 302 log.GeneralLogger.Printf("[work] get report id failed: [%s]", err) 303 } 304 msg := tgbotapi.NewMessage(reportChatId, strings.Join(results, "\n")) 305 msg.ParseMode = "" 306 telegram.GetBot().Send(msg) 307 } 308 time.Sleep(time.Second * 30) 309 } 310 } 311 312 func (c *CommandService) ListenToCommand() error { 313 telegramUpdateChan, err := telegram.GetUpdates() 314 if err != nil { 315 return fmt.Errorf("%v", err.Error()) 316 } 317 for update := range telegramUpdateChan { 318 if update.Message == nil { 319 continue 320 } 321 322 fromUser := update.Message.From.UserName 323 324 if !telegram.IsWhitelisted(fromUser) { 325 telegram.ReportInvalidAccess(fromUser) 326 continue 327 } 328 reportChatId, err := telegram.GetReportChatId() 329 if err != nil { 330 log.ErrorLogger.Print(err) 331 } 332 log.GeneralLogger.Printf("[%s:%d] %s", fromUser, reportChatId, update.Message.Text) 333 msg := tgbotapi.NewMessage(reportChatId, c.process(update.Message.Text)) 334 msg.ReplyToMessageID = update.Message.MessageID 335 msg.ParseMode = "" 336 337 telegram.GetBot().Send(msg) 338 } 339 return nil 340 } 341 342 func NewService() *CommandService { 343 parser := cmdparser.GetParser() 344 // keys := bson.M{ 345 // "_id": 1, // index in ascending order; -1 for descending order 346 // } 347 // index := mongo.IndexModel{ 348 // Keys: keys, 349 // Options: options.Index().SetUnique(true), 350 // } 351 // _, err := db.GetDb().GetCollection("commands").Indexes().CreateOne(context.Background(), index) 352 // if err != nil { 353 // log.ErrorLogger.Fatal(err) 354 // } 355 356 ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 357 defer cancel() 358 359 collection := db.GetDb().GetCollection("commands_lock") 360 361 // Create a MongoDB lock client. 362 cli := lock.NewClient(collection) 363 364 // Create the required and recommended indexes. 365 cli.CreateIndexes(ctx) 366 367 return &CommandService{Factory: NewCommandFactory(), Parser: parser, LockCli: cli} 368 }