slashInterceptor.go (57802B)
1 package interceptors 2 3 import ( 4 "dkforest/pkg/clockwork" 5 "dkforest/pkg/config" 6 "dkforest/pkg/database" 7 dutils "dkforest/pkg/database/utils" 8 "dkforest/pkg/levenshtein" 9 "dkforest/pkg/managers" 10 "dkforest/pkg/utils" 11 "dkforest/pkg/web/handlers/interceptors/command" 12 "dkforest/pkg/web/handlers/poker" 13 "dkforest/pkg/web/handlers/streamModals" 14 "errors" 15 "fmt" 16 "github.com/ProtonMail/go-crypto/openpgp/clearsign" 17 "github.com/ProtonMail/go-crypto/openpgp/packet" 18 "github.com/asaskevich/govalidator" 19 "github.com/dustin/go-humanize" 20 "github.com/sirupsen/logrus" 21 "html" 22 "os" 23 "path/filepath" 24 "sort" 25 "strconv" 26 "strings" 27 "time" 28 ) 29 30 // SlashInterceptor handle all forward slash commands. 31 // 32 // If by the end of this function, the c.err is set, it will trigger 33 // different behavior according to the type of error it holds. 34 // if c.err is set to ErrRedirect, the chat-bar iframe will refresh completely. 35 // if c.err is set to ErrStop, no further processing of the user input will be done, 36 // 37 // and the chat iframe will be rendered instead of redirected. 38 // This is useful to keep a prefix in the text box (eg: /pm user ) 39 // 40 // if c.err is set to an instance of ErrSuccess, 41 // 42 // a green message will appear beside the text box. 43 // 44 // otherwise if c.err is set to a different error, 45 // 46 // text box is retested to original message, 47 // and a red message will appear beside the text box. 48 type SlashInterceptor struct{} 49 50 type CmdHandler func(c *command.Command) (handled bool) 51 52 var userCmdsMap = map[string]CmdHandler{ 53 "/i": handleIgnoreCmd, 54 "/ignore": handleIgnoreCmd, 55 "/ui": handleUnIgnoreCmd, 56 "/unignore": handleUnIgnoreCmd, 57 "/toggle-autocomplete": handleToggleAutocomplete, 58 "/tuto": handleTutorialCmd, 59 "/d": handleDeleteMsgCmd, 60 "/hide": handleHideMsgCmd, 61 "/unhide": handleUnHideMsgCmd, 62 "/pmwhitelist": handleListPmWhitelistCmd, 63 "/setpmmode": handleSetPmModeCmd, 64 "/pmb": handleTogglePmBlacklistedUser, 65 "/pmw": handleTogglePmWhitelistedUser, 66 "/g": handleGroupChatCmd, 67 "/me": handleMeCmd, 68 "/e": handleEditCmd, 69 "/pm": handlePMCmd, 70 "/subscribe": handleSubscribeCmd, 71 "/unsubscribe": handleUnsubscribeCmd, 72 "/p": handleProfileCmd, 73 "/inbox": handleInboxCmd, 74 "/chess": handleChessCmd, 75 "/hbm": handleHbmCmd, 76 "/hbmt": handleHbmtCmd, 77 "/token": handleTokenCmd, 78 "/md5": handleMd5Cmd, 79 "/sha1": handleSha1Cmd, 80 "/sha256": handleSha256Cmd, 81 "/sha512": handleSha512Cmd, 82 "/dice": handleDiceCmd, 83 "/rand": handleRandCmd, 84 "/choice": handleChoiceCmd, 85 "/memes": handleListMemes, 86 "/success": handleSuccessCmd, 87 "/afk": handleAfkCmd, 88 "/date": handleDateCmd, 89 "/r": handleUpdateReadMarkerCmd, 90 "/code": handleCodeCmd, 91 "/locate": handleLocateCmd, 92 "/error": handleErrorCmd, 93 "/chips": handleChipsBalanceCmd, 94 "/chips-reset": handleChipsResetCmd, 95 "/wizz": handleWizzCmd, 96 "/itr": handleInThisRoomCmd, 97 "/check": handleCheckCmd, 98 "/call": handleCallCmd, 99 "/fold": handleFoldCmd, 100 "/raise": handleRaiseCmd, 101 "/allin": handleAllInCmd, 102 "/bet": handleBetCmd, 103 "/deal": handleDealCmd, 104 "/dist": handleDistCmd, 105 //"/chips-send": handleChipsSendCmd, 106 } 107 108 var privateRoomCmdsMap = map[string]CmdHandler{ 109 "/mode": handleGetModeCmd, 110 "/wl": handleWhitelistCmd, 111 "/whitelist": handleWhitelistCmd, 112 } 113 114 var privateRoomOwnerCmdsMap = map[string]CmdHandler{ 115 "/addgroup": handleAddGroupCmd, 116 "/rmgroup": handleRmGroupCmd, 117 "/glock": handleLockGroupCmd, 118 "/gunlock": handleUnlockGroupCmd, 119 "/gusers": handleGroupUsersCmd, 120 "/groups": handleListGroupsCmd, 121 "/gadduser": handleGroupAddUserCmd, 122 "/grmuser": handleGroupRmUserCmd, 123 "/mode": handleSetModeCmd, 124 "/ro": handleToggleReadOnlyCmd, 125 "/wl": handleGetRoomWhitelistCmd, 126 "/whitelist": handleGetRoomWhitelistCmd, 127 } 128 129 var moderatorCmdsMap = map[string]CmdHandler{ 130 "/m": handleModeratorGroupCmd, 131 "/n": handleModeratorGroupCmd, 132 "/moderators": handleListModeratorsCmd, 133 "/mods": handleListModeratorsCmd, 134 "/k": handleKickCmd, 135 "/kick": handleKickCmd, 136 "/kk": handleKickKeepCmd, 137 "/ks": handleKickSilentCmd, 138 "/kks": handleKickKeepSilentCmd, 139 "/uk": handleUnkickCmd, 140 "/unkick": handleUnkickCmd, 141 "/logout": handleLogoutCmd, 142 "/captcha": handleForceCaptchaCmd, 143 "/rtuto": handleResetTutorialCmd, 144 "/hb": handleHellbanCmd, 145 "/hellban": handleHellbanCmd, 146 "/unhellban": handleUnhellbanCmd, 147 "/uhb": handleUnhellbanCmd, 148 "/invite": handleInviteCmd, 149 } 150 151 var adminCmdsMap = map[string]CmdHandler{ 152 "/sys": handleSystemCmd, 153 "/system": handleSystemCmd, 154 "/seturl": handleSetChatRoomExternalLink, 155 "/purge": handlePurge, 156 "/rename": handleRename, 157 "/meme": handleNewMeme, 158 "/memerm": handleRemoveMeme, 159 "/refresh": handleRefreshCmd, 160 "/chips": handleChipsCmd, 161 "/close": handleCloseCmd, 162 "/closem": handleCloseMenuCmd, 163 } 164 165 func (i SlashInterceptor) InterceptMsg(c *command.Command) { 166 if !strings.HasPrefix(c.Message, "/") || 167 strings.HasPrefix(c.Message, "/u/") { 168 return 169 } 170 handled := handleUserCmd(c) || 171 handlePrivateRoomCmd(c) || 172 handlePrivateRoomOwnerCmd(c) || 173 handleModeratorCmd(c) || 174 handleAdminCmd(c) 175 if !handled { 176 c.Err = errors.New("invalid slash command") 177 } 178 } 179 180 func handleUserCmd(c *command.Command) (handled bool) { 181 cmd := strings.Fields(c.Message)[0] 182 if cmdFn, found := userCmdsMap[cmd]; found { 183 return cmdFn(c) 184 } 185 return 186 } 187 188 func handlePrivateRoomCmd(c *command.Command) (handled bool) { 189 cmd := strings.Fields(c.Message)[0] 190 if cmdFn, found := privateRoomCmdsMap[cmd]; found { 191 return cmdFn(c) 192 } 193 return 194 } 195 196 func handlePrivateRoomOwnerCmd(c *command.Command) (handled bool) { 197 if c.Room.IsRoomOwner(c.AuthUser.ID) || c.AuthUser.IsAdmin { 198 cmd := strings.Fields(c.Message)[0] 199 if cmdFn, found := privateRoomOwnerCmdsMap[cmd]; found { 200 return cmdFn(c) 201 } 202 } 203 return false 204 } 205 206 func handleModeratorCmd(c *command.Command) (handled bool) { 207 if c.AuthUser.IsModerator() { 208 cmd := strings.Fields(c.Message)[0] 209 if cmdFn, found := moderatorCmdsMap[cmd]; found { 210 return cmdFn(c) 211 } 212 } 213 return false 214 } 215 216 func handleAdminCmd(c *command.Command) (handled bool) { 217 if c.AuthUser.IsAdmin { 218 cmd := strings.Fields(c.Message)[0] 219 if cmdFn, found := adminCmdsMap[cmd]; found { 220 return cmdFn(c) 221 } 222 } 223 return false 224 } 225 226 func handleModeratorGroupCmd(c *command.Command) (handled bool) { 227 if strings.HasPrefix(c.Message, "/m ") || strings.HasPrefix(c.Message, "/n ") { 228 if strings.HasPrefix(c.Message, "/n ") { 229 c.Message = strings.Replace(c.Message, "/n ", "/m ", 1) 230 } 231 c.Message = strings.TrimPrefix(c.Message, "/m ") 232 c.RedirectQP.Set(command.RedirectModQP, "1") 233 c.ModMsg = true 234 if handleMeCmd(c) { 235 return true 236 } else if handleCodeCmd(c) { 237 return true 238 } 239 return true 240 } 241 return false 242 } 243 244 func handleListModeratorsCmd(c *command.Command) (handled bool) { 245 if c.Message == "/moderators" || c.Message == "/mods" { 246 mods, err := c.DB.GetModeratorsUsers() 247 if err != nil { 248 c.Err = err 249 return true 250 } 251 msg := "Moderators:\n" 252 if len(mods) > 0 { 253 msg += "\n" 254 for _, mod := range mods { 255 msg += mod.Username.AtStr() + "\n" 256 } 257 } else { 258 msg += "no moderators" 259 } 260 c.ZeroProcMsg(msg) 261 c.Err = command.ErrRedirect 262 return true 263 } 264 return false 265 } 266 267 func handleInThisRoomCmd(c *command.Command) (handled bool) { 268 if c.Message == "/itr" { 269 membersInRoom, _ := managers.ActiveUsers.GetRoomUsers(c.Room, managers.GetUserIgnoreSet(c.DB, c.AuthUser)) 270 271 msg := "In this room:" 272 if len(membersInRoom) > 0 { 273 msg += " " 274 for _, mod := range membersInRoom { 275 msg += mod.Username.AtStr() + " " 276 } 277 } else { 278 msg += "no one" 279 } 280 c.ZeroProcMsg(msg) 281 c.Err = command.ErrRedirect 282 return true 283 } 284 return false 285 } 286 287 func handleKickCmd(c *command.Command) (handled bool) { 288 if m := kickRgx.FindStringSubmatch(c.Message); len(m) == 2 { 289 username := database.Username(m[1]) 290 if err := kickCmd(c, username, true, false); err != nil { 291 c.Err = err 292 return true 293 } 294 c.Err = command.ErrRedirect 295 return true 296 } 297 return 298 } 299 300 // Kick a user but keep the messages 301 func handleKickKeepCmd(c *command.Command) (handled bool) { 302 if m := kickKeepRgx.FindStringSubmatch(c.Message); len(m) == 2 { 303 username := database.Username(m[1]) 304 if err := kickCmd(c, username, false, false); err != nil { 305 c.Err = err 306 return true 307 } 308 c.Err = command.ErrRedirect 309 return true 310 } 311 return 312 } 313 314 // Kick a user, no system message in chat 315 func handleKickSilentCmd(c *command.Command) (handled bool) { 316 if m := kickSilentRgx.FindStringSubmatch(c.Message); len(m) == 2 { 317 username := database.Username(m[1]) 318 if err := kickCmd(c, username, true, true); err != nil { 319 c.Err = err 320 return true 321 } 322 c.Err = command.ErrRedirect 323 return true 324 } 325 return 326 } 327 328 // Kick a user, keep the messages, no system message in chat 329 func handleKickKeepSilentCmd(c *command.Command) (handled bool) { 330 if m := kickKeepSilentRgx.FindStringSubmatch(c.Message); len(m) == 2 { 331 username := database.Username(m[1]) 332 if err := kickCmd(c, username, false, true); err != nil { 333 c.Err = err 334 return true 335 } 336 c.Err = command.ErrRedirect 337 return true 338 } 339 return 340 } 341 342 func kickCmd(c *command.Command, username database.Username, purge, silent bool) error { 343 user, err := c.DB.GetUserByUsername(username) 344 if err != nil { 345 return ErrUsernameNotFound 346 } 347 return dutils.Kick(c.DB, user, *c.AuthUser, purge, silent) 348 } 349 350 var ErrUsernameNotFound = errors.New("username not found") 351 var ErrUnauthorized = errors.New("unauthorized") 352 353 func handleUnkickCmd(c *command.Command) (handled bool) { 354 if m := unkickRgx.FindStringSubmatch(c.Message); len(m) == 2 { 355 username := database.Username(m[1]) 356 user, err := c.DB.GetUserByUsername(username) 357 if err != nil { 358 c.Err = ErrUsernameNotFound 359 return true 360 } 361 if user.Verified { 362 c.Err = errors.New("user already not kicked") 363 return true 364 } 365 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("unkick %s #%d", user.Username, user.ID)) 366 user.SetVerified(c.DB, true) 367 368 // Display unkick message 369 c.DB.CreateUnkickMsg(user, *c.AuthUser) 370 371 c.Err = command.ErrRedirect 372 return true 373 } 374 return 375 } 376 377 func handleForceCaptchaCmd(c *command.Command) (handled bool) { 378 if m := forceCaptchaRgx.FindStringSubmatch(c.Message); len(m) == 2 { 379 username := database.Username(m[1]) 380 user, err := c.DB.GetUserByUsername(username) 381 if err != nil { 382 c.Err = ErrUsernameNotFound 383 return true 384 } 385 if c.AuthUser.IsAdmin || !user.IsModerator() || c.AuthUser.Username == username { 386 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("force captcha %s #%d", user.Username, user.ID)) 387 user.SetCaptchaRequired(c.DB, true) 388 database.MsgPubSub.Pub("refresh_"+string(user.Username), database.ChatMessageType{Typ: database.ForceRefresh}) 389 } 390 c.Err = command.ErrRedirect 391 return true 392 } 393 return 394 } 395 396 func handleLogoutCmd(c *command.Command) (handled bool) { 397 if m := logoutRgx.FindStringSubmatch(c.Message); len(m) == 2 { 398 username := database.Username(m[1]) 399 user, err := c.DB.GetUserByUsername(username) 400 if err != nil { 401 c.Err = ErrUsernameNotFound 402 return true 403 } 404 if !c.AuthUser.IsAdmin && user.Vetted { 405 c.Err = ErrUnauthorized 406 return true 407 } 408 if c.AuthUser.IsAdmin || !user.IsModerator() { 409 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("logout %s #%d", user.Username, user.ID)) 410 411 _ = c.DB.DeleteUserSessions(user.ID) 412 413 // Remove user from the user cache 414 managers.ActiveUsers.RemoveUser(user.ID) 415 database.MsgPubSub.Pub("refresh_"+string(user.Username), database.ChatMessageType{Typ: database.ForceRefresh}) 416 } 417 418 c.Err = command.ErrRedirect 419 return true 420 } 421 return 422 } 423 424 func handleResetTutorialCmd(c *command.Command) (handled bool) { 425 if m := rtutoRgx.FindStringSubmatch(c.Message); len(m) == 2 { 426 username := database.Username(m[1]) 427 user, err := c.DB.GetUserByUsername(username) 428 if err != nil { 429 c.Err = ErrUsernameNotFound 430 return true 431 } 432 if !c.AuthUser.IsAdmin && user.Vetted { 433 c.Err = ErrUnauthorized 434 return true 435 } 436 if c.AuthUser.IsAdmin || !user.IsModerator() { 437 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("rtuto %s #%d", user.Username, user.ID)) 438 user.ResetTutorial(c.DB) 439 } 440 c.Err = command.ErrRedirect 441 return true 442 } 443 return 444 } 445 446 func handleHellbanCmd(c *command.Command) (handled bool) { 447 if m := hellbanRgx.FindStringSubmatch(c.Message); len(m) == 2 { 448 username := database.Username(m[1]) 449 user, err := c.DB.GetUserByUsername(username) 450 if err != nil { 451 c.Err = ErrUsernameNotFound 452 return true 453 } 454 if !c.AuthUser.IsAdmin && (user.Vetted || user.IsModerator()) { 455 c.Err = ErrUnauthorized 456 return true 457 } 458 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("hellban %s #%d", user.Username, user.ID)) 459 user.HellBan(c.DB) 460 managers.ActiveUsers.UpdateUserHBInRooms(managers.NewUserInfo(&user)) 461 462 c.Err = command.ErrRedirect 463 return true 464 } 465 return 466 } 467 468 func handleUnhellbanCmd(c *command.Command) (handled bool) { 469 if m := unhellbanRgx.FindStringSubmatch(c.Message); len(m) == 2 { 470 username := database.Username(m[1]) 471 user, err := c.DB.GetUserByUsername(username) 472 if err != nil { 473 c.Err = ErrUsernameNotFound 474 return true 475 } 476 if !c.AuthUser.IsAdmin && (user.Vetted || user.IsModerator()) { 477 c.Err = ErrUnauthorized 478 return true 479 } 480 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("unhellban %s #%d", user.Username, user.ID)) 481 user.UnHellBan(c.DB) 482 managers.ActiveUsers.UpdateUserHBInRooms(managers.NewUserInfo(&user)) 483 484 c.Err = command.ErrRedirect 485 return true 486 } 487 return false 488 } 489 490 func handleInviteCmd(c *command.Command) (handled bool) { 491 if m := inviteRgx.FindStringSubmatch(c.Message); len(m) == 3 { 492 username := database.Username(m[1]) 493 nbInvites := utils.Clamp(utils.DoParseInt(m[2]), 1, 10) 494 if err := c.SetToUser(username); err != nil { 495 return true 496 } 497 c.RawMessage(buildInviteMessage(c, nbInvites)) 498 c.RedirectQP.Set(command.RedirectPmQP, string(c.ToUser.Username)) 499 return true 500 } 501 return 502 } 503 504 func buildInviteMessage(c *command.Command, nbInvites int) string { 505 tokens := make([]string, 0) 506 for i := 0; i < nbInvites; i++ { 507 if inviteToken, err := c.DB.CreateInvitation(c.AuthUser.ID); err == nil { 508 tokens = append(tokens, fmt.Sprintf(`<span style="color: Aqua; user-select: all; -webkit-user-select: all;">%s</span>`, inviteToken.Token)) 509 } 510 } 511 return fmt.Sprintf(`invitation tokens:<br />%s`, strings.Join(tokens, "<br />")) 512 } 513 514 func handleHbmCmd(c *command.Command) (handled bool) { 515 if !c.AuthUser.CanSeeHB() { 516 return 517 } 518 if strings.HasPrefix(c.Message, "/hbm ") { 519 c.Message = strings.TrimPrefix(c.Message, "/hbm ") 520 c.HellbanMsg = true 521 c.RedirectQP.Set(command.RedirectHbmQP, "1") 522 return true 523 } 524 return 525 } 526 527 func handleHbmtCmd(c *command.Command) (handled bool) { 528 if !c.AuthUser.CanSeeHB() { 529 return 530 } 531 if m := hbmtRgx.FindStringSubmatch(c.Message); len(m) == 2 { 532 date := m[1] 533 if dt, err := utils.ParsePrevDatetimeAt(date, clockwork.NewRealClock()); err == nil { 534 if msg, err := c.DB.GetRoomChatMessageByDate(c.Room.ID, c.AuthUser.ID, dt.UTC()); err == nil { 535 msg.IsHellbanned = !msg.IsHellbanned 536 msg.DoSave(c.DB) 537 } else { 538 c.Err = errors.New("no message found at this timestamp") 539 return true 540 } 541 } 542 c.Err = command.ErrRedirect 543 return true 544 } 545 return 546 } 547 548 func handleDiceCmd(c *command.Command) (handled bool) { 549 if strings.HasPrefix(c.Message, "/dice") { 550 dice := utils.RandInt(1, 6) 551 raw := fmt.Sprintf(`rolling dice for @%s ... "%d"`, c.AuthUser.Username, dice) 552 msg := fmt.Sprintf(`rolling dice for @%s ... "<span style="color: white;">%d</span>"`, c.AuthUser.Username, dice) 553 msg, _ = dutils.ColorifyTaggedUsers(msg, c.DB.GetUsersByUsername) 554 go func() { 555 time.Sleep(time.Second) 556 c.ZeroPublicMsg(raw, msg) 557 }() 558 return true 559 } 560 return 561 } 562 563 func handleRandCmd(c *command.Command) (handled bool) { 564 if strings.HasPrefix(c.Message, "/rand") { 565 minV := 1 566 maxV := 6 567 var dice int 568 if m := randRgx.FindStringSubmatch(c.Message); len(m) == 3 { 569 var err error 570 minV, err = strconv.Atoi(m[1]) 571 if err != nil { 572 c.Err = err 573 return true 574 } 575 maxV, err = strconv.Atoi(m[2]) 576 if err != nil { 577 c.Err = err 578 return true 579 } 580 if maxV <= minV { 581 c.Err = errors.New("max must be greater than min") 582 return true 583 } 584 } else if c.Message != "/rand" { 585 c.Err = errors.New("invalid /rand command") 586 return true 587 } 588 dice = utils.RandInt(minV, maxV) 589 raw := fmt.Sprintf(`rolling dice for @%s ... "%d"`, c.AuthUser.Username, dice) 590 msg := fmt.Sprintf(`rolling dice for @%s ... "<span style="color: white;">%d</span>"`, c.AuthUser.Username, dice) 591 msg, _ = dutils.ColorifyTaggedUsers(msg, c.DB.GetUsersByUsername) 592 go func() { 593 time.Sleep(time.Second) 594 c.ZeroPublicMsg(raw, msg) 595 }() 596 return true 597 } 598 return 599 } 600 601 func handleChoiceCmd(c *command.Command) (handled bool) { 602 if strings.HasPrefix(c.Message, "/choice ") { 603 tmp := html.EscapeString(strings.TrimPrefix(c.Message, "/choice ")) 604 words := strings.Fields(tmp) 605 answer := utils.RandChoice(words) 606 raw := fmt.Sprintf(`@%s choice %s ... "%s"`, c.AuthUser.Username, words, answer) 607 msg := fmt.Sprintf(`@%s choice %s ... "<span style="color: white;">%s</span>"`, c.AuthUser.Username, words, answer) 608 msg, _ = dutils.ColorifyTaggedUsers(msg, c.DB.GetUsersByUsername) 609 go func() { 610 time.Sleep(time.Second) 611 c.ZeroPublicMsg(raw, msg) 612 }() 613 c.SkipInboxes = true 614 return true 615 } 616 return 617 } 618 619 func handleTokenCmd(c *command.Command) (handled bool) { 620 if c.Message == "/token" { 621 c.ZeroMsg(utils.GenerateToken10()) 622 c.Err = command.ErrRedirect 623 return true 624 } else if m := tokenRgx.FindStringSubmatch(c.Message); len(m) == 2 { 625 n, _ := strconv.Atoi(m[1]) 626 if n < 1 || n > 32 { 627 c.Err = errors.New("value must be [1;32]") 628 return true 629 } 630 n = utils.Clamp(n, 1, 32) 631 c.ZeroMsg(utils.GenerateTokenN(n)) 632 c.Err = command.ErrRedirect 633 return true 634 } 635 return 636 } 637 638 func handleMd5Cmd(c *command.Command) (handled bool) { 639 return handleHasherCmd(c, "/md5 ", utils.MD5) 640 } 641 642 func handleSha1Cmd(c *command.Command) (handled bool) { 643 return handleHasherCmd(c, "/sha1 ", utils.Sha1) 644 } 645 646 func handleSha256Cmd(c *command.Command) (handled bool) { 647 return handleHasherCmd(c, "/sha256 ", utils.Sha256) 648 } 649 650 func handleSha512Cmd(c *command.Command) (handled bool) { 651 return handleHasherCmd(c, "/sha512 ", utils.Sha512) 652 } 653 654 func handleHasherCmd(c *command.Command, prefix string, fn func([]byte) string) (handled bool) { 655 if strings.HasPrefix(c.Message, prefix) { 656 c.Message = strings.TrimPrefix(c.Message, prefix) 657 c.DataMessage = prefix 658 c.ZeroMsg(fn([]byte(c.Message))) 659 c.Err = command.ErrStop 660 return true 661 } 662 return 663 } 664 665 func handleRmGroupCmd(c *command.Command) (handled bool) { 666 if m := rmGroupRgx.FindStringSubmatch(c.Message); len(m) == 2 { 667 groupName := m[1] 668 if err := c.DB.DeleteChatRoomGroup(c.Room.ID, groupName); err != nil { 669 c.Err = err 670 return true 671 } 672 c.Err = command.ErrRedirect 673 return true 674 } 675 return false 676 } 677 678 func handleLockGroupCmd(c *command.Command) (handled bool) { 679 if m := lockGroupRgx.FindStringSubmatch(c.Message); len(m) == 2 { 680 groupName := m[1] 681 group, err := c.DB.GetRoomGroupByName(c.Room.ID, groupName) 682 if err != nil { 683 c.Err = err 684 return true 685 } 686 group.Locked = true 687 group.DoSave(c.DB) 688 c.Err = command.ErrRedirect 689 return true 690 } 691 return false 692 } 693 694 func handleUnlockGroupCmd(c *command.Command) (handled bool) { 695 if m := unlockGroupRgx.FindStringSubmatch(c.Message); len(m) == 2 { 696 groupName := m[1] 697 group, err := c.DB.GetRoomGroupByName(c.Room.ID, groupName) 698 if err != nil { 699 c.Err = err 700 return true 701 } 702 group.Locked = false 703 group.DoSave(c.DB) 704 c.Err = command.ErrRedirect 705 return true 706 } 707 return false 708 } 709 710 func handleGroupUsersCmd(c *command.Command) (handled bool) { 711 if m := groupUsersRgx.FindStringSubmatch(c.Message); len(m) == 2 { 712 groupName := m[1] 713 group, err := c.DB.GetRoomGroupByName(c.Room.ID, groupName) 714 if err != nil { 715 c.Err = err 716 return true 717 } 718 users, err := c.DB.GetRoomGroupUsers(c.Room.ID, group.ID) 719 sort.Slice(users, func(i, j int) bool { 720 return users[i].User.Username < users[j].User.Username 721 }) 722 msg := "" 723 if len(users) > 0 { 724 msg += "\n" 725 for _, user := range users { 726 msg += user.User.Username.AtStr() + "\n" 727 } 728 } else { 729 msg += "no user in th group: " + groupName 730 } 731 c.ZeroProcMsg(msg) 732 c.Err = command.ErrRedirect 733 return true 734 } 735 return false 736 } 737 738 func handleListGroupsCmd(c *command.Command) (handled bool) { 739 if c.Message == "/groups" { 740 groups, err := c.DB.GetRoomGroups(c.Room.ID) 741 if err != nil { 742 c.Err = err 743 return true 744 } 745 msg := "" 746 if len(groups) > 0 { 747 msg += "\n" 748 for _, group := range groups { 749 msg += group.Name + " (" + group.Color + ")\n" 750 } 751 } else { 752 msg += "no groups" 753 } 754 c.ZeroProcMsg(msg) 755 c.Err = command.ErrRedirect 756 return true 757 } 758 return false 759 } 760 761 func handleGroupAddUserCmd(c *command.Command) (handled bool) { 762 if m := groupAddUserRgx.FindStringSubmatch(c.Message); len(m) == 3 { 763 groupName := m[1] 764 username := database.Username(m[2]) 765 user, err := c.DB.GetUserByUsername(username) 766 if err != nil { 767 c.Err = err 768 return true 769 } 770 group, err := c.DB.GetRoomGroupByName(c.Room.ID, groupName) 771 if err != nil { 772 c.Err = err 773 return true 774 } 775 _, err = c.DB.AddUserToRoomGroup(c.Room.ID, group.ID, user.ID) 776 if err != nil { 777 c.Err = err 778 return true 779 } 780 c.Err = command.ErrRedirect 781 return true 782 } else if strings.HasPrefix(c.Message, "/gadduser ") { 783 c.Err = errors.New("invalid /gadduser command") 784 } 785 return false 786 } 787 788 func handleGroupRmUserCmd(c *command.Command) (handled bool) { 789 if m := groupRmUserRgx.FindStringSubmatch(c.Message); len(m) == 3 { 790 groupName := m[1] 791 username := database.Username(m[2]) 792 user, err := c.DB.GetUserByUsername(username) 793 if err != nil { 794 c.Err = err 795 return true 796 } 797 group, err := c.DB.GetRoomGroupByName(c.Room.ID, groupName) 798 if err != nil { 799 c.Err = err 800 return true 801 } 802 err = c.DB.RmUserFromRoomGroup(c.Room.ID, group.ID, user.ID) 803 if err != nil { 804 c.Err = err 805 return true 806 } 807 c.Err = command.ErrRedirect 808 return true 809 } else if strings.HasPrefix(c.Message, "/grmuser ") { 810 c.Err = errors.New("invalid /grmuser command") 811 } 812 return false 813 } 814 815 func handleSetModeCmd(c *command.Command) (handled bool) { 816 if c.Message == "/mode user-whitelist" { 817 c.Room.Mode = database.UserWhitelistRoomMode 818 c.Room.DoSave(c.DB) 819 msg := `room mode set to "user whitelist"` 820 c.ZeroProcMsg(msg) 821 c.Err = command.ErrRedirect 822 return true 823 824 } else if c.Message == "/mode standard" { 825 c.Room.Mode = database.NormalRoomMode 826 c.Room.DoSave(c.DB) 827 msg := `room mode set to "standard"` 828 c.ZeroProcMsg(msg) 829 c.Err = command.ErrRedirect 830 return true 831 } 832 return false 833 } 834 835 func handleGetRoomWhitelistCmd(c *command.Command) (handled bool) { 836 if m := whitelistUserRgx.FindStringSubmatch(c.Message); len(m) == 2 { 837 username := database.Username(m[1]) 838 user, err := c.DB.GetUserByUsername(username) 839 var msg string 840 if err != nil { 841 msg = fmt.Sprintf(`username "%s" not found`, username) 842 } else { 843 if _, err := c.DB.WhitelistUser(c.Room.ID, user.ID); err != nil { 844 if err := c.DB.DeWhitelistUser(c.Room.ID, user.ID); err != nil { 845 msg = fmt.Sprintf("failed to toggle @%s in whitelist", user.Username) 846 } else { 847 msg = fmt.Sprintf("@%s removed from whitelist", user.Username) 848 } 849 } else { 850 msg = fmt.Sprintf("@%s added to whitelist", user.Username) 851 } 852 } 853 c.ZeroProcMsg(msg) 854 c.Err = command.ErrRedirect 855 return true 856 } 857 return false 858 } 859 860 func handleToggleReadOnlyCmd(c *command.Command) (handled bool) { 861 if c.Message == "/ro" { 862 c.Room.ReadOnly = !c.Room.ReadOnly 863 c.Room.DoSave(c.DB) 864 if c.Room.ReadOnly { 865 c.Err = command.NewErrSuccess("room is now read-only") 866 } else { 867 c.Err = command.NewErrSuccess("room is no longer read-only") 868 } 869 return true 870 } 871 return 872 } 873 874 func handleAddGroupCmd(c *command.Command) (handled bool) { 875 if m := addGroupRgx.FindStringSubmatch(c.Message); len(m) == 2 { 876 name := m[1] 877 _, err := c.DB.CreateChatRoomGroup(c.Room.ID, name, "#fff") 878 if err != nil { 879 c.Err = err 880 return true 881 } 882 c.Err = command.ErrRedirect 883 return true 884 } 885 return false 886 } 887 888 func handleWhitelistCmd(c *command.Command) (handled bool) { 889 if c.Message == "/whitelist" || c.Message == "/wl" { 890 usernames := make([]string, 0) 891 whitelistedUsers, _ := c.DB.GetWhitelistedUsers(c.Room.ID) 892 if c.Room.OwnerUserID != nil { 893 owner, _ := c.DB.GetUserByID(*c.Room.OwnerUserID) 894 usernames = append(usernames, owner.Username.AtStr()) 895 } 896 for _, whitelistedUser := range whitelistedUsers { 897 usernames = append(usernames, whitelistedUser.User.Username.AtStr()) 898 } 899 sort.Slice(usernames, func(i, j int) bool { return usernames[i] < usernames[j] }) 900 var msg string 901 if len(whitelistedUsers) > 0 { 902 msg = "whitelisted users: " + strings.Join(usernames, ", ") 903 } else { 904 msg = "no whitelisted user" 905 } 906 c.ZeroProcMsg(msg) 907 c.Err = command.ErrRedirect 908 return true 909 } 910 return false 911 } 912 913 func handleGetModeCmd(c *command.Command) (handled bool) { 914 if c.Message == "/mode" { 915 var msg string 916 if c.Room.Mode == database.NormalRoomMode { 917 msg = `room is in "standard" mode` 918 } else if c.Room.Mode == database.UserWhitelistRoomMode { 919 msg = `room is in "user whitelist" mode` 920 } 921 c.ZeroProcMsg(msg) 922 c.Err = command.ErrRedirect 923 return true 924 } 925 return false 926 } 927 928 func handleMeCmd(c *command.Command) (handled bool) { 929 if c.Message == "/me " { 930 c.Err = errors.New("invalid /me command") 931 return true 932 } 933 if strings.HasPrefix(c.Message, "/me ") { 934 return true 935 } 936 return 937 } 938 939 func handleEditCmd(c *command.Command) (handled bool) { 940 if m := editRgx.FindStringSubmatch(c.Message); len(m) == 3 { 941 date := m[1] 942 newMsg := m[2] 943 dt, err := utils.ParsePrevDatetimeAt(date, clockwork.NewRealClock()) 944 if err != nil { 945 c.Err = errors.New("failed to parse timestamp") 946 return true 947 } 948 if time.Since(dt) > config.EditMessageTimeLimit { 949 c.Err = errors.New("message too old to be edited") 950 return true 951 } 952 msg, err := c.DB.GetRoomChatMessageByDate(c.Room.ID, c.AuthUser.ID, dt.UTC()) 953 if err != nil { 954 c.Err = fmt.Errorf("failed to get message at timestamp %s", date) 955 return true 956 } 957 c.EditMsg = &msg 958 c.OrigMessage = newMsg 959 c.Message = newMsg 960 961 // If we're editing a message which contains a link to an uploaded file, 962 // we need to re-add the link to the html. 963 if msg.UploadID != nil { 964 if newUpload, err := c.DB.GetUploadByID(*msg.UploadID); err == nil { 965 c.Upload = &newUpload 966 } 967 } 968 969 if pmRgx.MatchString(c.Message) { 970 handlePMCmd(c) 971 } else if c.AuthUser.IsModerator() && strings.HasPrefix(c.Message, "/m ") { 972 handleModeratorGroupCmd(c) 973 } else if strings.HasPrefix(c.Message, "/hbm ") { 974 handleHbmCmd(c) 975 } else if strings.HasPrefix(c.Message, "/g ") { 976 handleGroupChatCmd(c) 977 } else if strings.HasPrefix(c.Message, "/system ") || strings.HasPrefix(c.Message, "/sys ") { 978 handleSystemCmd(c) 979 } 980 return true 981 982 } else if c.Message == "/e" { 983 msg, err := c.DB.GetUserLastChatMessageInRoom(c.AuthUser.ID, c.Room.ID) 984 if err != nil { 985 return true 986 } 987 c.RedirectQP.Set(command.RedirectEditQP, msg.CreatedAt.Format("15:04:05")) 988 c.Err = command.ErrRedirect 989 return true 990 } 991 return 992 } 993 994 func canUserInboxOther(db *database.DkfDB, user, other database.User) error { 995 doesNotMatter := utils.False() 996 _, err := dutils.CanUserPmOther(db, user, other, doesNotMatter) 997 return err 998 } 999 1000 func handlePMCmd(c *command.Command) (handled bool) { 1001 if m := pmRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1002 username := database.Username(m[1]) 1003 newMsg := m[2] 1004 redirectPmQP := command.RedirectPmQP 1005 1006 // Chat helpers 1007 if username == config.NullUsername { 1008 c.RedirectQP.Set(redirectPmQP, config.NullUsername) 1009 return handlePm0(c, newMsg) 1010 } 1011 1012 // Hack to have 1 on 1 chat with the user 1013 if strings.TrimSpace(newMsg) == "" && c.Upload == nil { 1014 if _, err := c.DB.GetUserByUsername(username); err != nil { 1015 c.Err = errors.New("invalid username") 1016 return true 1017 } 1018 redirectPmUsernameQP := command.RedirectPmUsernameQP 1019 newURL := fmt.Sprintf("/api/v1/chat/messages/%s/stream?%s=%s", c.Room.Name, redirectPmUsernameQP, username) 1020 database.MsgPubSub.Pub("refresh_"+string(c.AuthUser.Username), database.ChatMessageType{Typ: database.Redirect, NewURL: newURL}) 1021 c.RedirectQP.Set(redirectPmUsernameQP, username.String()) 1022 c.Err = command.ErrRedirect 1023 return true 1024 } 1025 1026 if err := c.SetToUser(username); err != nil { 1027 return true 1028 } 1029 c.Message = newMsg 1030 c.RedirectQP.Set(redirectPmQP, string(c.ToUser.Username)) 1031 1032 if newMsg == "/d" || strings.HasPrefix(newMsg, "/d ") { 1033 handled = handleDeleteMsgCmd(c) 1034 if c.Err != nil && !errors.Is(c.Err, command.ErrRedirect) { 1035 return handled 1036 } 1037 c.Err = command.ErrRedirect 1038 return handled 1039 } 1040 1041 if handleCodeCmd(c) { 1042 return true 1043 } 1044 1045 return true 1046 } else if strings.HasPrefix(c.Message, "/pm ") { 1047 c.Err = errors.New("invalid /pm command") 1048 return true 1049 } 1050 return false 1051 } 1052 1053 // Handle PMs sent to user 0 (/pm 0 msg) 1054 func handlePm0(c *command.Command, msg string) (handled bool) { 1055 if msg == "ping" { 1056 c.ZeroMsg("pong") 1057 c.Err = command.ErrRedirect 1058 return true 1059 1060 } else if msg == "talk" { 1061 c.ZeroMsg("talking") 1062 c.Err = command.ErrRedirect 1063 return true 1064 1065 } else if msg == "pgp" || msg == "gpg" { 1066 pkey := c.AuthUser.GPGPublicKey 1067 if pkey == "" { 1068 c.Message = "I could not find a public pgp key in your profile." 1069 } else { 1070 msg := "This is a sample text " + utils.GenerateToken10() 1071 if encrypted, err := utils.GeneratePgpEncryptedMessage(pkey, msg); err != nil { 1072 c.Message = err.Error() 1073 } else { 1074 c.Message = strings.Join(strings.Split(encrypted, "\n"), " ") 1075 } 1076 } 1077 c.ZeroProcMsg(c.Message) 1078 c.Err = command.ErrRedirect 1079 return true 1080 1081 } else if pgpMsg, _, _ := dutils.ExtractPGPMessage(msg); pgpMsg != "" { 1082 decrypted, err := utils.PgpDecryptMessage(config.NullUserPrivateKey, pgpMsg) 1083 if err != nil { 1084 c.Message = err.Error() 1085 } else { 1086 c.Message = "Decrypted message: " + decrypted 1087 } 1088 c.ZeroProcMsg(c.Message) 1089 c.Err = command.ErrRedirect 1090 return true 1091 1092 } else if b, _ := clearsign.Decode([]byte(msg)); b != nil { 1093 if p, err := packet.Read(b.ArmoredSignature.Body); err == nil { 1094 if sig, ok := p.(*packet.Signature); ok { 1095 zero := c.GetZeroUser() 1096 msg := fmt.Sprintf("<br />"+ 1097 "<table %s>"+ 1098 "<tr><td align=\"right\">Signature made: </td><td><span style=\"color: #82e17f;\">%s</span></td></tr>"+ 1099 "<tr><td align=\"right\">Fingerprint: </td><td><span style=\"color: #82e17f;\">%s</span></td></tr>"+ 1100 "<tr><td align=\"right\">Issuer: </td><td><span style=\"color: #82e17f;\">%s</span></td></tr>"+ 1101 "</table>", 1102 zero.GenerateChatStyle(), 1103 sig.CreationTime.Format(time.RFC1123), 1104 utils.FormatPgPFingerprint(sig.IssuerFingerprint), 1105 utils.Ternary(sig.SignerUserId != nil, *sig.SignerUserId, "n/a")) 1106 c.ZeroMsg(msg) 1107 c.Err = command.ErrRedirect 1108 return true 1109 } 1110 } 1111 1112 } else if c.Upload != nil { 1113 1114 // If we sent a clearsign file to @0, the bot will reply with information about the signature 1115 if c.Upload.FileSize < config.MaxFileSizeBeforeDownload { 1116 if file, err := c.DB.GetUploadByFileName(c.Upload.FileName); err == nil { 1117 if _, by, err := file.GetContent(); err == nil { 1118 if b, _ := clearsign.Decode(by); b != nil { 1119 if p, err := packet.Read(b.ArmoredSignature.Body); err == nil { 1120 if sig, ok := p.(*packet.Signature); ok { 1121 zero := c.GetZeroUser() 1122 msg := fmt.Sprintf("<br />"+ 1123 "<table %s>"+ 1124 "<tr><td align=\"right\">File: </td><td><span style=\"color: #82e17f;\">%s</span> (<span style=\"color: #82e17f;\">%s</span>)</td></tr>"+ 1125 "<tr><td align=\"right\">Signature made: </td><td><span style=\"color: #82e17f;\">%s</span></td></tr>"+ 1126 "<tr><td align=\"right\">Fingerprint: </td><td><span style=\"color: #82e17f;\">%s</span></td></tr>"+ 1127 "<tr><td align=\"right\">Issuer: </td><td><span style=\"color: #82e17f;\">%s</span></td></tr>"+ 1128 "</table>", 1129 zero.GenerateChatStyle(), 1130 c.Upload.OrigFileName, 1131 humanize.Bytes(uint64(c.Upload.FileSize)), 1132 sig.CreationTime.Format(time.RFC1123), 1133 utils.FormatPgPFingerprint(sig.IssuerFingerprint), 1134 utils.Ternary(sig.SignerUserId != nil, *sig.SignerUserId, "n/a")) 1135 c.ZeroMsg(msg) 1136 c.Err = command.ErrRedirect 1137 return true 1138 } 1139 } 1140 } 1141 } 1142 } 1143 } 1144 } 1145 1146 zeroUser := c.GetZeroUser() 1147 c.ToUser = &zeroUser 1148 c.Message = msg 1149 1150 return true 1151 } 1152 1153 func handleSubscribeCmd(c *command.Command) (handled bool) { 1154 if c.Message == "/subscribe" { 1155 _ = c.DB.SubscribeToRoom(c.AuthUser.ID, c.Room.ID) 1156 c.Err = command.ErrRedirect 1157 return true 1158 } 1159 return 1160 } 1161 1162 func handleUnsubscribeCmd(c *command.Command) (handled bool) { 1163 if m := unsubscribeRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1164 room, err := c.DB.GetChatRoomByName(m[1]) 1165 if err != nil { 1166 c.Err = err 1167 return true 1168 } 1169 _ = c.DB.UnsubscribeFromRoom(c.AuthUser.ID, room.ID) 1170 c.Err = command.ErrRedirect 1171 return true 1172 1173 } else if c.Message == "/unsubscribe" { 1174 _ = c.DB.UnsubscribeFromRoom(c.AuthUser.ID, c.Room.ID) 1175 c.Err = command.ErrRedirect 1176 return true 1177 } 1178 return 1179 } 1180 1181 func handleGroupChatCmd(c *command.Command) (handled bool) { 1182 if m := groupRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1183 groupName := m[1] 1184 c.Message = m[2] 1185 group, err := c.DB.GetRoomGroupByName(c.Room.ID, groupName) 1186 if err != nil { 1187 c.Err = err 1188 return true 1189 } 1190 if group.Locked { 1191 c.Err = errors.New("group is locked") 1192 return true 1193 } 1194 c.RedirectQP.Set(command.RedirectGroupQP, group.Name) 1195 c.GroupID = &group.ID 1196 return true 1197 } else if strings.HasPrefix(c.Message, "/g ") { 1198 c.Err = errors.New("invalid /g command") 1199 return true 1200 } 1201 return false 1202 } 1203 1204 func handleListPmWhitelistCmd(c *command.Command) (handled bool) { 1205 if c.Message == "/pmwhitelist" { 1206 pmWhitelistUsers, _ := c.DB.GetPmWhitelistedUsers(c.AuthUser.ID) 1207 sort.Slice(pmWhitelistUsers, func(i, j int) bool { 1208 return pmWhitelistUsers[i].WhitelistedUser.Username < pmWhitelistUsers[j].WhitelistedUser.Username 1209 }) 1210 msg := "" 1211 if len(pmWhitelistUsers) > 0 { 1212 msg += "\n" 1213 for _, ignoredUser := range pmWhitelistUsers { 1214 msg += ignoredUser.WhitelistedUser.Username.AtStr() + "\n" 1215 } 1216 } else { 1217 msg += "no PM whitelisted users" 1218 } 1219 c.ZeroProcMsg(msg) 1220 c.Err = command.ErrRedirect 1221 return true 1222 } 1223 return false 1224 } 1225 1226 func handleSetPmModeCmd(c *command.Command) (handled bool) { 1227 if c.Message == "/setpmmode whitelist" { 1228 c.AuthUser.SetPmMode(c.DB, database.PmModeWhitelist) 1229 msg := `pm mode set to "whitelist"` 1230 c.ZeroProcMsg(msg) 1231 c.Err = command.ErrRedirect 1232 return true 1233 1234 } else if c.Message == "/setpmmode standard" { 1235 c.AuthUser.SetPmMode(c.DB, database.PmModeStandard) 1236 msg := `pm mode set to "standard"` 1237 c.ZeroProcMsg(msg) 1238 c.Err = command.ErrRedirect 1239 return true 1240 } 1241 return false 1242 } 1243 1244 func handleTogglePmBlacklistedUser(c *command.Command) (handled bool) { 1245 if m := pmToggleBlacklistUserRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1246 username := database.Username(m[1]) 1247 user, err := c.DB.GetUserByUsername(username) 1248 if err != nil { 1249 c.Err = command.ErrRedirect 1250 return true 1251 } 1252 if c.DB.ToggleBlacklistedUser(c.AuthUser.ID, user.ID) { 1253 c.Err = command.NewErrSuccess("added to blacklist") 1254 } else { 1255 c.Err = command.NewErrSuccess("removed from blacklist") 1256 } 1257 return true 1258 } 1259 return false 1260 } 1261 1262 func handleTogglePmWhitelistedUser(c *command.Command) (handled bool) { 1263 if m := pmToggleWhitelistUserRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1264 username := database.Username(m[1]) 1265 user, err := c.DB.GetUserByUsername(username) 1266 if err != nil { 1267 c.Err = command.ErrRedirect 1268 return true 1269 } 1270 if c.DB.ToggleWhitelistedUser(c.AuthUser.ID, user.ID) { 1271 c.Err = command.NewErrSuccess("added to whitelist") 1272 } else { 1273 c.Err = command.NewErrSuccess("removed from whitelist") 1274 } 1275 return true 1276 } 1277 return false 1278 } 1279 1280 func handleChessCmd(c *command.Command) (handled bool) { 1281 if m := chessRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1282 username := database.Username(m[1]) 1283 color := m[2] 1284 player1 := *c.AuthUser 1285 player2, err := c.DB.GetUserByUsername(username) 1286 if err != nil { 1287 c.Err = errors.New("invalid username") 1288 return true 1289 } 1290 if _, err := ChessInstance.NewGame1(c.RoomKey, c.Room.ID, player1, player2, color); err != nil { 1291 c.Err = err 1292 return true 1293 } 1294 c.Err = command.NewErrSuccess("chess game created") 1295 return true 1296 } 1297 return 1298 } 1299 1300 func handleInboxCmd(c *command.Command) (handled bool) { 1301 if m := inboxRgx.FindStringSubmatch(c.Message); len(m) == 4 { 1302 username := database.Username(m[1]) 1303 encryptRaw := m[2] 1304 message := m[3] 1305 tryEncrypt := false 1306 if encryptRaw == " -e" { 1307 tryEncrypt = true 1308 } 1309 toUser, err := c.DB.GetUserByUsername(username) 1310 if err != nil { 1311 c.Err = errors.New("invalid username") 1312 return true 1313 } 1314 1315 if err := canUserInboxOther(c.DB, *c.AuthUser, toUser); err != nil { 1316 c.Err = err 1317 return true 1318 } 1319 1320 inboxHTML := message 1321 if tryEncrypt { 1322 if toUser.GPGPublicKey == "" { 1323 c.Err = errors.New("user has no pgp public key") 1324 return true 1325 } 1326 inboxHTML, err = utils.GeneratePgpEncryptedMessage(toUser.GPGPublicKey, message) 1327 if err != nil { 1328 c.Err = errors.New("failed to encrypt") 1329 return true 1330 } 1331 inboxHTML = strings.Join(strings.Split(inboxHTML, "\n"), " ") 1332 } 1333 1334 inboxHTML, _, _ = dutils.ProcessRawMessage(c.DB, inboxHTML, c.RoomKey, c.AuthUser.ID, c.Room.ID, nil, c.AuthUser.IsModerator(), c.AuthUser.CanUseMultiline, c.AuthUser.ManualMultiline) 1335 c.DB.CreateInboxMessage(inboxHTML, c.Room.ID, c.AuthUser.ID, toUser.ID, true, false, nil) 1336 1337 c.DataMessage = "/inbox " + string(username) + " " 1338 c.Err = command.NewErrSuccess("inbox sent") 1339 return true 1340 1341 } else if strings.HasPrefix(c.Message, "/inbox ") { 1342 c.Err = errors.New("invalid /inbox command") 1343 return true 1344 } 1345 return 1346 } 1347 1348 func handleProfileCmd(c *command.Command) (handled bool) { 1349 if m := profileRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1350 username := database.Username(m[1]) 1351 user, err := c.DB.GetUserByUsername(username) 1352 if err != nil { 1353 c.Err = ErrUsernameNotFound 1354 return true 1355 } 1356 profile := `/u/` + user.Username 1357 c.ZeroMsg(fmt.Sprintf(`[<a href="%s" rel="noopener noreferrer" target="_blank">profile of %s</a>]`, profile, user.Username)) 1358 c.Err = command.ErrRedirect 1359 return true 1360 } else if strings.HasPrefix(c.Message, "/p ") { 1361 c.Err = errors.New("invalid profile command") 1362 return true 1363 } 1364 return 1365 } 1366 1367 func handleChipsSendCmd(c *command.Command) (handled bool) { 1368 if m := chipsSendRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1369 username := database.Username(m[1]) 1370 chips := database.PokerChip(utils.DoParseInt(m[2])) 1371 if chips <= 0 { 1372 c.Err = errors.New("must send at least 1 chip") 1373 return true 1374 } 1375 if chips > 1000000 { 1376 c.Err = errors.New("cannot send more than 1000000 chips") 1377 return true 1378 } 1379 if c.AuthUser.ChipsTest < chips { 1380 c.Err = errors.New("you do not have enough chips") 1381 return true 1382 } 1383 user, err := c.DB.GetUserByUsername(username) 1384 if err != nil { 1385 c.Err = errors.New("username does not exists") 1386 return true 1387 } 1388 user.ChipsTest += chips 1389 user.DoSave(c.DB) 1390 c.DataMessage = "/chips-send " + username.String() + " " 1391 c.Err = command.NewErrSuccess("chips sent") 1392 return true 1393 } 1394 return 1395 } 1396 1397 func handleChipsResetCmd(c *command.Command) (handled bool) { 1398 if c.Message == "/chips-reset" { 1399 c.AuthUser.ResetChipsTest(c.DB) 1400 c.Err = command.ErrRedirect 1401 return true 1402 } 1403 return 1404 } 1405 1406 func handleChipsBalanceCmd(c *command.Command) (handled bool) { 1407 if c.Message == "/chips" { 1408 c.ZeroMsg(fmt.Sprintf(`Balance: %d`, c.AuthUser.ChipsTest)) 1409 c.Err = command.ErrRedirect 1410 return true 1411 } 1412 return 1413 } 1414 1415 func handleTutorialCmd(c *command.Command) (handled bool) { 1416 if c.Message == "/tuto" && utils.False() { 1417 name := "tuto_" + utils.GenerateToken10() 1418 room, _ := c.DB.CreateRoom(name, "", c.AuthUser.ID, false) 1419 c.Err = command.ErrRedirect 1420 c.ZeroProcMsg("Tutorial here -> #" + room.Name) 1421 c.ZeroPublicProcMsgRoom("Welcome to the tutorial", "", room.ID) 1422 return true 1423 } 1424 return 1425 } 1426 1427 func handleDeleteMsgCmd(c *command.Command) (handled bool) { 1428 getMsgForUsername := func(msgs []database.ChatMessage, username database.Username) (database.ChatMessage, error) { 1429 var msg database.ChatMessage 1430 for _, msgTmp := range msgs { 1431 if msgTmp.User.Username == username { 1432 msg = msgTmp 1433 return msg, nil 1434 } 1435 } 1436 return msg, errors.New("failed to find msg") 1437 } 1438 delMsgFn := func(msgs []database.ChatMessage) error { 1439 msg, err := getMsgForUsername(msgs, c.AuthUser.Username) 1440 if err != nil { 1441 return err 1442 } 1443 if err := msg.UserCanDeleteErr(c.AuthUser); err != nil { 1444 return err 1445 } 1446 if msg.RoomID == config.GeneralRoomID && !msg.IsPm() { 1447 msg.User.DecrGeneralMessagesCount(c.DB) 1448 } 1449 _ = msg.Delete(c.DB) 1450 return command.ErrRedirect 1451 } 1452 if c.Message == "/d" { 1453 lastMsg, err := c.DB.GetUserLastChatMessageInRoom(c.AuthUser.ID, c.Room.ID) 1454 if err != nil { 1455 c.Err = errors.New("unable to find last message") 1456 return true 1457 } 1458 msgs := []database.ChatMessage{lastMsg} 1459 c.Err = delMsgFn(msgs) 1460 return true 1461 1462 } else if m := deleteMsgRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1463 date := m[1] 1464 matchUsername := m[2] 1465 dt, err := utils.ParsePrevDatetimeAt(date, clockwork.NewRealClock()) 1466 if err != nil { 1467 logrus.Error(err) 1468 c.Err = err 1469 return true 1470 } 1471 msgs, err := c.DB.GetRoomChatMessagesByDate(c.Room.ID, dt.UTC()) 1472 if err != nil { 1473 c.Err = err 1474 return true 1475 } 1476 if len(msgs) == 0 { 1477 c.Err = errors.New("failed to find msg") 1478 return true 1479 } 1480 1481 if !c.AuthUser.IsModerator() { 1482 c.Err = delMsgFn(msgs) 1483 return true 1484 } 1485 1486 // Moderator 1487 var msg database.ChatMessage 1488 if len(msgs) == 1 { 1489 msg = msgs[0] 1490 } else if len(msgs) > 1 { 1491 if matchUsername == "" { 1492 c.Err = errors.New("more the 1 msg with this timestamp") 1493 return true 1494 } 1495 msg, err = getMsgForUsername(msgs, database.Username(matchUsername)) 1496 if err != nil { 1497 c.Err = err 1498 return true 1499 } 1500 } 1501 if err := msg.UserCanDeleteErr(c.AuthUser); err != nil { 1502 c.Err = err 1503 return true 1504 } 1505 _ = msg.Delete(c.DB) 1506 c.Err = command.ErrRedirect 1507 return true 1508 1509 } else if strings.HasPrefix(c.Message, "/d ") { 1510 c.Err = errors.New("invalid /d command") 1511 return true 1512 } 1513 return 1514 } 1515 1516 func handleHideMsgCmd(c *command.Command) (handled bool) { 1517 if m := hideRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1518 date := m[1] 1519 dt, err := utils.ParsePrevDatetimeAt(date, clockwork.NewRealClock()) 1520 if err != nil { 1521 logrus.Error(err) 1522 c.Err = err 1523 return true 1524 } 1525 msgs, err := c.DB.GetRoomChatMessagesByDate(c.Room.ID, dt.UTC()) 1526 if err != nil { 1527 c.Err = err 1528 return true 1529 } 1530 if len(msgs) == 1 { 1531 c.DB.IgnoreMessage(c.AuthUser.ID, msgs[0].ID) 1532 c.Err = command.ErrRedirect 1533 } else { 1534 c.Err = errors.New("more than 1 message") 1535 } 1536 return true 1537 } 1538 return 1539 } 1540 1541 func handleUnHideMsgCmd(c *command.Command) (handled bool) { 1542 if m := unhideRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1543 date := m[1] 1544 dt, err := utils.ParsePrevDatetimeAt(date, clockwork.NewRealClock()) 1545 if err != nil { 1546 logrus.Error(err) 1547 c.Err = err 1548 return true 1549 } 1550 msgs, err := c.DB.GetRoomChatMessagesByDate(c.Room.ID, dt.UTC()) 1551 if err != nil { 1552 c.Err = err 1553 return true 1554 } 1555 if len(msgs) == 1 { 1556 c.DB.UnIgnoreMessage(c.AuthUser.ID, msgs[0].ID) 1557 c.Err = command.ErrRedirect 1558 } else { 1559 c.Err = errors.New("more than 1 message") 1560 } 1561 return true 1562 } 1563 return 1564 } 1565 1566 func handleIgnoreCmd(c *command.Command) (handled bool) { 1567 if m := ignoreRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1568 username := database.Username(m[1]) 1569 user, err := c.DB.GetUserByUsername(username) 1570 if err != nil { 1571 c.Err = command.ErrRedirect 1572 return true 1573 } 1574 c.DB.IgnoreUser(c.AuthUser.ID, user.ID) 1575 database.MsgPubSub.Pub("refresh_"+string(c.AuthUser.Username), database.ChatMessageType{Typ: database.ForceRefresh}) 1576 c.Err = command.ErrRedirect 1577 return true 1578 1579 } else if c.Message == "/i" || c.Message == "/ignore" { 1580 ignoredUsers, _ := c.DB.GetIgnoredUsers(c.AuthUser.ID) 1581 sort.Slice(ignoredUsers, func(i, j int) bool { 1582 return ignoredUsers[i].IgnoredUser.Username < ignoredUsers[j].IgnoredUser.Username 1583 }) 1584 msg := "" 1585 if len(ignoredUsers) > 0 { 1586 msg += "\n" 1587 for _, ignoredUser := range ignoredUsers { 1588 msg += ignoredUser.IgnoredUser.Username.AtStr() + "\n" 1589 } 1590 } else { 1591 msg += "no ignored users" 1592 } 1593 c.ZeroProcMsg(msg) 1594 c.Err = command.ErrRedirect 1595 return true 1596 1597 } else if strings.HasPrefix(c.Message, "/ignore ") || strings.HasPrefix(c.Message, "/i ") { 1598 c.Err = errors.New("invalid ignore command") 1599 return true 1600 } 1601 return 1602 } 1603 1604 func handleUnIgnoreCmd(c *command.Command) (handled bool) { 1605 if m := unIgnoreRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1606 username := database.Username(m[1]) 1607 user, err := c.DB.GetUserByUsername(username) 1608 if err != nil { 1609 c.Err = command.ErrRedirect 1610 return true 1611 } 1612 c.DB.UnIgnoreUser(c.AuthUser.ID, user.ID) 1613 database.MsgPubSub.Pub("refresh_"+string(c.AuthUser.Username), database.ChatMessageType{Typ: database.ForceRefresh}) 1614 c.Err = command.ErrRedirect 1615 return true 1616 } else if strings.HasPrefix(c.Message, "/unignore ") || strings.HasPrefix(c.Message, "/ui ") { 1617 c.Err = errors.New("invalid unignore command") 1618 return true 1619 } 1620 return 1621 } 1622 1623 func handleToggleAutocomplete(c *command.Command) (handled bool) { 1624 if c.Message == "/toggle-autocomplete" { 1625 c.AuthUser.ToggleAutocompleteCommandsEnabled(c.DB) 1626 c.Err = command.ErrRedirect 1627 return true 1628 } 1629 return 1630 } 1631 1632 func handleAfkCmd(c *command.Command) (handled bool) { 1633 if c.Message == "/afk" { 1634 c.AuthUser.ToggleAFK(c.DB) 1635 c.Err = command.ErrRedirect 1636 return true 1637 } 1638 return 1639 } 1640 1641 func handleDateCmd(c *command.Command) (handled bool) { 1642 if c.Message == "/date" { 1643 c.ZeroMsg(time.Now().Format(time.RFC1123)) 1644 c.Err = command.ErrRedirect 1645 return true 1646 } 1647 return 1648 } 1649 1650 func handleSuccessCmd(c *command.Command) (handled bool) { 1651 if c.Message == "/success" { 1652 c.Err = command.NewErrSuccess("success message") 1653 return true 1654 } 1655 return 1656 } 1657 1658 func handleErrorCmd(c *command.Command) (handled bool) { 1659 if c.Message == "/error" { 1660 c.Err = errors.New("error message") 1661 return true 1662 } 1663 return 1664 } 1665 1666 func handleSystemCmd(c *command.Command) (handled bool) { 1667 if strings.HasPrefix(c.Message, "/sys ") { 1668 c.Message = strings.Replace(c.Message, "/sys ", "/system ", 1) 1669 } 1670 if strings.HasPrefix(c.Message, "/system ") { 1671 c.Message = strings.TrimPrefix(c.Message, "/system ") 1672 c.SystemMsg = true 1673 return true 1674 } 1675 return false 1676 } 1677 1678 func handleSetChatRoomExternalLink(c *command.Command) (handled bool) { 1679 if m := setUrlRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1680 externalURL := m[1] 1681 if !govalidator.IsURL(externalURL) { 1682 externalURL = "" 1683 } 1684 room, err := c.DB.GetChatRoomByID(c.Room.ID) 1685 if err != nil { 1686 c.Err = err 1687 return true 1688 } 1689 room.ExternalLink = externalURL 1690 room.DoSave(c.DB) 1691 c.Err = command.ErrRedirect 1692 return true 1693 } 1694 return 1695 } 1696 1697 func handlePurge(c *command.Command) (handled bool) { 1698 if m := purgeRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1699 isHB := m[1] == " -hb" 1700 username := database.Username(m[2]) 1701 user, err := c.DB.GetUserByUsername(username) 1702 if err != nil { 1703 c.Err = err 1704 return true 1705 } 1706 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("purge %s #%d", user.Username, user.ID)) 1707 if isHB { 1708 _ = c.DB.DeleteUserHbChatMessages(user.ID) 1709 } else { 1710 _ = c.DB.DeleteUserChatMessages(user.ID) 1711 } 1712 database.MsgPubSub.Pub(database.RefreshTopic, database.ChatMessageType{Typ: database.ForceRefresh}) 1713 c.Err = command.ErrRedirect 1714 return true 1715 1716 } else if c.Message == "/purge" { 1717 c.Err = command.ErrRedirect 1718 if !c.AuthUser.UseStream { 1719 c.Err = errors.New("only work on stream version of this chat") 1720 return true 1721 } 1722 payload := database.ChatMessageType{} 1723 streamModals.PurgeModal{}.Show(c.AuthUser.ID, c.Room.ID, payload) 1724 return true 1725 } 1726 return 1727 } 1728 1729 func handleRename(c *command.Command) (handled bool) { 1730 if m := renameRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1731 oldUsername := database.Username(m[1]) 1732 newUsername := database.Username(m[2]) 1733 user, err := c.DB.GetUserByUsername(oldUsername) 1734 if err != nil { 1735 c.Err = err 1736 return true 1737 } 1738 c.DB.NewAudit(*c.AuthUser, fmt.Sprintf("rename %s -> %s #%d", user.Username, newUsername, user.ID)) 1739 1740 if err := c.DB.CanRenameTo(oldUsername, newUsername); err != nil { 1741 c.Err = err 1742 return true 1743 } 1744 1745 managers.ActiveUsers.RemoveUser(user.ID) 1746 user.Username = newUsername 1747 user.DoSave(c.DB) 1748 1749 c.Err = command.ErrRedirect 1750 return true 1751 } 1752 return 1753 } 1754 1755 func handleNewMeme(c *command.Command) (handled bool) { 1756 if m := memeRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1757 if c.Upload == nil { 1758 c.Err = errors.New("no file uploaded") 1759 return true 1760 } 1761 slug := m[1] 1762 oldPath := filepath.Join(config.Global.ProjectUploadsPath.Get(), c.Upload.FileName) 1763 newPath := filepath.Join(config.Global.ProjectMemesPath.Get(), c.Upload.FileName) 1764 _ = os.Rename(oldPath, newPath) 1765 1766 if err := c.DB.DB().Delete(&c.Upload).Error; err != nil { 1767 logrus.Error(err) 1768 } 1769 1770 meme := database.Meme{ 1771 Slug: slug, 1772 FileName: c.Upload.FileName, 1773 OrigFileName: c.Upload.OrigFileName, 1774 FileSize: c.Upload.FileSize, 1775 } 1776 if err := c.DB.DB().Create(&meme).Error; err != nil { 1777 _ = os.Remove(newPath) 1778 logrus.Error(err) 1779 } 1780 1781 c.Err = command.ErrRedirect 1782 return true 1783 1784 } else if m := memeRenameRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1785 slug := m[1] 1786 newSlug := m[2] 1787 meme, err := c.DB.GetMemeBySlug(slug) 1788 if err != nil { 1789 c.Err = errors.New("meme not found") 1790 return true 1791 } 1792 meme.Slug = newSlug 1793 meme.DoSave(c.DB) 1794 c.Err = command.NewErrSuccess("meme renamed") 1795 return true 1796 } 1797 return 1798 } 1799 1800 func handleRemoveMeme(c *command.Command) (handled bool) { 1801 if m := memeRemoveRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1802 slug := m[1] 1803 meme, err := c.DB.GetMemeBySlug(slug) 1804 if err != nil { 1805 c.Err = errors.New("meme not found") 1806 return true 1807 } 1808 if err := meme.Delete(c.DB); err != nil { 1809 c.Err = err 1810 return true 1811 } 1812 c.Err = command.NewErrSuccess("meme removed") 1813 return true 1814 } 1815 return 1816 } 1817 1818 func handleListMemes(c *command.Command) (handled bool) { 1819 if m := memesRgx.FindStringSubmatch(c.Message); len(m) == 1 { 1820 memes, _ := c.DB.GetMemes() 1821 msg := "" 1822 for _, m := range memes { 1823 msg += fmt.Sprintf(`<a href="/memes/%s" rel="noopener noreferrer" target="_blank">%s</a>`, m.Slug, m.Slug) 1824 if c.AuthUser.IsAdmin { 1825 msg += fmt.Sprintf(` (%s)`, humanize.Bytes(uint64(m.FileSize))) 1826 } 1827 msg += "<br />" 1828 } 1829 c.ZeroMsg(msg) 1830 c.Err = command.ErrRedirect 1831 return true 1832 } 1833 return 1834 } 1835 1836 func handleRefreshCmd(c *command.Command) (handled bool) { 1837 if c.Message == "/refresh" { 1838 c.Err = command.ErrRedirect 1839 database.MsgPubSub.Pub(database.RefreshTopic, database.ChatMessageType{Typ: database.ForceRefresh}) 1840 return true 1841 } 1842 return 1843 } 1844 1845 func handleWizzCmd(c *command.Command) (handled bool) { 1846 m := wizzRgx.FindStringSubmatch(c.Message) 1847 if c.Message == "/wizz" || len(m) == 2 { 1848 var wizzedUser *database.User 1849 wizzedUser = c.AuthUser 1850 1851 if len(m) == 2 { 1852 username := database.Username(m[1]) 1853 if username != c.AuthUser.Username { 1854 user, err := c.DB.GetUserByUsername(username) 1855 if err != nil { 1856 c.Err = ErrUsernameNotFound 1857 return true 1858 } 1859 wizzedUser = &user 1860 } 1861 c.ZeroSysMsgToSkipNotify(c.AuthUser, "you wizzed "+wizzedUser.Username.String()) 1862 } 1863 1864 c.ZeroSysMsgTo(wizzedUser, "wizzed by "+c.AuthUser.Username.String()) 1865 database.MsgPubSub.Pub("wizz_"+wizzedUser.Username.String(), database.ChatMessageType{Typ: database.Wizz}) 1866 c.Err = command.ErrRedirect 1867 return true 1868 } 1869 return 1870 } 1871 1872 func handleChipsCmd(c *command.Command) (handled bool) { 1873 if m := chipsRgx.FindStringSubmatch(c.Message); len(m) == 3 { 1874 username := database.Username(m[1]) 1875 chips := utils.DoParseInt64(m[2]) 1876 1877 if c.DB.DB().Model(&database.User{}). 1878 Where("username = ?", username). 1879 Select("ChipsTest"). 1880 Updates(database.User{ChipsTest: database.PokerChip(chips)}).RowsAffected == 0 { 1881 c.Err = errors.New("username does not exists") 1882 return true 1883 } 1884 c.Err = command.NewErrSuccess("chips set") 1885 return true 1886 } 1887 return 1888 } 1889 1890 func handleLocateCmd(c *command.Command) (handled bool) { 1891 if m := locateRgx.FindStringSubmatch(c.Message); len(m) == 2 { 1892 username := database.Username(m[1]) 1893 user, err := c.DB.GetUserByUsername(username) 1894 if err != nil { 1895 c.Err = errors.New("username does not exists") 1896 return true 1897 } 1898 roomIDs := managers.ActiveUsers.LocateUser(user.Username) 1899 rooms, _ := c.DB.GetChatRoomsByID(roomIDs) 1900 var msg string 1901 if len(rooms) > 0 { 1902 roomLinks := make([]string, len(rooms)) 1903 for idx, room := range rooms { 1904 roomLinks[idx] = "#" + room.Name 1905 } 1906 msg = username.AtStr() + " is in " + strings.Join(roomLinks, " ") 1907 } else { 1908 msg = username.AtStr() + " could not be located in a public room" 1909 } 1910 c.ZeroProcMsg(msg) 1911 c.DataMessage = "/locate " 1912 c.Err = command.ErrStop 1913 return true 1914 } 1915 return 1916 } 1917 1918 func handleCodeCmd(c *command.Command) (handled bool) { 1919 if c.Message == "/code" { 1920 c.Err = command.ErrRedirect 1921 if !c.AuthUser.CanUseMultiline { 1922 c.Err = errors.New("multiline is disabled for your account") 1923 return true 1924 } else if !c.AuthUser.UseStream { 1925 c.Err = errors.New("only work on stream version of this chat") 1926 return true 1927 } 1928 payload := database.ChatMessageType{} 1929 if c.ModMsg { 1930 payload.IsMod = true 1931 } 1932 if c.ToUser != nil { 1933 toUserUsername := c.ToUser.Username 1934 payload.ToUserUsername = &toUserUsername 1935 } 1936 streamModals.CodeModal{}.Show(c.AuthUser.ID, c.Room.ID, payload) 1937 return true 1938 } 1939 return 1940 } 1941 1942 func handleUpdateReadMarkerCmd(c *command.Command) (handled bool) { 1943 if c.Message == "/r" { 1944 c.DB.UpdateChatReadMarker(c.AuthUser.ID, c.Room.ID) 1945 c.Err = command.ErrRedirect 1946 return true 1947 } 1948 return 1949 } 1950 1951 func handleCheckCmd(c *command.Command) (handled bool) { 1952 if c.Message == "/check" { 1953 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 1954 if g := poker.PokerInstance.GetGame(roomID); g != nil { 1955 g.Check(c.AuthUser.ID) 1956 } 1957 c.Err = command.ErrRedirect 1958 return true 1959 } 1960 return false 1961 } 1962 1963 func handleCallCmd(c *command.Command) (handled bool) { 1964 if c.Message == "/call" { 1965 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 1966 if g := poker.PokerInstance.GetGame(roomID); g != nil { 1967 g.Call(c.AuthUser.ID) 1968 } 1969 c.Err = command.ErrRedirect 1970 return true 1971 } 1972 return false 1973 } 1974 1975 func handleFoldCmd(c *command.Command) (handled bool) { 1976 if c.Message == "/fold" { 1977 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 1978 if g := poker.PokerInstance.GetGame(roomID); g != nil { 1979 g.Fold(c.AuthUser.ID) 1980 } 1981 c.Err = command.ErrRedirect 1982 return true 1983 } 1984 return false 1985 } 1986 1987 func handleRaiseCmd(c *command.Command) (handled bool) { 1988 if c.Message == "/raise" { 1989 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 1990 if g := poker.PokerInstance.GetGame(roomID); g != nil { 1991 g.Raise(c.AuthUser.ID) 1992 } 1993 c.Err = command.ErrRedirect 1994 return true 1995 } 1996 return false 1997 } 1998 1999 func handleBetCmd(c *command.Command) (handled bool) { 2000 if m := betRgx.FindStringSubmatch(c.Message); len(m) == 2 { 2001 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 2002 if g := poker.PokerInstance.GetGame(roomID); g != nil { 2003 bet := database.PokerChip(utils.DoParseUint64(m[1])) 2004 g.Bet(c.AuthUser.ID, bet) 2005 } 2006 c.Err = command.ErrRedirect 2007 return true 2008 } 2009 return false 2010 } 2011 2012 func handleAllInCmd(c *command.Command) (handled bool) { 2013 if c.Message == "/allin" { 2014 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 2015 if g := poker.PokerInstance.GetGame(roomID); g != nil { 2016 g.AllIn(c.AuthUser.ID) 2017 } 2018 c.Err = command.ErrRedirect 2019 return true 2020 } 2021 return false 2022 } 2023 2024 func handleDealCmd(c *command.Command) (handled bool) { 2025 if c.Message == "/deal" { 2026 roomID := poker.RoomID(strings.ReplaceAll(c.Room.Name, "_", "-")) 2027 if g := poker.PokerInstance.GetGame(roomID); g != nil { 2028 g.Deal(c.AuthUser.ID) 2029 } 2030 c.Err = command.ErrRedirect 2031 return true 2032 } 2033 return false 2034 } 2035 2036 func handleDistCmd(c *command.Command) (handled bool) { 2037 if m := distRgx.FindStringSubmatch(c.Message); len(m) == 3 { 2038 u1 := strings.ToLower(m[1]) 2039 u2 := strings.ToLower(m[2]) 2040 dist := levenshtein.ComputeDistance(u1, u2) 2041 c.ZeroProcMsg(fmt.Sprintf("levenshtein distance is %d", dist)) 2042 c.Err = command.ErrRedirect 2043 return true 2044 } 2045 return false 2046 } 2047 2048 func handleCloseCmd(c *command.Command) (handled bool) { 2049 if c.Message == "/close" { 2050 database.MsgPubSub.Pub("refresh_"+string(c.AuthUser.Username), database.ChatMessageType{Typ: database.Close}) 2051 c.Err = command.ErrRedirect 2052 return true 2053 } 2054 return false 2055 } 2056 2057 func handleCloseMenuCmd(c *command.Command) (handled bool) { 2058 if c.Message == "/closem" { 2059 database.MsgPubSub.Pub("refresh_loading_icon_"+string(c.AuthUser.Username), database.ChatMessageType{Typ: database.CloseMenu}) 2060 c.Err = command.ErrRedirect 2061 return true 2062 } 2063 return false 2064 }