From 79780d8b3a4a278ccae262f7233a7e0ffb7a386d Mon Sep 17 00:00:00 2001 From: LyMysterious Date: Fri, 17 Oct 2025 18:33:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 400 ++++++++++++++++++ .../Controllers/EntryController.cs | 26 ++ .../NapCatRobotClient.API.csproj | 12 + .../NapCatRobotClient.API/Program.cs | 47 ++ .../Properties/launchSettings.json | 31 ++ .../appsettings.Development.json | 8 + .../NapCatRobotClient.API/appsettings.json | 21 + .../NapCatRobotClient.Core/Consts.cs | 11 + .../NapCatRobotClient.Core/GlobalUsings.cs | 2 + .../Helper/RedisHelper.cs | 32 ++ .../NapCatRobotClient.Core.csproj | 18 + .../Dto/Request/GroupSendMessageRequest.cs | 53 +++ .../RobotAPI/RobotAPI.cs | 30 ++ .../NapCatRobotClient.Core/Utils.cs | 73 ++++ .../Dispatcher/Service/DispatcherService.cs | 29 ++ .../Dispatcher/Service/IDispatcherService.cs | 12 + .../NapCatRobotClient.Service/GlobalUsings.cs | 9 + .../Group/Dto/GoodsInfo.cs | 30 ++ .../Group/Dto/WantedTaskInfo.cs | 54 +++ .../Group/Service/GroupService.cs | 31 ++ .../Group/Service/IGroupService.cs | 12 + .../InertOrUpdateGoodsInfoProcess.cs | 55 +++ .../Group/TextProcess/LingTianProcess.cs | 165 ++++++++ .../Group/TextProcess/WantedPriceProcess.cs | 162 +++++++ .../NapCatRobotClient.Service.csproj | 12 + NapCatRobotClient/NapCatRobotClient.sln | 37 ++ 26 files changed, 1372 insertions(+) create mode 100644 .gitignore create mode 100644 NapCatRobotClient/NapCatRobotClient.API/Controllers/EntryController.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.API/NapCatRobotClient.API.csproj create mode 100644 NapCatRobotClient/NapCatRobotClient.API/Program.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.API/Properties/launchSettings.json create mode 100644 NapCatRobotClient/NapCatRobotClient.API/appsettings.Development.json create mode 100644 NapCatRobotClient/NapCatRobotClient.API/appsettings.json create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/Consts.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/GlobalUsings.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/Helper/RedisHelper.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/NapCatRobotClient.Core.csproj create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/Dto/Request/GroupSendMessageRequest.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/RobotAPI.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Core/Utils.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/DispatcherService.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/IDispatcherService.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/GlobalUsings.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/GoodsInfo.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/WantedTaskInfo.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/Service/GroupService.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/Service/IGroupService.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/InertOrUpdateGoodsInfoProcess.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/LingTianProcess.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/WantedPriceProcess.cs create mode 100644 NapCatRobotClient/NapCatRobotClient.Service/NapCatRobotClient.Service.csproj create mode 100644 NapCatRobotClient/NapCatRobotClient.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca1c7a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,400 @@ +# ---> VisualStudio +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + diff --git a/NapCatRobotClient/NapCatRobotClient.API/Controllers/EntryController.cs b/NapCatRobotClient/NapCatRobotClient.API/Controllers/EntryController.cs new file mode 100644 index 0000000..33f9724 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.API/Controllers/EntryController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; +using NapCatRobotClient.Service.Dispatcher.Service; + +namespace NapCatRobotClient.API.Controllers +{ + [ApiController] + [Route("api/[controller]")] + public class EntryController : ControllerBase + { + private readonly IDispatcherService _dispatcherService; + + public EntryController(IDispatcherService dispatcherService) + { + _dispatcherService = dispatcherService; + } + + [HttpPost] + public async Task Post() + { + using StreamReader reader = new(Request.Body); + string body = await reader.ReadToEndAsync(); + bool result = await _dispatcherService.ReceiveMessageAndProcess(body); + return Ok(result); + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.API/NapCatRobotClient.API.csproj b/NapCatRobotClient/NapCatRobotClient.API/NapCatRobotClient.API.csproj new file mode 100644 index 0000000..7a8ab45 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.API/NapCatRobotClient.API.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + + + + + + + diff --git a/NapCatRobotClient/NapCatRobotClient.API/Program.cs b/NapCatRobotClient/NapCatRobotClient.API/Program.cs new file mode 100644 index 0000000..399e1f2 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.API/Program.cs @@ -0,0 +1,47 @@ + +using Furion; +using Microsoft.AspNetCore.HttpOverrides; +using NapCatRobotClient.Core.Helper; +using System.Net; + +namespace NapCatRobotClient.API +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args).Inject(); + builder.WebHost.UseUrls(App.Configuration["AppSettings:Urls"]); + + builder.Services.AddRedis(App.Configuration["ConnectionStrings:Redis"]); + builder.Services.AddControllers(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + var app = builder.Build(); + + app.UseInject(string.Empty); + + app.EnableBuffering(); + + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, + KnownProxies = { IPAddress.Parse("127.0.0.1") } + }); + + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseAuthorization(); + + + app.MapControllers(); + + app.Run(); + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.API/Properties/launchSettings.json b/NapCatRobotClient/NapCatRobotClient.API/Properties/launchSettings.json new file mode 100644 index 0000000..fb3895b --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.API/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39930", + "sslPort": 0 + } + }, + "profiles": { + "NapCatRobotClient.API": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.API/appsettings.Development.json b/NapCatRobotClient/NapCatRobotClient.API/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.API/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.API/appsettings.json b/NapCatRobotClient/NapCatRobotClient.API/appsettings.json new file mode 100644 index 0000000..b2b9a09 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.API/appsettings.json @@ -0,0 +1,21 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "AppSettings": { + "Urls": "http://0.0.0.0:5000" + }, + "ConnectionStrings": { + "Redis": "127.0.0.1:6379,defaultDatabase=0,max pool size=50,tryit=0" + }, + "QQConfig": { + "SendApiUrl": "http://192.168.142.133:3000", + "AccessToken": "123456", + "RobotQQ": "3902582794", + "XiuXianGroupId": "705807264" + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Core/Consts.cs b/NapCatRobotClient/NapCatRobotClient.Core/Consts.cs new file mode 100644 index 0000000..90d903e --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/Consts.cs @@ -0,0 +1,11 @@ +namespace NapCatRobotClient.Core +{ + /// + /// 缓存前缀 + /// + public class RedisPrefix + { + public const string GoodsKey = "GoodsInfo"; + + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Core/GlobalUsings.cs b/NapCatRobotClient/NapCatRobotClient.Core/GlobalUsings.cs new file mode 100644 index 0000000..ce2c3c1 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Furion; +global using Newtonsoft.Json; diff --git a/NapCatRobotClient/NapCatRobotClient.Core/Helper/RedisHelper.cs b/NapCatRobotClient/NapCatRobotClient.Core/Helper/RedisHelper.cs new file mode 100644 index 0000000..ed3ab02 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/Helper/RedisHelper.cs @@ -0,0 +1,32 @@ +using FreeRedis; +using Microsoft.Extensions.DependencyInjection; + +namespace NapCatRobotClient.Core.Helper +{ + public static class RedisExtensions + { + public static IServiceCollection AddRedis(this IServiceCollection services, string redisConnection) + { + RedisHelper.Initialization(redisConnection); + return services; + } + } + + /// + /// Redis + /// + public abstract class RedisHelper + { + public static RedisClient Client; + + public static void Initialization(string redisConnection) + { + Client = new(redisConnection) + { + Serialize = JsonConvert.SerializeObject, + Deserialize = JsonConvert.DeserializeObject + }; + Client.Set(Guid.NewGuid().ToString(), "", 10); + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Core/NapCatRobotClient.Core.csproj b/NapCatRobotClient/NapCatRobotClient.Core/NapCatRobotClient.Core.csproj new file mode 100644 index 0000000..d214593 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/NapCatRobotClient.Core.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + enable + + + + + + + + + + + + + diff --git a/NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/Dto/Request/GroupSendMessageRequest.cs b/NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/Dto/Request/GroupSendMessageRequest.cs new file mode 100644 index 0000000..7a73da2 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/Dto/Request/GroupSendMessageRequest.cs @@ -0,0 +1,53 @@ +namespace NapCatRobotClient.Core.RobotAPI.Dto.Request +{ + public class GroupSendMessageRequest + { + /// + /// 群号码 + /// + [JsonProperty("group_id")] + public string GroupId { get; set; } + + /// + /// 消息内容 + /// + [JsonProperty("message")] + public List Message { get; set; } + } + + public class MessageItem + { + /// + /// 消息类型 每一种消息一个item (text:文本 at:艾特 reply:引用) + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// 数据 + /// + [JsonProperty("data")] + public MessageData Data { get; set; } + } + + public class MessageData + { + /// + /// 消息内容 + /// + [JsonProperty("text", NullValueHandling = NullValueHandling.Ignore)] + public string Text { get; set; } + + /// + /// 如果是艾特,这里填写QQ + /// + [JsonProperty("qq", NullValueHandling = NullValueHandling.Ignore)] + public string QQ { get; set; } + + /// + /// 如果是引用,这里填写引用的消息ID + /// + [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)] + public string Id { get; set; } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/RobotAPI.cs b/NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/RobotAPI.cs new file mode 100644 index 0000000..465278e --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/RobotAPI/RobotAPI.cs @@ -0,0 +1,30 @@ +using Flurl.Http; +using NapCatRobotClient.Core.RobotAPI.Dto.Request; + +namespace NapCatRobotClient.Core.RobotAPI +{ + public class RobotAPI + { + /// + /// 发送群文本消息 + /// + /// + /// + /// + public static async Task SendGroupText(GroupSendMessageRequest request) + { + return await Post(JsonConvert.SerializeObject(request), "/send_group_msg"); + } + + private static async Task Post(string parameters, string action) + { + string url = App.Configuration["QQConfig:SendApiUrl"] + action; + string response = await url.WithHeader("Authorization", "Bearer " + App.Configuration["QQConfig:AccessToken"]) + .WithHeader("Content-Type", "application/json;charset=utf-8") + .PostStringAsync(parameters) + .ReceiveString(); + Console.WriteLine($"{DateTime.Now} 请求地址:{url} 请求结果:{response}"); + return response; + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Core/Utils.cs b/NapCatRobotClient/NapCatRobotClient.Core/Utils.cs new file mode 100644 index 0000000..2e568e0 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Core/Utils.cs @@ -0,0 +1,73 @@ +namespace NapCatRobotClient.Core +{ + public class Utils + { + /// + /// 格式化数字为中文单位 + /// + /// + /// + public static string FormatNumberToChineseUnit(decimal number) + { + if (number >= 100000000) // 亿 + { + decimal value = number / 100000000.0M; + return $"{value:0.#}亿"; + } + else if (number >= 10000) // 万 + { + decimal value = number / 10000.0M; + return $"{value:0.#}万"; + } + else + { + return number.ToString(); + } + } + + /// + /// 解析中文数字 + /// + /// + /// + public static decimal ParseChineseNumber(string chineseNumber) + { + if (string.IsNullOrWhiteSpace(chineseNumber)) return 0; + + chineseNumber = chineseNumber.Trim(); + + decimal multiplier = 1; + if (chineseNumber.Contains("亿")) + multiplier = 100000000; + else if (chineseNumber.Contains("万")) + multiplier = 10000; + var numberPart = chineseNumber.Replace("亿", "").Replace("万", ""); + if (!decimal.TryParse(numberPart, out decimal value)) return 0; + + return value * multiplier; + } + + /// + /// 计算手续费 + /// + /// + /// + public static decimal CalculateFee(decimal amount) + { + decimal rate = 0; + + if (amount < 500_0000) // 0 ~ 499w + rate = 0.05m; // 5% + else if (amount < 1000_0000) // 500 ~ 999w + rate = 0.10m; // 10% + else if (amount < 1500_0000) // 1000 ~ 1499w + rate = 0.15m; // 15% + else if (amount < 2000_0000) // 1500 ~ 1999w + rate = 0.20m; // 20% + else // 2000w以上 + rate = 0.30m; // 30% + + return amount * rate; + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/DispatcherService.cs b/NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/DispatcherService.cs new file mode 100644 index 0000000..c6e751e --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/DispatcherService.cs @@ -0,0 +1,29 @@ +using NapCatRobotClient.Service.Group.Service; + +namespace NapCatRobotClient.Service.Dispatcher.Service +{ + public class DispatcherService : IDispatcherService, IScoped + { + + private readonly IGroupService _groupService; + public DispatcherService(IGroupService groupService) + { + _groupService = groupService; + } + + /// + /// 接收消息并处理 + /// + /// + /// + public async Task ReceiveMessageAndProcess(string message) + { + JObject json = JObject.Parse(message); + if (string.IsNullOrWhiteSpace(json["group_id"]?.ToString()) is false) + { + await _groupService.ProcessGroupRequest(message); + } + return true; + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/IDispatcherService.cs b/NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/IDispatcherService.cs new file mode 100644 index 0000000..08d04ab --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Dispatcher/Service/IDispatcherService.cs @@ -0,0 +1,12 @@ +namespace NapCatRobotClient.Service.Dispatcher.Service +{ + public interface IDispatcherService + { + /// + /// 接收消息并处理 + /// + /// + /// + public Task ReceiveMessageAndProcess(string message); + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/GlobalUsings.cs b/NapCatRobotClient/NapCatRobotClient.Service/GlobalUsings.cs new file mode 100644 index 0000000..dfff2a5 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/GlobalUsings.cs @@ -0,0 +1,9 @@ +global using Furion; +global using Newtonsoft.Json; +global using Furion.DependencyInjection; +global using Newtonsoft.Json.Linq; +global using NapCatRobotClient.Core; +global using NapCatRobotClient.Core.Helper; +global using NapCatRobotClient.Core.RobotAPI; +global using NapCatRobotClient.Service.Group.Dto; +global using System.Text.RegularExpressions; \ No newline at end of file diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/GoodsInfo.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/GoodsInfo.cs new file mode 100644 index 0000000..ce1dd65 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/GoodsInfo.cs @@ -0,0 +1,30 @@ +namespace NapCatRobotClient.Service.Group.Dto +{ + public class GoodsInfo + { + /// + /// 物品名称 + /// + public string Name { get; set; } + + /// + /// 实际价格 + /// + public decimal Price { get; set; } + + /// + /// 展示价格 + /// + public string ShowPriceDesc { get; set; } + + /// + /// 价格更新时间 + /// + public DateTime LastUpdateTime { get; set; } + + /// + /// 数量 + /// + public int? Num { get; set; } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/WantedTaskInfo.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/WantedTaskInfo.cs new file mode 100644 index 0000000..41ef7ab --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/Dto/WantedTaskInfo.cs @@ -0,0 +1,54 @@ +namespace NapCatRobotClient.Service.Group.Dto +{ + /// + /// 悬赏令 + /// + public class WantedTaskInfo + { + /// + /// 序号 + /// + public int Id { get; set; } + + /// + /// 任务名称 + /// + public string Name { get; set; } + + /// + /// 成功几率 + /// + public string SuccessRate { get; set; } + + /// + /// 修为值 + /// + public long BaseReward { get; set; } + + /// + /// 需要时间 + /// + public string Duration { get; set; } + + /// + /// 额外奖励 + /// + public ExtraReward ExtraReward { get; set; } + } + + /// + /// 额外奖励 + /// + public class ExtraReward + { + /// + /// 奖励等级 + /// + public string Grade { get; set; } + + /// + /// 奖励物品 + /// + public string Item { get; set; } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/Service/GroupService.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/Service/GroupService.cs new file mode 100644 index 0000000..5bf724a --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/Service/GroupService.cs @@ -0,0 +1,31 @@ +using NapCatRobotClient.Service.Group.TextProcess; + +namespace NapCatRobotClient.Service.Group.Service +{ + public class GroupService : IGroupService, IScoped + { + private static string GroupQQ = App.Configuration["QQConfig:XiuXianGroupId"]; + + /// + /// 处理群消息 + /// + /// + /// + public async Task ProcessGroupRequest(string message) + { + JObject json = JObject.Parse(message); + string groupId = json["group_id"]?.ToString() ?? string.Empty; + string groupMsg = json["message"]?.ToString() ?? string.Empty; + if (GroupQQ == groupId && string.IsNullOrWhiteSpace(groupMsg) is false) + { + _ = WantedPriceProcess.ProcessGroupRequest(groupId, message); + + _ = InertOrUpdateGoodsInfoProcess.ProcessGroupRequest(groupId, message); + + _= LingTianProcess.ProcessGroupRequest(groupId, message); + } + + return await Task.FromResult(true); + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/Service/IGroupService.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/Service/IGroupService.cs new file mode 100644 index 0000000..dc94116 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/Service/IGroupService.cs @@ -0,0 +1,12 @@ +namespace NapCatRobotClient.Service.Group.Service +{ + public interface IGroupService + { + /// + /// 处理群消息 + /// + /// + /// + public Task ProcessGroupRequest(string message); + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/InertOrUpdateGoodsInfoProcess.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/InertOrUpdateGoodsInfoProcess.cs new file mode 100644 index 0000000..78a707e --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/InertOrUpdateGoodsInfoProcess.cs @@ -0,0 +1,55 @@ +using Furion.Logging; + +namespace NapCatRobotClient.Service.Group.TextProcess +{ + /// + /// 保存或更新物品价格 + /// + public class InertOrUpdateGoodsInfoProcess + { + private static Regex Regex = new Regex(@"价格[::](\d+(?:\.\d+)?[万亿])\s+([^\s]+)"); + + public static async Task ProcessGroupRequest(string groupId, string message) + { + try + { + if (message.Contains("不鼓励不保障")) + { + JObject json = JObject.Parse(message); + message = json["message"]?.ToString(); + message = JArray.Parse(message).FirstOrDefault(o => o["type"].ToString() == "text" && !string.IsNullOrWhiteSpace(o["data"]["text"].ToString()))["data"]["text"]?.ToString(); + if (string.IsNullOrWhiteSpace(message)) return false; + + List results = new(); + + MatchCollection matches = Regex.Matches(message); + + foreach (Match match in matches) + { + string price = match.Groups[1].Value; + string name = match.Groups[2].Value.Replace("\u200b", "").Replace("\u200c", "").Replace("\u200d", ""); // 去除零宽字符 + GoodsInfo gds = new() + { + Name = name, + Price = Utils.ParseChineseNumber(price), + ShowPriceDesc = price, + LastUpdateTime = DateTime.Now + }; + results.Add(gds); + } + + foreach (GoodsInfo item in results) + { + RedisHelper.Client.HSet(RedisPrefix.GoodsKey, item.Name, item); + } + } + } + catch (Exception ex) + { + Log.Error($@"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 保存或更新物品价格 发生异常,异常信息:{ex.Message},异常堆栈:{ex.StackTrace}", true); + + } + return await Task.FromResult(true); + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/LingTianProcess.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/LingTianProcess.cs new file mode 100644 index 0000000..276849e --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/LingTianProcess.cs @@ -0,0 +1,165 @@ +using Furion.Logging; +using NapCatRobotClient.Core.RobotAPI.Dto.Request; + +namespace NapCatRobotClient.Service.Group.TextProcess +{ + public class LingTianProcess + { + /// + /// 处理群消息 + /// + /// + /// + public static async Task ProcessGroupRequest(string groupId, string message) + { + try + { + JObject json = JObject.Parse(message); + string groupMsg = JArray.Parse(json["message"].ToString()).FirstOrDefault(o => o["type"].ToString() == "text" + && !string.IsNullOrWhiteSpace(o["data"]["text"].ToString()) && (o["data"]["text"].ToString().Contains("道友本次采集成果") || o["data"]["text"].ToString().Contains("道友成功收获药材")))?["data"]?["text"]?.ToString(); + if (string.IsNullOrWhiteSpace(groupMsg)) return false; + + List goods = new(); + if (groupMsg.Contains("道友本次采集成果")) + { + goods = await NewCmd(groupMsg); + } + else if (groupMsg.Contains("道友成功收获药材")) + { + goods = await OldCmd(groupMsg); + } + if (goods.Count > 0) + { + string msg = ""; + decimal totalPrice = 0; + decimal fee = 0; + // 打印结果 + foreach (var good in goods) + { + var current = RedisHelper.Client.HGet(RedisPrefix.GoodsKey, good.Name); + if (current is not null) + { + int num = good.Num.Value > 10 ? 10 : good.Num.Value; + decimal nicePrice = current.Price - 100000; + totalPrice += Convert.ToDecimal(nicePrice * num); + + fee += Math.Round(Utils.CalculateFee(nicePrice) * num, 0); + } + } + if (totalPrice > 0) + { + msg = $"恭喜道友成功收取{goods.Sum(o => o.Num)}株药材\r\n"; + msg += $"总价值约:{Utils.FormatNumberToChineseUnit(totalPrice)}\r\n"; + msg += $"手续费约:{Utils.FormatNumberToChineseUnit(fee)}\r\n"; + msg += $"到账约:{Utils.FormatNumberToChineseUnit(totalPrice - fee)}"; + + GroupSendMessageRequest request = new() + { + GroupId = groupId, + Message = new() + { + new MessageItem() + { + Type = "text", + Data = new() + { + Text = msg + } + }, + new MessageItem() + { + Type = "reply", + Data = new() + { + Id = json["message_id"].ToString() + } + } + } + }; + await RobotAPI.SendGroupText(request); + } + + var atData = JArray.Parse(message).FirstOrDefault(o => o["type"].ToString() == "at"); + if (atData is not null) + { + ScheduleNextNotify(groupId, atData["data"]["qq"].ToString()); + } + } + + } + catch (Exception ex) + { + Log.Error($@"{DateTime.Now:yyyy-MM-dd HH:mm:ss} 灵田结算查价格 发生异常,异常信息:{ex.Message},异常堆栈:{ex.StackTrace}", true); + + } + return await Task.FromResult(true); + } + + private static async Task> OldCmd(string message) + { + List goodsInfos = new(); + var result = new List<(string Name, int Count)>(); + var regex = new Regex(@"道友成功收获药材:(.+?) (\d+) 个!"); + + foreach (Match match in regex.Matches(message)) + { + string name = match.Groups[1].Value; + int count = int.Parse(match.Groups[2].Value); + goodsInfos.Add(new() { Name = name, Num = count }); + } + return await Task.FromResult(goodsInfos); + } + + private static async Task> NewCmd(string message) + { + List goodsInfos = new(); + var result = new List<(string Name, int Count)>(); + var regex = new Regex(@"收获药材:(.+?) (\d+) 个!"); + + foreach (Match match in regex.Matches(message)) + { + string name = match.Groups[1].Value; + int count = int.Parse(match.Groups[2].Value); + goodsInfos.Add(new() { Name = name, Num = count }); + } + return await Task.FromResult(goodsInfos); + } + + private static void ScheduleNextNotify(string groupId, string userId) + { + TimeSpan delay = TimeSpan.FromHours(47.01); + + Log.Information($@"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {userId} 触发下次灵田结算通知 "); + + _ = Task.Run(async () => + { + await Task.Delay(delay); + + GroupSendMessageRequest request = new() + { + GroupId = groupId, + Message = new() + { + new MessageItem() + { + Type = "text", + Data = new() + { + Text = $"\r\n【灵田结算通知】\r\n该结算奖励了!" + } + }, + new MessageItem() + { + Type = "at", + Data = new() + { + QQ = userId + } + } + } + }; + await RobotAPI.SendGroupText(request); + }); + } + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/WantedPriceProcess.cs b/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/WantedPriceProcess.cs new file mode 100644 index 0000000..419e95e --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/Group/TextProcess/WantedPriceProcess.cs @@ -0,0 +1,162 @@ +using NapCatRobotClient.Core.RobotAPI.Dto.Request; + +namespace NapCatRobotClient.Service.Group.TextProcess +{ + /// + /// 悬赏令 + /// + public class WantedPriceProcess + { + /// + /// 处理群消息 + /// + /// + /// + public static async Task ProcessGroupRequest(string groupId, string message) + { + List wantedTasks = new(); + JObject json = JObject.Parse(message); + string groupMsg = JArray.Parse(json["message"].ToString()).FirstOrDefault(o => o["type"].ToString() == "text" + && !string.IsNullOrWhiteSpace(o["data"]["text"].ToString()) && o["data"]["text"].ToString().Contains("悬赏"))?["data"]?["text"]?.ToString(); + + if (string.IsNullOrWhiteSpace(groupMsg)) return false; + + if (groupMsg.Contains("个人悬赏令")) + { + wantedTasks = await SingleWanted(groupMsg); + } + else if (groupMsg.Contains("天机悬赏令")) + { + wantedTasks = await SpecialWanted(groupMsg); + } + if (wantedTasks.Count > 0) + { + string msg = ""; + List<(int Id, decimal Price)> prices = new(); + foreach (var want in wantedTasks) + { + msg += $"✨悬赏令 {want.Id} 奖励:{want.ExtraReward.Item}\r\n"; + msg += $"🎁修为:{Utils.FormatNumberToChineseUnit(want.BaseReward)} ({want.SuccessRate})\r\n"; + var goodsInfo = RedisHelper.Client.HGet(RedisPrefix.GoodsKey, want.ExtraReward.Item); + if (goodsInfo is not null) + { + msg += $"💵坊市价格:{goodsInfo.ShowPriceDesc}\r\n"; + + prices.Add((want.Id, goodsInfo.Price)); + } + //msg += $"炼金价格:\r\n"; + msg += $"\r\n"; + } + var maxWanted = wantedTasks.MaxBy(o => o.BaseReward); + + msg += "━━━━━━━━━━━━━━━\r\n"; + msg += $"✨最高修为:悬赏令 {maxWanted.Id} ({Utils.FormatNumberToChineseUnit(maxWanted.BaseReward)})\r\n"; + if (prices.Count > 0) + { + var maxPrice = prices.MaxBy(o => o.Price); + msg += $"💰最高价格:悬赏令 {maxPrice.Id} ({Utils.FormatNumberToChineseUnit(maxPrice.Price)})"; + } + + GroupSendMessageRequest request = new() + { + GroupId = groupId, + Message = new() + { + new MessageItem() + { + Type = "text", + Data = new() + { + Text = msg + } + } + } + }; + await RobotAPI.SendGroupText(request); + } + + + return await Task.FromResult(true); + } + + /// + /// 个人悬赏令 + /// + /// + /// + private static async Task> SingleWanted(string message) + { + List result = new(); + + // 解析任务信息 + var taskRegex = new Regex( + @"(?\d+)、(?.*?), + 完成几率(?\d+), + 基础报酬(?\d+)修为, + 预计需(?\d+分钟), + 可能额外获得:(?[^::]+):(?[^!!]+)!?", + RegexOptions.IgnorePatternWhitespace); + + foreach (Match match in taskRegex.Matches(message)) + { + result.Add(new() + { + Id = int.Parse(match.Groups[1].Value), + Name = match.Groups[2].Value.Trim(), + SuccessRate = match.Groups[3].Value + "%", + BaseReward = long.Parse(match.Groups[4].Value), + Duration = match.Groups[5].Value, + ExtraReward = new ExtraReward + { + Grade = match.Groups[6].Value.Trim(), + Item = match.Groups[7].Value.Trim() + } + }); + } + return await Task.FromResult(result); + } + + /// + /// 天机悬赏令 + /// + /// + /// + private static async Task> SpecialWanted(string message) + { + List result = new(); + + var blockRegex = new Regex( + @"悬赏(?[壹贰叁肆伍陆柒捌玖拾])·(?[^\n\r]+)\s+" + + @".*?成功率:(?\d+)%\s+" + + @".*?预计耗时:(?[\d一二三四五六七八九十]+分钟)\s+" + + @".*?基础奖励(?\d+)修为\s+" + + @".*?额外机缘:(?[^「」]+)「(?[^」]+)」", + RegexOptions.Multiline); + + foreach (Match match in blockRegex.Matches(message)) + { + string idCN = match.Groups["idCN"].Value; + result.Add(new() + { + Id = ChineseNumberMap.ContainsKey(idCN) ? ChineseNumberMap[idCN] : 0, + Name = match.Groups["name"].Value.Trim(), + SuccessRate = match.Groups["rate"].Value + "%", + Duration = match.Groups["duration"].Value.Trim(), + BaseReward = long.Parse(match.Groups["reward"].Value), + ExtraReward = new ExtraReward + { + Grade = match.Groups["grade"].Value.Trim(), + Item = match.Groups["item"].Value.Trim() + } + }); + } + return await Task.FromResult(result); + } + + private static readonly Dictionary ChineseNumberMap = new() + { + { "壹", 1 }, { "贰", 2 }, { "叁", 3 }, { "肆", 4 }, { "伍", 5 }, + { "陆", 6 }, { "柒", 7 }, { "捌", 8 }, { "玖", 9 }, { "拾", 10 } + }; + } +} diff --git a/NapCatRobotClient/NapCatRobotClient.Service/NapCatRobotClient.Service.csproj b/NapCatRobotClient/NapCatRobotClient.Service/NapCatRobotClient.Service.csproj new file mode 100644 index 0000000..e7563b6 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.Service/NapCatRobotClient.Service.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + enable + + + + + + + diff --git a/NapCatRobotClient/NapCatRobotClient.sln b/NapCatRobotClient/NapCatRobotClient.sln new file mode 100644 index 0000000..8387db7 --- /dev/null +++ b/NapCatRobotClient/NapCatRobotClient.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36429.23 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NapCatRobotClient.API", "NapCatRobotClient.API\NapCatRobotClient.API.csproj", "{1D25220A-3FF4-4F21-89B7-BC4156C9546E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NapCatRobotClient.Service", "NapCatRobotClient.Service\NapCatRobotClient.Service.csproj", "{001CCBBB-530D-4F44-BACC-6BE8C29B99AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NapCatRobotClient.Core", "NapCatRobotClient.Core\NapCatRobotClient.Core.csproj", "{A6418E9D-51D0-4D19-A494-604C7FB65F9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D25220A-3FF4-4F21-89B7-BC4156C9546E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D25220A-3FF4-4F21-89B7-BC4156C9546E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D25220A-3FF4-4F21-89B7-BC4156C9546E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D25220A-3FF4-4F21-89B7-BC4156C9546E}.Release|Any CPU.Build.0 = Release|Any CPU + {001CCBBB-530D-4F44-BACC-6BE8C29B99AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {001CCBBB-530D-4F44-BACC-6BE8C29B99AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {001CCBBB-530D-4F44-BACC-6BE8C29B99AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {001CCBBB-530D-4F44-BACC-6BE8C29B99AA}.Release|Any CPU.Build.0 = Release|Any CPU + {A6418E9D-51D0-4D19-A494-604C7FB65F9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A6418E9D-51D0-4D19-A494-604C7FB65F9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A6418E9D-51D0-4D19-A494-604C7FB65F9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A6418E9D-51D0-4D19-A494-604C7FB65F9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7FDBB0C0-7B3A-4044-B232-833C20A15E15} + EndGlobalSection +EndGlobal