public async Task ProcessAsync(Sisters.WudiLib.Posts.Message message, HttpApiClient api)
        {
            // TODO 验证用户名是否合法
            var(success, userInfo) = await OsuApi.GetUserInfoAsync(_userName, OsuMixedApi.Mode.Standard);

            if (!success)
            {
                await api.SendMessageAsync(message.Endpoint, "网络错误。");

                return;
            }
            if (userInfo == null)
            {
                await api.SendMessageAsync(message.Endpoint, "没有此用户。");

                return;
            }
            var dbResult = await Database.AddNewBindAsync(message.UserId, userInfo.Id, userInfo.Name, "自己绑定", message.UserId, userInfo.Name);

            if (dbResult.Success)
            {
                await api.SendMessageAsync(message.Endpoint, $"成功绑定为{userInfo.Name}。");
            }
            else if (dbResult.Exception is DbUpdateException && dbResult.Exception.InnerException?.Message.Contains("Duplicate", StringComparison.Ordinal) == true)
            {
                await api.SendMessageAsync(message.Endpoint, "在已绑定的情况下不允许修改,如需修改请联系 bleatingsheep。");
            }
            else
            {
                await api.SendMessageAsync(message.Endpoint, "数据库访问错误。");

                FLogger.LogException(dbResult.Exception);
            }
        }
        protected async Task <UserInfo> EnsureGetUserInfo(string name, Bleatingsheep.Osu.Mode mode)
        {
            var(success, result) = await OsuApi.GetUserInfoAsync(name, mode);

            ExecutingException.Ensure(success, "网络错误。");
            ExecutingException.Ensure(result != null, "无此用户!");
            return(result);
        }
        protected async Task <(bool, UserInfo)> GetCachedUserInfo(int id, Bleatingsheep.Osu.Mode mode)
        {
            var hasCache = s_cache.TryGetValue <UserInfo>((id, mode), out var cachedInfo);

            if (hasCache)
            {
                return(true, cachedInfo);
            }
            var(success, userInfo) = await OsuApi.GetUserInfoAsync(id, mode);

            if (success)
            {
                s_cache.Set((id, mode), userInfo, CacheAvailable);
                return(true, userInfo);
            }
            else
            {
                // fail
                return(default);
        private async Task ParseInfoAsync(
            HttpApiClient api,
            Endpoint sendBackEndpoint,
            GroupRequest r,
            int?osuId = null,
            TrustedUserInfo userInfo = default)
        {
            long   userId  = r.UserId;
            string comment = r.Comment;
            var    hints   = new List <Message>();

            if (!string.IsNullOrEmpty(comment))
            {
                var userNames = OsuHelper.DiscoverUsernames(comment).Where(n => !string.Equals(n, "osu", StringComparison.OrdinalIgnoreCase));
                if (osuId != null)
                {
                    bool success = userInfo != null; // 由于当前未从调用方法处获得上级 API 是否调用成功,在信息不为 null 时默认成功。
                    if (userInfo == null)
                    {                                // 可能的重试。
                        (success, userInfo) = await OsuApi.GetUserInfoAsync(osuId.Value, Mode.Standard).ConfigureAwait(false);
                    }
                    _ = await ProcessApplicantReportAsync(hints, null, (success, userInfo)).ConfigureAwait(false);

                    if (userInfo != null && !userNames.Any(n => string.Equals(userInfo.Name, n, StringComparison.OrdinalIgnoreCase)))
                    {// 绑定不一致
                        hints.Add(new Message("警告:其绑定的账号与申请不符。"));
                    }
                }
                else
                {// 忽略已绑定的情况,因为可能绑定不一致或者查询失败。
                    foreach (var name in userNames)
                    {
                        var userTuple = await OsuApi.GetUserInfoAsync(name, Bleatingsheep.Osu.Mode.Standard);

                        //// 我想用 8.0 新语法
                        //hints.Add(new Message($"{info?.Name ?? name}: " +
                        //    $"{(success ? info == null ? "不存在此用户。" : $"PP: {info.Performance}, PC: {info.PlayCount}, TTH: {info.TotalHits}" : "查询失败。")}"));

                        var info = await ProcessApplicantReportAsync(hints, name, userTuple).ConfigureAwait(false);

                        if (info == null)
                        {// 属于没有查到的情况(因为网络问题或者用户不存在),并且之前已经给出错误信息。
                            continue;
                        }

                        // 自动绑定,在请求消息完全匹配 osu! 用户名的前提下。
                        if (userNames.Count() == 1 &&
                            comment.TrimEnd().EndsWith($"答案:{name}", StringComparison.Ordinal) &&
                            info != null)
                        {
                            var bindingResult = await Database.AddNewBindAsync(
                                qq : r.UserId,
                                osuId : info.Id,
                                osuName : info.Name,
                                source : "Auto (Request)",
                                operatorId : r.UserId,
                                operatorName : info.Name).ConfigureAwait(false);

                            if (bindingResult.Success)
                            {
                                hints.Add(new Message($"自动绑定为 {info.Name}"));
                                goto binding_end;
                            }
                            else
                            {
                                hints.Add(new Message($"自动绑定失败。"));
                            }
                        }
                        // 提供绑定并放行的捷径。
                        if (info?.Performance < 2500)
                        {
                            var ms = new MemoryStream();
                            using (var bw = new BinaryWriter(ms, Encoding.UTF8, true))
                            {
                                bw.Write(userId);
                                bw.Write(info.Id);
                                bw.Write(r.Flag);
                            }
#pragma warning disable CA5351 // 不要使用损坏的加密算法
                            using var md5 = MD5.Create();
#pragma warning restore CA5351 // 不要使用损坏的加密算法
                            ms.Write(md5.ComputeHash(ms.ToArray()));
                            var bytes  = ms.ToArray();
                            var base64 = Convert.ToBase64String(bytes);
                            await api.SendMessageAsync(sendBackEndpoint, $"(占位)绑定为 {info.Name} 并放行:#{base64}#");
                        }
                        binding_end :;
                    }
                }
            }
            if (hints.Count > 0)
            {
                var newLine = new Message("\r\n");
                await api.SendMessageAsync(sendBackEndpoint, hints.Aggregate((m1, m2) =>
                {
                    return((m1.Sections.LastOrDefault()?.Type, m2.Sections.FirstOrDefault()?.Type) switch
                    {
                        ("text", "text") => m1 + newLine + m2,
                        _ => m1 + m2,
                    });
                })).ConfigureAwait(false);
示例#5
0
        public async Task ProcessAsync(MessageContext context, HttpApiClient api)
        {
            var uid = await EnsureGetBindingIdAsync(context.UserId).ConfigureAwait(false);

            var apiTask = OsuApi.GetUserInfoAsync(uid, Bleatingsheep.Osu.Mode.Standard).ConfigureAwait(false);

            using (var page = await Chrome.OpenNewPageAsync().ConfigureAwait(false))
            {
                await page.SetViewportAsync(new ViewPortOptions
                {
                    DeviceScaleFactor = 2.75,
                    Width             = 1440,
                    Height            = 900,
                }).ConfigureAwait(false);

                await page.GoToAsync($"https://osu.ppy.sh/users/{uid}/osu").ConfigureAwait(false);

                // wait for load complete
                const string waitSelector = @"body > div.osu-layout__section.osu-layout__section--full.js-content.community_profile > div > div > div > div.osu-layout__section.osu-layout__section--users-extra > div > div > div > div:nth-child(2)";
                await page.WaitForSelectorAsync(waitSelector).ConfigureAwait(false);

                const string bestSelector = "body > div.osu-layout__section.osu-layout__section--full.js-content.community_profile > div > div > div > div.osu-layout__section.osu-layout__section--users-extra > div > div > div > div:nth-child(2) > div > div.play-detail-list";
                //ElementHandle bpsElement = await page.QuerySelectorAsync(bestSelector).ConfigureAwait(false);

                const string  buttonSelector = "body > div.osu-layout__section.osu-layout__section--full.js-content.community_profile > div > div > div > div.osu-layout__section.osu-layout__section--users-extra > div > div > div > div:nth-child(2) > div > div.profile-extra-entries__item > button";
                ElementHandle button         = await page.QuerySelectorAsync(buttonSelector).ConfigureAwait(false);

                ElementHandle[]? bpList = default;
                int maxTryTimes = 4;
                while (button != null && maxTryTimes-- != 0)
                {
                    //check bp counts.

                    bpList = await page.QuerySelectorAllAsync(bestSelector + " > div").ConfigureAwait(false);

                    await button.ClickAsync().ConfigureAwait(false);

                    // wait for click complete
                    await page.WaitForSelectorAsync(bestSelector + $" > div:nth-child({bpList.Length})",
                                                    new WaitForSelectorOptions { Timeout = 8000 /*ms*/ }).ConfigureAwait(false);

                    // seems that bp div adding and button availability changing is NOT synchronized
                    // wait for button availability change
                    await page.WaitForTimeoutAsync(500).ConfigureAwait(false);

                    // requery button
                    button = await page.QuerySelectorAsync(buttonSelector).ConfigureAwait(false);
                }

                if (bpList == null)
                {
                    await api.SendMessageAsync(context.Endpoint, "查询失败。").ConfigureAwait(false);

                    return;
                }

                // filter

                /*
                 * bpList = document.querySelectorAll("body > div.osu-layout__section.osu-layout__section--full.js-content.community_profile > div > div > div > div.osu-layout__section.osu-layout__section--users-extra > div > div > div > div:nth-child(2) > div > div.play-detail-list > div");
                 *
                 * bpList.forEach((element) => { var dateTime = element.querySelector("div.play-detail__group.play-detail__group--top > div.play-detail__detail > div > span.play-detail__time > time").getAttribute("datetime"); if (new Date() - Date.parse(dateTime) > 86400000) element.remove();  })
                 */

                await page.EvaluateExpressionAsync(@"bpList = document.querySelectorAll(""body > div.osu-layout__section.osu-layout__section--full.js-content.community_profile > div > div > div > div.osu-layout__section.osu-layout__section--users-extra > div > div > div > div:nth-child(2) > div > div.play-detail-list > div"");").ConfigureAwait(false);

                await page.EvaluateExpressionAsync(@"bpList.forEach((element) => { var dateTime = element.querySelector(""div.play-detail__group.play-detail__group--top > div.play-detail__detail > div > span.play-detail__time > time"").getAttribute(""datetime""); if (new Date() - Date.parse(dateTime) > 86400000) element.remove();  })").ConfigureAwait(false);

                // check
                bpList = await page.QuerySelectorAllAsync(bestSelector + " > div").ConfigureAwait(false);

                if (bpList.Length == 0)
                {
                    await api.SendMessageAsync(context.Endpoint, "最近 24 小时没有更新 bp。").ConfigureAwait(false);

                    return;
                }

                //screenshot
                //delete pinned elements
                await(await page.QuerySelectorAsync("body > div.js-pinned-header.hidden-xs.no-print.nav2-header > div.nav2-header__body").ConfigureAwait(false)).EvaluateFunctionAsync(@"(element) => element.remove()").ConfigureAwait(false);
                await(await page.QuerySelectorAsync("body > div.osu-layout__section.osu-layout__section--full.js-content.community_profile > div > div > div > div.hidden-xs.page-extra-tabs.page-extra-tabs--profile-page.js-switchable-mode-page--scrollspy-offset").ConfigureAwait(false)).EvaluateFunctionAsync(@"(element) => element.remove()").ConfigureAwait(false);

                // add extra information
                await page.EvaluateExpressionAsync($@"document.querySelector(""{bestSelector}"").parentElement.parentElement.querySelector(""h3"").textContent += "" (用户名:"" + {JsonConvert.SerializeObject((await apiTask).Item2?.Name ?? "获取失败")} + "" 查询时间:"" + {JsonConvert.SerializeObject(DateTime.Now.ToString("yyyy-MM-dd H:mm)"))}").ConfigureAwait(false);

                //ElementHandle printElement = await page.QuerySelectorAsync(bestSelector).ConfigureAwait(false);
                var printElement =
                    await(await page.QuerySelectorAsync(bestSelector).ConfigureAwait(false))
                    .EvaluateFunctionHandleAsync("(element) => element.parentNode.parentNode").ConfigureAwait(false) as ElementHandle;
                printElement ??= await page.QuerySelectorAsync(bestSelector).ConfigureAwait(false);

                var data = await printElement.ScreenshotDataAsync(new ScreenshotOptions()).ConfigureAwait(false);

                await api.SendMessageAsync(context.Endpoint, Message.ByteArrayImage(data)).ConfigureAwait(false);
            }
        }
        public async Task ProcessAsync(MessageContext context, HttpApiClient api)
        {
            //await api.SendMessageAsync(context.Endpoint, $"[DEBUG] 比较:{_other};模式:{_mode}");

            var id = await EnsureGetBindingIdAsync(context.UserId).ConfigureAwait(false);

            //var browser = GetBrowser();

            byte[] data = null;
            var    mode = Bleatingsheep.Osu.Mode.Standard;

            try
            {
                if (!string.IsNullOrEmpty(_mode))
                {
                    mode = Bleatingsheep.Osu.ModeExtensions.Parse(_mode);
                }
            }
            catch
            {
                await api.SendMessageAsync(context.Endpoint, "模式识别失败,fallback 到 Standard。").ConfigureAwait(false);
            }

            var url = $"http://hydrantweb/pptth/mini/{id}?height=350&mode={(int)mode}";

            if (!string.IsNullOrEmpty(_other))
            {
                var(_, user) = await OsuApi.GetUserInfoAsync(_other, mode);

                if (user == null)
                {
                    ExecutingException.Ensure(false, "对比玩家错误");
                }
                url += $"&compared={user.Id}";
            }
            using (var page = await Chrome.OpenNewPageAsync().ConfigureAwait(false))
            {
                await page.SetViewportAsync(new ViewPortOptions
                {
                    DeviceScaleFactor = 1.15,
                    Width             = 640,
                    Height            = 350,
                }).ConfigureAwait(false);

                await page.GoToAsync(url).ConfigureAwait(false);

                await Task.Delay(0).ConfigureAwait(false);

                data = await page.ScreenshotDataAsync(new ScreenshotOptions
                {
                    FullPage = true,
                    //Type = ScreenshotType.Jpeg,
                    //Quality = 100,
                }).ConfigureAwait(false);
            }

            var stopwatch    = Stopwatch.StartNew();
            var sendResponse = await api.SendMessageAsync(context.Endpoint, Message.ByteArrayImage(data)).ConfigureAwait(false);

            var elapsedTime = stopwatch.ElapsedMilliseconds;
            var failed      = sendResponse is null;

            if (failed)
            {
                await api.SendMessageAsync(context.Endpoint, "图片发送失败(确定)").ConfigureAwait(false);
            }
            (failed ? FailedElapsed : SuccessfulElapsed).Add(elapsedTime);
        }