package service import ( "context" "fmt" "strconv" "strings" "time" "github.com/ShukeBta/MediaStationGo/internal/model" ) // pendingTTL bounds how long a button-initiated text prompt stays valid. const pendingTTL = 5 * time.Minute func (s *TelegramBotService) setPending(userID int64, kind string) { s.pendingMu.Lock() s.pending[userID] = pendingInput{Kind: kind, CreatedAt: time.Now()} s.pendingMu.Unlock() } func (s *TelegramBotService) takePending(userID int64) (pendingInput, bool) { s.pendingMu.Lock() defer s.pendingMu.Unlock() p, ok := s.pending[userID] if ok { delete(s.pending, userID) } if ok && time.Since(p.CreatedAt) > pendingTTL { return pendingInput{}, false } return p, ok } // boundUser resolves the local user bound to a Telegram account, or nil. func (s *TelegramBotService) boundUser(ctx context.Context, telegramUserID int) *model.User { binding := s.telegramBinding(ctx, telegramUserID) if binding == nil { return nil } u, _ := s.repo.User.FindByID(ctx, binding.UserID) return u } // handleMenuCallback routes inline-button taps. Returns (reply, handled). func (s *TelegramBotService) handleMenuCallback(ctx context.Context, channel *model.NotifyChannel, msg *TelegramMessage, data string) (telegramCommandReply, bool) { isAdmin := s.telegramUserIsAdmin(ctx, channel, msg.From.ID) isGroup := telegramIsGroupChat(msg.Chat.Type) if reply, handled := s.handleUserMenuCallback(ctx, channel, msg, data, isGroup); handled { return reply, true } if !isAdmin { if isGroup { return telegramCommandReply{}, true } return telegramCommandReply{Text: "此功能仅管理员可用。"}, true } return s.handleAdminMenuCallback(ctx, msg, data) } func (s *TelegramBotService) handleUserMenuCallback(ctx context.Context, channel *model.NotifyChannel, msg *TelegramMessage, data string, isGroup bool) (telegramCommandReply, bool) { switch { case data == "noop": return telegramCommandReply{}, true case data == "menu_main": return s.mainMenu(ctx, channel, msg), true case data == "act_account": return s.replyAccount(ctx, msg), true case data == "act_signin": return s.replySignIn(ctx, msg), true case data == "act_devices": return s.replyDevices(ctx, msg), true case strings.HasPrefix(data, "kick:"): return s.replyKick(ctx, msg, strings.TrimPrefix(data, "kick:")), true } return s.handlePrivatePromptMenuCallback(ctx, msg, data, isGroup) } func (s *TelegramBotService) handlePrivatePromptMenuCallback(ctx context.Context, msg *TelegramMessage, data string, isGroup bool) (telegramCommandReply, bool) { switch data { case "act_bind": return telegramPrivateOnlyMenuReply(isGroup, "绑定账号", "请发送:/start 用户名 密码 绑定已有账号。"), true case "act_register": if isGroup { return telegramCommandReply{Text: telegramGroupPrivateUserHint("注册账号")}, true } if !s.openRegEnabled(ctx) { return telegramCommandReply{Text: "注册功能未开放,请联系管理员。"}, true } s.setPending(int64(msg.From.ID), "register") return telegramCommandReply{Text: "请发送新账号的 用户名 密码(空格分隔),例如:alice mypass123"}, true case "act_redeem_register": if isGroup { return telegramCommandReply{Text: telegramGroupPrivateUserHint("兑换码注册")}, true } s.setPending(int64(msg.From.ID), "redeem_register") return telegramCommandReply{Text: "请发送你的注册兑换码,例如:ABCD2345EFGH\n(兑换后会要求设置用户名密码)"}, true case "act_redeem_renew": if isGroup { return telegramCommandReply{Text: telegramGroupPrivateUserHint("兑换码续期")}, true } s.setPending(int64(msg.From.ID), "redeem_renew") return telegramCommandReply{Text: "请发送你的续期兑换码,将为当前绑定账号续期。"}, true case "act_setname": return s.setPendingPrivatePrompt(msg, isGroup, "修改用户名", "setname", "请发送:当前密码 新用户名。"), true case "act_setpass": return s.setPendingPrivatePrompt(msg, isGroup, "修改密码", "setpass", "请发送:当前密码 新密码(新密码至少 6 位)。"), true } return telegramCommandReply{}, false } func telegramPrivateOnlyMenuReply(isGroup bool, action, privateText string) telegramCommandReply { if isGroup { return telegramCommandReply{Text: telegramGroupPrivateUserHint(action)} } return telegramCommandReply{Text: privateText} } func (s *TelegramBotService) setPendingPrivatePrompt(msg *TelegramMessage, isGroup bool, action, kind, text string) telegramCommandReply { if isGroup { return telegramCommandReply{Text: telegramGroupPrivateUserHint(action)} } s.setPending(int64(msg.From.ID), kind) return telegramCommandReply{Text: text} } func (s *TelegramBotService) handleAdminMenuCallback(ctx context.Context, msg *TelegramMessage, data string) (telegramCommandReply, bool) { if reply, handled := s.handleAdminRegistrationCallback(ctx, msg, data); handled { return reply, true } if reply, handled := s.handleAdminUserCallback(ctx, data); handled { return reply, true } switch { case data == "adm_capacity": return s.replyCapacity(ctx), true case data == "adm_devicepolicy": return s.replyDevicePolicy(ctx), true case data == "adm_mgo_commands": return telegramCommandReply{Text: telegramMgoAdminCommandHelp(), Buttons: [][]telegramInlineButton{{{Text: "⬅️ 返回菜单", Data: "menu_main"}}}}, true case strings.HasPrefix(data, "dp_toggle:"): return s.replyDevicePolicyToggle(ctx, strings.TrimPrefix(data, "dp_toggle:")), true } return telegramCommandReply{}, false } func (s *TelegramBotService) handleAdminRegistrationCallback(ctx context.Context, msg *TelegramMessage, data string) (telegramCommandReply, bool) { switch { case data == "adm_openreg": return s.replyOpenRegMenu(ctx), true case data == "adm_openreg_close": _ = s.closeRegistration(ctx) return telegramCommandReply{Text: "已关闭注册。"}, true case strings.HasPrefix(data, "adm_openreg_set:"): n, _ := strconv.Atoi(strings.TrimPrefix(data, "adm_openreg_set:")) if err := s.openRegistration(ctx, n); err != nil { return telegramCommandReply{Text: "开注失败:" + err.Error()}, true } label := "不限" if n > 0 { label = fmt.Sprintf("%d 个名额", n) } return telegramCommandReply{Text: "已开放注册:" + label + "。"}, true case data == "adm_gencode": return s.replyGenCodeMenu(), true case strings.HasPrefix(data, "gc:"): return s.replyGenCode(ctx, msg, data), true } return telegramCommandReply{}, false } func (s *TelegramBotService) handleAdminUserCallback(ctx context.Context, data string) (telegramCommandReply, bool) { switch { case data == "adm_users": return s.replyUserList(ctx), true case strings.HasPrefix(data, "usr:"): return s.replyUserActions(ctx, strings.TrimPrefix(data, "usr:")), true case strings.HasPrefix(data, "uban:"): return s.replyUserBan(ctx, strings.TrimPrefix(data, "uban:"), false), true case strings.HasPrefix(data, "uunban:"): return s.replyUserBan(ctx, strings.TrimPrefix(data, "uunban:"), true), true case strings.HasPrefix(data, "udel:"): return s.replyUserDelete(ctx, strings.TrimPrefix(data, "udel:")), true case strings.HasPrefix(data, "urenew:"): return s.replyUserRenew(ctx, strings.TrimPrefix(data, "urenew:")), true } return telegramCommandReply{}, false } // handlePendingText consumes a button-initiated text prompt. Returns (reply, // handled). handled=false means there was no pending prompt for this user. func (s *TelegramBotService) handlePendingText(ctx context.Context, channel *model.NotifyChannel, msg *TelegramMessage, text string) (telegramCommandReply, bool) { p, ok := s.takePending(int64(msg.From.ID)) if !ok { return telegramCommandReply{}, false } switch p.Kind { case "register": return s.cmdRegister(ctx, channel, msg, strings.Fields(text)), true case "redeem_register": return s.redeemRegisterFlow(ctx, channel, msg, text), true case "redeem_renew": return s.redeemRenewFlow(ctx, msg, text), true case "setname": return s.selfSetName(ctx, msg, text), true case "setpass": return s.selfSetPass(ctx, msg, text), true case "openreg_limit": n, err := strconv.Atoi(strings.TrimSpace(text)) if err != nil || n < 0 { return telegramCommandReply{Text: "请输入有效的非负整数。"}, true } if err := s.openRegistration(ctx, n); err != nil { return telegramCommandReply{Text: "开注失败:" + err.Error()}, true } return telegramCommandReply{Text: fmt.Sprintf("已开放注册:%d 个名额。", n)}, true } return telegramCommandReply{}, false }