/// <summary>
        ///     当用户与机器人不再为好友时,通过此方法通知协调器
        /// </summary>
        /// <param name="userSteamId">用户 Steam ID</param>
        /// <param name="botId">机器人 ID</param>
        public async Task OnUserBotRelationshipNone(string userSteamId, string botId)
        {
            using (var dbContext = new KeylolDbContext())
            {
                var userManager = new KeylolUserManager(dbContext);
                var user        = await userManager.FindBySteamIdAsync(userSteamId);

                if (user == null)
                {
                    // 非会员不再为好友时,如果存在已绑定的 SteamBindingToken 则清除之
                    var bindingTokens = await dbContext.SteamBindingTokens
                                        .Where(t => t.SteamId == userSteamId && t.BotId == botId)
                                        .ToListAsync();

                    dbContext.SteamBindingTokens.RemoveRange(bindingTokens);
                    await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin);
                }
                else if (user.SteamBotId == botId)
                {
                    // 会员与自己的机器人不再为好友时,解除机器人绑定
                    user.SteamBotId = null;
                    await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin);
                }
            }
        }
        /// <summary>
        ///     判断指定 Steam 账户是不是其乐用户并且匹配指定机器人
        /// </summary>
        /// <param name="steamId">Steam ID</param>
        /// <param name="botId">机器人 ID</param>
        /// <returns><c>true</c> 表示是其乐用户并于目标机器人匹配,<c>false</c> 表示不是</returns>
        public async Task <bool> IsKeylolUser(string steamId, string botId)
        {
            using (var dbContext = new KeylolDbContext())
            {
                var userManager = new KeylolUserManager(dbContext);
                var user        = await userManager.FindBySteamIdAsync(steamId);

                return(user != null && user.SteamBotId == botId);
            }
        }
        /// <summary>
        ///     更新指定用户的属性
        /// </summary>
        /// <param name="steamId">要更新的用户 Steam ID</param>
        /// <param name="profileName">Steam 昵称,<c>null</c> 表示不更新</param>
        public async Task UpdateUser(string steamId, string profileName)
        {
            using (var dbContext = new KeylolDbContext())
            {
                var userManager = new KeylolUserManager(dbContext);
                var user        = await userManager.FindBySteamIdAsync(steamId);

                if (user == null)
                {
                    return;
                }
                if (profileName != null)
                {
                    user.SteamProfileName = profileName;
                }
                await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin);
            }
        }
        /// <summary>
        ///     当机器人收到新的聊天消息时,通过此方法通知协调器
        /// </summary>
        /// <param name="senderSteamId">消息发送人 Steam ID</param>
        /// <param name="botId">机器人 ID</param>
        /// <param name="message">聊天消息内容</param>
        public async Task OnBotNewChatMessage(string senderSteamId, string botId, string message)
        {
            using (var dbContext = new KeylolDbContext())
            {
                var userManager = new KeylolUserManager(dbContext);
                var user        = await userManager.FindBySteamIdAsync(senderSteamId);

                if (user == null)
                {
                    // 非会员,只接受绑定验证码
                    var code  = message.Trim();
                    var token = await dbContext.SteamBindingTokens
                                .SingleOrDefaultAsync(t => t.Code == code && t.SteamId == null);

                    if (token == null)
                    {
                        await Client.SendChatMessage(botId, senderSteamId,
                                                     "你的输入无法被识别,请确认登录验证码的长度和格式。如果需要帮助,请与其乐职员取得联系。");
                    }
                    else
                    {
                        token.BotId   = botId;
                        token.SteamId = senderSteamId;
                        await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin);

                        NotificationProvider.Hub <SteamBindingHub, ISteamBindingHubClient>()
                        .Client(token.BrowserConnectionId)?
                        .OnBind(await Client.GetUserProfileName(botId, senderSteamId),
                                await Client.GetUserAvatarHash(botId, senderSteamId));
                        await Client.SendChatMessage(botId, senderSteamId,
                                                     "绑定成功,欢迎加入其乐!今后你可以向机器人发送对话快速登录社区,请勿将机器人从好友列表移除。");

                        await Task.Delay(TimeSpan.FromSeconds(5));

                        await Client.SendChatMessage(botId, senderSteamId,
                                                     "若希望在其乐上获得符合游戏兴趣的据点推荐,请避免将 Steam 资料隐私设置为「仅自己可见」。");
                    }
                }
                else
                {
                    // 已有会员,接受登录验证码和调侃调戏
                    var match = Regex.Match(message, @"^\s*(\d{4})\s*$");
                    if (match.Success)
                    {
                        var code = match.Groups[1].Value;
                        try
                        {
                            var connectionId = await _oneTimeToken.Consume <string>(code, OneTimeTokenPurpose.SteamLogin);

                            var loginToken = await _oneTimeToken.Generate(user.Id, TimeSpan.FromMinutes(1),
                                                                          OneTimeTokenPurpose.UserLogin);

                            NotificationProvider.Hub <SteamLoginHub, ISteamLoginHubClient>()
                            .Client(connectionId).OnLoginOneTimeToken(loginToken, user.UserName, user.AvatarImage);
                            await Client.SendChatMessage(botId, senderSteamId, "欢迎回来,你已成功登录其乐社区。");
                        }
                        catch (Exception)
                        {
                            await Client.SendChatMessage(botId, senderSteamId,
                                                         "你的输入无法被识别,请确认登录验证码的长度和格式。如果需要帮助,请与其乐职员取得联系。");
                        }
                    }
                    else
                    {
                        if (!AutoChatDisabledBots.ContainsKey(botId))
                        {
                            await Client.SendChatMessage(botId, senderSteamId, await AskTulingBotAsync(message, user.Id),
                                                         true);
                        }
                    }
                }
            }
        }
        /// <summary>
        ///     当机器人接收到用户好友请求时,通过此方法通知协调器
        /// </summary>
        /// <param name="userSteamId">用户 Steam ID</param>
        /// <param name="botId">机器人 ID</param>
        public async Task OnBotNewFriendRequest(string userSteamId, string botId)
        {
            await Client.AddFriend(botId, userSteamId); // 先接受请求

            using (var dbContext = new KeylolDbContext())
            {
                var userManager = new KeylolUserManager(dbContext);
                var user        = await userManager.FindBySteamIdAsync(userSteamId);

                if (user == null)
                {
                    // 非会员,在注册时绑定机器人
                    NotificationProvider.Hub <SteamBindingHub, ISteamBindingHubClient>()
                    .Clients(await dbContext.SteamBindingTokens.Where(t => t.BotId == botId)
                             .Select(t => t.BrowserConnectionId)
                             .ToListAsync())?
                    .OnFriend();
                    await Task.Delay(TimeSpan.FromSeconds(3));

                    await Client.SendChatMessage(botId, userSteamId,
                                                 "欢迎使用当前 Steam 账号加入其乐,请输入你在网页上获取的 8 位绑定验证码。");

                    var queueName = MqClientProvider.SteamBotDelayedActionQueue(botId);
                    _mqChannel.QueueDeclare(queueName, true, false, false, null);
                    _mqChannel.SendMessage(MqClientProvider.DelayedMessageExchange,
                                           queueName, new SteamBotDelayedActionDto
                    {
                        Type       = SteamBotDelayedActionType.RemoveFriend,
                        Properties = new
                        {
                            OnlyIfNotKeylolUser = true,
                            Message             = "抱歉,你的会话因超时被强制结束,机器人已将你从好友列表中暂时移除。若要加入其乐,请重新按照网页指示注册账号。",
                            SteamId             = userSteamId
                        }
                    }, 300000);
                }
                else
                {
                    // 现有会员添加机器人为好友
                    var bot = await dbContext.SteamBots.FindAsync(botId);

                    if (user.SteamBotId == null && bot != null && bot.FriendCount < bot.FriendUpperLimit)
                    {
                        // 用户此前删除了机器人好友,重新设定当前机器人为绑定的机器人
                        user.SteamBotId       = botId;
                        user.SteamBindingTime = DateTime.Now;
                        await dbContext.SaveChangesAsync(KeylolDbContext.ConcurrencyStrategy.DatabaseWin);

                        await Task.Delay(TimeSpan.FromSeconds(3));

                        await Client.SendChatMessage(botId, userSteamId,
                                                     "你已成功与其乐机器人再次绑定,请务必不要将其乐机器人从好友列表中移除。");
                    }
                    else
                    {
                        // 用户状态正常但是添加了新的机器人好友,应当移除好友
                        await Task.Delay(TimeSpan.FromSeconds(3));

                        await Client.SendChatMessage(botId, userSteamId,
                                                     "你已绑定另一其乐机器人,当前机器人已拒绝你的好友请求。如有需要,你可以在其乐设置表单中找到你绑定的机器人帐号。");

                        await Client.RemoveFriend(botId, userSteamId);
                    }
                }
            }
        }