├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── Andreal.Core
├── Andreal.Core.csproj
├── Common
│ ├── BackgroundTask.cs
│ ├── CommandPrefixAttribute.cs
│ ├── ConfigJson.cs
│ ├── ConfigWatcher.cs
│ ├── ExceptionLogger.cs
│ ├── External.cs
│ ├── GlobalConfig.cs
│ ├── MessageInfo.cs
│ ├── MessageInfoType.cs
│ ├── Path.cs
│ └── RobotReply.cs
├── Data
│ ├── Api
│ │ ├── OtherApi.cs
│ │ └── UnofficialArcaeaAPI.cs
│ ├── Json
│ │ ├── Arcaea
│ │ │ ├── ArcaeaUnlimited
│ │ │ │ ├── AccountInfo.cs
│ │ │ │ ├── ArcSongdata.cs
│ │ │ │ ├── ResponseRoot.cs
│ │ │ │ ├── SessionQueryingContent.cs
│ │ │ │ ├── SongListContent.cs
│ │ │ │ ├── UserBestContent.cs
│ │ │ │ ├── UserBestsContent.cs
│ │ │ │ └── UserInfoContent.cs
│ │ │ └── PartnerPosInfoBase
│ │ │ │ ├── PartnerPosInfoBase.cs
│ │ │ │ └── PosInfoItem.cs
│ │ ├── Bandori
│ │ │ ├── BandoriRoomInfo.cs
│ │ │ ├── ResponseItem.cs
│ │ │ └── SourceInfo.cs
│ │ ├── Hitokoto
│ │ │ └── Hitokoto.cs
│ │ └── Ycm
│ │ │ ├── CarsItem.cs
│ │ │ └── YcmResponse.cs
│ └── Sqlite
│ │ ├── BotUserInfo.cs
│ │ └── ImgVersion.cs
├── Executor
│ ├── ArcExecutor.cs
│ ├── ExecutorBase.cs
│ ├── OtherExecutor.cs
│ └── PermissionExecutor.cs
├── Message
│ ├── IMessage.cs
│ ├── ImageMessage.cs
│ ├── MessageChain.cs
│ ├── ReplyMessage.cs
│ └── TextMessage.cs
├── Model
│ └── Arcaea
│ │ ├── ArcaeaChart.cs
│ │ ├── ArcaeaCharts.cs
│ │ ├── ArcaeaSong.cs
│ │ ├── Best30Data.cs
│ │ ├── Best40Data.cs
│ │ ├── DifficultyInfo.cs
│ │ ├── IBest30Data.cs
│ │ ├── IBest40Data.cs
│ │ ├── PlayerInfo.cs
│ │ ├── RecordData.cs
│ │ ├── RecordInfo.cs
│ │ └── Side.cs
├── UI
│ ├── BackGround.cs
│ ├── Color.cs
│ ├── Font.cs
│ ├── Image.cs
│ ├── ImageGenerator
│ │ ├── ArcBackgroundGenerator.cs
│ │ ├── ArcBest30ImageGenerator.cs
│ │ ├── ArcBest40ImageGenerator.cs
│ │ ├── ArcRecord5ImageGenerator.cs
│ │ ├── ArcRecordImageGenerator.cs
│ │ └── ArcSongLevelListImageGenerator.cs
│ ├── Model
│ │ ├── IGraphicsModel.cs
│ │ ├── ImageModel.cs
│ │ ├── LineModel.cs
│ │ ├── PartnerModel.cs
│ │ ├── PolygonModel.cs
│ │ ├── PotentitalModel.cs
│ │ ├── RectangleModel.cs
│ │ ├── TextOnlyModel.cs
│ │ ├── TextWithShadowModel.cs
│ │ └── TextWithStrokeModel.cs
│ └── StackBlur.cs
└── Utils
│ ├── AbbreviationHelper.cs
│ ├── ArcaeaHelper.cs
│ ├── BotStatementHelper.cs
│ ├── ConcurrentDictionaryExtend.cs
│ ├── DateTimeHelper.cs
│ ├── LastExceptionHelper.cs
│ ├── RandStringHelper.cs
│ ├── RandomHelper.cs
│ ├── SqliteHelper.cs
│ ├── StringHelper.cs
│ └── SystemHelper.cs
├── Andreal.Test
├── AIRandomSongTest.cs
├── Andreal.Test.csproj
└── Usings.cs
├── Andreal.Window
├── Andreal.Window.csproj
├── Assets
│ ├── Fonts
│ │ └── Roboto-Light.ttf
│ ├── chevron-down.png
│ ├── close.png
│ ├── favicon.ico
│ ├── img_contact.png
│ ├── img_map.png
│ ├── img_message.png
│ ├── img_setting.png
│ └── min.png
├── Common
│ ├── AssemblyInfo.cs
│ ├── DelegateCommand.cs
│ ├── EnumDescriptionConverter.cs
│ ├── ExceptionLog.cs
│ ├── NotifyIconViewModel.cs
│ ├── Program.cs
│ └── TimeConverter.cs
└── UI
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── Login.xaml
│ ├── Login.xaml.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── SliderSubmit.xaml
│ ├── SliderSubmit.xaml.cs
│ ├── SliderVerify.xaml
│ ├── SliderVerify.xaml.cs
│ ├── SmsCodeVerify.xaml
│ ├── SmsCodeVerify.xaml.cs
│ ├── SourceDownloader.xaml
│ ├── SourceDownloader.xaml.cs
│ └── UserControl
│ ├── Accounts.xaml
│ ├── Accounts.xaml.cs
│ ├── ExceptionLog.xaml
│ ├── ExceptionLog.xaml.cs
│ ├── MessageLog.xaml
│ ├── MessageLog.xaml.cs
│ ├── ReplySetting.xaml
│ ├── ReplySetting.xaml.cs
│ ├── Setting.xaml
│ └── Setting.xaml.cs
├── Andreal.sln
├── Andreal.sln.DotSettings.user
├── LICENSE
├── README.md
└── UpdateLog.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ['https://afdian.net/@Awbugl']
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | *.dll
3 | *.xml
4 | *.cache
5 | *.pdb
6 | *.txt
7 | *.nupkg
8 | *.p7s
9 | *._
10 |
11 | [Dd]ebug/
12 | [Dd]ebugPublic/
13 | [Rr]elease/
14 | [Rr]eleases/
15 | x64/
16 | x86/
17 | bld/
18 | [Bb]in/
19 | [Oo]bj/
20 | [Ll]og/
21 |
--------------------------------------------------------------------------------
/Andreal.Core/Andreal.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 | true
8 | embedded
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/BackgroundTask.cs:
--------------------------------------------------------------------------------
1 | using System.Timers;
2 | using Timer = System.Timers.Timer;
3 |
4 | namespace Andreal.Core.Common;
5 |
6 | internal static class BackgroundTask
7 | {
8 | internal static void Init()
9 | {
10 | Timer timer = new(240000);
11 | timer.Elapsed += Clean;
12 | timer.AutoReset = true;
13 | timer.Enabled = true;
14 | }
15 |
16 | private static void Clean(object? source, ElapsedEventArgs e)
17 | {
18 | var time = DateTime.Now.AddMinutes(-2);
19 |
20 | foreach (var j in new DirectoryInfo(Path.TempImageRoot).GetFiles().Where(j => time > j.LastWriteTime)) j.Delete();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/CommandPrefixAttribute.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Common;
2 |
3 | [Serializable]
4 | [AttributeUsage(AttributeTargets.Method)]
5 | internal class CommandPrefixAttribute : Attribute
6 | {
7 | internal CommandPrefixAttribute(params string[] prefixs)
8 | {
9 | Prefixs = prefixs;
10 | }
11 |
12 | internal string[] Prefixs { get; }
13 | }
14 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/ConfigJson.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Konata.Core.Common;
3 | using Newtonsoft.Json;
4 |
5 | namespace Andreal.Core.Common;
6 |
7 | public class ConfigJson
8 | {
9 | [JsonProperty("protocol")]
10 | public OicqProtocol Protocol { get; set; }
11 |
12 | [JsonProperty("keystore")]
13 | public BotKeyStore? KeyStore { get; set; }
14 |
15 | [JsonProperty("device")]
16 | public BotDevice? Device { get; set; }
17 | }
18 |
19 | public class AccountInfo
20 | {
21 | [JsonProperty("uid")]
22 | public uint Account { get; set; }
23 |
24 | [JsonProperty("password")]
25 | public string Password { get; set; } = "";
26 | }
27 |
28 | public class ApiConfig
29 | {
30 | [JsonProperty("url")]
31 | public string Url { get; set; } = "";
32 |
33 | [JsonProperty("token")]
34 | public string Token { get; set; } = "";
35 | }
36 |
37 | public class AndrealSettings
38 | {
39 | [JsonProperty("friend_add")]
40 | public bool FriendAdd { get; set; }
41 |
42 | [JsonProperty("group_add")]
43 | public bool GroupAdd { get; set; }
44 |
45 | [JsonProperty("group_inviter_whitelist")]
46 | public List GroupInviterWhitelist { get; set; } = new();
47 | }
48 |
49 | public class AndrealConfig
50 | {
51 | [JsonProperty("master")]
52 | public uint Master { get; set; }
53 |
54 | [JsonProperty("protocol")]
55 | public OicqProtocol Protocol { get; set; }
56 |
57 | [JsonProperty("slider")]
58 | public SliderType SliderType { get; set; }
59 |
60 | [JsonProperty("enable_handle_message")]
61 | public bool EnableHandleMessage { get; set; } = true;
62 |
63 | [JsonProperty("accounts")]
64 | public List Accounts { get; set; } = new();
65 |
66 | [JsonProperty("api")]
67 | public Dictionary Api { get; set; } = new();
68 |
69 | [JsonProperty("approve_settings")]
70 | public AndrealSettings Settings { get; set; } = new();
71 | }
72 |
73 | public enum SliderType
74 | {
75 | [Description("滑块验证助手")]
76 | Helper,
77 |
78 | [Description("浏览器模拟滑块(不建议)")]
79 | Browser
80 | }
81 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/ConfigWatcher.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Common;
2 |
3 | internal static class ConfigWatcher
4 | {
5 | private const int TimeoutMillis = 2000;
6 | private static readonly FileSystemWatcher Watcher = new(Path.AndreaConfigRoot);
7 | private static readonly List TmpFiles = new();
8 |
9 | internal static void Init()
10 | {
11 | Watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
12 | Watcher.Filter = "*.json";
13 | Watcher.EnableRaisingEvents = true;
14 |
15 | Timer timer = new(OnTimer, null, Timeout.Infinite, Timeout.Infinite);
16 | Watcher.Changed += (_, e) =>
17 | {
18 | TmpFiles.Add(e.Name!);
19 | timer.Change(TimeoutMillis, Timeout.Infinite);
20 | };
21 | }
22 |
23 | private static void OnTimer(object? _)
24 | {
25 | lock (TmpFiles)
26 | {
27 | foreach (var file in TmpFiles) GlobalConfig.Init(file);
28 | TmpFiles.Clear();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/ExceptionLogger.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Utils;
2 | using static Andreal.Core.Utils.BotStatementHelper;
3 |
4 | namespace Andreal.Core.Common;
5 |
6 | public static class ExceptionLogger
7 | {
8 | public delegate void ExceptionEventHandler(Exception e);
9 |
10 | private static readonly object SyncObj = new();
11 |
12 | public static event ExceptionEventHandler? OnExceptionRecorded;
13 |
14 | public static void Log(Exception? ex)
15 | {
16 | if (ex is null) return;
17 | LastExceptionHelper.Set(ex);
18 |
19 | switch (ex)
20 | {
21 | case HttpRequestException or TaskCanceledException:
22 | ++WebExceptionCount;
23 | return;
24 |
25 | case InvalidCastException when ex.Message.Contains("Konata.Core.Events.ProtocolEvent"):
26 | return;
27 |
28 | default:
29 | ++ExceptionCount;
30 | WriteException(ex);
31 | OnExceptionRecorded?.Invoke(ex);
32 | return;
33 | }
34 | }
35 |
36 | private static void WriteException(Exception ex)
37 | {
38 | lock (SyncObj)
39 | {
40 | File.AppendAllText(Path.ExceptionReport, $"{DateTime.Now}\n{ex}\n\n");
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/External.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Utils;
2 | using Konata.Core;
3 | using Konata.Core.Message;
4 |
5 | namespace Andreal.Core.Common;
6 |
7 | [Serializable]
8 | public static class External
9 | {
10 | public static void Process(
11 | Bot bot,
12 | int type,
13 | uint fromGroup,
14 | uint fromQq,
15 | MessageStruct message)
16 | => MessageInfo.Process(bot, type, fromGroup, fromQq, message);
17 |
18 | public static void Initialize(AndrealConfig andrealConfig) => SystemHelper.Init(andrealConfig);
19 | }
20 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/GlobalConfig.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using Andreal.Core.Data.Json.Arcaea.PartnerPosInfoBase;
3 | using Newtonsoft.Json;
4 |
5 | namespace Andreal.Core.Common;
6 |
7 | public static class GlobalConfig
8 | {
9 | internal static ConcurrentDictionary> Locations
10 | = new(JsonConvert.DeserializeObject>>(File.ReadAllText(Path.PartnerConfig)) !);
11 |
12 | public static RobotReply RobotReply = JsonConvert.DeserializeObject(File.ReadAllText(Path.RobotReply))!;
13 |
14 | internal static List RandomReply = JsonConvert.DeserializeObject>(File.ReadAllText(Path.RandomReply))!;
15 |
16 | internal static void Init(string file)
17 | {
18 | switch (file)
19 | {
20 | case "positioninfo.json":
21 | Locations = new(JsonConvert.DeserializeObject>>(File.ReadAllText(Path.PartnerConfig))!);
22 | return;
23 |
24 | case "replytemplate.json":
25 | RobotReply = JsonConvert.DeserializeObject(File.ReadAllText(Path.RobotReply))!;
26 | return;
27 |
28 | case "randomtemplate.json":
29 | RandomReply = JsonConvert.DeserializeObject>(File.ReadAllText(Path.RandomReply))!;
30 | return;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/MessageInfoType.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Common;
2 |
3 | // ReSharper disable UnusedMember.Global
4 | [Serializable]
5 | internal enum MessageInfoType
6 | {
7 | Friend = 0,
8 | Group = 1,
9 | Temp = 2
10 | }
11 |
--------------------------------------------------------------------------------
/Andreal.Core/Common/RobotReply.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 | using Andreal.Core.Utils;
3 | using Newtonsoft.Json;
4 |
5 | namespace Andreal.Core.Common;
6 |
7 | public class RobotReply
8 | {
9 | [JsonProperty("NotBind")]
10 | public string NotBind { get; set; } = "";
11 |
12 | [JsonProperty("NotBindArc")]
13 | public string NotBindArc { get; set; } = "";
14 |
15 | [JsonProperty("ParameterLengthError")]
16 | public string ParameterLengthError { get; set; } = "";
17 |
18 | [JsonProperty("ParameterError")]
19 | public string ParameterError { get; set; } = "";
20 |
21 | [JsonProperty("ConfigNotFound")]
22 | public string ConfigNotFound { get; set; } = "";
23 |
24 | [JsonProperty("NotPlayedTheSong")]
25 | public string NotPlayedTheSong { get; set; } = "";
26 |
27 | [JsonProperty("ArcUidNotFound")]
28 | public string ArcUidNotFound { get; set; } = "";
29 |
30 | [JsonProperty("TooManyArcUid")]
31 | public string TooManyArcUid { get; set; } = "";
32 |
33 | [JsonProperty("NoSongFound")]
34 | public string NoSongFound { get; set; } = "";
35 |
36 | [JsonProperty("NoBydChart")]
37 | public string NoBydChart { get; set; } = "";
38 |
39 | [JsonProperty("NotPlayedTheChart")]
40 | public string NotPlayedTheChart { get; set; } = "";
41 |
42 | [JsonProperty("UnBindSuccess")]
43 | public string UnBindSuccess { get; set; } = "";
44 |
45 | [JsonProperty("GotShadowBanned")]
46 | public string GotShadowBanned { get; set; } = "";
47 |
48 | [JsonProperty("APIQueryFailed")]
49 | public string APIQueryFailed { get; set; } = "";
50 |
51 | [JsonProperty("TooManySongFound")]
52 | public string TooManySongFound { get; set; } = "";
53 |
54 | [JsonProperty("JrrpResult")]
55 | public string JrrpResult { get; set; } = "";
56 |
57 | [JsonProperty("SendMessageFailed")]
58 | public string SendMessageFailed { get; set; } = "";
59 |
60 | [JsonProperty("BindSuccess")]
61 | public string BindSuccess { get; set; } = "";
62 |
63 | [JsonProperty("HelpMessage")]
64 | public string HelpMessage { get; set; } = "";
65 |
66 | [JsonProperty("GroupLeave")]
67 | public string GroupLeave { get; set; } = "";
68 |
69 | [JsonProperty("BelowTheThreshold")]
70 | public string BelowTheThreshold { get; set; } = "";
71 |
72 | [JsonProperty("NeedUpdateAUA")]
73 | public string NeedUpdateAUA { get; set; } = "";
74 |
75 | [JsonProperty("InvalidSessionInfo")]
76 | public string InvalidSessionInfo { get; set; } = "";
77 |
78 | [JsonProperty("SessionExpired")]
79 | public string SessionExpired { get; set; } = "";
80 |
81 | [JsonProperty("SessionQuerying")]
82 | public string SessionQuerying { get; set; } = "";
83 |
84 | [JsonProperty("SessionWaitingForAccount")]
85 | public string SessionWaitingForAccount { get; set; } = "";
86 |
87 | [JsonProperty("DuplicateBestsRequests")]
88 | public string DuplicateBestsRequests { get; set; } = "";
89 |
90 | [JsonProperty("UserBestsSession")]
91 | public string UserBestsSession { get; set; } = "";
92 |
93 | [JsonProperty("ExceptionOccured")]
94 | public string ExceptionOccured { get; set; } = "";
95 |
96 | internal TextMessage OnExceptionOccured(Exception ex) => ExceptionOccured.Replace("$exception$", ex.Message);
97 | internal TextMessage OnAPIQueryFailed(Exception ex) => APIQueryFailed.Replace("$exception$", ex.Message);
98 |
99 | internal TextMessage OnAPIQueryFailed(int status, string message) => APIQueryFailed.Replace("$exception$", $"{status}: {message}");
100 |
101 | internal TextMessage OnJrrpResult(string value) => JrrpResult.Replace("$jrrp$", value);
102 | internal TextMessage OnBindSuccess(string value) => BindSuccess.Replace("$info$", value);
103 |
104 | internal TextMessage OnUserBestsSession(string value) => UserBestsSession.Replace("$session$", value);
105 |
106 | internal TextMessage OnSessionQuerying(string value) => SessionQuerying.Replace("$count$", value);
107 |
108 | internal TextMessage OnSessionWaitingForAccount(string value) => SessionWaitingForAccount.Replace("$count$", value);
109 |
110 | internal string GetRandomAIReply(string songname, string artist)
111 | => GlobalConfig.RandomReply.GetRandomItem().Replace("$songname$", songname).Replace("$artist$", artist);
112 | }
113 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Api/OtherApi.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Andreal.Core.Data.Json.Bandori;
3 | using Andreal.Core.Data.Json.Hitokoto;
4 | using Andreal.Core.Data.Json.Ycm;
5 | using Newtonsoft.Json;
6 |
7 | namespace Andreal.Core.Data.Api;
8 |
9 | internal static class OtherApi
10 | {
11 | [NonSerialized]
12 | private static readonly HttpClient Client;
13 |
14 | static OtherApi()
15 | {
16 | Client = new();
17 | }
18 |
19 | private static async Task GetString(string url) => await Client.GetStringAsync(url);
20 |
21 | internal static async Task HitokotoApi()
22 | {
23 | var data = JsonConvert.DeserializeObject(await GetString("https://v1.hitokoto.cn"));
24 | return $"{data?.Content}\n ----「{data?.From}」";
25 | }
26 |
27 | internal static async Task JrrpApi(long uin)
28 | {
29 | Task msg = Client.PostAsync("http://api.kokona.tech:5555/jrrp",
30 | new StringContent($"QQ=2967373629&v=20190114&QueryQQ={uin}", Encoding.UTF8,
31 | new("application/x-www-form-urlencoded")));
32 |
33 | return await (await msg).Content.ReadAsStringAsync();
34 | }
35 |
36 | internal static async Task YcmApi(string carType)
37 | => JsonConvert.DeserializeObject(await
38 | GetString($"https://ycm.chinosk6.cn/get_car?car_type={carType}&time_limit=900&token=L4ETRgHBPUt8KSkvcO"));
39 |
40 | internal static async Task AddCarApi(
41 | string carType,
42 | string roomid,
43 | string description,
44 | long uin)
45 | => JsonConvert.DeserializeObject(await
46 | GetString($"https://ycm.chinosk6.cn/add_car?car_type={carType}&room_id={roomid}&description={description}&creator_id={uin}&data_from=AndreaBot&token=L4ETRgHBPUt8KSkvcO"));
47 |
48 | internal static async Task BandoriYcmApi()
49 | {
50 | var jsonData = JsonConvert.DeserializeObject(await GetString("https://api.bandoristation.com/?function=query_room_number"));
51 |
52 | return jsonData?.Response.Count == 0
53 | ? "myc"
54 | : jsonData?.Response.Aggregate("",
55 | (current, i) => current +
56 | $"\n\n{((DateTime.UtcNow - DateTime.UnixEpoch).TotalMilliseconds - i.Time) / 1000:##.000}秒前\n来自 {i.SourceInfo.Name} 的车牌:\n{i.RawMessage}");
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Api/UnofficialArcaeaAPI.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Common;
2 | using Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
3 | using Andreal.Core.Message;
4 | using Newtonsoft.Json;
5 | using Path = Andreal.Core.Common.Path;
6 |
7 | namespace Andreal.Core.Data.Api;
8 |
9 | public static class UnofficialArcaeaAPI
10 | {
11 | private static HttpClient? _client;
12 |
13 | public static void Init(AndrealConfig config)
14 | {
15 | _client = new();
16 | if (config.Api.TryGetValue("unlimited", out var apiConfig))
17 | {
18 | _client.BaseAddress = new(apiConfig.Url);
19 | _client.DefaultRequestHeaders.Authorization = new("Bearer", apiConfig.Token);
20 | }
21 | }
22 |
23 | private static async Task GetString(string url)
24 | => JsonConvert.DeserializeObject(await (await _client!.SendAsync(new(HttpMethod.Get, url))).Content.ReadAsStringAsync());
25 |
26 | private static async Task GetImage(string url, Path filename)
27 | {
28 | FileStream? fileStream = null;
29 | var message = await _client!.GetAsync(url);
30 |
31 | if (message.Content.Headers.ContentType?.MediaType?.StartsWith("image/") != true)
32 | throw new ArgumentException(JsonConvert.DeserializeObject(await message.Content.ReadAsStringAsync())!.Message);
33 |
34 | var exflag = false;
35 |
36 | try
37 | {
38 | fileStream = new(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
39 | await message.Content.CopyToAsync(fileStream);
40 | }
41 | catch
42 | {
43 | exflag = true;
44 | throw;
45 | }
46 | finally
47 | {
48 | try
49 | {
50 | if (fileStream is not null)
51 | {
52 | fileStream.Close();
53 | await fileStream.DisposeAsync();
54 | }
55 | }
56 | catch
57 | {
58 | // ignore
59 | }
60 |
61 | try
62 | {
63 | if (exflag && File.Exists(filename)) File.Delete(filename);
64 | }
65 | catch
66 | {
67 | // ignore
68 | }
69 | }
70 | }
71 |
72 | internal static async Task UserInfo(long ucode) => await GetString($"user/info?user_code={ucode:D9}");
73 |
74 | internal static async Task UserInfo(string uname) => await GetString($"user/info?user_name={uname}");
75 |
76 | internal static async Task UserBest(long ucode, string song, object dif)
77 | => await GetString($"user/best?usercode={ucode:D9}&song_id={song}&difficulty={dif}");
78 |
79 | internal static async Task UserBestsSession(long ucode) => await GetString($"user/bests/session?user_code={ucode:D9}");
80 |
81 | internal static async Task UserBestsResult(string session)
82 | => await GetString($"user/bests/result?session_info={session}&overflow=9");
83 |
84 | internal static async Task SongList() => await GetString("song/list");
85 |
86 | internal static async Task SongAssets(string sid, int difficulty, Path pth)
87 | => await GetImage($"assets/song?song_id={sid}&difficulty={difficulty}", pth);
88 |
89 | internal static async Task CharAssets(int partner, bool awakened, Path pth)
90 | => await GetImage($"assets/char?partner={partner}&awakened={(awakened ? "true" : "false")}", pth);
91 |
92 | internal static async Task IconAssets(int partner, bool awakened, Path pth)
93 | => await GetImage($"assets/icon?partner={partner}&awakened={(awakened ? "true" : "false")}", pth);
94 |
95 | internal static async Task PreviewAssets(string sid, int difficulty, Path pth)
96 | => await GetImage($"assets/preview?song_id={sid}&difficulty={difficulty}", pth);
97 |
98 | internal static TextMessage GetErrorMessage(RobotReply info, int status, string message)
99 | => status switch
100 | {
101 | -1 or -2 or -3 or -13 => info.ArcUidNotFound,
102 | -4 => info.TooManyArcUid,
103 | -14 => info.NoBydChart,
104 | -15 => info.NotPlayedTheChart,
105 | -16 => info.GotShadowBanned,
106 | -23 => info.BelowTheThreshold,
107 | -24 => info.NeedUpdateAUA,
108 | -29 => info.InvalidSessionInfo,
109 | -30 => info.SessionExpired,
110 | _ => info.OnAPIQueryFailed(status, message)
111 | };
112 | }
113 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/AccountInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
6 |
7 | public class AccountInfo
8 | {
9 | [JsonProperty("code")]
10 | public int Code { get; set; }
11 |
12 | [JsonProperty("name")]
13 | public string Name { get; set; }
14 |
15 | [JsonProperty("user_id")]
16 | public int UserID { get; set; }
17 |
18 | [JsonProperty("is_mutual")]
19 | public bool IsMutual { get; set; }
20 |
21 | [JsonProperty("is_char_uncapped_override")]
22 | public bool IsCharUncappedOverride { get; set; }
23 |
24 | [JsonProperty("is_char_uncapped")]
25 | public bool IsCharUncapped { get; set; }
26 |
27 | [JsonProperty("is_skill_sealed")]
28 | public bool IsSkillSealed { get; set; }
29 |
30 | [JsonProperty("rating")]
31 | public short Rating { get; set; }
32 |
33 | [JsonProperty("character")]
34 | public int Character { get; set; }
35 | }
36 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/ArcSongdata.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
6 |
7 | public class ArcSongdata
8 | {
9 | [JsonProperty("song_id")]
10 | public string SongID { get; set; }
11 |
12 | [JsonProperty("difficulty")]
13 | public sbyte Difficulty { get; set; }
14 |
15 | [JsonProperty("score")]
16 | public int Score { get; set; }
17 |
18 | [JsonProperty("shiny_perfect_count")]
19 | public string MaxPure { get; set; }
20 |
21 | [JsonProperty("perfect_count")]
22 | public string Pure { get; set; }
23 |
24 | [JsonProperty("near_count")]
25 | public string Far { get; set; }
26 |
27 | [JsonProperty("miss_count")]
28 | public string Lost { get; set; }
29 |
30 | [JsonProperty("time_played")]
31 | public long TimePlayed { get; set; }
32 |
33 | [JsonProperty("clear_type")]
34 | public sbyte ClearType { get; set; }
35 |
36 | [JsonProperty("rating")]
37 | public double Rating { get; set; }
38 | }
39 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/ResponseRoot.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
6 |
7 | public class ResponseRoot
8 | {
9 | [JsonProperty("status")]
10 | public int Status { get; set; }
11 |
12 | [JsonProperty("message")]
13 | public string Message { get; set; }
14 |
15 | [JsonProperty("content")]
16 | public dynamic Content { get; set; }
17 |
18 | internal T DeserializeContent() => JsonConvert.DeserializeObject(Content.ToString());
19 | }
20 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/SessionQueryingContent.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
4 |
5 | #pragma warning disable CS8618
6 | public class SessionQueryingContent
7 | {
8 | [JsonProperty("queried_charts")]
9 | public string QueriedCharts { get; set; } = "0";
10 |
11 | [JsonProperty("current_account")]
12 | public string CurrentAccount { get; set; } = "0";
13 | }
14 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/SongListContent.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Model.Arcaea;
2 | using Newtonsoft.Json;
3 |
4 | #pragma warning disable CS8618
5 |
6 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
7 |
8 | public class SongListContent
9 | {
10 | [JsonProperty("songs")]
11 | public List Songs { get; set; }
12 | }
13 |
14 | public class SongsItem
15 | {
16 | [JsonProperty("song_id")]
17 | public string SongID { get; set; }
18 |
19 | [JsonProperty("difficulties")]
20 | public ArcaeaSong Difficulties { get; set; }
21 |
22 | [JsonProperty("alias")]
23 | public List Alias { get; set; }
24 | }
25 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/UserBestContent.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
6 |
7 | public class UserBestContent
8 | {
9 | [JsonProperty("account_info")]
10 | public AccountInfo AccountInfo { get; set; }
11 |
12 | [JsonProperty("record")]
13 | public ArcSongdata Record { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/UserBestsContent.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
6 |
7 | public class UserBestsContent
8 | {
9 | [JsonProperty("best30_avg")]
10 | public double Best30Avg { get; set; }
11 |
12 | [JsonProperty("recent10_avg")]
13 | public double Recent10Avg { get; set; }
14 |
15 | [JsonProperty("account_info")]
16 | public AccountInfo AccountInfo { get; set; }
17 |
18 | [JsonProperty("best30_list")]
19 | public List Best30List { get; set; }
20 |
21 | [JsonProperty("best30_overflow")]
22 | public List? OverflowList { get; set; }
23 | }
24 |
25 | public class UserBestsSessionContent
26 | {
27 | [JsonProperty("session_info")]
28 | public string SessionInfo { get; set; }
29 | }
30 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/ArcaeaUnlimited/UserInfoContent.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
6 |
7 | public class UserInfoContent
8 | {
9 | [JsonProperty("account_info")]
10 | public AccountInfo AccountInfo { get; set; }
11 |
12 | [JsonProperty("recent_score")]
13 | public List RecentScore { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/PartnerPosInfoBase/PartnerPosInfoBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using Andreal.Core.Common;
3 | using Andreal.Core.Data.Sqlite;
4 |
5 | namespace Andreal.Core.Data.Json.Arcaea.PartnerPosInfoBase;
6 |
7 | internal static class PartnerPosInfoBase
8 | {
9 | private static readonly Lazy>> Dict = new(() => new(Init()));
10 |
11 | private static readonly PosInfoItem ImgV1 = new() { PositionX = 770, PositionY = 58, Size = 950 };
12 | private static readonly PosInfoItem ImgV2 = new() { PositionX = 850, PositionY = 0, Size = 1400 };
13 | private static readonly PosInfoItem ImgV4 = new() { PositionX = 550, PositionY = 50, Size = 1500 };
14 |
15 | private static Dictionary> Init()
16 | {
17 | var ls = new Dictionary>();
18 | foreach (var (key, value) in GlobalConfig.Locations) ls.Add(key, value.ToDictionary(i => i.Partner));
19 | return ls;
20 | }
21 |
22 | internal static PosInfoItem? Get(string partner, ImgVersion imgVersion)
23 | => imgVersion switch
24 | {
25 | ImgVersion.ImgV1 => Dict.Value["1"].TryGetValue(partner, out var result) ? result : ImgV1,
26 | ImgVersion.ImgV2 => Dict.Value["2"].TryGetValue(partner, out var result) ? result : ImgV2,
27 | ImgVersion.ImgV4 => Dict.Value["4"].TryGetValue(partner, out var result) ? result : ImgV4,
28 | _ => null
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Arcaea/PartnerPosInfoBase/PosInfoItem.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Data.Json.Arcaea.PartnerPosInfoBase;
2 |
3 | #pragma warning disable CS8618
4 | // ReSharper disable MemberCanBeInternal
5 | public class PosInfoItem
6 | {
7 | public string Partner { get; set; }
8 | public int PositionX { get; set; }
9 | public int PositionY { get; set; }
10 | public int Size { get; set; }
11 | }
12 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Bandori/BandoriRoomInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Bandori;
6 |
7 | [Serializable]
8 | public class BandoriRoomInfo
9 | {
10 | [JsonProperty("response")]
11 | public List Response { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Bandori/ResponseItem.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Bandori;
6 |
7 | [Serializable]
8 | public class ResponseItem
9 | {
10 | [JsonProperty("raw_message")]
11 | public string RawMessage { get; set; }
12 |
13 | [JsonProperty("source_info")]
14 | public SourceInfo SourceInfo { get; set; }
15 |
16 | [JsonProperty("time")]
17 | public long Time { get; set; }
18 | }
19 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Bandori/SourceInfo.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Bandori;
6 |
7 | [Serializable]
8 | public class SourceInfo
9 | {
10 | [JsonProperty("name")]
11 | public string Name { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Hitokoto/Hitokoto.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Hitokoto;
6 |
7 | [Serializable]
8 | public class Hitokoto
9 | {
10 | [JsonProperty("hitokoto")]
11 | public string Content { get; set; }
12 |
13 | [JsonProperty("from")]
14 | public string From { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Ycm/CarsItem.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Ycm;
6 |
7 | [Serializable]
8 | public class CarsItem
9 | {
10 | [JsonProperty("add_time")]
11 | public long AddTime { get; set; }
12 |
13 | [JsonProperty("creator_id")]
14 | public string CreatorID { get; set; }
15 |
16 | [JsonProperty("data_from")]
17 | public string DataFrom { get; set; }
18 |
19 | [JsonProperty("description")]
20 | public string Description { get; set; }
21 |
22 | [JsonProperty("id")]
23 | public long ID { get; set; }
24 |
25 | [JsonProperty("more_info")]
26 | public string MoreInfo { get; set; }
27 |
28 | [JsonProperty("room_id")]
29 | public string RoomID { get; set; }
30 | }
31 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Json/Ycm/YcmResponse.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 |
3 | #pragma warning disable CS8618
4 |
5 | namespace Andreal.Core.Data.Json.Ycm;
6 |
7 | [Serializable]
8 | public class YcmResponse
9 | {
10 | [JsonProperty("car_type")]
11 | public string CarType { get; set; }
12 |
13 | [JsonProperty("code")]
14 | public int Code { get; set; }
15 |
16 | [JsonProperty("message")]
17 | public string Message { get; set; }
18 |
19 | [JsonProperty("cars")]
20 | public List Cars { get; set; }
21 | }
22 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Sqlite/BotUserInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using Andreal.Core.Utils;
3 | using SQLite;
4 |
5 | namespace Andreal.Core.Data.Sqlite;
6 |
7 | [Serializable]
8 | [Table("BotUserInfo")]
9 | internal class BotUserInfo
10 | {
11 | private static Lazy> _list
12 | = new(() => new(SqliteHelper.SelectAll().ToDictionary(i => i.Uin)));
13 |
14 | [PrimaryKey] [Column("QQId")]
15 | public long Uin { get; set; }
16 |
17 | [Column("ArcId")]
18 | public int ArcCode { get; set; }
19 |
20 | [Column("IsHide")]
21 | public int IsHide { get; set; }
22 |
23 | [Column("IsText")]
24 | public int IsText { get; set; }
25 |
26 | [Column("ImgVer")]
27 | public ImgVersion UiVersion { get; set; }
28 |
29 | internal static void Set(BotUserInfo user)
30 | {
31 | if (_list.Value.ContainsKey(user.Uin))
32 | {
33 | _list.Value[user.Uin] = user;
34 | SqliteHelper.Update(user);
35 | }
36 | else
37 | {
38 | _list.Value.TryAdd(user.Uin, user);
39 | SqliteHelper.Insert(user);
40 | }
41 | }
42 |
43 | internal static BotUserInfo? Get(long uin) => _list.Value.TryGetValue(uin, out var user) ? user : null;
44 | }
45 |
--------------------------------------------------------------------------------
/Andreal.Core/Data/Sqlite/ImgVersion.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Data.Sqlite;
2 |
3 | internal enum ImgVersion
4 | {
5 | ImgV1 = 0,
6 | ImgV2,
7 | ImgV3,
8 | ImgV4
9 | }
10 |
--------------------------------------------------------------------------------
/Andreal.Core/Executor/ExecutorBase.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Common;
2 | using Andreal.Core.Data.Sqlite;
3 | using Konata.Core;
4 |
5 | namespace Andreal.Core.Executor;
6 |
7 | [Serializable]
8 | internal abstract class ExecutorBase
9 | {
10 | protected readonly MessageInfo Info;
11 |
12 | protected ExecutorBase(MessageInfo info)
13 | {
14 | Info = info;
15 | }
16 |
17 | protected Bot Bot => Info.Bot;
18 | protected bool IsGroup => Info.MessageType == MessageInfoType.Group;
19 | protected string[] Command => Info.CommandWithoutPrefix;
20 | protected int CommandLength => Info.CommandWithoutPrefix.Length;
21 | protected BotUserInfo? User => Info.UserInfo.Value;
22 | protected static RobotReply RobotReply => GlobalConfig.RobotReply;
23 | }
24 |
--------------------------------------------------------------------------------
/Andreal.Core/Executor/OtherExecutor.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Common;
2 | using Andreal.Core.Data.Api;
3 | using Andreal.Core.Data.Sqlite;
4 | using Andreal.Core.Message;
5 | using Andreal.Core.Utils;
6 | using Konata.Core.Interfaces.Api;
7 | using Konata.Core.Message.Model;
8 |
9 | namespace Andreal.Core.Executor;
10 |
11 | #pragma warning disable CS8600
12 | #pragma warning disable CS8602
13 | #pragma warning disable CS8604
14 |
15 | [Serializable]
16 | internal class OtherExecutor : ExecutorBase
17 | {
18 | public OtherExecutor(MessageInfo info) : base(info) { }
19 |
20 | [CommandPrefix("/help", "/arc help")]
21 | private MessageChain HelpMessage() => CommandLength == 0 ? RobotReply.HelpMessage : null;
22 |
23 | [CommandPrefix("/yiyan")]
24 | private static async Task Hitokoto() => await OtherApi.HitokotoApi();
25 |
26 | [CommandPrefix("/jrrp")]
27 | private async Task Jrrp() => RobotReply.OnJrrpResult(await OtherApi.JrrpApi(Info.FromQQ));
28 |
29 | [CommandPrefix("/dismiss")]
30 | private async Task Dismiss()
31 | {
32 | if (Info.Message.Chain.FindChain()?.Any(i => i.AtUin == Info.Bot.Uin) != true) return null;
33 | if (!IsGroup) return null;
34 | if (!Info.MasterCheck() && !await Info.PermissionCheck()) return null;
35 |
36 | await Info.SendMessage(RobotReply.GroupLeave);
37 | await Task.Delay(5000);
38 | await Bot.GroupLeave(Info.FromGroup);
39 |
40 | return null;
41 | }
42 |
43 | [CommandPrefix("/geterr")]
44 | private static MessageChain ExceptionReport() => LastExceptionHelper.GetDetails();
45 |
46 | [CommandPrefix("/config")]
47 | private MessageChain Config()
48 | {
49 | if (CommandLength != 1) return RobotReply.ParameterLengthError;
50 | if (User == null) return RobotReply.NotBind;
51 |
52 | switch (Command[0])
53 | {
54 | case "hide":
55 | User.IsHide ^= 1;
56 | BotUserInfo.Set(User);
57 | return $"私密模式已{(User.IsHide == 1 ? "开启" : "关闭")}。";
58 |
59 | case "text":
60 | case "txt":
61 | User.IsText = 1;
62 | BotUserInfo.Set(User);
63 | return "默认显示方式已更改为文字。";
64 |
65 | case "pic":
66 | case "img":
67 | User.IsText = 0;
68 | BotUserInfo.Set(User);
69 | return "默认显示方式已更改为图片。";
70 |
71 | default:
72 | return RobotReply.ConfigNotFound;
73 | }
74 | }
75 |
76 | [CommandPrefix("/ycm")]
77 | private static async Task GetBandoriRoom() => await OtherApi.BandoriYcmApi();
78 |
79 | [CommandPrefix("/state")]
80 | private static MessageChain Statement() => BotStatementHelper.Statement;
81 | }
82 |
--------------------------------------------------------------------------------
/Andreal.Core/Executor/PermissionExecutor.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Common;
2 | using Andreal.Core.Message;
3 | using Andreal.Core.Model.Arcaea;
4 | using Konata.Core.Interfaces.Api;
5 |
6 | namespace Andreal.Core.Executor;
7 |
8 | [Serializable]
9 | internal class PermissionExecutor : ExecutorBase
10 | {
11 | public PermissionExecutor(MessageInfo info) : base(info) { }
12 |
13 | [CommandPrefix("/echo")]
14 | private async Task Echo()
15 | {
16 | if (Info.MasterCheck()) await Info.SendMessageOnly(string.Join(" ", Command));
17 | return null;
18 | }
19 |
20 | [CommandPrefix("/remove")]
21 | private async Task RemoveGroup()
22 | {
23 | if (!Info.MasterCheck()) return null;
24 | if (CommandLength != 1) return RobotReply.ParameterLengthError;
25 | await Info.Bot.GroupLeave(uint.Parse(Command[0]));
26 | return "Removed.";
27 | }
28 |
29 | [CommandPrefix("/update")]
30 | private MessageChain? Update()
31 | {
32 | if (!Info.MasterCheck()) return null;
33 | ArcaeaCharts.Init();
34 | return "Arcaea songlist updated";
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Andreal.Core/Message/IMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Message;
2 |
3 | internal interface IMessage { }
4 |
--------------------------------------------------------------------------------
/Andreal.Core/Message/ImageMessage.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.UI;
2 | using Path = Andreal.Core.Common.Path;
3 |
4 | namespace Andreal.Core.Message;
5 |
6 | [Serializable]
7 | public class ImageMessage : IMessage
8 | {
9 | private readonly string _path;
10 |
11 | private ImageMessage(string path)
12 | {
13 | _path = path;
14 | }
15 |
16 | public override string ToString() => _path;
17 |
18 | internal static ImageMessage FromPath(Path path) => new(path);
19 |
20 | public static implicit operator ImageMessage(Image value)
21 | {
22 | var pth = Path.RandImageFileName();
23 | value.SaveAsJpgWithQuality(pth);
24 | value.Dispose();
25 | return FromPath(pth);
26 | }
27 |
28 | public static implicit operator ImageMessage(BackGround value)
29 | {
30 | var pth = Path.RandImageFileName();
31 | value.SaveAsJpgWithQuality(pth);
32 | value.Dispose();
33 | return FromPath(pth);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Andreal.Core/Message/MessageChain.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Message;
2 |
3 | public class MessageChain
4 | {
5 | private IEnumerable _messages;
6 |
7 | internal MessageChain()
8 | {
9 | _messages = Array.Empty();
10 | }
11 |
12 | internal MessageChain(params IMessage[] messages)
13 | {
14 | _messages = messages.ToList();
15 | }
16 |
17 | internal void Append(IMessage? message)
18 | {
19 | if (message is not null) _messages = _messages.Append(message);
20 | }
21 |
22 | internal void Append(string? message)
23 | {
24 | if (message is not null) _messages = _messages.Append((TextMessage)message);
25 | }
26 |
27 | internal MessageChain Prepend(IMessage message)
28 | {
29 | _messages = _messages.Prepend(message);
30 | return this;
31 | }
32 |
33 | internal IEnumerable ToArray() => _messages;
34 |
35 | public static implicit operator MessageChain(string value) => new((TextMessage)value);
36 |
37 | public static implicit operator MessageChain(TextMessage value) => new(value);
38 |
39 | public static implicit operator MessageChain(ImageMessage value) => new(value);
40 | }
41 |
--------------------------------------------------------------------------------
/Andreal.Core/Message/ReplyMessage.cs:
--------------------------------------------------------------------------------
1 | using Konata.Core.Message;
2 |
3 | namespace Andreal.Core.Message;
4 |
5 | [Serializable]
6 | public class ReplyMessage : IMessage
7 | {
8 | public readonly MessageStruct Message;
9 |
10 | internal ReplyMessage(MessageStruct message)
11 | {
12 | Message = message;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Andreal.Core/Message/TextMessage.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Message;
2 |
3 | [Serializable]
4 | public class TextMessage : IMessage
5 | {
6 | private readonly string _message;
7 |
8 | private TextMessage(string message)
9 | {
10 | _message = message;
11 | }
12 |
13 | public override string ToString() => _message;
14 |
15 | public static implicit operator string(TextMessage value) => value._message;
16 |
17 | public static implicit operator TextMessage(string value) => new(value);
18 | }
19 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/ArcaeaChart.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using Andreal.Core.UI;
3 | using Newtonsoft.Json;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | #pragma warning disable CS8618
7 |
8 | namespace Andreal.Core.Model.Arcaea;
9 |
10 | public class ArcaeaChart
11 | {
12 | private static readonly ConcurrentDictionary SongImage = new();
13 |
14 | [JsonProperty("name_en")]
15 | public string NameEn { get; set; }
16 |
17 | [JsonProperty("name_jp")]
18 | public string NameJp { get; set; }
19 |
20 | [JsonProperty("artist")]
21 | public string Artist { get; set; }
22 |
23 | [JsonProperty("bpm")]
24 | public string Bpm { get; set; }
25 |
26 | [JsonProperty("bpm_base")]
27 | public double BpmBase { get; set; }
28 |
29 | [JsonProperty("set")]
30 | public string Set { get; set; }
31 |
32 | [JsonProperty("set_friendly")]
33 | public string SetFriendly { get; set; }
34 |
35 | [JsonProperty("time")]
36 | public int Time { get; set; }
37 |
38 | [JsonProperty("side")]
39 | public int Side { get; set; }
40 |
41 | [JsonProperty("world_unlock")]
42 | public bool WorldUnlock { get; set; }
43 |
44 | [JsonProperty("remote_download")]
45 | public bool RemoteDownload { get; set; }
46 |
47 | [JsonProperty("bg")]
48 | public string Bg { get; set; }
49 |
50 | [JsonProperty("date")]
51 | public int Date { get; set; }
52 |
53 | [JsonProperty("version")]
54 | public string Version { get; set; }
55 |
56 | [JsonProperty("difficulty")]
57 | public int Difficulty { get; set; }
58 |
59 | [JsonProperty("rating")]
60 | public int Rating { get; set; }
61 |
62 | [JsonProperty("note")]
63 | public int Note { get; set; }
64 |
65 | [JsonProperty("chart_designer")]
66 | public string ChartDesigner { get; set; }
67 |
68 | [JsonProperty("jacket_designer")]
69 | public string JacketDesigner { get; set; }
70 |
71 | [JsonProperty("jacket_override")]
72 | public bool JacketOverride { get; set; }
73 |
74 | [JsonProperty("audio_override")]
75 | public bool AudioOverride { get; set; }
76 |
77 | internal string SongID { get; set; }
78 |
79 | internal double Const => (double)Rating / 10;
80 |
81 | internal int RatingClass { get; set; }
82 |
83 | internal DifficultyInfo DifficultyInfo => DifficultyInfo.GetByIndex(RatingClass);
84 |
85 | internal string ConstString => $"[{DifficultyInfo.ShortStr} {Const:0.0}]";
86 |
87 | internal string NameWithPackageAndConst => $"{NameEn} (Package: {SetFriendly}) {ConstString}";
88 |
89 | internal string GetSongName(byte length) => NameEn.Length < length + 3 ? NameEn : $"{NameEn[..length]}...";
90 |
91 | internal async Task GetSongImage()
92 | {
93 | var path = await Path.ArcaeaSong(this);
94 |
95 | try
96 | {
97 | if (!SongImage.TryGetValue(path, out var stream))
98 | {
99 | await using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
100 |
101 | using var reader = new BinaryReader(fileStream);
102 | var bytes = reader.ReadBytes((int)fileStream.Length);
103 | stream = new MemoryStream(bytes);
104 | reader.Close();
105 | fileStream.Close();
106 |
107 | SongImage.TryAdd(path, stream);
108 | }
109 |
110 | var img = new Image(stream);
111 | if (img.Width == 512) return img;
112 | var newimg = new Image(img, 512, 512);
113 | newimg.SaveAsPng(path);
114 | img.Dispose();
115 | return newimg;
116 | }
117 | catch (Exception e)
118 | {
119 | SongImage.TryRemove(path, out var s);
120 | s?.DisposeAsync();
121 | throw new ArgumentException("GetSongImage Failed.", NameEn, e);
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/ArcaeaCharts.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using Andreal.Core.Data.Api;
3 | using Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
4 | using Andreal.Core.Utils;
5 | using Newtonsoft.Json;
6 | using Path = Andreal.Core.Common.Path;
7 |
8 | namespace Andreal.Core.Model.Arcaea;
9 |
10 | public static partial class ArcaeaCharts
11 | {
12 | private static readonly ConcurrentDictionary Songs = new();
13 | private static readonly ConcurrentDictionary> Aliases = new();
14 |
15 | internal static ArcaeaSong? QueryByID(string? songid) => GetByID(songid);
16 |
17 | internal static List? Query(string alias)
18 | {
19 | if (string.IsNullOrWhiteSpace(alias)) return default;
20 |
21 | if (AliasCache.ContainsKey(alias)) return AliasCache[alias];
22 |
23 | var data = GetByID(alias) ?? GetByName(Songs, alias) ?? GetByAlias(alias);
24 |
25 | if (data != null) return new() { data };
26 |
27 | var abbrdata = new List();
28 |
29 | Abbreviations.ForAllItems>((song, value) =>
30 | {
31 | if (StringHelper.Equals(value, alias) && !abbrdata.Contains(song)) abbrdata.Add(song);
32 | });
33 |
34 | if (abbrdata.Count > 0) return abbrdata;
35 |
36 | return GetByPriorityQueue(alias);
37 | }
38 |
39 | internal static List GetSongAlias(string songid) => Aliases[songid];
40 |
41 | internal static ArcaeaSong RandomSong() => Songs.Values.GetRandomItem();
42 |
43 | internal static ArcaeaChart? RandomSong(double start, double end)
44 | {
45 | ArcaeaChart[] ls = GetByConstRange(start, end).ToArray();
46 | if (ls.Length == 0) return null;
47 | return ls.GetRandomItem();
48 | }
49 | }
50 |
51 | public static partial class ArcaeaCharts
52 | {
53 | [NonSerialized]
54 | private static readonly ConcurrentDictionary> Abbreviations = new();
55 |
56 | [NonSerialized]
57 | private static readonly ConcurrentDictionary> Names = new();
58 |
59 | [NonSerialized]
60 | private static readonly ConcurrentDictionary> AliasCache = new();
61 |
62 | static ArcaeaCharts()
63 | {
64 | Init();
65 | }
66 |
67 | public static void Init()
68 | {
69 | Songs.Clear();
70 | Aliases.Clear();
71 | Abbreviations.Clear();
72 | Names.Clear();
73 |
74 | List? slst;
75 |
76 | try
77 | {
78 | slst = UnofficialArcaeaAPI.SongList().Result?.DeserializeContent().Songs;
79 | if (slst != null) File.WriteAllText(Path.TmpSongList, JsonConvert.SerializeObject(slst));
80 | }
81 | catch
82 | {
83 | if (!File.Exists(Path.TmpSongList)) throw;
84 | slst = JsonConvert.DeserializeObject>(File.ReadAllText(Path.TmpSongList));
85 | }
86 |
87 | if (slst == null) return;
88 |
89 | foreach (var songitem in slst)
90 | {
91 | songitem.Difficulties.SongID = songitem.SongID;
92 |
93 | for (var i = 0; i < songitem.Difficulties.Count; ++i)
94 | {
95 | songitem.Difficulties[i].RatingClass = i;
96 | songitem.Difficulties[i].SongID = songitem.SongID;
97 | }
98 |
99 | Aliases.TryAdd(songitem.SongID, songitem.Alias);
100 | Songs.TryAdd(songitem.SongID, songitem.Difficulties);
101 | }
102 |
103 | foreach (var (_, value) in Songs)
104 | {
105 | var abbrs = new List();
106 | var names = new List();
107 |
108 | for (var index = 0; index < value.Count; index++)
109 | {
110 | if (index == 0 || value[index].AudioOverride)
111 | {
112 | abbrs.Add(value[index].NameEn.GetAbbreviation());
113 | names.Add(value[index].NameEn);
114 |
115 | if (string.IsNullOrWhiteSpace(value[index].NameJp)) continue;
116 |
117 | abbrs.Add(value[index].NameJp.GetAbbreviation());
118 | names.Add(value[index].NameJp);
119 | }
120 | }
121 |
122 | Abbreviations.TryAdd(value, abbrs);
123 | Names.TryAdd(value, names);
124 | }
125 | }
126 | }
127 |
128 | public static partial class ArcaeaCharts
129 | {
130 | private static ArcaeaSong? GetByID(string? songid) => songid is not null && Songs.TryGetValue(songid, out var value) ? value : null;
131 |
132 | private static ArcaeaSong? GetByName(ConcurrentDictionary values, string alias)
133 | {
134 | values.TryTakeValues((ArcaeaChart item) => StringHelper.Equals(item.NameEn, alias) || StringHelper.Equals(item.NameJp, alias),
135 | out var result);
136 |
137 | return result;
138 | }
139 |
140 | private static ArcaeaSong? GetByAlias(string alias)
141 | {
142 | Aliases.TryTakeKey>(value => StringHelper.Equals(value, alias), out var result);
143 | return GetByID(result);
144 | }
145 |
146 | private static List? GetByPriorityQueue(string alias)
147 | {
148 | var dic = new PriorityQueue();
149 |
150 | Aliases.ForAllItems>((song, sid) => Enqueue(dic, alias, sid, GetByID(song)!, 1, 4));
151 |
152 | dic.TryPeek(out _, out var firstpriority);
153 |
154 | if (firstpriority != 1)
155 | foreach (var (sid, song) in Songs)
156 | Enqueue(dic, alias, sid, song, 2, 5);
157 |
158 | dic.TryPeek(out _, out firstpriority);
159 |
160 | if (firstpriority != 2) Names.ForAllItems>((song, name) => Enqueue(dic, alias, name, song, 3, 6));
161 |
162 | if (dic.Count == 0) return default;
163 |
164 | dic.TryDequeue(out var firstobj, out var lowestpriority);
165 |
166 | var ls = new List { firstobj! };
167 |
168 | while (dic.TryDequeue(out var obj, out var priority) && priority == lowestpriority)
169 | if (!ls.Contains(obj))
170 | ls.Add(obj);
171 |
172 | AliasCache.TryAdd(alias, ls);
173 | return ls;
174 | }
175 |
176 | private static void Enqueue(
177 | PriorityQueue dic,
178 | string alias,
179 | string key,
180 | ArcaeaSong song,
181 | byte upperpriority,
182 | byte lowerpriority)
183 | {
184 | if (StringHelper.Contains(key, alias)) dic.Enqueue(song, upperpriority);
185 | if (StringHelper.Contains(alias, key)) dic.Enqueue(song, lowerpriority);
186 | }
187 |
188 | internal static IEnumerable GetByConst(double @const)
189 | {
190 | const double lerance = 0.001;
191 |
192 | // ReSharper disable once LoopCanBePartlyConvertedToQuery
193 | foreach (var song in Songs.Values)
194 | // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
195 | {
196 | foreach (var chart in song)
197 | if (Math.Abs(chart.Const - @const) < lerance)
198 | yield return chart;
199 | }
200 | }
201 |
202 | private static IEnumerable GetByConstRange(double lowerlimit, double upperlimit)
203 | {
204 | // ReSharper disable once LoopCanBeConvertedToQuery
205 | foreach (var song in Songs.Values)
206 | // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
207 | {
208 | foreach (var chart in song)
209 | if (chart.Const >= lowerlimit && chart.Const <= upperlimit)
210 | yield return chart;
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/ArcaeaSong.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 | using Path = Andreal.Core.Common.Path;
3 |
4 | namespace Andreal.Core.Model.Arcaea;
5 |
6 | public class ArcaeaSong : List, IEquatable
7 | {
8 | internal string SongID { get; set; } = null!;
9 |
10 | internal string NameWithPackage => $"{this[0].NameEn}\n(Package: {this[0].SetFriendly})";
11 |
12 | public bool Equals(ArcaeaSong? other)
13 | {
14 | if (ReferenceEquals(null, other)) return false;
15 | if (ReferenceEquals(this, other)) return true;
16 | return SongID.Equals(other.SongID);
17 | }
18 |
19 | internal async Task FullConstString()
20 | {
21 | var msg = new MessageChain();
22 |
23 | for (var i = 0; i < Count; i++)
24 | if (i == 2 || this[i].JacketOverride)
25 | msg.Append(ImageMessage.FromPath(await Path.ArcaeaSong(this[i])));
26 |
27 | msg.Append(NameWithPackage);
28 |
29 | for (var i = 0; i < Count; i++)
30 | {
31 | msg.Append("\n" + this[i].ConstString);
32 | if (this[i].AudioOverride) msg.Append($" ({this[i].NameEn})");
33 | }
34 |
35 | return msg;
36 | }
37 |
38 | public override bool Equals(object? obj)
39 | {
40 | if (ReferenceEquals(null, obj)) return false;
41 | if (ReferenceEquals(this, obj)) return true;
42 | if (obj.GetType() != GetType()) return false;
43 | return Equals((ArcaeaSong)obj);
44 | }
45 |
46 | // ReSharper disable once NonReadonlyMemberInGetHashCode
47 | public override int GetHashCode() => SongID.GetHashCode();
48 | }
49 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/Best30Data.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
2 |
3 | namespace Andreal.Core.Model.Arcaea;
4 |
5 | internal class Best30Data : IBest30Data
6 | {
7 | internal Best30Data(UserBestsContent b30data)
8 | {
9 | B30data = b30data;
10 | }
11 |
12 | private UserBestsContent B30data { get; }
13 |
14 | private string Best30Avg => B30data.Best30Avg.ToString("0.0000");
15 |
16 | private string Recent10Avg => B30data.Recent10Avg > 0 ? B30data.Recent10Avg.ToString("0.0000") : "--";
17 |
18 | private List Best30List => B30data.Best30List.Select(i => new RecordInfo(i)).ToList();
19 |
20 | string IBest30Data.Best30Avg => Best30Avg;
21 |
22 | string IBest30Data.Recent10Avg => Recent10Avg;
23 |
24 | List IBest30Data.Best30List => Best30List;
25 | }
26 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/Best40Data.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
2 |
3 | namespace Andreal.Core.Model.Arcaea;
4 |
5 | internal class Best40Data : IBest40Data
6 | {
7 | internal Best40Data(UserBestsContent b40data)
8 | {
9 | B40data = b40data;
10 | }
11 |
12 | private UserBestsContent B40data { get; }
13 |
14 | private string Best30Avg => B40data.Best30Avg.ToString("0.0000");
15 |
16 | private string Recent10Avg => B40data.Recent10Avg > 0 ? B40data.Recent10Avg.ToString("0.0000") : "--";
17 |
18 | private List Best30List => B40data.Best30List.Select(i => new RecordInfo(i)).ToList();
19 |
20 | private List? OverflowList => B40data.OverflowList?.Select(i => new RecordInfo(i)).ToList();
21 |
22 | string IBest40Data.Best30Avg => Best30Avg;
23 |
24 | string IBest40Data.Recent10Avg => Recent10Avg;
25 |
26 | List IBest40Data.Best30List => Best30List;
27 |
28 | List? IBest40Data.OverflowList => OverflowList;
29 | }
30 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/DifficultyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Drawing;
3 |
4 | #pragma warning disable CS8618
5 |
6 | namespace Andreal.Core.Model.Arcaea;
7 |
8 | [Serializable]
9 | internal class DifficultyInfo
10 | {
11 | private static readonly ConcurrentDictionary List = new();
12 |
13 | static DifficultyInfo()
14 | {
15 | List.TryAdd(3, new() { LongStr = "Beyond", ShortStr = "BYD", Alias = new[] { "byn", "byd", "beyond" }, Color = Color.FromArgb(165, 20, 49) });
16 |
17 | List.TryAdd(2, new() { LongStr = "Future", ShortStr = "FTR", Alias = new[] { "ftr", "future" }, Color = Color.FromArgb(115, 35, 100) });
18 |
19 | List.TryAdd(1, new() { LongStr = "Present", ShortStr = "PRS", Alias = new[] { "prs", "present" }, Color = Color.FromArgb(120, 155, 80) });
20 |
21 | List.TryAdd(0, new() { LongStr = "Past", ShortStr = "PST", Alias = new[] { "pst", "past" }, Color = Color.FromArgb(20, 165, 215) });
22 | }
23 |
24 | private string[] Alias { get; set; }
25 | internal string LongStr { get; private set; }
26 | internal string ShortStr { get; private set; }
27 | internal Color Color { get; private set; }
28 |
29 | internal static DifficultyInfo GetByIndex(int index) => List[index];
30 |
31 | internal static (string, int) DifficultyConverter(string dif)
32 | {
33 | foreach (var (key, value) in List)
34 | {
35 | foreach (var alias in value.Alias.Where(dif.EndsWith)) return (dif[..^alias.Length], key);
36 | }
37 |
38 | return (dif, -1);
39 | }
40 |
41 | public static implicit operator string(DifficultyInfo info) => info.ShortStr;
42 | }
43 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/IBest30Data.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Model.Arcaea;
2 |
3 | internal interface IBest30Data
4 | {
5 | internal string Best30Avg { get; }
6 | internal string Recent10Avg { get; }
7 | internal List Best30List { get; }
8 | }
9 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/IBest40Data.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Model.Arcaea;
2 |
3 | internal interface IBest40Data
4 | {
5 | internal string Best30Avg { get; }
6 | internal string Recent10Avg { get; }
7 | internal List Best30List { get; }
8 | internal List? OverflowList { get; }
9 | }
10 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/PlayerInfo.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
2 | using Andreal.Core.Data.Sqlite;
3 |
4 | namespace Andreal.Core.Model.Arcaea;
5 |
6 | [Serializable]
7 | internal class PlayerInfo
8 | {
9 | private readonly bool _isHide;
10 |
11 | private readonly string _playerCode, _playerName;
12 |
13 | public PlayerInfo(AccountInfo accountInfo, BotUserInfo user)
14 | {
15 | _playerCode = accountInfo.Code.ToString("D9");
16 | _playerName = accountInfo.Name;
17 | _isHide = user.IsHide == 0;
18 | ImgVersion = user.UiVersion;
19 | Partner = accountInfo.Character;
20 | IsAwakened = accountInfo.IsCharUncapped && !accountInfo.IsCharUncappedOverride;
21 | Potential = accountInfo.Rating;
22 | }
23 |
24 | internal string PlayerName => _isHide ? _playerName : $"{_playerName[0]}......{_playerName[^1]}";
25 |
26 | internal string PlayerCode => _isHide ? _playerCode : "xxxxxxxxx";
27 |
28 | internal ImgVersion ImgVersion { get; }
29 |
30 | internal int Partner { get; }
31 |
32 | internal bool IsAwakened { get; }
33 |
34 | internal short Potential { get; }
35 | }
36 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/RecordData.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Data.Sqlite;
2 | using Andreal.Core.Message;
3 | using Andreal.Core.UI.ImageGenerator;
4 |
5 | namespace Andreal.Core.Model.Arcaea;
6 |
7 | [Serializable]
8 | internal class RecordData
9 | {
10 | internal RecordData(PlayerInfo playerInfo, RecordInfo recordInfo, BotUserInfo userInfo)
11 | {
12 | PlayerInfo = playerInfo;
13 | RecordInfo = recordInfo;
14 | UserInfo = userInfo;
15 | }
16 |
17 | private PlayerInfo PlayerInfo { get; }
18 | private RecordInfo RecordInfo { get; }
19 | private BotUserInfo UserInfo { get; }
20 |
21 | private TextMessage RecordTextResult
22 | => $"{PlayerInfo.PlayerName}({PlayerInfo.PlayerCode}) 的最近记录\n曲名:{RecordInfo.SongName(50)} {RecordInfo.SongInfo.ConstString}\n分数:{RecordInfo.Score}\nPure:{RecordInfo.Pure} (+{RecordInfo.MaxPure}) Far:{RecordInfo.Far} Lost:{RecordInfo.Lost}\n单曲PTT:{RecordInfo.Rating}\n时间:{RecordInfo.TimeStr}";
23 |
24 | internal async Task GetResult()
25 | {
26 | if (UserInfo.IsText == 1) return RecordTextResult;
27 | var imageGenerator = new ArcRecordImageGenerator(PlayerInfo, RecordInfo);
28 |
29 | switch (UserInfo.UiVersion)
30 | {
31 | case ImgVersion.ImgV1:
32 | return await imageGenerator.Version1();
33 |
34 | case ImgVersion.ImgV3:
35 | return await imageGenerator.Version3();
36 |
37 | case ImgVersion.ImgV2:
38 | default:
39 | return await imageGenerator.Version2();
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/RecordInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 | using Andreal.Core.Common;
3 | using Andreal.Core.Data.Json.Arcaea.ArcaeaUnlimited;
4 | using Andreal.Core.UI;
5 |
6 | namespace Andreal.Core.Model.Arcaea;
7 |
8 | [Serializable]
9 | internal class RecordInfo
10 | {
11 | private readonly int _score;
12 |
13 | public RecordInfo(ArcSongdata recentdata)
14 | {
15 | Difficulty = recentdata.Difficulty;
16 | SongInfo = ArcaeaCharts.QueryByID(recentdata.SongID)?[Difficulty] ?? throw new ArgumentException(MessageInfo.RobotReply.NoSongFound);
17 | Cleartype = recentdata.ClearType;
18 | SongID = recentdata.SongID;
19 | Rating = recentdata.Rating.ToString("0.0000");
20 | Pure = recentdata.Pure;
21 | MaxPure = recentdata.MaxPure;
22 | Far = recentdata.Far;
23 | Lost = recentdata.Lost;
24 | Time = DateTime.UnixEpoch.AddMilliseconds(recentdata.TimePlayed).ToLocalTime();
25 | _score = recentdata.Score;
26 | }
27 |
28 | internal ArcaeaChart SongInfo { get; }
29 |
30 | internal sbyte Cleartype { get; }
31 |
32 | internal int Difficulty { get; }
33 |
34 | internal bool IsRecent => Difficulty == -128;
35 |
36 | internal string SongID { get; }
37 |
38 | internal string Rating { get; }
39 |
40 | internal string Pure { get; }
41 |
42 | internal string MaxPure { get; }
43 |
44 | internal string Far { get; }
45 |
46 | internal string Lost { get; }
47 |
48 | internal DateTime Time { get; }
49 |
50 | internal string TimeStr => Time.ToString("yyyy/MM/dd HH:mm:ss");
51 |
52 | internal DifficultyInfo DifficultyInfo => SongInfo.DifficultyInfo;
53 |
54 | internal double Const => SongInfo.Const;
55 |
56 | internal string Score
57 | {
58 | get
59 | {
60 | var scorestr = _score.ToString();
61 | var result = new StringBuilder();
62 | var len = scorestr.Length;
63 | for (var i = 0; i < 8; i++)
64 | {
65 | var j = len - 8 + i;
66 | result.Append(j < 0 ? '0' : scorestr[j]);
67 | if (i is 1 or 4) result.Append('\'');
68 | }
69 |
70 | return result.ToString();
71 | }
72 | }
73 |
74 | internal string Rate
75 | => _score switch
76 | {
77 | >= 9900000 => "[EX+]",
78 | >= 9800000 => "[EX]",
79 | >= 9500000 => "[AA]",
80 | >= 9200000 => "[A]",
81 | >= 8900000 => "[B]",
82 | >= 8600000 => "[C]",
83 | _ => "[D]"
84 | };
85 |
86 | internal Task GetSongImage() => SongInfo.GetSongImage();
87 |
88 | internal string SongName(byte length) => SongInfo.GetSongName(length);
89 |
90 | internal bool Compare(RecordInfo? info)
91 | {
92 | if (info == null) return true;
93 | return _score > info._score;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Andreal.Core/Model/Arcaea/Side.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Model.Arcaea;
2 |
3 | internal enum Side
4 | {
5 | Hikari,
6 | Tairitsu,
7 | Achromic
8 | }
9 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/BackGround.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Drawing2D;
3 | using System.Drawing.Imaging;
4 | using System.Drawing.Text;
5 | using Andreal.Core.UI.Model;
6 | using Path = Andreal.Core.Common.Path;
7 |
8 | namespace Andreal.Core.UI;
9 |
10 | #pragma warning disable CA1416
11 |
12 | public class BackGround : Image, IDisposable
13 | {
14 | private Graphics? _g;
15 |
16 | internal BackGround(Path path) : base(path) { }
17 |
18 | private BackGround(Bitmap bitmap) : base(bitmap) { }
19 |
20 | internal BackGround(int width, int height) : base(width, height) { }
21 |
22 | internal BackGround(Image origin, int width, int height) : base(origin, width, height) { }
23 |
24 | public new void Dispose()
25 | {
26 | if (Alreadydisposed) return;
27 | _g?.Dispose();
28 | base.Dispose();
29 | }
30 |
31 | ~BackGround()
32 | {
33 | _g?.Dispose();
34 | base.Dispose();
35 | }
36 |
37 | internal Graphics GraphicsFromBackGround()
38 | {
39 | if (_g is not null) return _g;
40 | _g = Graphics.FromImage(Bitmap);
41 | _g.InterpolationMode = InterpolationMode.HighQualityBicubic;
42 | _g.PixelOffsetMode = PixelOffsetMode.HighQuality;
43 | _g.CompositingQuality = CompositingQuality.HighQuality;
44 | _g.SmoothingMode = SmoothingMode.HighQuality;
45 | _g.TextRenderingHint = TextRenderingHint.AntiAlias;
46 | return _g;
47 | }
48 |
49 | internal new BackGround Cut(Rectangle rectangle) => new(Bitmap.Clone(rectangle, PixelFormat.Format32bppArgb));
50 |
51 | internal void FillColor(System.Drawing.Color color, int alpha = 120)
52 | => GraphicsFromBackGround().FillRectangle(new SolidBrush(System.Drawing.Color.FromArgb(alpha, color)), 0, 0, Width, Height);
53 |
54 | internal void Draw(params IGraphicsModel[] graphicsModelCollection)
55 | {
56 | foreach (var i in graphicsModelCollection) i.Draw(GraphicsFromBackGround());
57 | }
58 |
59 | internal BackGround Blur(byte round)
60 | {
61 | StackBlur.StackBlurRGBA32(Bitmap, round);
62 | return this;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Color.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.UI;
2 |
3 | [Serializable]
4 | internal static class Color
5 | {
6 | internal static readonly System.Drawing.Color White = System.Drawing.Color.White,
7 | Black = System.Drawing.Color.Black,
8 | Hikari = FromArgb(150, 100, 200, 225),
9 | Tairitsu = FromArgb(150, 50, 20, 75),
10 | Achromic = FromArgb(150, 180, 180, 180),
11 | PmColor = FromArgb(150, 180, 200),
12 | ArcGray = FromArgb(60, 60, 60),
13 | ArcPurple = FromArgb(31, 30, 51),
14 | ArcRed = FromArgb(104, 9, 52),
15 | GnaqGray = FromArgb(110, 110, 110),
16 | AzusaGray = FromArgb(90, 90, 90);
17 |
18 | internal static System.Drawing.Color FromArgb(int alpha, System.Drawing.Color baseColor) => System.Drawing.Color.FromArgb(alpha, baseColor);
19 |
20 | internal static System.Drawing.Color FromArgb(int r, int g, int b) => System.Drawing.Color.FromArgb(r, g, b);
21 |
22 | internal static System.Drawing.Color FromArgb(
23 | int a,
24 | int r,
25 | int g,
26 | int b)
27 | => System.Drawing.Color.FromArgb(a, r, g, b);
28 |
29 | internal static System.Drawing.Color GetBySide(int side)
30 | => side switch
31 | {
32 | 0 => Hikari,
33 | 1 => Tairitsu,
34 | 2 => Achromic,
35 | _ => White
36 | };
37 | }
38 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Font.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Text;
3 | using Path = Andreal.Core.Common.Path;
4 |
5 | namespace Andreal.Core.UI;
6 |
7 | #pragma warning disable CA1416
8 |
9 | [Serializable]
10 | internal static class Font
11 | {
12 | private static PrivateFontCollection _pfc;
13 | private static Dictionary _fontFamily;
14 |
15 | static Font()
16 | {
17 | _pfc = new();
18 | foreach (var s in new DirectoryInfo(Path.ArcaeaFontRoot).GetFiles().Select(s => s.FullName)) _pfc.AddFontFile(s);
19 | _fontFamily = _pfc.Families.ToDictionary(i => i.Name, i => i);
20 | Exo64 = GetFont("Exo", 64);
21 | Exo60 = GetFont("Exo", 60);
22 | Exo44 = GetFont("Exo", 44);
23 | Exo40 = GetFont("Exo", 40);
24 | Exo36 = GetFont("Exo", 36);
25 | Exo32 = GetFont("Exo", 32);
26 | Exo26 = GetFont("Exo", 26);
27 | Exo24 = GetFont("Exo", 24);
28 | Exo20 = GetFont("Exo", 20);
29 | Andrea108 = GetFont("Andrea", 108);
30 | Andrea90 = GetFont("Andrea", 90);
31 | Andrea72 = GetFont("Andrea", 72);
32 | Andrea60 = GetFont("Andrea", 60);
33 | Andrea56 = GetFont("Andrea", 56);
34 | Andrea36 = GetFont("Andrea", 36);
35 | Andrea28 = GetFont("Andrea", 28);
36 | Andrea20 = GetFont("Andrea", 20);
37 | Beatrice36 = GetFont("Beatrice", 36);
38 | Beatrice26 = GetFont("Beatrice", 26);
39 | Beatrice24 = GetFont("Beatrice", 24);
40 | Beatrice20 = GetFont("Beatrice", 20);
41 | ExoLight42 = GetFont("Exo Andrea", 42);
42 | ExoLight36 = GetFont("Exo Andrea", 36);
43 | ExoLight28 = GetFont("Exo Andrea", 28);
44 | ExoLight24 = GetFont("Exo Andrea", 24);
45 | ExoLight20 = GetFont("Exo Andrea", 20);
46 | KazesawaLight72 = GetFont("Kazesawa Light", 72);
47 | KazesawaLight56 = GetFont("Kazesawa Light", 56);
48 | KazesawaLight48 = GetFont("Kazesawa Light", 48);
49 | KazesawaLight40 = GetFont("Kazesawa Light", 40);
50 | KazesawaLight32 = GetFont("Kazesawa Light", 32);
51 | KazesawaLight24 = GetFont("Kazesawa Light", 24);
52 | KazesawaRegular56 = GetFont("Kazesawa Regular", 56);
53 | KazesawaRegular27 = GetFont("Kazesawa Regular", 27);
54 | }
55 |
56 | private static System.Drawing.Font GetFont(string name, float emSize) => new(_fontFamily[name], emSize);
57 |
58 | internal static readonly System.Drawing.Font Exo64,
59 | Exo60,
60 | Exo44,
61 | Exo40,
62 | Exo36,
63 | Exo32,
64 | Exo26,
65 | Exo24,
66 | Exo20,
67 | Andrea108,
68 | Andrea90,
69 | Andrea72,
70 | Andrea60,
71 | Andrea56,
72 | Andrea36,
73 | Andrea28,
74 | Andrea20,
75 | Beatrice36,
76 | Beatrice26,
77 | Beatrice24,
78 | Beatrice20,
79 | ExoLight42,
80 | ExoLight36,
81 | ExoLight28,
82 | ExoLight24,
83 | ExoLight20,
84 | KazesawaLight72,
85 | KazesawaLight56,
86 | KazesawaLight48,
87 | KazesawaLight40,
88 | KazesawaLight32,
89 | KazesawaLight24,
90 | KazesawaRegular56,
91 | KazesawaRegular27;
92 | }
93 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Image.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Drawing2D;
3 | using System.Drawing.Imaging;
4 | using System.Runtime.InteropServices;
5 | using Path = Andreal.Core.Common.Path;
6 |
7 | namespace Andreal.Core.UI;
8 |
9 | #pragma warning disable CA1416
10 |
11 | public class Image : IDisposable
12 | {
13 | protected readonly Bitmap Bitmap;
14 |
15 | private protected bool Alreadydisposed;
16 |
17 | internal Image(Path path)
18 | {
19 | Bitmap = new(path);
20 | Bitmap.SetResolution(96, 96);
21 | }
22 |
23 | internal Image(int width, int height)
24 | {
25 | Bitmap = new(width, height);
26 | Bitmap.SetResolution(96, 96);
27 | }
28 |
29 | internal Image(Bitmap bitmap)
30 | {
31 | lock (bitmap) Bitmap = bitmap;
32 | Bitmap.SetResolution(96, 96);
33 | }
34 |
35 | internal Image(Image origin, int width, int height)
36 | {
37 | lock (origin) Bitmap = new(origin.Bitmap, width, height);
38 | Bitmap.SetResolution(96, 96);
39 | }
40 |
41 | internal Image(Stream stream)
42 | {
43 | lock (stream) Bitmap = (Bitmap)System.Drawing.Image.FromStream(stream);
44 | Bitmap.SetResolution(96, 96);
45 | }
46 |
47 | internal int Width => Bitmap.Width;
48 |
49 | internal int Height => Bitmap.Height;
50 |
51 | internal System.Drawing.Color MainColor => DeserializeColor();
52 |
53 | public void Dispose()
54 | {
55 | if (Alreadydisposed) return;
56 | Bitmap.Dispose();
57 | GC.SuppressFinalize(this);
58 | Alreadydisposed = true;
59 | }
60 |
61 | ~Image()
62 | {
63 | Bitmap.Dispose();
64 | }
65 |
66 | private System.Drawing.Color DeserializeColor()
67 | {
68 | var bm = Bitmap.LockBits(new(0, 0, Bitmap.Width, Bitmap.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
69 | var ptr = bm.Scan0;
70 | var bytes = Math.Abs(bm.Stride) * Bitmap.Height;
71 | var rgbValues = new byte[bytes];
72 | Marshal.Copy(ptr, rgbValues, 0, bytes);
73 | Bitmap.UnlockBits(bm);
74 | long red = 0, green = 0, blue = 0, count = 10;
75 | for (var counter = 0; counter < rgbValues.Length; counter += 12)
76 | {
77 | blue += rgbValues[counter];
78 | green += rgbValues[counter + 1];
79 | red += rgbValues[counter + 2];
80 | ++count;
81 | }
82 |
83 | count = count / 2 * 3;
84 | var col = System.Drawing.Color.FromArgb((byte)(red / count), (byte)(green / count), (byte)(blue / count));
85 |
86 | return col;
87 | }
88 |
89 | internal Image Cut(Rectangle rectangle) => new(Bitmap.Clone(rectangle, PixelFormat.Format32bppArgb));
90 |
91 | internal void SaveAsPng(Path path) => Bitmap.Save(path, ImageFormat.Png);
92 |
93 | internal void SaveAsJpgWithQuality(Path path, int quality = 50) => ImageExtend.SaveAsJpeg(Bitmap, path, quality);
94 |
95 | internal static class ImageExtend
96 | {
97 | internal static void PngWithWhiteBg(Path originPath, Path newPath)
98 | {
99 | using Image img = new(originPath);
100 | using var bmpTemp = new Bitmap(img.Width, img.Height);
101 | using var g = Graphics.FromImage(bmpTemp);
102 | g.SmoothingMode = SmoothingMode.AntiAlias;
103 | g.Clear(System.Drawing.Color.White);
104 | DrawImage(g, img, 0, 0, img.Width, img.Height);
105 | bmpTemp.Save(newPath, ImageFormat.Png);
106 | }
107 |
108 | internal static void DrawImage(
109 | Graphics g,
110 | Image image,
111 | int posX,
112 | int posY)
113 | => g.DrawImage(image.Bitmap, posX, posY);
114 |
115 | internal static void DrawImage(
116 | Graphics g,
117 | Image image,
118 | int posX,
119 | int posY,
120 | int newWidth,
121 | int newHeight)
122 | => g.DrawImage(image.Bitmap, posX, posY, newWidth, newHeight);
123 |
124 | private static ImageCodecInfo? _imageCodecInfo;
125 | private static ImageCodecInfo ImageCodecInfo => _imageCodecInfo ??= GetCodecInfo("image/jpeg")!;
126 |
127 | private static ImageCodecInfo? GetCodecInfo(string mimeType)
128 | => ImageCodecInfo.GetImageEncoders().FirstOrDefault(ici => ici.MimeType == mimeType);
129 |
130 | public static void SaveAsJpeg(Bitmap bmp, Path path, int quality)
131 | {
132 | var ps = new EncoderParameters() { Param = { [0] = new(Encoder.Quality, quality) } };
133 | bmp.Save(path, ImageCodecInfo, ps);
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/ImageGenerator/ArcBackgroundGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Andreal.Core.Model.Arcaea;
3 | using Andreal.Core.UI.Model;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | namespace Andreal.Core.UI.ImageGenerator;
7 |
8 | #pragma warning disable CA1416
9 |
10 | internal class ArcBackgroundGenerator
11 | {
12 | private readonly ArcaeaChart _info;
13 |
14 | public ArcBackgroundGenerator(RecordInfo recordInfo)
15 | {
16 | _info = recordInfo.SongInfo;
17 | }
18 |
19 | internal async Task ArcV1()
20 | {
21 | var path = Path.ArcaeaBackground(1, _info);
22 | return path.FileInfo.Exists ? new(path) : await GenerateArcV1(path);
23 | }
24 |
25 | private async Task GenerateArcV1(Path path)
26 | {
27 | using var song = await _info.GetSongImage();
28 | using var temp = song.Cut(new(0, 87, 512, 341));
29 | var background = new BackGround(temp, 1440, 960);
30 | using var masktmp = background.Blur(20).Cut(new(50, 50, 1340, 860)).Blur(80);
31 | background.FillColor(song.MainColor);
32 | background.Draw(new ImageModel(Path.ArcaeaBg1Mask, 0, 0, 1440, 960), new ImageModel(masktmp, 50, 50, 1340, 860),
33 | new ImageModel(Path.ArcaeaDivider, 100, 840, 1240), new TextWithStrokeModel("Pure", Font.Exo40, Color.White, 518, 488),
34 | new TextWithStrokeModel("Far", Font.Exo40, Color.White, 518, 553),
35 | new TextWithStrokeModel("Lost", Font.Exo40, Color.White, 518, 618),
36 | new TextWithStrokeModel("PTT", Font.Exo40, Color.White, 518, 683),
37 | new TextWithStrokeModel("Generated by Project Andreal", Font.KazesawaLight24, Color.White, 80, 865));
38 | background.SaveAsPng(path);
39 | return background;
40 | }
41 |
42 | internal async Task ArcV2()
43 | {
44 | var path = Path.ArcaeaBackground(2, _info);
45 | return path.FileInfo.Exists ? new(path) : await GenerateArcV2(path);
46 | }
47 |
48 | private async Task GenerateArcV2(Path path)
49 | {
50 | using var song = await _info.GetSongImage();
51 | using var temp = song.Cut(new(0, 112, 512, 288));
52 | var background = new BackGround(temp, 1920, 1080).Blur(60);
53 | background.FillColor(song.MainColor);
54 | background.Draw(new TextWithShadowModel("Play PTT", Font.Exo36, 123, 355), new TextWithShadowModel("Pure", Font.Exo32, 127, 455),
55 | new TextWithShadowModel("Far", Font.Exo32, 127, 525), new TextWithShadowModel("Lost", Font.Exo32, 410, 525),
56 | new TextWithShadowModel("Played at", Font.Exo32, 127, 595),
57 | new ImageModel(new BackGround(song.Cut(new(0, 19, 512, 48)), 1920, 180).Blur(20), 0, 740),
58 | new LineModel(Color.White, 3, new(0, 740), new(1920, 740)), new LineModel(Color.White, 3, new(0, 920), new(1920, 920)),
59 | new LineModel(Color.White, 1, new(0, 705), new(1920, 705)), new LineModel(Color.White, 1, new(0, 955), new(1920, 955)),
60 | new RectangleModel(Color.GetBySide(_info.Side), new(145, 685, 320, 320)), new ImageModel(song, 130, 670, 320, 320),
61 | new TextWithShadowModel(_info.GetSongName(50), Font.Andrea56, 510, 750));
62 | background.SaveAsPng(path);
63 | return background;
64 | }
65 |
66 | internal async Task ArcV3()
67 | {
68 | var path = Path.ArcaeaBackground(3, _info);
69 | return path.FileInfo.Exists ? new(path) : await GenerateArcV3(path);
70 | }
71 |
72 | private async Task GenerateArcV3(Path path)
73 | {
74 | using var song = await _info.GetSongImage();
75 | using var temp = song.Cut(new(78, 0, 354, 512));
76 | var background = new BackGround(temp, 1000, 1444).Blur(10);
77 | background.FillColor(Color.White, 100);
78 | background.Draw(new ImageModel(Path.ArcaeaBg3Mask(_info.Side), 0, 0, 1000),
79 | new TextOnlyModel(_info.GetSongName(30), Font.Beatrice36, Color.Black, 500, 860, StringAlignment.Center),
80 | new ImageModel(song, 286, 408, 428), new TextOnlyModel("PlayPtt:", Font.Exo24, Color.GnaqGray, 110, 1275),
81 | new TextOnlyModel("PlayTime:", Font.Exo24, Color.GnaqGray, 110, 1355),
82 | new TextOnlyModel("Pure", Font.Exo24, Color.Black, 635, 1260), new TextOnlyModel("Far", Font.Exo24, Color.Black, 635, 1315),
83 | new TextOnlyModel("Lost", Font.Exo24, Color.Black, 635, 1370));
84 | background.SaveAsPng(path);
85 | return background;
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/ImageGenerator/ArcBest30ImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 | using Andreal.Core.Model.Arcaea;
3 | using Andreal.Core.UI.Model;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | namespace Andreal.Core.UI.ImageGenerator;
7 |
8 | internal class ArcBest30ImageGenerator
9 | {
10 | internal ArcBest30ImageGenerator(IBest30Data b30data, PlayerInfo info)
11 | {
12 | B30data = b30data;
13 | Info = info;
14 | }
15 |
16 | private IBest30Data B30data { get; }
17 | private PlayerInfo Info { get; }
18 |
19 | internal async Task Generate()
20 | {
21 | var bg = new BackGround(Path.ArcaeaBest30Bg);
22 | bg.Draw(new TextOnlyModel(Info.PlayerName, Font.Andrea72, Color.White, 345, 175),
23 | new TextOnlyModel($"ArcCode: {Info.PlayerCode}", Font.ExoLight28, Color.White, 370, 320),
24 | new TextOnlyModel($"Total Best 30: {B30data.Best30Avg}", Font.Andrea60, Color.White, 145, 480),
25 | new TextOnlyModel($"Recent Best 10: {B30data.Recent10Avg}", Font.Andrea60, Color.White, 1095, 480),
26 | new ImageModel(await Path.ArcaeaPartnerIcon(Info.Partner, Info.IsAwakened), 75, 130, 255),
27 | new PotentitalModel(Info.Potential, 175, 235));
28 |
29 | var len = Math.Min(B30data.Best30List.Count, 30);
30 | for (var i = 0; i < len; ++i)
31 | {
32 | var record = B30data.Best30List[i];
33 | int x = 75 + i % 2 * 950, y = 660 + i / 2 * 350;
34 | using var song = await record.GetSongImage();
35 |
36 | bg.Draw(
37 | new PolygonModel(Color.White, new(x + 9, y), new(x + 891, y), new(x + 900, y + 9), new(x + 900, y + 291), new(x + 891, y + 300),
38 | new(x + 9, y + 300), new(x, y + 291), new(x, y + 9)),
39 | new PolygonModel(record.DifficultyInfo.Color, new(x + 278, y + 22), new(x + 278, y + 70), new(x + 503, y + 70),
40 | new(x + 458, y + 22)), new ImageModel(song, x + 22, y + 22, 256, 256),
41 | new TextOnlyModel($"[{record.Const:0.0}] {record.SongName(11)}", Font.Beatrice36, song.MainColor, x + 295, y + 80),
42 | new TextOnlyModel(record.Score, Font.Exo44, song.MainColor, x + 290, y + 145),
43 | new TextOnlyModel(record.Rating, Font.Exo26, System.Drawing.Color.White, x + 297, y + 24),
44 | new TextOnlyModel($"#{i + 1}", Font.Beatrice26, System.Drawing.Color.Black, x + 800, y + 24),
45 | new TextOnlyModel($"Pure: {record.Pure} (+{record.MaxPure})", Font.Beatrice20, System.Drawing.Color.FromArgb(105, 68, 100),
46 | x + 300, y + 235),
47 | new TextOnlyModel($"Far: {record.Far}", Font.Beatrice20, System.Drawing.Color.FromArgb(216, 157, 49), x + 570, y + 235),
48 | new TextOnlyModel($"Lost: {record.Lost}", Font.Beatrice20, System.Drawing.Color.FromArgb(159, 83, 109), x + 730, y + 235));
49 | }
50 |
51 | return bg;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/ImageGenerator/ArcBest40ImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 | using Andreal.Core.Model.Arcaea;
3 | using Andreal.Core.UI.Model;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | namespace Andreal.Core.UI.ImageGenerator;
7 |
8 | internal class ArcBest40ImageGenerator
9 | {
10 | internal ArcBest40ImageGenerator(IBest40Data b40data, PlayerInfo info)
11 | {
12 | B40data = b40data;
13 | Info = info;
14 | }
15 |
16 | private IBest40Data B40data { get; }
17 | private PlayerInfo Info { get; }
18 |
19 | internal async Task Generate()
20 | {
21 | var bg = new BackGround(Path.ArcaeaBest40Bg);
22 | bg.Draw(new TextOnlyModel(Info.PlayerName, Font.Andrea108, Color.White, 560, 190),
23 | new TextOnlyModel($"ArcCode: {Info.PlayerCode}", Font.ExoLight42, Color.White, 590, 405),
24 | new TextOnlyModel("Total Best 30:", Font.Andrea90, Color.White, 1593, 150),
25 | new TextOnlyModel("Recent Best 10:", Font.Andrea90, Color.White, 1595, 350),
26 | new TextOnlyModel(B40data.Best30Avg, Font.Andrea90, Color.White, 2373, 150),
27 | new TextOnlyModel(B40data.Recent10Avg, Font.Andrea90, Color.White, 2375, 350),
28 | new ImageModel(await Path.ArcaeaPartnerIcon(Info.Partner, Info.IsAwakened), 140, 120, 383),
29 | new PotentitalModel(Info.Potential, 305, 270, 300));
30 |
31 | var len = Math.Min(B40data.Best30List.Count, 30);
32 |
33 | for (var i = 0; i < len; ++i)
34 | {
35 | var record = B40data.Best30List[i];
36 | int x = 93 + i % 3 * 950, y = 590 + i / 3 * 350;
37 |
38 | using var song = await record.GetSongImage();
39 |
40 | bg.Draw(
41 | new PolygonModel(Color.White, new(x + 9, y), new(x + 891, y), new(x + 900, y + 9), new(x + 900, y + 291), new(x + 891, y + 300),
42 | new(x + 9, y + 300), new(x, y + 291), new(x, y + 9)),
43 | new PolygonModel(record.DifficultyInfo.Color, new(x + 278, y + 22), new(x + 278, y + 70), new(x + 503, y + 70),
44 | new(x + 458, y + 22)), new ImageModel(song, x + 22, y + 22, 256, 256),
45 | new TextOnlyModel($"[{record.Const:0.0}] {record.SongName(11)}", Font.Beatrice36, song.MainColor, x + 295, y + 80),
46 | new TextOnlyModel(record.Score, Font.Exo44, song.MainColor, x + 290, y + 145),
47 | new TextOnlyModel(record.Rating, Font.Exo26, System.Drawing.Color.White, x + 297, y + 24),
48 | new TextOnlyModel($"#{i + 1}", Font.Beatrice26, System.Drawing.Color.Black, x + 800, y + 24),
49 | new TextOnlyModel($"Pure: {record.Pure} (+{record.MaxPure})", Font.Beatrice20, System.Drawing.Color.FromArgb(105, 68, 100),
50 | x + 300, y + 235),
51 | new TextOnlyModel($"Far: {record.Far}", Font.Beatrice20, System.Drawing.Color.FromArgb(216, 157, 49), x + 570, y + 235),
52 | new TextOnlyModel($"Lost: {record.Lost}", Font.Beatrice20, System.Drawing.Color.FromArgb(159, 83, 109), x + 730, y + 235));
53 | }
54 |
55 | if (!(B40data.OverflowList?.Count > 0)) return bg;
56 |
57 | bg.Draw(new ImageModel(Path.ArcaeaDivider, 0, 4042, 2980));
58 |
59 | var overLen = Math.Min(B40data.OverflowList.Count, 9) + 30;
60 |
61 | for (var i = 30; i < overLen; ++i)
62 | {
63 | var record = B40data.OverflowList[i - 30];
64 | int x = 93 + i % 3 * 950, y = 625 + i / 3 * 350;
65 |
66 | using var song = await record.GetSongImage();
67 |
68 | bg.Draw(
69 | new PolygonModel(Color.White, new(x + 9, y), new(x + 891, y), new(x + 900, y + 9), new(x + 900, y + 291), new(x + 891, y + 300),
70 | new(x + 9, y + 300), new(x, y + 291), new(x, y + 9)),
71 | new PolygonModel(record.DifficultyInfo.Color, new(x + 278, y + 22), new(x + 278, y + 70), new(x + 503, y + 70),
72 | new(x + 458, y + 22)), new ImageModel(song, x + 22, y + 22, 256, 256),
73 | new TextOnlyModel($"[{record.Const:0.0}] {record.SongName(11)}", Font.Beatrice36, song.MainColor, x + 295, y + 80),
74 | new TextOnlyModel(record.Score, Font.Exo44, song.MainColor, x + 290, y + 145),
75 | new TextOnlyModel(record.Rating, Font.Exo26, System.Drawing.Color.White, x + 297, y + 24),
76 | new TextOnlyModel($"#{i + 1}", Font.Beatrice26, System.Drawing.Color.Black, x + 800, y + 24),
77 | new TextOnlyModel($"Pure: {record.Pure} (+{record.MaxPure})", Font.Beatrice20, System.Drawing.Color.FromArgb(105, 68, 100),
78 | x + 300, y + 235),
79 | new TextOnlyModel($"Far: {record.Far}", Font.Beatrice20, System.Drawing.Color.FromArgb(216, 157, 49), x + 570, y + 235),
80 | new TextOnlyModel($"Lost: {record.Lost}", Font.Beatrice20, System.Drawing.Color.FromArgb(159, 83, 109), x + 730, y + 235));
81 | }
82 |
83 | return bg;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/ImageGenerator/ArcRecord5ImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 | using Andreal.Core.Model.Arcaea;
3 | using Andreal.Core.UI.Model;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | namespace Andreal.Core.UI.ImageGenerator;
7 |
8 | internal class ArcRecord5ImageGenerator
9 | {
10 | internal ArcRecord5ImageGenerator(IBest30Data b30data, bool isFloor)
11 | {
12 | B30data = b30data;
13 | IsFloor = isFloor;
14 | }
15 |
16 | private IBest30Data B30data { get; }
17 | private bool IsFloor { get; }
18 |
19 | internal async Task Generate()
20 | {
21 | var bg = new BackGround(Path.ArcaeaBest5Bg);
22 |
23 | bg.Draw(new TextWithStrokeModel(B30data.Best30Avg, Font.KazesawaLight48, Color.Black, 740, 90, Color.Black, 1),
24 | new TextWithStrokeModel(B30data.Recent10Avg, Font.KazesawaLight48, Color.Black, 740, 180, Color.Black, 1));
25 |
26 | for (var i = 0; i < Math.Min(5, B30data.Best30List.Count); ++i)
27 | {
28 | var record = B30data.Best30List[i + (IsFloor ? Math.Max(0, B30data.Best30List.Count - 5) : 0)];
29 |
30 | using var song = await record.GetSongImage();
31 |
32 | bg.Draw(new ImageModel(song, 94, 304 + i * 340, 280, 280),
33 | new TextWithStrokeModel(record.SongName(18), Font.KazesawaLight72, song.MainColor, 420, 295 + i * 340, Color.Black, 1),
34 | new TextWithStrokeModel(record.Score, Font.KazesawaLight56, Color.ArcGray, 426, 403 + i * 340, Color.Black, 1),
35 | new TextWithStrokeModel($"{record.DifficultyInfo.LongStr} {record.Const:0.0}", Font.KazesawaLight56, record.DifficultyInfo.Color,
36 | 804, 403 + i * 340, Color.Black, 1),
37 | new TextWithStrokeModel("F", Font.KazesawaLight56, Color.ArcGray, 420, 493 + i * 340, Color.Black, 1),
38 | new TextWithStrokeModel("L", Font.KazesawaLight56, Color.ArcGray, 633, 493 + i * 340, Color.Black, 1),
39 | new TextWithStrokeModel("PTT", Font.KazesawaLight56, Color.ArcGray, 814, 493 + i * 340, Color.Black, 1),
40 | new TextWithStrokeModel(record.Far, Font.KazesawaLight56, Color.ArcGray, 470, 493 + i * 340, Color.Black, 1),
41 | new TextWithStrokeModel(record.Lost, Font.KazesawaLight56, Color.ArcGray, 683, 493 + i * 340, Color.Black, 1),
42 | new TextWithStrokeModel(record.Rating, Font.KazesawaLight56, Color.ArcGray, 964, 493 + i * 340, Color.Black, 1));
43 | }
44 |
45 | return bg;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/ImageGenerator/ArcRecordImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Andreal.Core.Message;
3 | using Andreal.Core.Model.Arcaea;
4 | using Andreal.Core.UI.Model;
5 | using Path = Andreal.Core.Common.Path;
6 |
7 | namespace Andreal.Core.UI.ImageGenerator;
8 |
9 | #pragma warning disable CA1416
10 |
11 | internal class ArcRecordImageGenerator
12 | {
13 | private static readonly string[] CleartypeString = { "[TL]", "[NC]", "[FR]", "[PM]", "[EC]", "[HC]" };
14 |
15 | internal ArcRecordImageGenerator(PlayerInfo playerInfo, RecordInfo recordInfo)
16 | {
17 | PlayerInfo = playerInfo;
18 | RecordInfo = recordInfo;
19 | }
20 |
21 | private PlayerInfo PlayerInfo { get; }
22 | private RecordInfo RecordInfo { get; }
23 |
24 | internal async Task Version1()
25 | {
26 | var bg = await new ArcBackgroundGenerator(RecordInfo).ArcV1();
27 |
28 | using var song = await RecordInfo.GetSongImage();
29 |
30 | bg.Draw(new PartnerModel(PlayerInfo.Partner, PlayerInfo.IsAwakened, PlayerInfo.ImgVersion), new PotentitalModel(PlayerInfo.Potential, 87, 60),
31 | new ImageModel(Path.ArcaeaGlass, 810, 240, 630),
32 | new TextWithStrokeModel(PlayerInfo.PlayerName, Font.KazesawaLight40, Color.White, 275, 100),
33 | new TextWithStrokeModel("Code " + PlayerInfo.PlayerCode, Font.KazesawaLight32, Color.White, 275, 160),
34 | new ImageModel(Path.ArcaeaCleartypeV1(RecordInfo.Cleartype), -56, 224, 700), new ImageModel(song, 150, 430, 290),
35 | new ImageModel(Path.ArcaeaDifficultyForV1(RecordInfo.Difficulty), 333, 680, 150),
36 | new TextWithStrokeModel(RecordInfo.Const.ToString("0.0"), Font.Exo36, Color.White, 350, 677, Color.Black, 2, StringAlignment.Center),
37 | new TextWithStrokeModel(RecordInfo.SongName(23), Font.KazesawaRegular56, Color.White, 105, 253),
38 | new TextWithStrokeModel(RecordInfo.Score, Font.Exo64, RecordInfo.Cleartype == 3 ? Color.PmColor : Color.White, 515, 370),
39 | new TextWithStrokeModel(RecordInfo.Pure + $" (+{RecordInfo.MaxPure})", Font.Exo40, Color.White, 638, 488),
40 | new TextWithStrokeModel(RecordInfo.Far, Font.Exo40, Color.White, 638, 553),
41 | new TextWithStrokeModel(RecordInfo.Lost, Font.Exo40, Color.White, 638, 618),
42 | new TextWithStrokeModel(RecordInfo.Rating, Font.Exo40, Color.White, 638, 683),
43 | new TextWithStrokeModel("Played at " + RecordInfo.TimeStr, Font.Exo40, Color.White, 368, 758));
44 | return bg;
45 | }
46 |
47 | internal async Task Version2()
48 | {
49 | var bg = await new ArcBackgroundGenerator(RecordInfo).ArcV2();
50 | bg.Draw(new TextWithShadowModel($"{RecordInfo.DifficultyInfo.LongStr} {RecordInfo.Const:0.0}", Font.Andrea36, 514, 850),
51 | new PartnerModel(PlayerInfo.Partner, PlayerInfo.IsAwakened, PlayerInfo.ImgVersion), new PotentitalModel(PlayerInfo.Potential, 79, 38),
52 | new TextWithShadowModel(RecordInfo.IsRecent ? "Recent" : "Best", Font.Exo44, 120, 260),
53 | new TextWithShadowModel(RecordInfo.Score, Font.Exo36, 398, 270),
54 | new TextWithShadowModel($"{RecordInfo.Rate}{CleartypeString[RecordInfo.Cleartype]}", Font.Exo36, 730, 270),
55 | new TextWithShadowModel(RecordInfo.Rating, Font.Exo36, 398, 354), new TextWithShadowModel(RecordInfo.Pure, Font.Exo32, 240, 455),
56 | new TextWithShadowModel($"(+{RecordInfo.MaxPure})", Font.Exo32, 415, 455),
57 | new TextWithShadowModel(RecordInfo.Far, Font.Exo32, 240, 525), new TextWithShadowModel(RecordInfo.Lost, Font.Exo32, 560, 525),
58 | new TextWithShadowModel(RecordInfo.TimeStr, Font.Exo32, 350, 595),
59 | new TextWithShadowModel(PlayerInfo.PlayerName, Font.Andrea56, 290, 60),
60 | new TextWithShadowModel($"ArcCode: {PlayerInfo.PlayerCode}", Font.Andrea28, 297, 150));
61 | return bg;
62 | }
63 |
64 | internal async Task Version3()
65 | {
66 | var bg = await new ArcBackgroundGenerator(RecordInfo).ArcV3();
67 | bg.Draw(new ImageModel(Path.ArcaeaCleartypeV3(RecordInfo.Cleartype), 185, 1035, 630),
68 | new ImageModel(await Path.ArcaeaPartnerIcon(PlayerInfo.Partner, PlayerInfo.IsAwakened), 150, 160, 160),
69 | new PotentitalModel(PlayerInfo.Potential, 215, 215, 140),
70 | new TextOnlyModel(PlayerInfo.PlayerName, Font.Andrea36, Color.Black, 340, 200),
71 | new TextOnlyModel($"ArcCode: {PlayerInfo.PlayerCode}", Font.Andrea20, Color.GnaqGray, 340, 270),
72 | new TextOnlyModel($"{RecordInfo.DifficultyInfo.LongStr} | {RecordInfo.Const:0.0}", Font.Beatrice24, RecordInfo.DifficultyInfo.Color,
73 | 500, 925, StringAlignment.Center),
74 | new TextOnlyModel($"{RecordInfo.Score} {RecordInfo.Rate}", Font.Exo44, Color.Black, 500, 1130, StringAlignment.Center),
75 | new TextOnlyModel(RecordInfo.Rating, Font.Exo20, Color.GnaqGray, 260, 1280),
76 | new TextOnlyModel(RecordInfo.TimeStr, Font.Exo20, Color.GnaqGray, 260, 1360),
77 | new TextOnlyModel(RecordInfo.Pure + $" (+{RecordInfo.MaxPure})", Font.Exo20, Color.Black, 730, 1265),
78 | new TextOnlyModel(RecordInfo.Far, Font.Exo20, Color.Black, 730, 1320),
79 | new TextOnlyModel(RecordInfo.Lost, Font.Exo20, Color.Black, 730, 1375));
80 | return bg;
81 | }
82 |
83 | internal async Task Version4(IBest30Data b30data)
84 | {
85 | var bg = new BackGround(Path.ArcaeaBg4(RecordInfo.Difficulty, RecordInfo.SongInfo.Set));
86 | bg.Draw(new PotentitalModel(PlayerInfo.Potential, 80, 691, 100),
87 | new PartnerModel(PlayerInfo.Partner, PlayerInfo.IsAwakened, PlayerInfo.ImgVersion),
88 | new ImageModel(Path.ArcaeaCleartypeV4(RecordInfo.Cleartype), 116, 834, newHeight: 35),
89 | new ImageModel(await Path.ArcaeaPartnerIcon(15, false), 89, 384, 83),
90 | new ImageModel(await Path.ArcaeaPartnerIcon(7, false), 189, 384, 83),
91 | new TextOnlyModel($"Total Best 30 Avg: {b30data.Best30Avg}", Font.ExoLight20, Color.AzusaGray, 195, 792),
92 | new TextOnlyModel($"Recent Top 10 Avg: {b30data.Recent10Avg}", Font.ExoLight20, Color.AzusaGray, 195, 832),
93 | new TextOnlyModel($"Time: {RecordInfo.TimeStr}", Font.ExoLight20, Color.AzusaGray, 195, 872),
94 | new TextOnlyModel($"ArcCode: {PlayerInfo.PlayerCode}", Font.ExoLight20, Color.Black, 195, 748),
95 | new TextOnlyModel(PlayerInfo.PlayerName, Font.Andrea36, Color.Black, 195, 685),
96 | new TextOnlyModel($"{RecordInfo.SongName(30)} {RecordInfo.SongInfo.ConstString}", Font.Andrea36, Color.Black, 100, 470),
97 | new TextOnlyModel(RecordInfo.Score, Font.ExoLight36, Color.Black, 100, 535),
98 | new TextOnlyModel($"Pure: {RecordInfo.Pure + $"(+{RecordInfo.MaxPure})"}", Font.ExoLight24, Color.Black, 100, 600),
99 | new TextOnlyModel($"Far: {RecordInfo.Far} Lost: {RecordInfo.Lost} [{RecordInfo.Rating}]", Font.ExoLight24, Color.Black, 100, 645));
100 | return bg;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/ImageGenerator/ArcSongLevelListImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 | using Andreal.Core.Model.Arcaea;
3 | using Andreal.Core.UI.Model;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | namespace Andreal.Core.UI.ImageGenerator;
7 |
8 | #pragma warning disable CA1416
9 |
10 | internal class ArcSongLevelListImageGenerator
11 | {
12 | private static readonly System.Drawing.Color WhiteBackground = Color.FromArgb(180, 245, 245, 245);
13 |
14 | internal ArcSongLevelListImageGenerator(ArcaeaChart[] list)
15 | {
16 | List = list;
17 | }
18 |
19 | private ArcaeaChart[] List { get; }
20 |
21 | internal async Task Generate()
22 | {
23 | var height = 160 + 200 * List.Length;
24 |
25 | var bg = new BackGround(1080, height);
26 | bg.Draw(new ImageModel(Path.ArcaeaConstListBg, 0, 0, 1080, height), new RectangleModel(WhiteBackground, new(40, 40, 1000, height - 80)));
27 |
28 | for (var i = 0; i < List.Length; ++i)
29 | {
30 | var y = 110 + i * 200;
31 |
32 | var info = List[i];
33 |
34 | using var song = await info.GetSongImage();
35 |
36 | var color = song.MainColor;
37 |
38 | bg.Draw(new RectangleModel(Color.GetBySide(info.Side), new(212, y + 6, 140, 140)),
39 | new ImageModel(Path.ArcaeaDivider, 40, 64 + i * 200, 1000, 32), new ImageModel(song, 206, y, 140, 140),
40 | new TextOnlyModel(info.GetSongName(22), Font.KazesawaRegular27, color, 380, y - 5),
41 | new TextOnlyModel($"{info.DifficultyInfo.LongStr} {info.Const:0.0} (+{info.Note})", Font.Exo20, info.DifficultyInfo.Color, 390,
42 | y + 60), new TextOnlyModel(info.SetFriendly, Font.Beatrice20, System.Drawing.Color.Black, 390, y + 100));
43 | }
44 |
45 | bg.Draw(new ImageModel(Path.ArcaeaDivider, 40, 64 + List.Length * 200, 1000, 32));
46 |
47 | return bg;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/IGraphicsModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Andreal.Core.UI.Model;
4 |
5 | internal interface IGraphicsModel
6 | {
7 | internal void Draw(Graphics g);
8 | }
9 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/ImageModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Path = Andreal.Core.Common.Path;
3 |
4 | namespace Andreal.Core.UI.Model;
5 |
6 | #pragma warning disable CA1416
7 |
8 | internal class ImageModel : IGraphicsModel
9 | {
10 | private readonly Image? _image;
11 | private readonly int? _newWidth, _newHeight;
12 | private readonly Path? _path;
13 | private readonly int _posX, _posY;
14 |
15 | internal ImageModel(
16 | Image image,
17 | int posX,
18 | int posY,
19 | int? newWidth = null,
20 | int? newHeight = null)
21 | {
22 | _posX = posX;
23 | _posY = posY;
24 | _path = null;
25 | _image = image;
26 | _newWidth = newWidth;
27 | _newHeight = newHeight;
28 | }
29 |
30 | internal ImageModel(
31 | Path path,
32 | int posX,
33 | int posY,
34 | int? newWidth = null,
35 | int? newHeight = null)
36 | {
37 | _posX = posX;
38 | _posY = posY;
39 | _path = path;
40 | _image = null;
41 | _newWidth = newWidth;
42 | _newHeight = newHeight;
43 | }
44 |
45 | void IGraphicsModel.Draw(Graphics g)
46 | {
47 | var image = _image ?? new Image(_path!);
48 |
49 | if (_newWidth == null && _newHeight == null)
50 | {
51 | Image.ImageExtend.DrawImage(g, image, _posX, _posY);
52 | }
53 | else
54 | {
55 | var newWidth = _newWidth ?? _newHeight * image.Width / image.Height ?? image.Width;
56 | var newHeight = _newHeight ?? _newWidth * image.Height / image.Width ?? image.Height;
57 | Image.ImageExtend.DrawImage(g, image, _posX, _posY, newWidth, newHeight);
58 | }
59 |
60 | image.Dispose();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/LineModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Andreal.Core.UI.Model;
4 |
5 | #pragma warning disable CA1416
6 |
7 | internal class LineModel : IGraphicsModel
8 | {
9 | private readonly System.Drawing.Color _color;
10 | private readonly Point _end;
11 | private readonly int _penwidth;
12 | private readonly Point _start;
13 |
14 | internal LineModel(
15 | System.Drawing.Color color,
16 | int penwidth,
17 | Point start,
18 | Point end)
19 | {
20 | _color = color;
21 | _penwidth = penwidth;
22 | _start = start;
23 | _end = end;
24 | }
25 |
26 | void IGraphicsModel.Draw(Graphics g) => g.DrawLine(new(_color, _penwidth), _start, _end);
27 | }
28 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/PartnerModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Andreal.Core.Data.Json.Arcaea.PartnerPosInfoBase;
3 | using Andreal.Core.Data.Sqlite;
4 | using Path = Andreal.Core.Common.Path;
5 |
6 | namespace Andreal.Core.UI.Model;
7 |
8 | #pragma warning disable CA1416
9 |
10 | internal class PartnerModel : IGraphicsModel
11 | {
12 | private readonly ImageModel _imageModel;
13 |
14 | internal PartnerModel(int partner, bool awakened, ImgVersion imgVersion)
15 | {
16 | var location = PartnerPosInfoBase.Get($"{partner}{(awakened ? "u" : "")}", imgVersion)!;
17 | _imageModel = new(Path.ArcaeaPartner(partner, awakened).Result, location.PositionX, location.PositionY, location.Size, location.Size);
18 | }
19 |
20 | void IGraphicsModel.Draw(Graphics g) => (_imageModel as IGraphicsModel).Draw(g);
21 | }
22 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/PolygonModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Andreal.Core.UI.Model;
4 |
5 | #pragma warning disable CA1416
6 |
7 | internal class PolygonModel : IGraphicsModel
8 | {
9 | private readonly System.Drawing.Color _color;
10 | private readonly Point[] _region;
11 |
12 | internal PolygonModel(System.Drawing.Color color, params Point[] region)
13 | {
14 | _color = color;
15 | _region = region;
16 | }
17 |
18 | void IGraphicsModel.Draw(Graphics g)
19 | {
20 | using var brash = new SolidBrush(_color);
21 | g.FillPolygon(brash, _region);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/PotentitalModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Drawing2D;
3 | using Path = Andreal.Core.Common.Path;
4 |
5 | namespace Andreal.Core.UI.Model;
6 |
7 | #pragma warning disable CA1416
8 |
9 | internal class PotentitalModel : IGraphicsModel
10 | {
11 | private readonly int _posX, _posY, _size;
12 | private readonly short _potential;
13 |
14 | internal PotentitalModel(
15 | short potential,
16 | int positionX,
17 | int positionY,
18 | int size = 200)
19 | {
20 | _potential = potential;
21 | _posX = positionX;
22 | _posY = positionY;
23 | _size = size;
24 | }
25 |
26 | void IGraphicsModel.Draw(Graphics g)
27 | {
28 | using var temp = new BackGround(200, 200);
29 | using var gr = temp.GraphicsFromBackGround();
30 | temp.Draw(new ImageModel(Path.ArcaeaRating(_potential), 21, 21, 158));
31 | if (_potential < 0)
32 | {
33 | temp.Draw(new TextWithStrokeModel("--", Font.Exo64, Color.White, 65, 52, Color.ArcPurple, 8));
34 | }
35 | else
36 | {
37 | var ptt = Convert.ToString(_potential + 10000, 10).ToCharArray();
38 | var lx = 5 +
39 | (ptt[1] == '0' ? 0 :
40 | ptt[1] == '1' ? 23 : 37) +
41 | (ptt[2] == '1' ? 23 :
42 | ptt[2] == '7' ? 25 : 37);
43 | var rx = 20 + (ptt[3] == '1' ? 17 : 30) + (ptt[4] == '1' ? 17 : 30);
44 | var posx = 98 - (lx + rx) / 2;
45 | string l = ptt[1] == '0' ? $"{ptt[2]}" : $"{ptt[1]}{ptt[2]}", r = $".{ptt[3]}{ptt[4]}";
46 | using var s = new StringFormat { Alignment = StringAlignment.Near };
47 | using var path = new GraphicsPath();
48 | using var pen = new Pen(_potential < 1300 ? Color.ArcPurple : Color.ArcRed, 8);
49 | path.AddString(l, Font.Exo60.FontFamily, 0, 60, new Rectangle(posx, 56, 150, 70), s);
50 | path.AddString(r, Font.Exo44.FontFamily, 0, 44, new Rectangle(posx + lx, 73, 150, 53), s);
51 | gr.DrawPath(pen, path);
52 | gr.FillPath(Brushes.White, path);
53 | }
54 |
55 | Image.ImageExtend.DrawImage(g, temp, _posX, _posY, _size, _size);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/RectangleModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Andreal.Core.UI.Model;
4 |
5 | #pragma warning disable CA1416
6 |
7 | internal class RectangleModel : IGraphicsModel
8 | {
9 | private readonly System.Drawing.Color _color;
10 | private readonly Rectangle _rect;
11 |
12 | internal RectangleModel(System.Drawing.Color color, Rectangle rect)
13 | {
14 | _color = color;
15 | _rect = rect;
16 | }
17 |
18 | void IGraphicsModel.Draw(Graphics g)
19 | {
20 | using var brash = new SolidBrush(_color);
21 | g.FillRectangle(brash, _rect);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/TextOnlyModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Andreal.Core.UI.Model;
4 |
5 | #pragma warning disable CA1416
6 |
7 | internal class TextOnlyModel : IGraphicsModel
8 | {
9 | private readonly System.Drawing.Color _color;
10 | private readonly System.Drawing.Font _font;
11 | private readonly int _posX, _posY;
12 | private readonly StringAlignment _stringAlignment;
13 | private readonly string _text;
14 |
15 | internal TextOnlyModel(
16 | string text,
17 | System.Drawing.Font font,
18 | System.Drawing.Color color,
19 | int posX,
20 | int posY,
21 | StringAlignment stringAlignment = StringAlignment.Near)
22 | {
23 | _text = text;
24 | _color = color;
25 | _font = font;
26 | _posX = posX;
27 | _posY = posY;
28 | _stringAlignment = stringAlignment;
29 | }
30 |
31 | void IGraphicsModel.Draw(Graphics g)
32 | {
33 | using var sf = new StringFormat { Alignment = _stringAlignment };
34 | using var brush = new SolidBrush(_color);
35 | g.DrawString(_text, _font, brush, _posX, _posY, sf);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/TextWithShadowModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace Andreal.Core.UI.Model;
4 |
5 | #pragma warning disable CA1416
6 |
7 | internal class TextWithShadowModel : IGraphicsModel
8 | {
9 | private static readonly int[] Alpha = { 85, 42, 28, 21, 17 };
10 | private readonly System.Drawing.Font _font;
11 | private readonly int _posX, _posY;
12 | private readonly string _text;
13 |
14 | internal TextWithShadowModel(
15 | string text,
16 | System.Drawing.Font font,
17 | int posX,
18 | int posY)
19 | {
20 | _text = text;
21 | _font = font;
22 | _posX = posX;
23 | _posY = posY;
24 | }
25 |
26 | void IGraphicsModel.Draw(Graphics g)
27 | {
28 | for (var i = 0; i < 5; ++i)
29 | {
30 | using Brush brush = new SolidBrush(Color.FromArgb(Alpha[i], Color.ArcPurple));
31 | for (var j = 0; j < 5; ++j)
32 | {
33 | g.DrawString(_text, _font, brush, _posX + i, _posY + j);
34 | g.DrawString(_text, _font, brush, _posX - i, _posY - j);
35 | }
36 |
37 | g.DrawString(_text, _font, brush, _posX - i, _posY + i);
38 | g.DrawString(_text, _font, brush, _posX + i, _posY - i);
39 | }
40 |
41 | g.DrawString(_text, _font, Brushes.White, _posX, _posY);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Andreal.Core/UI/Model/TextWithStrokeModel.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.Drawing.Drawing2D;
3 |
4 | namespace Andreal.Core.UI.Model;
5 |
6 | #pragma warning disable CA1416
7 |
8 | internal class TextWithStrokeModel : IGraphicsModel
9 | {
10 | private static readonly System.Drawing.Color Defcolor = System.Drawing.Color.FromArgb(82, 82, 82);
11 | private readonly System.Drawing.Color _color;
12 | private readonly System.Drawing.Font _font;
13 | private readonly System.Drawing.Color? _penColor;
14 | private readonly int _posX, _posY, _penWidth;
15 | private readonly StringAlignment _stringAlignment;
16 | private readonly string _text;
17 | private readonly int? _width, _height;
18 |
19 | internal TextWithStrokeModel(
20 | string text,
21 | System.Drawing.Font font,
22 | System.Drawing.Color color,
23 | int posX,
24 | int posY,
25 | System.Drawing.Color? penColor = null,
26 | int penWidth = 3,
27 | StringAlignment stringAlignment = StringAlignment.Near,
28 | int? width = null,
29 | int? height = null)
30 | {
31 | _text = text;
32 | _color = color;
33 | _font = font;
34 | _posX = posX;
35 | _posY = posY;
36 | _penColor = penColor;
37 | _penWidth = penWidth;
38 | _stringAlignment = stringAlignment;
39 | _width = width;
40 | _height = height;
41 | }
42 |
43 | void IGraphicsModel.Draw(Graphics g)
44 | {
45 | var origin = new Point(_posX, _posY);
46 | using var s = new StringFormat { Alignment = _stringAlignment };
47 | using var path = new GraphicsPath();
48 | using var brush = new SolidBrush(_color);
49 | using var pen = new Pen(_penColor ?? Defcolor, _penWidth);
50 | path.AddString(_text, _font.FontFamily, (int)_font.Style, _font.SizeInPoints,
51 | _width == null || _height == null
52 | ? new(origin, g.MeasureString(_text + "?", _font, origin, s))
53 | : new RectangleF(_posX, _posY, (float)_width, (float)_width), s);
54 | g.DrawPath(pen, path);
55 | g.FillPath(brush, path);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/AbbreviationHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Andreal.Core.Utils;
4 |
5 | internal static class AbbreviationHelper
6 | {
7 | internal static string GetAbbreviation(this string str)
8 | {
9 | var sb = new StringBuilder();
10 | sb.Append(str[0]);
11 |
12 | for (var index = 0; index < str.Length - 1; ++index)
13 | if (str[index] == ' ')
14 | sb.Append(str[index + 1]);
15 |
16 | return sb.ToString();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/ArcaeaHelper.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Common;
2 | using Andreal.Core.Model.Arcaea;
3 |
4 | namespace Andreal.Core.Utils;
5 |
6 | internal static class ArcaeaHelper
7 | {
8 | internal static (double, double) ConvertToArcaeaRange(this string rawdata)
9 | => rawdata switch
10 | {
11 | "11" => (11.0, 11.6),
12 | "10+" => (10.7, 10.9),
13 | "10" => (10.0, 10.6),
14 | "9+" => (9.7, 9.9),
15 | "9" => (9.0, 9.6),
16 | "8" => (8.0, 8.9),
17 | "7" => (7.0, 7.9),
18 | "6" => (6.0, 6.9),
19 | "5" => (5.0, 5.9),
20 | "4" => (4.0, 4.9),
21 | "3" => (3.0, 3.9),
22 | "2" => (2.0, 2.9),
23 | "1" => (1.0, 1.9),
24 | _ => double.TryParse(rawdata, out var value) ? (value, value) : (-1, -1)
25 | };
26 |
27 | internal static bool SongInfoParser(
28 | IEnumerable command,
29 | out ArcaeaSong song,
30 | out int dif,
31 | out string errMessage)
32 | {
33 | song = null!;
34 | dif = -1;
35 | errMessage = "";
36 |
37 | var enumerable = command.ToArray();
38 |
39 | if (enumerable.Length == 0) return false;
40 |
41 | (var songstr, dif) = DifficultyInfo.DifficultyConverter(enumerable[^1]);
42 |
43 | songstr = string.Join("", enumerable, 0, enumerable.Length - 1) + songstr;
44 |
45 | List? result = ArcaeaCharts.Query(songstr);
46 |
47 | if (result == null || result.Count == 0)
48 | {
49 | errMessage = MessageInfo.RobotReply.NoSongFound;
50 | return false;
51 | }
52 |
53 | if (result.Count > 1)
54 | {
55 | errMessage = result.Aggregate(MessageInfo.RobotReply.TooManySongFound, (cur, i) => cur + "\n" + i[0].NameEn);
56 | return false;
57 | }
58 |
59 | song = result[0];
60 |
61 | if (song.SongID == "lasteternity") dif = 3;
62 |
63 | return true;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/BotStatementHelper.cs:
--------------------------------------------------------------------------------
1 | using Andreal.Core.Message;
2 |
3 | namespace Andreal.Core.Utils;
4 |
5 | public static class BotStatementHelper
6 | {
7 | private static readonly DateTime Time = DateTime.Now;
8 |
9 | internal static ulong ProcessCount = 0, ExceptionCount = 0, WebExceptionCount = 0;
10 |
11 | public static ulong GroupMessageCount = 0, PrivateMessageCount = 0;
12 |
13 | internal static TextMessage Statement
14 | => $"状态简要:\n已运行: {DateTime.Now - Time:d\\.hh\\:mm\\:ss} day(s)\n群聊消息: {GroupMessageCount}\n私聊消息: {PrivateMessageCount}\n发送消息: {ProcessCount}\n记录异常: {ExceptionCount} + [Web] {WebExceptionCount}\n最近异常: \n{LastExceptionHelper.Get()}";
15 |
16 | public static string Status
17 | => $"运行时间: {DateTime.Now - Time:d\\.hh\\:mm\\:ss} day(s) 群聊消息: {GroupMessageCount} 私聊消息: {PrivateMessageCount} 发送消息: {ProcessCount} 记录异常: {ExceptionCount} + [Web] {WebExceptionCount}";
18 | }
19 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/ConcurrentDictionaryExtend.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.Diagnostics.CodeAnalysis;
3 |
4 | namespace Andreal.Core.Utils;
5 |
6 | internal static class ConcurrentDictionaryExtend
7 | {
8 | public static bool TryAddOrInsert(this ConcurrentDictionary dict, TKey key, TValue value)
9 | where TList : List, new() where TKey : notnull
10 | {
11 | if (!dict.ContainsKey(key)) return dict.TryAdd(key, new() { value });
12 |
13 | dict[key].Add(value);
14 | return true;
15 | }
16 |
17 | public static void ForAllItems(this ConcurrentDictionary dict, Action action)
18 | where TList : List, new() where TKey : notnull
19 | {
20 | foreach (var (key, values) in dict)
21 | {
22 | foreach (var value in values) action(key, value);
23 | }
24 | }
25 |
26 | public static void ForAllItems(
27 | this ConcurrentDictionary dict,
28 | Action keyaction,
29 | Action kvaction) where TList : List, new() where TKey : notnull
30 | {
31 | foreach (var (key, values) in dict)
32 | {
33 | keyaction(key);
34 | foreach (var value in values) kvaction(key, value);
35 | }
36 | }
37 |
38 | public static bool TryTakeKey(
39 | this ConcurrentDictionary dict,
40 | Func action,
41 | [MaybeNullWhen(false)] out TKey result) where TList : List, new() where TKey : notnull
42 | {
43 | foreach (var (key, values) in dict)
44 | {
45 | // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
46 | foreach (var value in values)
47 | {
48 | if (action(value))
49 | {
50 | result = key;
51 | return true;
52 | }
53 | }
54 | }
55 |
56 | result = default;
57 | return false;
58 | }
59 |
60 | public static bool TryTakeValues(
61 | this ConcurrentDictionary dict,
62 | Func action,
63 | [MaybeNullWhen(false)] out TList result) where TList : List, new() where TKey : notnull
64 | {
65 | // ReSharper disable once LoopCanBePartlyConvertedToQuery
66 | foreach (var (_, values) in dict)
67 | {
68 | // ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
69 | foreach (var value in values)
70 | {
71 | if (action(value))
72 | {
73 | result = values;
74 | return true;
75 | }
76 | }
77 | }
78 |
79 | result = default;
80 | return false;
81 | }
82 |
83 | public static bool TryTakeValue(
84 | this ConcurrentDictionary dict,
85 | Func action,
86 | [MaybeNullWhen(false)] out TValue result) where TList : List, new() where TKey : notnull
87 | {
88 | // ReSharper disable once LoopCanBePartlyConvertedToQuery
89 | foreach (var (_, values) in dict)
90 | {
91 | // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
92 | foreach (var value in values)
93 | {
94 | if (action(value))
95 | {
96 | result = value;
97 | return true;
98 | }
99 | }
100 | }
101 |
102 | result = default;
103 | return false;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/DateTimeHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Utils;
2 |
3 | internal static class DateTimeHelper
4 | {
5 | internal static string DateStringFromNow(this long unixTime)
6 | {
7 | var span = DateTime.UtcNow - DateTime.UnixEpoch.AddSeconds(unixTime);
8 |
9 | return span.TotalMinutes switch
10 | {
11 | > 1 => $"{(int)Math.Floor(span.TotalMinutes)}分钟前",
12 | _ => $"{(int)Math.Ceiling(span.TotalSeconds)}秒前"
13 | };
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/LastExceptionHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Utils;
2 |
3 | internal static class LastExceptionHelper
4 | {
5 | private static (DateTime Time, Exception Exception) _lastException = (DateTime.Now, new ArgumentNullException());
6 |
7 | internal static string GetDetails() => _lastException.Exception.ToString();
8 |
9 | internal static string Get() => $"{_lastException.Item1}\n {_lastException.Exception.GetType().FullName}";
10 |
11 | internal static void Set(Exception ex) => _lastException = (DateTime.Now, ex);
12 | }
13 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/RandStringHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace Andreal.Core.Utils;
4 |
5 | internal static class RandStringHelper
6 | {
7 | private const string Chars = "0123456789abcdefghijklmnopqrstuvwxyz";
8 | private static readonly Random Random = new();
9 |
10 | public static string GetRandString(int length = 10)
11 | {
12 | var res = new StringBuilder();
13 | for (var i = 0; i < length; i++) res.Append(Chars[Random.Next(36)]);
14 | return res.ToString();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/RandomHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Core.Utils;
2 |
3 | internal static class RandomHelper
4 | {
5 | private static readonly Random Random = new();
6 |
7 | internal static T GetRandomItem(this T[] ls) => ls[Random.Next(ls.Length)];
8 |
9 | internal static T GetRandomItem(this IEnumerable ls)
10 | {
11 | T[] enumerable = ls as T[] ?? ls.ToArray();
12 | return GetRandomItem(enumerable);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/SqliteHelper.cs:
--------------------------------------------------------------------------------
1 | using SQLite;
2 | using Path = Andreal.Core.Common.Path;
3 |
4 | namespace Andreal.Core.Utils;
5 |
6 | internal static class SqliteHelper
7 | {
8 | private static readonly SQLiteConnection DbConnection;
9 |
10 | static SqliteHelper()
11 | {
12 | DbConnection = new(Path.Database,
13 | SQLiteOpenFlags.ReadWrite | SQLiteOpenFlags.Create | SQLiteOpenFlags.SharedCache | SQLiteOpenFlags.FullMutex);
14 |
15 | DbConnection.Execute("CREATE TABLE IF NOT EXISTS [BotUserInfo]([QQId] INTEGER PRIMARY KEY NOT NULL, [ArcId] INTEGER DEFAULT(-1), [IsHide] INTEGER DEFAULT(0), [IsText] INTEGER DEFAULT(0), [ImgVer] INTEGER DEFAULT(0));");
16 | }
17 |
18 | internal static IEnumerable SelectAll() where T : new() => DbConnection.Table();
19 |
20 | internal static void Insert(T obj) where T : new() => DbConnection.Insert(obj);
21 |
22 | internal static void Update(T obj) where T : new() => DbConnection.Update(obj);
23 | }
24 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/StringHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace Andreal.Core.Utils;
4 |
5 | internal static class StringHelper
6 | {
7 | private static readonly Regex Reg = new(@"\s|\(|\)|(|)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
8 |
9 | internal static bool Contains(string? raw, string? seed)
10 | => seed != null && raw != null && Reg.Replace(raw, "").Contains(Reg.Replace(seed, ""), StringComparison.OrdinalIgnoreCase);
11 |
12 | internal static bool Equals(string? raw, string? seed)
13 | => seed != null && raw != null && string.Equals(Reg.Replace(raw, ""), Reg.Replace(seed, ""), StringComparison.OrdinalIgnoreCase);
14 | }
15 |
--------------------------------------------------------------------------------
/Andreal.Core/Utils/SystemHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Runtime.CompilerServices;
3 | using Andreal.Core.Common;
4 | using Andreal.Core.Data.Api;
5 |
6 | [assembly: InternalsVisibleTo("Andreal.Test")]
7 |
8 | namespace Andreal.Core.Utils;
9 |
10 | internal static class SystemHelper
11 | {
12 | public static void Init(AndrealConfig andrealConfig)
13 | {
14 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
15 | ServicePointManager.ServerCertificateValidationCallback = (
16 | _,
17 | _,
18 | _,
19 | _) => true;
20 | ServicePointManager.DefaultConnectionLimit = 512;
21 | ServicePointManager.Expect100Continue = false;
22 | ServicePointManager.UseNagleAlgorithm = false;
23 | ServicePointManager.ReusePort = true;
24 | ServicePointManager.CheckCertificateRevocationList = true;
25 | WebRequest.DefaultWebProxy = null;
26 |
27 | UnofficialArcaeaAPI.Init(andrealConfig);
28 | MessageInfo.Init(andrealConfig.Master);
29 | BackgroundTask.Init();
30 | ConfigWatcher.Init();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Andreal.Test/AIRandomSongTest.cs:
--------------------------------------------------------------------------------
1 | namespace Andreal.Test;
2 |
3 | public class Tests
4 | {
5 | [SetUp]
6 | public void Setup()
7 | {
8 | var config = JsonConvert.DeserializeObject(File.ReadAllText(Core.Common.Path.Config))!;
9 | External.Initialize(config);
10 | }
11 |
12 | [Test]
13 | public void AIRandomSongTest()
14 | {
15 | var robotReply = GlobalConfig.RobotReply;
16 | var info = ArcaeaCharts.RandomSong(0, 15)!;
17 | var format = $"Ai酱:{robotReply.GetRandomAIReply(info.NameWithPackageAndConst, info.Artist)}";
18 | Console.WriteLine(format);
19 | Assert.Pass();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Andreal.Test/Andreal.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 | enable
6 | enable
7 |
8 | false
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/Andreal.Test/Usings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
2 | global using Andreal.Core.Common;
3 | global using Andreal.Core.Model.Arcaea;
4 | global using Newtonsoft.Json;
5 |
--------------------------------------------------------------------------------
/Andreal.Window/Andreal.Window.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | enable
7 | true
8 | true
9 | true
10 | true
11 | embedded
12 | true
13 | None
14 | win-x64
15 | true
16 | Assets\favicon.ico
17 | Andreal
18 | zh-Hans
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | MSBuild:Compile
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/Andreal.Window/Assets/Fonts/Roboto-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/Fonts/Roboto-Light.ttf
--------------------------------------------------------------------------------
/Andreal.Window/Assets/chevron-down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/chevron-down.png
--------------------------------------------------------------------------------
/Andreal.Window/Assets/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/close.png
--------------------------------------------------------------------------------
/Andreal.Window/Assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/favicon.ico
--------------------------------------------------------------------------------
/Andreal.Window/Assets/img_contact.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/img_contact.png
--------------------------------------------------------------------------------
/Andreal.Window/Assets/img_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/img_map.png
--------------------------------------------------------------------------------
/Andreal.Window/Assets/img_message.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/img_message.png
--------------------------------------------------------------------------------
/Andreal.Window/Assets/img_setting.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/img_setting.png
--------------------------------------------------------------------------------
/Andreal.Window/Assets/min.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Awbugl/Andreal/b053a189a8f9960c34faf8ef0375078e9580af19/Andreal.Window/Assets/min.png
--------------------------------------------------------------------------------
/Andreal.Window/Common/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
4 |
--------------------------------------------------------------------------------
/Andreal.Window/Common/DelegateCommand.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Input;
3 |
4 | namespace Andreal.Window.Common;
5 |
6 | public class DelegateCommand : ICommand
7 | {
8 | public Action