├── .gitattributes
├── .gitignore
├── .gitmodules
├── Daylily.sln
├── LICENSE
├── README.md
├── daylily.sln.DotSettings
└── daylily
├── App.xaml
├── App.xaml.cs
├── Configs.cs
├── Migrations
├── 20220316084525_initial.Designer.cs
├── 20220316084525_initial.cs
└── OsuDbContextModelSnapshot.cs
├── MyCommandLineAnalyzer.cs
├── NLog.config
├── NLog.debug.config
├── Plugins
├── Basic
│ ├── CheckAt.cs
│ ├── CsxPlugin.cs
│ ├── DragonLanguage.cs
│ ├── Echo.cs
│ ├── HelpPlugin
│ │ ├── Help.cs
│ │ ├── HelpDetailControl.xaml
│ │ ├── HelpDetailControl.xaml.cs
│ │ ├── HelpDetailVm.cs
│ │ ├── HelpListControl.xaml
│ │ ├── HelpListControl.xaml.cs
│ │ ├── HelpListVm.cs
│ │ ├── PluginDetailVm.cs
│ │ ├── PluginInfoVm.cs
│ │ ├── ScopeInfoVm.cs
│ │ ├── String2DescriptionConverter.cs
│ │ ├── String2LowerSnakeConverter.cs
│ │ └── ZeroToCollapsedConverter.cs
│ ├── KeywordTrigger.cs
│ ├── Konachan.cs
│ ├── PeroControl.xaml
│ ├── PeroControl.xaml.cs
│ ├── Roll.cs
│ ├── RpsDice.cs
│ ├── Smoke.cs
│ ├── Tuling.cs
│ ├── WashingMachine.cs
│ └── WhatToEat.cs
├── Core
│ ├── CliPrint.cs
│ ├── CommandRate.cs
│ ├── GuiManaging
│ │ ├── GuiManager.cs
│ │ ├── ManagerWindow.xaml
│ │ └── ManagerWindow.xaml.cs
│ ├── GuiMessage
│ │ ├── GuiMessageSend.cs
│ │ ├── GuiMessageWindow.xaml
│ │ └── GuiMessageWindow.xaml.cs
│ ├── MessageLogging.cs
│ ├── PluginFilter.cs
│ ├── PowerOff.cs
│ ├── PowerOffFilterService.cs
│ ├── Reboot.cs
│ └── SendMessage.cs
├── Osu
│ ├── ApiService.cs
│ ├── AuthenticationController.cs
│ ├── BeatmapStats
│ │ ├── BeatmapStatistics.cs
│ │ ├── BeatmapStatsControl.xaml
│ │ ├── BeatmapStatsControl.xaml.cs
│ │ ├── BeatmapStatsVm.cs
│ │ ├── DateModel.cs
│ │ ├── FavoriteComparer.cs
│ │ ├── PlayCountComparer.cs
│ │ └── TickComparer.cs
│ ├── Data
│ │ ├── BeatmapScan.cs
│ │ ├── BeatmapStat.cs
│ │ ├── BeatmapSubscribe.cs
│ │ ├── OsuDbContext.cs
│ │ ├── OsuToken.cs
│ │ └── OsuUserInfo.cs
│ ├── Me
│ │ ├── MeControl.xaml
│ │ └── MeControl.xaml.cs
│ ├── MePlugin.cs
│ ├── OsuTokenReceivedEvent.cs
│ ├── Qqm.cs
│ ├── RecentPlayPlugin.cs
│ ├── SearchBnPlugins
│ │ ├── AllUsersByMode.cs
│ │ ├── RelevantInfo.cs
│ │ ├── SearchBn.cs
│ │ ├── SearchBnControl.xaml
│ │ ├── SearchBnControl.xaml.cs
│ │ └── SearchBnVm.cs
│ ├── SetId.cs
│ └── UserPage
│ │ ├── UserPageControl.xaml
│ │ ├── UserPageControl.xaml.cs
│ │ ├── UserPagePlugin.cs
│ │ ├── UserPageVm.cs
│ │ └── WebPrintScreen.cs
└── Services
│ ├── CacheManagerService.cs
│ ├── CliPrintMe.cs
│ ├── FailBindingReplyService.cs
│ ├── FailExecuteReplyService.cs
│ ├── MemoryCheckService.cs
│ ├── MessageAvoidRepeatService.cs
│ └── SensitiveMatchService.cs
├── Program.cs
├── Resources
└── Fonts
│ ├── SourceHanSerifCn.ttf
│ ├── SourceSansPro-Black.ttf
│ ├── SourceSansPro-Bold.ttf
│ ├── SourceSansPro-ExtraLight.ttf
│ ├── SourceSansPro-Light.ttf
│ ├── SourceSansPro-Regular.ttf
│ └── SourceSansPro-Semibold.ttf
├── ThirdParty
├── Moebooru
│ ├── Api.cs
│ └── Post.cs
├── TinyPinyin.Core.Standard
│ ├── Data
│ │ ├── PinyinCode1.cs
│ │ ├── PinyinCode2.cs
│ │ ├── PinyinCode3.cs
│ │ └── PinyinData.cs
│ ├── Engine.cs
│ ├── IPinyinDict.cs
│ ├── PinyinHelper.cs
│ ├── PinyinMapDict.cs
│ ├── SegmentationSelector.cs
│ └── readme.txt
├── ToolGood.Words
│ ├── BaseSearchEx2.cs
│ ├── StringSearchEx3.cs
│ ├── TrieNode.cs
│ ├── TrieNodeEx.cs
│ └── readme.txt
└── Tuling
│ ├── RequestModel
│ ├── InputImage.cs
│ ├── InputMedia.cs
│ ├── InputText.cs
│ ├── Location.cs
│ ├── Perception.cs
│ ├── Request.cs
│ ├── RequestType.cs
│ ├── SelfInfo.cs
│ └── UserInfo.cs
│ ├── ResponseModel
│ ├── Intent.cs
│ ├── Parameters.cs
│ ├── RequestType.cs
│ ├── Response.cs
│ ├── Result.cs
│ └── Values.cs
│ └── TulingClient.cs
├── Utils
├── EncryptUtil.cs
├── ExceptionExtensions.cs
└── StringUtil.cs
├── app.manifest
├── appsettings.yaml
└── daylily.csproj
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "depencencies/MilkiBotFramework"]
2 | path = depencencies/MilkiBotFramework
3 | url = https://github.com/Milkitic/MilkiBotFramework.git
4 |
--------------------------------------------------------------------------------
/Daylily.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32126.317
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "daylily", "daylily\daylily.csproj", "{DB9E989D-D1E8-49B8-861C-93294AA62758}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependencies", "dependencies", "{CAD00200-1E33-4ECF-BDB0-E4C44BAFD280}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MilkiBotFramework.Aspnetcore", "depencencies\MilkiBotFramework\src\MilkiBotFramework.Aspnetcore\MilkiBotFramework.Aspnetcore.csproj", "{5B90A1C7-04E1-428D-9212-2A26124CE6E6}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MilkiBotFramework", "depencencies\MilkiBotFramework\src\MilkiBotFramework\MilkiBotFramework.csproj", "{5732688A-40D5-4FD1-812D-09242527E059}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MilkiBotFramework.Imaging.Wpf", "depencencies\MilkiBotFramework\src\MilkiBotFramework.Imaging.Wpf\MilkiBotFramework.Imaging.Wpf.csproj", "{805400BF-AB0C-4D4F-925A-456A3680FC45}"
15 | EndProject
16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MilkiBotFramework.Platforms.GoCqHttp", "depencencies\MilkiBotFramework\src\Platforms\MilkiBotFramework.Platforms.GoCqHttp\MilkiBotFramework.Platforms.GoCqHttp.csproj", "{B35D87B8-FA4F-438C-A5D7-20CBDF8E5F78}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {DB9E989D-D1E8-49B8-861C-93294AA62758}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {DB9E989D-D1E8-49B8-861C-93294AA62758}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {DB9E989D-D1E8-49B8-861C-93294AA62758}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {DB9E989D-D1E8-49B8-861C-93294AA62758}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {5B90A1C7-04E1-428D-9212-2A26124CE6E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {5B90A1C7-04E1-428D-9212-2A26124CE6E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {5B90A1C7-04E1-428D-9212-2A26124CE6E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {5B90A1C7-04E1-428D-9212-2A26124CE6E6}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {5732688A-40D5-4FD1-812D-09242527E059}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {5732688A-40D5-4FD1-812D-09242527E059}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {5732688A-40D5-4FD1-812D-09242527E059}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {5732688A-40D5-4FD1-812D-09242527E059}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {805400BF-AB0C-4D4F-925A-456A3680FC45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {805400BF-AB0C-4D4F-925A-456A3680FC45}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {805400BF-AB0C-4D4F-925A-456A3680FC45}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {805400BF-AB0C-4D4F-925A-456A3680FC45}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {B35D87B8-FA4F-438C-A5D7-20CBDF8E5F78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {B35D87B8-FA4F-438C-A5D7-20CBDF8E5F78}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {B35D87B8-FA4F-438C-A5D7-20CBDF8E5F78}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {B35D87B8-FA4F-438C-A5D7-20CBDF8E5F78}.Release|Any CPU.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | GlobalSection(NestedProjects) = preSolution
49 | {5B90A1C7-04E1-428D-9212-2A26124CE6E6} = {CAD00200-1E33-4ECF-BDB0-E4C44BAFD280}
50 | {5732688A-40D5-4FD1-812D-09242527E059} = {CAD00200-1E33-4ECF-BDB0-E4C44BAFD280}
51 | {805400BF-AB0C-4D4F-925A-456A3680FC45} = {CAD00200-1E33-4ECF-BDB0-E4C44BAFD280}
52 | {B35D87B8-FA4F-438C-A5D7-20CBDF8E5F78} = {CAD00200-1E33-4ECF-BDB0-E4C44BAFD280}
53 | EndGlobalSection
54 | GlobalSection(ExtensibilityGlobals) = postSolution
55 | SolutionGuid = {A6BCFAD8-B150-476D-A331-F6289BBF956B}
56 | EndGlobalSection
57 | EndGlobal
58 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Milkitic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # daylily
2 | 我叫黄花菜w,是基于[MilkiBotFramework](https://github.com/Milkitic/MilkiBotFramework)的机器人。
3 | 这是我的开源仓库,包含了大部分的可开源的逻辑。
4 | > 请注意,实际运营的我包含着一些非公开插件。
5 |
6 | 本项目亦可作为MilkiBotFramework示例仓库,这将教你如何优雅地使用该框架编写机器人。
7 |
8 | ## 指引
9 | 不同插件使用了MilkiBotFramework的框架的不同处理方法,请根据实际需求参考。
10 |
11 | ### 基础互动插件
12 | * 【@检测】`CheckCqAt`:解析富文本、发送富文本,获取机器人账号信息
13 | * 【缩写查询】`CsxPlugin`:命令处理、调用HTTP请求
14 | * 【龙语识别】`DragonLanguage`:复杂逻辑相关
15 | * 【Ping-pong】`Echo`
16 | * 【关键词触发】`KeywordTrigger`
17 | * 【konachan】`Konachan`
18 | * 【获取随机数】`Roll`
19 | * 【自助禁言】`Smoke`:使用专用API
20 | * 【图灵】`Tuling`
21 | * 【洗衣机插件】`WashingMachine`:图片处理示例(包括gif)、上下文对话
22 | * 【今天吃什么】`WhatToEat`:托管配置处理,获取Bot配置文件
23 | * 【Bot帮助】`Help`:使用UI框架绘图,获取所有插件信息
24 |
25 | ### osu!插件
26 | * `AuthenticationController`:ASP.NET Core Web API的控制器声明
27 | * `OsuDbContext`:托管的数据库上下文声明
28 | * `ApiService`
29 | * 【绑定osu!账号】`SetId`:与Controller的互动、EventBus的使用、插件之间注入
30 | * 【成绩查询】`RecentPlayPlugin`:参数级权限声明
31 | * 【个人名片】`MePlugin`:使用托管的数据库
32 | * 【个人介绍页面】`UserPagePlugin`
33 | * 【BN信息搜索】`SearchBn`
34 | * 【地图数据订阅】`BeatmapStatistics`
35 |
36 | ### 基础处理/控制插件
37 | * 【控制台消息输出】`CliPrint`
38 | * 【命令热度分析】`CommandRate`
39 | * 【聊天记录(非持久化)】`MessageLogging`
40 | * 【插件管理】`PluginFilter`:控制插件行为、获取用户输入解析结果
41 | * 【睡眠】`PowerOff`:任务计划程序、原生MessageApi
42 | * 【睡眠控制消息输出】`PowerOffFilterService`:`ServicePlugin`的`BeforeSend`的使用
43 | * 【远程重启】`Reboot`:任务计划程序、原生MessageApi
44 | * 【发送自定义消息】`SendMessage`:限制命令的权限、群私聊
45 |
46 | ### 服务插件
47 | * 【缓存定时清理】`CacheManagerService`
48 | * 【控制台消息输出(Bot)】`CliPrintMe`
49 | * `FailBindingReplyService`:`ServicePlugin`的`OnBindingFailed`的使用
50 | * `FailExecuteReplyService`:`ServicePlugin`的`OnPluginException`的使用
51 | * 【内存溢出检测】`MemoryCheckService`
52 | * 【发言重复过滤】`MessageAvoidRepeatService`
53 | * 【敏感词屏蔽】`SensitiveMatchService`
54 |
55 | ## 许可声明
56 | 本项目代码部分采用MIT许可。
57 |
58 | 其中包含的Resources/Fonts的字体文件为 Google 的 Source Sans Pro,其采用 [Open Font License](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL)。
--------------------------------------------------------------------------------
/daylily.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/daylily/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 | pack://application:,,,/daylily;component/Resources/Fonts/#Source Sans Pro ExtraLight,pack://application:,,,/daylily;component/Resources/Fonts/#思源屏显臻宋 CN
8 | pack://application:,,,/daylily;component/Resources/Fonts/#Source Sans Pro Light,pack://application:,,,/daylily;component/Resources/Fonts/#思源屏显臻宋 CN
9 | pack://application:,,,/daylily;component/Resources/Fonts/#Source Sans Pro,pack://application:,,,/daylily;component/Resources/Fonts/#思源屏显臻宋 CN
10 | pack://application:,,,/daylily;component/Resources/Fonts/#Source Sans Pro Semibold,pack://application:,,,/daylily;component/Resources/Fonts/#思源屏显臻宋 CN
11 | pack://application:,,,/daylily;component/Resources/Fonts/#Source Sans Pro Black,pack://application:,,,/daylily;component/Resources/Fonts/#思源屏显臻宋 CN
12 |
13 |
14 |
--------------------------------------------------------------------------------
/daylily/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace daylily;
4 |
5 | public partial class App : Application
6 | {
7 | }
--------------------------------------------------------------------------------
/daylily/Configs.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.ComponentModel;
3 | using MilkiBotFramework.Messaging;
4 | using MilkiBotFramework.Plugining.Configuration;
5 | using MilkiBotFramework.Utils;
6 | using YamlDotNet.Serialization;
7 |
8 | // ReSharper disable once CheckNamespace
9 | namespace daylily;
10 |
11 | public sealed class PluginManagerConfig : ConfigurationBase
12 | {
13 | [Description("插件禁用列表")]
14 | [YamlMember(Alias = "DisabledList")]
15 | public ConcurrentDictionary> IdentityDisabledDictionary { get; set; } = new();
16 | }
17 |
18 | public sealed class TulingConfig : ConfigurationBase
19 | {
20 | public ConcurrentDictionary? ApiInfos { get; set; } = new();
21 | }
22 |
23 | public sealed class OsuConfig : ConfigurationBase
24 | {
25 | public string QQAesKey { get; set; } = "aeskey";
26 | public string QQAesIV { get; set; } = "aesiv";
27 | public int ClientId { get; set; } = 114514;
28 | public string ClientSecret { get; set; } = "ClientSecret";
29 | public string ServerRedirectUri { get; set; } = "ServerRedirectUri";
30 | public int ServerRedirectPort { get; set; } = 23333;
31 |
32 | }
33 |
34 | public class KeywordTriggerConfig : ConfigurationBase
35 | {
36 | public class TriggerObject
37 | {
38 | public TriggerObject()
39 | {
40 | }
41 |
42 | public TriggerObject(List words, List pictures, double chancePercent)
43 | {
44 | Words = words;
45 | Pictures = pictures;
46 | ChancePercent = chancePercent;
47 | }
48 |
49 | public List Words { get; set; }
50 | public List Pictures { get; set; }
51 | public double ChancePercent { get; set; }
52 | }
53 |
54 | public List UserDictionary { get; set; } = new List
55 | {
56 | new(new List { "我" },
57 | new List
58 | {
59 | "me1.jpg"
60 | }, 0.5),
61 | new(new List { "你" },
62 | new List
63 | {
64 | "you1.jpg", "you2.jpg"
65 | }, 2),
66 | new(new List { "为啥", "为什么", "为毛", "为嘛", "why " },
67 | new List
68 | {
69 | "why1.jpg"
70 | }, 20),
71 | new(new List { "看来", "原来" },
72 | new List
73 | {
74 | "kanlai1.jpg", "kanlai2.jpg"
75 | }, 30),
76 | new(new List { "黄花菜" },
77 | new List
78 | {
79 | "sb1.jpg", "sb2.jpg", "sb3.jpg", "sb4.jpg", "sb5.jpg", "sb6.jpg", "sb7.jpg", "sb8.jpg",
80 | "sb9.jpg"
81 | }, 50)
82 | };
83 | }
84 |
85 | public sealed class CommandRateConfig : ConfigurationBase
86 | {
87 | public ConcurrentDictionary CommandRate { get; set; } = new();
88 | }
89 |
90 | public sealed class DragonLanguageConfig : ConfigurationBase
91 | {
92 | public class UserExpression
93 | {
94 | public int Times { get; set; } = 1;
95 | public string Expression { get; set; }
96 | }
97 |
98 | public ConcurrentDictionary>? UserDictionary { get; set; } = new();
99 | }
100 |
101 | public sealed class ShuntDownConfig : ConfigurationBase
102 | {
103 | [YamlIgnore]
104 | internal readonly AsyncLock AsyncLock = new();
105 | public ConcurrentDictionary? ExpireTimeDictionary { get; set; } = new();
106 | }
107 |
108 | public sealed class WhatToEatConfig : ConfigurationBase
109 | {
110 | public class GroupMeals
111 | {
112 | [YamlIgnore]
113 | public readonly ReaderWriterLockSlim CollectionLock = new();
114 |
115 | [YamlIgnore]
116 | public DateTime Cooldown { get; set; } = DateTime.MinValue;
117 | [YamlIgnore]
118 | public Dictionary> UserCoolDownList { get; set; } = new();
119 | public bool IsSubChannelOnly { get; set; }
120 | public List Meals { get; set; } = new();
121 | }
122 |
123 | public ConcurrentDictionary Sessions { get; set; } = new();
124 | }
--------------------------------------------------------------------------------
/daylily/MyCommandLineAnalyzer.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using MilkiBotFramework;
3 | using MilkiBotFramework.Plugining.CommandLine;
4 |
5 | namespace daylily;
6 |
7 | public class MyCommandLineAnalyzer : CommandLineAnalyzer
8 | {
9 | public MyCommandLineAnalyzer(BotOptions botOptions) : base(botOptions)
10 | {
11 | }
12 |
13 | public override bool TryAnalyze(string input,
14 | [NotNullWhen(true)] out CommandLineResult? result,
15 | out CommandLineException? exception)
16 | {
17 | var source = input.AsSpan().Trim();
18 | if (source.Equals("帮助", StringComparison.InvariantCulture))
19 | {
20 | input = $"{GetCommandFlag()}help";
21 | }
22 | else if (source.Contains("吃啥", StringComparison.InvariantCulture) ||
23 | source.Contains("吃什么", StringComparison.InvariantCulture))
24 | {
25 | input = $"{GetCommandFlag()}what2eat";
26 | }
27 | else if (source.Equals("摆酒席", StringComparison.InvariantCulture))
28 | {
29 | input = $"{GetCommandFlag()}what2eat10";
30 | }
31 |
32 | return base.TryAnalyze(input, out result, out exception);
33 | }
34 | }
--------------------------------------------------------------------------------
/daylily/NLog.config:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/daylily/NLog.debug.config:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/CheckAt.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.IO;
3 | using MilkiBotFramework.ContactsManaging;
4 | using MilkiBotFramework.Messaging;
5 | using MilkiBotFramework.Messaging.RichMessages;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 |
9 | namespace daylily.Plugins.Basic;
10 |
11 | [PluginIdentifier("e6d765b3-a015-4192-9cc1-0cfa5c13ec55", "@检测")]
12 | [PluginLifetime(PluginLifetime.Scoped)]
13 | [Description("当自己被at时回击at对方")]
14 | public class CheckAt : BasicPlugin
15 | {
16 | private readonly IContactsManager _contactsManager;
17 |
18 | public CheckAt(IContactsManager contactsManager)
19 | {
20 | _contactsManager = contactsManager;
21 | }
22 |
23 | public override async IAsyncEnumerable OnMessageReceived(MessageContext context)
24 | {
25 | if (context.MessageIdentity?.MessageType != MessageType.Channel) yield break;
26 | var richMsg = context.GetRichMessage();
27 | var allAts = richMsg
28 | .Where(k => k is At)
29 | .Select(k => ((At)k).UserId)
30 | .ToHashSet();
31 | var result = await _contactsManager.TryGetOrUpdateSelfInfo();
32 | if (!result.IsSuccess) yield break;
33 | if (!allAts.Contains(result.SelfInfo!.UserId) && !allAts.Contains("-1")) yield break;
34 |
35 | await Task.Delay(Random.Shared.Next(5000));
36 | if (Random.Shared.NextDouble() < 0.9)
37 | {
38 | yield return Reply(new At(context.MessageUserIdentity!.UserId), false);
39 | }
40 | else
41 | {
42 | var imagePath = Path.Combine(PluginHome, "pandas", "at.jpg");
43 | yield return Reply(new FileImage(imagePath), false);
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/CsxPlugin.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Text;
3 | using System.Text.Json.Serialization;
4 | using System.Text.RegularExpressions;
5 | using MilkiBotFramework.Connecting;
6 | using MilkiBotFramework.Messaging;
7 | using MilkiBotFramework.Plugining;
8 | using MilkiBotFramework.Plugining.Attributes;
9 |
10 | namespace daylily.Plugins.Basic;
11 |
12 | [PluginIdentifier("77dabbaa-1f4a-49f0-9813-b6071936e7f6", "缩写查询")]
13 | [Description("缩写查询 (https://github.com/itorr/nbnhhsh)")]
14 | public class CsxPlugin : BasicPlugin
15 | {
16 | private readonly LightHttpClient _lightHttpClient;
17 |
18 | public CsxPlugin(LightHttpClient lightHttpClient)
19 | {
20 | _lightHttpClient = lightHttpClient;
21 | }
22 |
23 | private static readonly Regex Regex = new("[A-za-z0-9]+");
24 |
25 | [CommandHandler("csx")]
26 | public async Task Guess(
27 | [Argument, Description("缩写词或段落")] string? text = null)
28 | {
29 | if (string.IsNullOrWhiteSpace(text))
30 | return Reply("请提供关键词");
31 |
32 | var matches = Regex.Matches(text).Where(k => k.Success).ToArray();
33 | if (matches.Length == 0)
34 | return Reply("未检测到关键词");
35 |
36 | var enumerable = matches
37 | .SelectMany(k => k.Groups.Values
38 | .Where(o => o.Length > 1)
39 | .Select(o => o.Value))
40 | .Distinct()
41 | .ToList();
42 |
43 | if (enumerable.Count > 8)
44 | {
45 | return Reply("为防止刷屏,关键词不得同时超过8个");
46 | }
47 |
48 | text = string.Join(",", enumerable).ToLower();
49 | var result = await _lightHttpClient.HttpPost("https://lab.magiconch.com/api/nbnhhsh/guess",
50 | new Dictionary
51 | {
52 | ["text"] = text
53 | }
54 | );
55 |
56 | if (result.Length == 0 || result.All(k => k.Trans == null) && result.All(k => k.Inputting == null))
57 | return Reply("未检测到结果");
58 |
59 | var ret = string.Join("\r\n", result
60 | .Where(k => k.Trans != null || k.Inputting != null)
61 | .Select(k =>
62 | {
63 | var sb = new StringBuilder(k.Name + ": ");
64 | if (k.Trans != null)
65 | {
66 | sb.Append(string.Join(",", k.Trans));
67 | }
68 | else if (k.Inputting != null)
69 | {
70 | sb.Append(string.Join(",", k.Inputting.Select(o => o + " (大概?)")));
71 | }
72 |
73 | return sb.ToString();
74 | }));
75 | return Reply(ret);
76 | }
77 |
78 | private class Result
79 | {
80 | [JsonPropertyName("name")]
81 | public string Name { get; set; }
82 |
83 | [JsonPropertyName("trans")]
84 | public string[]? Trans { get; set; }
85 |
86 | [JsonPropertyName("inputting")]
87 | public string[]? Inputting { get; set; }
88 | }
89 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/Echo.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 |
6 | namespace daylily.Plugins.Basic;
7 |
8 | [PluginIdentifier("14f02b6a-44d5-4064-9e9d-c04796793ec7", "Ping-pong")]
9 | [Description("ping-pong!")]
10 | public class Echo : BasicPlugin
11 | {
12 | [CommandHandler("echo", Authority = MessageAuthority.Root)]
13 | public IResponse EchoHandler([Argument] string content, MessageContext context)
14 | {
15 | return Reply(context.CommandLineResult.SimpleArgument.ToString());
16 | }
17 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/HelpDetailControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using MilkiBotFramework.Imaging.Wpf;
16 | using Image = SixLabors.ImageSharp.Image;
17 |
18 | namespace daylily.Plugins.Basic.HelpPlugin
19 | {
20 | ///
21 | /// HelpDetailControl.xaml 的交互逻辑
22 | ///
23 | public sealed partial class HelpDetailControl : WpfDrawingControl
24 | {
25 | private readonly HelpDetailVm _viewModel;
26 |
27 | public HelpDetailControl(object viewModel, Image? sourceImage = null)
28 | : base(viewModel, sourceImage)
29 | {
30 | InitializeComponent();
31 | Loaded += async (_, _) => await FinishDrawing();
32 | _viewModel = (HelpDetailVm)ViewModel;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/HelpDetailVm.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace daylily.Plugins.Basic.HelpPlugin;
4 |
5 | public sealed class HelpDetailVm
6 | {
7 | public HelpDetailVm(PluginDetailVm pluginVm,char commandFlag)
8 | {
9 | PluginVm = pluginVm;
10 | CommandFlag = commandFlag;
11 | }
12 |
13 | public string AppName { get; } = Assembly.GetEntryAssembly()?.GetName().Name ?? "DynamicBot";
14 |
15 | public string VersionString { get; } = Assembly.GetEntryAssembly()
16 | ?.GetCustomAttribute()?.InformationalVersion ?? "0.0.1-alpha";
17 |
18 | public string CoreVersionString { get; } = typeof(MilkiBotFramework.Bot).Assembly
19 | .GetCustomAttribute()?.InformationalVersion ?? "0.0.1-alpha";
20 |
21 | public PluginDetailVm PluginVm { get; }
22 |
23 | public char CommandFlag { get; }
24 | //= new()
25 | //{
26 | // Name = "那没事了",
27 | // Helps = new List() { "demo help", "multi sentences" },
28 | // Commands = new List()
29 | // {
30 | // new CommandDefinition("nmsl", "你聋了", false),
31 | // new CommandDefinition("wslnm", "龙图小将", true),
32 | // },
33 | // FreeArgs = new List()
34 | // {
35 | // new StringKeyValuePair() { Key = "image", Value = null },
36 | // },
37 | // Args = new List()
38 | // {
39 | // new StringKeyValuePair() { Key = "x", Value = "抖动X范围" },
40 | // new StringKeyValuePair() { Key = "y", Value = "抖动Y范围" }
41 | // },
42 | // Authors = new[] { "lyt555", "test" },
43 | // Regexes = new List()
44 | // {
45 | // new RegexDefinition("test", "nmsl")
46 | // },
47 | // State = PluginVersion.Alpha,
48 | // Version = "1.0.0",
49 | // Usage = "[-x x_range] [image]",
50 | // CommandFlag = AppSettings.Default?.BotSettings.CommandFlag,
51 | // DefaultCommand = "lyt555"
52 | //};
53 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/HelpListControl.xaml:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
52 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
71 |
75 |
76 |
77 |
78 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
109 |
113 |
114 |
115 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/HelpListControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using MilkiBotFramework.Imaging.Wpf;
16 | using Image = SixLabors.ImageSharp.Image;
17 |
18 | namespace daylily.Plugins.Basic.HelpPlugin
19 | {
20 | ///
21 | /// HelpListControl.xaml 的交互逻辑
22 | ///
23 | public sealed partial class HelpListControl : WpfDrawingControl
24 | {
25 | private readonly HelpListVm _viewModel;
26 |
27 | public HelpListControl(object viewModel, Image? sourceImage = null)
28 | : base(viewModel, sourceImage)
29 | {
30 | InitializeComponent();
31 | Loaded += async (_, _) => await FinishDrawing();
32 | _viewModel = (HelpListVm)ViewModel;
33 | }
34 |
35 | private void HelpListControl_OnInitialized(object? sender, EventArgs e)
36 | {
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/HelpListVm.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace daylily.Plugins.Basic.HelpPlugin;
4 |
5 | public sealed class HelpListVm
6 | {
7 | public HelpListVm(IReadOnlyList assemblyInfoVms, char commandFlag)
8 | {
9 | AssemblyInfoVms = assemblyInfoVms;
10 | CommandFlag = commandFlag;
11 | }
12 |
13 | public string AppName { get; } = Assembly.GetEntryAssembly()?.GetName().Name ?? "DynamicBot";
14 |
15 | public string VersionString { get; } = Assembly.GetEntryAssembly()
16 | ?.GetCustomAttribute()?.InformationalVersion ?? "0.0.1-alpha";
17 |
18 | public string CoreVersionString { get; } = typeof(MilkiBotFramework.Bot).Assembly
19 | .GetCustomAttribute()?.InformationalVersion ?? "0.0.1-alpha";
20 |
21 | public IReadOnlyList AssemblyInfoVms { get; }
22 |
23 | public char CommandFlag { get; }
24 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/PluginDetailVm.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Plugining.Loading;
2 |
3 | namespace daylily.Plugins.Basic.HelpPlugin;
4 |
5 | public class PluginDetailVm
6 | {
7 | public PluginDetailVm(string name, string? description, string authors, PluginInfo pluginInfo,
8 | IReadOnlyCollection commands, CommandInfo? currentCommand)
9 | {
10 | Name = name;
11 | Description = description;
12 | Authors = authors;
13 | PluginInfo = pluginInfo;
14 | Commands = commands;
15 | CurrentCommand = currentCommand;
16 | }
17 |
18 | public string Name { get; }
19 | public string? Description { get; }
20 | public string Authors { get; }
21 | //public List Regexes { get; set; }
22 | public PluginInfo PluginInfo { get; }
23 | public IReadOnlyCollection Commands { get; }
24 | public CommandInfo? CurrentCommand { get; }
25 | public string? CurrentCommandUsage { get; set; }
26 | public IReadOnlyList CurrentArguments { get; set; } = Array.Empty();
27 | public IReadOnlyList CurrentOptions { get; set; } = Array.Empty();
28 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/PluginInfoVm.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Plugining.Loading;
2 |
3 | namespace daylily.Plugins.Basic.HelpPlugin;
4 |
5 | public sealed class PluginInfoVm
6 | {
7 | public PluginInfoVm(PluginInfo pluginInfo, IReadOnlyList commands)
8 | {
9 | PluginInfo = pluginInfo;
10 | Commands = commands;
11 | }
12 |
13 | public PluginInfo PluginInfo { get; }
14 | public IReadOnlyList Commands { get; }
15 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/ScopeInfoVm.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.Plugins.Basic.HelpPlugin;
2 |
3 | public sealed class ScopeInfoVm
4 | {
5 | public ScopeInfoVm(string scope, IReadOnlyList pluginInfos)
6 | {
7 | Scope = scope;
8 | PluginInfos = pluginInfos;
9 | }
10 |
11 | public string Scope { get; }
12 | public IReadOnlyList PluginInfos { get; }
13 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/String2DescriptionConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 |
4 | namespace daylily.Plugins.Basic.HelpPlugin;
5 |
6 | public class String2DescriptionConverter : IValueConverter
7 | {
8 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
9 | {
10 | if (value is string str && !string.IsNullOrWhiteSpace(str))
11 | return str.Trim();
12 | return "暂无帮助信息";
13 | }
14 |
15 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
16 | {
17 | throw new NotImplementedException();
18 | }
19 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/String2LowerSnakeConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows.Data;
3 | using daylily.Utils;
4 |
5 | namespace daylily.Plugins.Basic.HelpPlugin;
6 |
7 | public class String2LowerSnakeConverter : IValueConverter
8 | {
9 | public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | if (value is string str)
12 | return str.ToLowerSnake();
13 | return value?.ToString()?.ToLowerSnake();
14 | }
15 |
16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
17 | {
18 | throw new NotImplementedException();
19 | }
20 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/HelpPlugin/ZeroToCollapsedConverter.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 | using System.Windows;
3 | using System.Windows.Data;
4 |
5 | namespace daylily.Plugins.Basic.HelpPlugin;
6 |
7 | public class ZeroToCollapsedConverter : IValueConverter
8 | {
9 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
10 | {
11 | try
12 | {
13 | var i = System.Convert.ToInt32(value);
14 | if (i == 0) return Visibility.Collapsed;
15 | return Visibility.Visible;
16 | }
17 | catch (Exception e)
18 | {
19 | return Visibility.Visible;
20 | }
21 | }
22 |
23 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
24 | {
25 | throw new NotImplementedException();
26 | }
27 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/KeywordTrigger.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Diagnostics.CodeAnalysis;
3 | using System.IO;
4 | using MilkiBotFramework.Messaging;
5 | using MilkiBotFramework.Messaging.RichMessages;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 | using MilkiBotFramework.Plugining.Configuration;
9 |
10 | namespace daylily.Plugins.Basic;
11 |
12 | [PluginIdentifier("1abf7c43-bafb-4536-a2be-e9aff8a2fc51", "关键词触发")]
13 | [Description("收到已给的关键词时,根据已给几率返回一张熊猫图。")]
14 | public class KeywordTrigger : BasicPlugin
15 | {
16 | private readonly KeywordTriggerConfig _config;
17 |
18 | public KeywordTrigger(IConfiguration configuration)
19 | {
20 | _config = configuration.Instance;
21 | }
22 |
23 | protected override async Task OnInitialized()
24 | {
25 | _config.UserDictionary.Sort(new TriggerComparer());
26 | _config.UserDictionary.RemoveAll(p => p == null);
27 | await _config.SaveAsync();
28 | }
29 |
30 | public override async IAsyncEnumerable OnMessageReceived(MessageContext context)
31 | {
32 | var msg = await new RichMessage(context.GetRichMessage().Where(k => k is Text))
33 | .EncodeAsync();
34 | foreach (var item in _config.UserDictionary)
35 | {
36 | if (Trig(msg, item.Words, item.Pictures, out var img, item.ChancePercent))
37 | {
38 | yield return Reply(new FileImage(Path.Combine(PluginHome, "panda", img)));
39 | yield break;
40 | }
41 | }
42 | }
43 |
44 | private static bool Trig(string message, IEnumerable keywords, IReadOnlyList pics,
45 | [NotNullWhen(true)] out string? imgP, double chancePercent = 10)
46 | {
47 | var chance = chancePercent / 100d;
48 | if (keywords.Any(k => message.Contains(k, StringComparison.InvariantCultureIgnoreCase)))
49 | {
50 | imgP = pics[Random.Shared.Next(pics.Count)];
51 | return Random.Shared.NextDouble() < chance;
52 | }
53 |
54 | imgP = null;
55 | return false;
56 | }
57 |
58 | private class TriggerComparer : IComparer
59 | {
60 | public int Compare(KeywordTriggerConfig.TriggerObject? x, KeywordTriggerConfig.TriggerObject? y)
61 | {
62 | if (ReferenceEquals(x, y)) return 0;
63 | if (ReferenceEquals(null, y)) return 1;
64 | if (ReferenceEquals(null, x)) return -1;
65 | return x.ChancePercent.CompareTo(y.ChancePercent);
66 | }
67 | }
68 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/Konachan.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using daylily.ThirdParty.Moebooru;
3 | using MilkiBotFramework.Connecting;
4 | using MilkiBotFramework.Messaging;
5 | using MilkiBotFramework.Messaging.RichMessages;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 |
9 | namespace daylily.Plugins.Basic;
10 |
11 | [PluginIdentifier("a6cbd411-499f-4dde-bdb0-fc17431cb6c9", "konachan", Authors = "bleatingsheep")]
12 | [Description("设了")]
13 | public class Konachan : BasicPlugin
14 | {
15 | private readonly LightHttpClient _lightHttpClient;
16 |
17 | public Konachan(LightHttpClient lightHttpClient)
18 | {
19 | _lightHttpClient = lightHttpClient;
20 | }
21 |
22 | [CommandHandler("konachan")]
23 | public async Task OnKonachan()
24 | {
25 | return await GetResponse("https://konachan.net");
26 | }
27 |
28 | [CommandHandler("yandere")]
29 | public async Task OnYandere()
30 | {
31 | return await GetResponse("https://yande.re");
32 | }
33 |
34 | private async Task GetResponse(string domain)
35 | {
36 | var k = new Api(domain);
37 | var result = await k.PopularRecentAsync(_lightHttpClient);
38 | var post = result?.FirstOrDefault();
39 | return post == null ? null : Reply(new LinkImage(post.JpegUrl));
40 | }
41 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/PeroControl.xaml:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
63 |
64 |
69 |
70 |
71 |
72 |
73 |
74 |
80 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/PeroControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 | using System.Windows.Controls;
9 | using System.Windows.Data;
10 | using System.Windows.Documents;
11 | using System.Windows.Input;
12 | using System.Windows.Media;
13 | using System.Windows.Media.Imaging;
14 | using System.Windows.Navigation;
15 | using System.Windows.Shapes;
16 | using MilkiBotFramework.Connecting;
17 | using MilkiBotFramework.Data;
18 | using MilkiBotFramework.Imaging.Wpf;
19 | using MilkiBotFramework.Imaging;
20 | using MilkiBotFramework.Messaging.RichMessages;
21 | using MilkiBotFramework.Messaging;
22 | using MilkiBotFramework.Plugining.Attributes;
23 | using MilkiBotFramework.Plugining;
24 | using Image = SixLabors.ImageSharp.Image;
25 | using Path = System.IO.Path;
26 |
27 | namespace daylily.Plugins.Basic;
28 |
29 | [PluginIdentifier("411dd6b0-5557-4255-94ba-d31dced4a89e", "舔", Scope = "daylily",
30 | Authors = "milkiticyf")]
31 | public class Pero : BasicPlugin
32 | {
33 | private readonly LightHttpClient _lightHttpClient;
34 |
35 | public Pero(LightHttpClient lightHttpClient)
36 | {
37 | _lightHttpClient = lightHttpClient;
38 | }
39 |
40 | [CommandHandler("pero")]
41 | public async Task PeroCore(MessageContext context)
42 | {
43 | var richMsg = context.GetRichMessage();
44 | var userId = richMsg.OfType().FirstOrDefault()?.UserId;
45 | byte[]? avatarBytes;
46 | if (userId != null)
47 | {
48 | var uri = $"http://q1.qlogo.cn/g?b=qq&nk={userId}&s=640";
49 | (avatarBytes, _) = await _lightHttpClient.GetImageBytesFromUrlAsync(uri);
50 | }
51 | else
52 | {
53 | var firstImage = richMsg.OfType().FirstOrDefault();
54 | if (firstImage != null)
55 | {
56 | (avatarBytes, _) = await _lightHttpClient.GetImageBytesFromUrlAsync(firstImage.Uri);
57 | }
58 | else
59 | {
60 | var qq = context.MessageUserIdentity?.UserId;
61 | var uri = $"http://q1.qlogo.cn/g?b=qq&nk={qq}&s=640";
62 | (avatarBytes, _) = await _lightHttpClient.GetImageBytesFromUrlAsync(uri);
63 | }
64 | }
65 |
66 | var renderer = new WpfDrawingProcessor(true);
67 |
68 | var peroVm = new PeroVm(avatarBytes, Path.Combine(PluginHome, "base.png"));
69 | var image = await renderer.ProcessAsync(peroVm);
70 | return Reply(new MemoryImage(image, ImageType.Png));
71 | }
72 | }
73 |
74 | public class PeroVm : ViewModelBase
75 | {
76 | private ImageSource? _avatar;
77 |
78 | public PeroVm(byte[] avatarBytes, string baseImagePath)
79 | {
80 | AvatarBytes = avatarBytes;
81 | BaseImagePath = Path.GetFullPath(baseImagePath);
82 | }
83 |
84 | public string BaseImagePath { get; }
85 | public byte[] AvatarBytes { get; }
86 | public ImageSource? Avatar
87 | {
88 | get => _avatar;
89 | set => SetField(ref _avatar, value);
90 | }
91 | }
92 |
93 | ///
94 | /// PeroControl.xaml 的交互逻辑
95 | ///
96 | public partial class PeroControl : WpfDrawingControl
97 | {
98 | private readonly PeroVm _viewModel;
99 |
100 | public PeroControl(object viewModel, Image? sourceImage = null)
101 | : base(viewModel, sourceImage)
102 | {
103 | InitializeComponent();
104 | Loaded += async (_, _) => await FinishDrawing();
105 | _viewModel = (PeroVm)ViewModel;
106 |
107 | var memoryStream = new MemoryStream(_viewModel.AvatarBytes);
108 | var bitmapSource = new BitmapImage();
109 |
110 | bitmapSource.BeginInit();
111 | bitmapSource.StreamSource = memoryStream;
112 | bitmapSource.CacheOption = BitmapCacheOption.None;
113 | bitmapSource.EndInit();
114 |
115 | _viewModel.Avatar = bitmapSource;
116 | }
117 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/Roll.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 |
6 | namespace daylily.Plugins.Basic;
7 |
8 | [PluginIdentifier("bbcfc459-20b2-483b-89be-d7fe3289010d", "获取随机数")]
9 | [Description("获取一个或多个随机数。")]
10 | public class Roll : BasicPlugin
11 | {
12 | [CommandHandler("roll")]
13 | public IResponse RollNumbers(
14 | [Option("r"), Description("若启用,则使抽取含重复结果。否则结果不包含重复结果。")]
15 | bool repeat = false,
16 | [Argument, Description("当参数(m)为无效参数时,此参数(n)为上界(0~n)。否则此参数为下界(n~m)。")]
17 | int? param1 = null,
18 | [Argument, Description("此参数(m)为上界(n~m)。")]
19 | int? param2 = null,
20 | [Argument, Description("此参数(c)为抽取的数量。")]
21 | int? count = null)
22 | {
23 | var rand = Random.Shared;
24 |
25 | if (param1 == null)
26 | return Reply(GetRand(rand).ToString());
27 | if (param2 == null)
28 | return Reply(GetRand(param1.Value, rand).ToString());
29 | if (count == null)
30 | return Reply(GetRand(param1.Value, param2.Value, rand).ToString());
31 | return Reply(GetRandMessage(param1.Value, param2.Value, repeat, count.Value, rand));
32 | }
33 |
34 | private static int GetRand(Random random) => random.Next(0, 101);
35 |
36 | private static int GetRand(int uBound, Random random) => random.Next(0, uBound + 1);
37 |
38 | private static int GetRand(int lBound, int uBound, Random random) => random.Next(lBound, uBound + 1);
39 |
40 | private static string GetRandMessage(int lBound, int uBound, bool repeat, int count, Random random)
41 | {
42 | uBound += 1;
43 | if (uBound - lBound > 1000) return "总数只支持1000以内……";
44 | if (count > 30) return "次数不能大于30……";
45 | if (count < 0) return "缩不粗化";
46 |
47 | List newList = new List();
48 | if (repeat || count > uBound - lBound)
49 | {
50 | string repMsg = ((count > uBound - lBound) || repeat) ? "(包含重复结果)" : "";
51 | for (int i = 0; i < count; i++)
52 | {
53 | newList.Add(GetRand(lBound, uBound - 1, random));
54 | }
55 |
56 | newList.Sort();
57 | return repMsg + string.Join(", ", newList);
58 | }
59 |
60 | // else
61 | List list = new List();
62 | for (int i = lBound; i < uBound; i++)
63 | list.Add(i);
64 |
65 | for (int i = 0; i < count; i++)
66 | {
67 | int index = random.Next(0, list.Count);
68 | newList.Add(list[index]);
69 | list.RemoveAt(index);
70 | }
71 |
72 | newList.Sort();
73 | return string.Join(", ", newList);
74 | }
75 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/RpsDice.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Platforms.GoCqHttp.Messaging.CqCodes;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 |
7 | namespace daylily.Plugins.Basic;
8 |
9 | [PluginIdentifier("d6ba1003-4c02-46d6-94c5-52b737f7b967", "猜拳/掷骰子")]
10 | [Description("发送掷骰子或猜拳魔法表情。")]
11 | internal class RpsDice : BasicPlugin
12 | {
13 | [CommandHandler("rps")]
14 | [Description("掷骰子")]
15 | public IResponse Rps()
16 | {
17 | return Reply(CQRps.Instance);
18 | }
19 |
20 | [CommandHandler("dice")]
21 | [Description("猜拳")]
22 | public IResponse Dice()
23 | {
24 | return Reply(CQDice.Instance);
25 | }
26 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/Smoke.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Microsoft.Extensions.Logging;
3 | using MilkiBotFramework.ContactsManaging;
4 | using MilkiBotFramework.ContactsManaging.Models;
5 | using MilkiBotFramework.Messaging;
6 | using MilkiBotFramework.Platforms.GoCqHttp.Connecting;
7 | using MilkiBotFramework.Plugining;
8 | using MilkiBotFramework.Plugining.Attributes;
9 |
10 | namespace daylily.Plugins.Basic;
11 |
12 | [PluginIdentifier("4c729d16-3954-4e70-ad4c-8a0ea72efe1a", "自助禁言")]
13 | [Description("当${BotNick}是管理员时,将命令发送者禁言(30分钟到12小时)。")]
14 | public class Smoke : BasicPlugin
15 | {
16 | private readonly IContactsManager _contactsManager;
17 | private readonly ILogger _logger;
18 | private readonly GoCqApi _goCqApi;
19 |
20 | public Smoke(IContactsManager contactsManager, ILogger logger, GoCqApi goCqApi)
21 | {
22 | _contactsManager = contactsManager;
23 | _logger = logger;
24 | _goCqApi = goCqApi;
25 | }
26 |
27 | [CommandHandler("sleep", AllowedMessageType = MessageType.Channel)]
28 | public async Task SmokeHandler(MessageContext context,
29 | [Argument, Description("要禁言的时长,小时为单位,支持小数")] double sleepTime = 0)
30 | {
31 | var userIdentity = context.MessageUserIdentity;
32 | var messageIdentity = userIdentity!.MessageIdentity;
33 | var userId = userIdentity.UserId;
34 | var channelId = messageIdentity.Id!;
35 |
36 | var self = await _contactsManager.TryGetOrUpdateSelfInfo();
37 | if (self.IsSuccess)
38 | {
39 | var groupMember = await _contactsManager.TryGetOrAddMemberInfo(
40 | channelId, self.SelfInfo!.UserId, messageIdentity.SubId);
41 | if (groupMember.IsSuccess)
42 | {
43 | if (groupMember.MemberInfo!.MemberRole == MemberRole.Member)
44 | {
45 | return Reply("${BotNick}不是管理员,没办法自助禁言o");
46 | }
47 | }
48 | }
49 |
50 | if (sleepTime == 0)
51 | {
52 | return Reply("要禁多少小时?");
53 | }
54 |
55 | if (sleepTime > 12)
56 | {
57 | sleepTime = 12;
58 | }
59 | else if (sleepTime < 0.5)
60 | {
61 | sleepTime = 0.5;
62 | }
63 | else if (sleepTime > 0)
64 | {
65 | //ignore
66 | }
67 | else
68 | {
69 | return Reply("处于4维时空的我们,可不允许在时间轴上走回头路..");
70 | }
71 |
72 | var totalTime = TimeSpan.FromHours(sleepTime);
73 | try
74 | {
75 | await _goCqApi.SetGroupBan(long.Parse(channelId), long.Parse(userId), totalTime);
76 | }
77 | catch (Exception ex)
78 | {
79 | _logger.LogWarning(ex, "禁言时出错");
80 | return Reply("由于不可抗力,${BotNick}没有办法禁言..");
81 | }
82 |
83 | return Reply($"祝你一觉睡到{DateTime.Now.AddHours(sleepTime):HH:mm} 🙂");
84 | }
85 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Basic/Tuling.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using System.ComponentModel;
3 | using daylily.ThirdParty.Tuling;
4 | using MilkiBotFramework.Messaging;
5 | using MilkiBotFramework.Messaging.RichMessages;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 | using MilkiBotFramework.Plugining.Configuration;
9 | using MilkiBotFramework.Tasking;
10 |
11 | namespace daylily.Plugins.Basic;
12 |
13 | [PluginIdentifier("14e60bec-dc11-45da-8654-baed42588745", "图灵")]
14 | [Description("不时进行成精发言")]
15 | [PluginLifetime(PluginLifetime.Singleton)]
16 | public class Tuling : BasicPlugin
17 | {
18 | private readonly BotTaskScheduler _taskScheduler;
19 | private readonly TulingClient _tulingClient;
20 | private readonly TulingConfig _config;
21 |
22 | public Tuling(IConfiguration configuration,
23 | BotTaskScheduler taskScheduler,
24 | TulingClient tulingClient)
25 | {
26 | _taskScheduler = taskScheduler;
27 | _tulingClient = tulingClient;
28 | _config = configuration.Instance;
29 | }
30 |
31 | protected override async Task OnInitialized()
32 | {
33 | _config.ApiInfos ??= new ConcurrentDictionary();
34 | _taskScheduler.AddTask("ResetTuling", k => k
35 | .EachDayAt(new DateTime(1, 1, 1, 0, 0, 0, 0))
36 | .Do(ResetCount));
37 | }
38 |
39 | public override async IAsyncEnumerable OnMessageReceived(MessageContext context)
40 | {
41 | var rnd = Random.Shared.NextDouble();
42 | if (rnd >= 1d / 60)
43 | {
44 | yield break;
45 | }
46 |
47 | var text = await new RichMessage(context.GetRichMessage().Where(k => k is Text)).EncodeAsync();
48 | if (string.IsNullOrWhiteSpace(text))
49 | {
50 | yield break;
51 | }
52 |
53 | var apiInfos = _config.ApiInfos!;
54 | var apis = apiInfos
55 | .Where(k => k.Value < 100)
56 | .Select(k => k.Key)
57 | .ToArray();
58 | if (apis.Length == 0)
59 | {
60 | yield break;
61 | }
62 |
63 | var apiKey = apis[Random.Shared.Next(apis.Length)];
64 |
65 | var response = await _tulingClient.SendText(apiKey, text,
66 | context.MessageUserIdentity.UserId,
67 | context.MessageIdentity.ToString());
68 | if (response.Intent.Code == 4003)
69 | {
70 | apiInfos[apiKey] = 100;
71 | }
72 | else
73 | {
74 | apiInfos[apiKey]++;
75 | }
76 |
77 | await _config.SaveAsync();
78 | if (response.Intent.Code != 10004)
79 | {
80 | yield break;
81 | }
82 |
83 | var result = response.Results.FirstOrDefault();
84 | if (result?.Values.Text == null)
85 | {
86 | yield break;
87 | }
88 |
89 | var str = result.Values.Text.Trim('。', '?', '?', '!', '!', '~', '~');
90 | await Task.Delay(Random.Shared.Next(2, 7));
91 | yield return Reply(str, false);
92 | }
93 |
94 | private void ResetCount(TaskContext context, CancellationToken token)
95 | {
96 | foreach (var key in _config.ApiInfos.Keys)
97 | {
98 | _config.ApiInfos[key] = 0;
99 | }
100 |
101 | _config.SaveAsync().Wait(token);
102 | }
103 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/CliPrint.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Microsoft.Extensions.Logging;
3 | using MilkiBotFramework.Messaging;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 |
7 | namespace daylily.Plugins.Core;
8 |
9 | [PluginIdentifier("9a71f0a2-fd2e-4d7e-abd8-681d14d0d83e", "控制台消息输出", AllowDisable = false, Index = -99)]
10 | [PluginLifetime(PluginLifetime.Singleton)]
11 | [Description("用于后台DEBUG")]
12 | public class CliPrint : BasicPlugin
13 | {
14 | private readonly ILogger _logger;
15 |
16 | public CliPrint(ILoggerFactory loggerFactory)
17 | {
18 | _logger = loggerFactory.CreateLogger("raw");
19 | }
20 |
21 | public override IAsyncEnumerable OnMessageReceived(MessageContext context)
22 | {
23 | var identity = context.MessageIdentity!;
24 | string session = "未知会话";
25 | string sender = "未知发送者";
26 |
27 | if (identity.MessageType == MessageType.Private)
28 | {
29 | var privateInfo = context.PrivateInfo!;
30 | session = "私聊 " + privateInfo.UserId + " - " +
31 | (privateInfo.Remark ?? privateInfo.Nickname ?? privateInfo.UserId);
32 | sender = "对方";
33 | }
34 | else if (identity.MessageType == MessageType.Channel)
35 | {
36 | var channelInfo = context.ChannelInfo!;
37 | var memberInfo = context.MemberInfo!;
38 | session = identity.SubId == null
39 | ? $"群 {channelInfo.ChannelId} - {channelInfo.Name}"
40 | : $"频道 {channelInfo.ChannelId}.{channelInfo.SubChannelId} - {channelInfo.Name}";
41 | var name = memberInfo.Card ?? memberInfo.Nickname ?? memberInfo.UserId;
42 | sender = name == memberInfo.UserId ? name : name + $" ({memberInfo.UserId})";
43 | }
44 |
45 | var richMessage = context.GetRichMessage().ToString();
46 | var actualMessage = string.Join('\n', richMessage.Split('\n').Select(k => " " + k));
47 |
48 | _logger.LogInformation($"{context.ReceivedTime.LocalDateTime} ({session}) {sender}:\r\n" +
49 | $"{actualMessage}");
50 |
51 | return base.OnMessageReceived(context);
52 | }
53 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/CommandRate.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 | using MilkiBotFramework.Plugining.Configuration;
6 |
7 | namespace daylily.Plugins.Core;
8 |
9 | [PluginIdentifier("fe577f01-b63f-45e2-88bd-3236224b93b9", "命令统计", Index = -10, AllowDisable = false)]
10 | [Description("统计命令的使用情况,分析常用命令")]
11 | public class CommandRate : BasicPlugin
12 | {
13 | private readonly IConfiguration _config;
14 |
15 | public CommandRate(IConfiguration config)
16 | {
17 | _config = config;
18 | }
19 |
20 | public override async IAsyncEnumerable OnMessageReceived(MessageContext context)
21 | {
22 | var commandLineResult = context.CommandLineResult;
23 | if (commandLineResult == null) yield break;
24 |
25 | var command = commandLineResult.Command.ToString()!;
26 | var plugin = context.NextPlugins.FirstOrDefault(k => k.Commands.ContainsKey(command)) ??
27 | context.ExecutedPlugins.FirstOrDefault(k => k.Commands.ContainsKey(command));
28 |
29 | if (plugin == null) yield break;
30 |
31 | _config.Instance.CommandRate.AddOrUpdate(command, 1, (_, i) => i + 1);
32 | await _config.Instance.SaveAsync();
33 | }
34 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/GuiManaging/GuiManager.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using MilkiBotFramework;
3 | using MilkiBotFramework.Imaging.Wpf.Internal;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 |
7 | namespace daylily.Plugins.Core.GuiManaging;
8 |
9 | [PluginIdentifier("18bd9084-ece9-48e9-8c2d-91e26ca494de")]
10 | public class GuiManager : ServicePlugin
11 | {
12 | private readonly Bot _bot;
13 | private readonly PluginManager _pluginManager;
14 |
15 | public GuiManager(Bot bot, PluginManager pluginManager)
16 | {
17 | _bot = bot;
18 | _pluginManager = pluginManager;
19 | }
20 |
21 | protected override async Task OnInitialized()
22 | {
23 | await UiThreadHelper.EnsureUiThreadAsync();
24 | await Application.Current.Dispatcher.InvokeAsync(() =>
25 | {
26 | var managerWindow = new ManagerWindow(_bot, _pluginManager.GetAllPlugins());
27 | managerWindow.Show();
28 | });
29 | }
30 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/GuiManaging/ManagerWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/daylily/Plugins/Core/GuiManaging/ManagerWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Shapes;
16 | using MilkiBotFramework;
17 | using MilkiBotFramework.Plugining;
18 | using MilkiBotFramework.Plugining.Loading;
19 |
20 | namespace daylily.Plugins.Core.GuiManaging;
21 |
22 | ///
23 | /// ManagerWindow.xaml 的交互逻辑
24 | ///
25 | internal partial class ManagerWindow : Window
26 | {
27 | private readonly Bot _bot;
28 | public IReadOnlyList Plugins { get; }
29 |
30 | public ManagerWindow(Bot bot, IReadOnlyList plugins)
31 | {
32 | _bot = bot;
33 | Plugins = plugins;
34 | InitializeComponent();
35 | }
36 |
37 | private async void ManagerWindow_OnClosing(object? sender, CancelEventArgs e)
38 | {
39 | var messageBoxResult = MessageBox.Show("退出Bot?", "退出确认",
40 | MessageBoxButton.OKCancel, MessageBoxImage.Question);
41 | if (messageBoxResult != MessageBoxResult.OK)
42 | {
43 | e.Cancel = true;
44 | }
45 | else
46 | {
47 | await _bot.StopAsync();
48 | }
49 | }
50 |
51 | private void ButtonOpenHome_OnClick(object sender, RoutedEventArgs e)
52 | {
53 | var homePath = (string)((Button)sender).Tag;
54 | var fullPath = System.IO.Path.GetFullPath(homePath);
55 | Process.Start("explorer.exe", fullPath);
56 | //Process.Start("explorer.exe", $"/select,\"{fullPath}\"");
57 | }
58 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/GuiMessage/GuiMessageSend.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using MilkiBotFramework;
3 | using MilkiBotFramework.Connecting;
4 | using MilkiBotFramework.ContactsManaging;
5 | using MilkiBotFramework.Imaging.Wpf.Internal;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 |
9 | namespace daylily.Plugins.Core.GuiMessage;
10 |
11 | [PluginIdentifier("8bb00a92-a2ce-4985-93c6-48d2958f1a97")]
12 | public class GuiMessageSend : ServicePlugin
13 | {
14 | private readonly Bot _bot;
15 | private readonly IContactsManager _contactsManager;
16 | private readonly IMessageApi _messageApi;
17 |
18 | public GuiMessageSend(IContactsManager contactsManager, IMessageApi messageApi, Bot bot)
19 | {
20 | _contactsManager = contactsManager;
21 | _messageApi = messageApi;
22 | _bot = bot;
23 | }
24 |
25 | protected override async Task OnInitialized()
26 | {
27 | await UiThreadHelper.EnsureUiThreadAsync();
28 | await Application.Current.Dispatcher.InvokeAsync(() =>
29 | {
30 | var guiMessageWindow = new GuiMessageWindow(_contactsManager, _messageApi, _bot);
31 | guiMessageWindow.Show();
32 | });
33 | }
34 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/GuiMessage/GuiMessageWindow.xaml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
41 |
49 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/daylily/Plugins/Core/GuiMessage/GuiMessageWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Channels;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Data;
11 | using System.Windows.Documents;
12 | using System.Windows.Input;
13 | using System.Windows.Media;
14 | using System.Windows.Media.Imaging;
15 | using System.Windows.Shapes;
16 | using MilkiBotFramework;
17 | using MilkiBotFramework.Connecting;
18 | using MilkiBotFramework.ContactsManaging;
19 | using MilkiBotFramework.ContactsManaging.Models;
20 | using MilkiBotFramework.Data;
21 | using static ICSharpCode.AvalonEdit.Document.TextDocumentWeakEventManager;
22 |
23 | namespace daylily.Plugins.Core.GuiMessage;
24 |
25 | public class GuiMessageWindowVm : ViewModelBase
26 | {
27 | private ObservableCollection _allChannels = new();
28 | private ObservableCollection _allPrivates = new();
29 | private ChannelInfo? _selectedChannel;
30 |
31 | public ObservableCollection AllChannels
32 | {
33 | get => _allChannels;
34 | set => SetField(ref _allChannels, value);
35 | }
36 |
37 | public ObservableCollection AllPrivates
38 | {
39 | get => _allPrivates;
40 | set => SetField(ref _allPrivates, value);
41 | }
42 |
43 | public ChannelInfo? SelectedChannel
44 | {
45 | get => _selectedChannel;
46 | set => SetField(ref _selectedChannel, value);
47 | }
48 | }
49 |
50 | ///
51 | /// GuiMessageWindow.xaml 的交互逻辑
52 | ///
53 | public partial class GuiMessageWindow : Window
54 | {
55 | private readonly IContactsManager _contactsManager;
56 | private readonly IMessageApi _messageApi;
57 | private readonly Bot _bot;
58 | private readonly GuiMessageWindowVm _viewModel;
59 |
60 | private readonly HashSet _privateInfos = new();
61 | private readonly HashSet _channelInfos = new();
62 | public GuiMessageWindow(IContactsManager contactsManager, IMessageApi messageApi, Bot bot)
63 | {
64 | InitializeComponent();
65 | DataContext = _viewModel = new GuiMessageWindowVm();
66 | _contactsManager = contactsManager;
67 | _messageApi = messageApi;
68 | _bot = bot;
69 | }
70 |
71 | private void GuiMessageWindow_OnLoaded(object sender, RoutedEventArgs e)
72 | {
73 | new Task(() =>
74 | {
75 | while (true)
76 | {
77 | var channels = _contactsManager.GetAllChannels();
78 | var newChannels = new HashSet(channels);
79 | foreach (var channel in _channelInfos.Where(channel => !newChannels.Contains(channel)))
80 | {
81 | _channelInfos.Remove(channel);
82 | Dispatcher.Invoke(() => _viewModel.AllChannels.Remove(channel));
83 | }
84 |
85 | foreach (var channel in newChannels.Where(channel => !_channelInfos.Contains(channel)))
86 | {
87 | _channelInfos.Add(channel);
88 | Dispatcher.Invoke(() => _viewModel.AllChannels.Add(channel));
89 | }
90 |
91 | var privates = _contactsManager.GetAllPrivates();
92 | var newPrivates = new HashSet(privates);
93 | foreach (var privateInfo in _privateInfos.Where(channel => !newPrivates.Contains(channel)))
94 | {
95 | _privateInfos.Remove(privateInfo);
96 | Dispatcher.Invoke(() => _viewModel.AllPrivates.Remove(privateInfo));
97 | }
98 |
99 | foreach (var privateInfo in newPrivates.Where(channel => !_privateInfos.Contains(channel)))
100 | {
101 | _privateInfos.Add(privateInfo);
102 | Dispatcher.Invoke(() => _viewModel.AllPrivates.Add(privateInfo));
103 | }
104 |
105 | Thread.Sleep(5000);
106 | }
107 | }).Start();
108 | }
109 |
110 | private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
111 | {
112 | await SendMessage();
113 | }
114 |
115 | private async void TbMessage_OnKeyDown(object sender, KeyEventArgs e)
116 | {
117 | if (e.Key != Key.Enter) return;
118 | if (Keyboard.Modifiers == ModifierKeys.Control)
119 | {
120 | await SendMessage();
121 | }
122 | else
123 | {
124 | TbMessage.Text += Environment.NewLine;
125 | }
126 |
127 | e.Handled = true;
128 | }
129 |
130 | private async Task SendMessage()
131 | {
132 | var message = TbMessage.Text;
133 | if (string.IsNullOrEmpty(message)) return;
134 | if (_viewModel.SelectedChannel is not { } channelInfo) return;
135 | await _messageApi.SendChannelMessageAsync(channelInfo.ChannelId, message, null, null, null);
136 | TbMessage.Text = "";
137 | }
138 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/MessageLogging.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 |
6 | namespace daylily.Plugins.Core;
7 |
8 | [PluginIdentifier("4c955ee3-3826-44a0-8c80-8f8507ead572", AllowDisable = false)]
9 | [PluginLifetime(PluginLifetime.Singleton)]
10 | public class MessageLogging : BasicPlugin
11 | {
12 | public ConcurrentDictionary> IdentityDictionary { get; } = new();
13 |
14 | public override IAsyncEnumerable OnMessageReceived(MessageContext context)
15 | {
16 | var lightMessageContext = new LightMessage
17 | {
18 | UserId = context.MessageUserIdentity?.UserId,
19 | RawMessage = context.TextMessage,
20 | Timestamp = context.ReceivedTime
21 | };
22 | IdentityDictionary.AddOrUpdate(context.MessageIdentity!,
23 | new List
24 | {
25 | lightMessageContext
26 | }, (_, list) =>
27 | {
28 | if (list.Count > 20)
29 | list.RemoveAt(0);
30 | list.Add(lightMessageContext);
31 | return list;
32 | }
33 | );
34 |
35 | return base.OnMessageReceived(context);
36 | }
37 | }
38 |
39 | public class LightMessage
40 | {
41 | public string? UserId { get; set; }
42 | public string? RawMessage { get; set; }
43 | public DateTimeOffset Timestamp { get; set; }
44 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/PowerOffFilterService.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Messaging;
2 | using MilkiBotFramework.Plugining;
3 | using MilkiBotFramework.Plugining.Attributes;
4 | using MilkiBotFramework.Plugining.Configuration;
5 | using MilkiBotFramework.Plugining.Loading;
6 |
7 | namespace daylily.Plugins.Core;
8 |
9 | [PluginIdentifier("3d5d466c-e52c-4888-950c-5bcc36ac8294")]
10 | public class PowerOffFilterService : ServicePlugin
11 | {
12 | private readonly ShuntDownConfig _config;
13 |
14 | public PowerOffFilterService(IConfiguration configuration)
15 | {
16 | _config = configuration.Instance;
17 | }
18 |
19 | public override async Task BeforeSend(PluginInfo pluginInfo, IResponse response)
20 | {
21 | var context = response.MessageContext;
22 | var messageIdentity = context?.MessageIdentity;
23 | if (messageIdentity == null)
24 | {
25 | return true;
26 | }
27 |
28 | DateTimeOffset? expireTime;
29 | using (await _config.AsyncLock.LockAsync())
30 | {
31 | if (!_config.ExpireTimeDictionary!.TryGetValue(messageIdentity, out expireTime))
32 | {
33 | return true;
34 | }
35 | }
36 |
37 | if (expireTime <= DateTimeOffset.Now)
38 | {
39 | return true;
40 | }
41 |
42 | if (context!.Authority > MessageAuthority.Public)
43 | {
44 | return true;
45 | }
46 |
47 | response.Handled();
48 | return false;
49 | }
50 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Core/Reboot.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework;
3 | using MilkiBotFramework.Messaging;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 |
7 | namespace daylily.Plugins.Core;
8 |
9 | [PluginIdentifier("ee3fc222-b05d-403b-b8c2-542a61b72a4d", "远程重启", AllowDisable = false)]
10 | [PluginLifetime(PluginLifetime.Singleton)]
11 | [Description("允许退出程序或强制关闭进程并重启")]
12 | public class Reboot : BasicPlugin
13 | {
14 | public enum SubCommandType
15 | {
16 | C
17 | }
18 |
19 | private readonly Bot _bot;
20 | private readonly BotOptions _botOptions;
21 | private bool _state;
22 | private CancellationTokenSource? _cts;
23 |
24 | public Reboot(Bot bot, BotOptions botOptions)
25 | {
26 | _bot = bot;
27 | _botOptions = botOptions;
28 | }
29 |
30 | [CommandHandler("reboot", Authority = MessageAuthority.Root)]
31 | public async IAsyncEnumerable RebootCommand(MessageContext messageContext,
32 | [Option("force")] bool force = false,
33 | [Argument] SubCommandType? subCommand = null)
34 | {
35 | if (subCommand == SubCommandType.C)
36 | {
37 | _cts?.Cancel();
38 | _state = false;
39 | yield break;
40 | }
41 |
42 | if (_state) yield break;
43 |
44 | _state = true;
45 | _cts?.Dispose();
46 | _cts = new CancellationTokenSource();
47 | foreach (var rootAccount in _botOptions.RootAccounts)
48 | {
49 | yield return ToPrivate(rootAccount,
50 | "20秒后即将重启,发送 \"/reboot c\" 可取消。\r\n" +
51 | "注意:若守护进程没有正确运行,将无法自动启动");
52 | }
53 |
54 | var isCanceled = false;
55 | try
56 | {
57 | await Task.Delay(TimeSpan.FromSeconds(20), _cts.Token);
58 | if (!force) await _bot.StopAsync(FrameworkConstants.ManualExitCode);
59 | }
60 | catch (TaskCanceledException)
61 | {
62 | isCanceled = true;
63 | }
64 |
65 | if (!isCanceled) yield break;
66 | foreach (var rootAccount in _botOptions.RootAccounts)
67 | {
68 | yield return ToPrivate(rootAccount, "已取消重启操作。");
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/daylily/Plugins/Core/SendMessage.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using MilkiBotFramework.ContactsManaging;
3 | using MilkiBotFramework.Messaging;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 |
7 | namespace daylily.Plugins.Core;
8 |
9 | [PluginIdentifier("09983821-0238-4b0d-b1c1-2921eb7e52d1", "发送自定义消息", AllowDisable = false)]
10 | [Description("支持发送任意格式的消息(包含cq码)。")]
11 | public class SendMessage : BasicPlugin
12 | {
13 | private readonly IContactsManager _contactsManager;
14 |
15 | public SendMessage(IContactsManager contactsManager)
16 | {
17 | _contactsManager = contactsManager;
18 | }
19 |
20 | [CommandHandler(AllowedMessageType = MessageType.Private, Authority = MessageAuthority.Root)]
21 | public async IAsyncEnumerable Broadcast(MessageContext messageContext,
22 | [Argument, Description("要发送的信息。")]
23 | string? message = null)
24 | {
25 | var response = ValidateInput(message, out var innerMessage);
26 | if (response != null)
27 | {
28 | yield return response;
29 | yield break;
30 | }
31 |
32 | var groups = _contactsManager.GetAllChannels();
33 |
34 | string ok = $"◈◈ {DateTime.Now:M月d日 H:mm}公告 ◈◈\r\n";
35 | string titledMessage = ok + innerMessage;
36 |
37 | bool hasResult = false;
38 | int i = 0;
39 | foreach (var groupInfo in groups)
40 | {
41 | hasResult = true;
42 |
43 | var id = groupInfo.ChannelId;
44 | yield return ToChannel(id, titledMessage);
45 | i++;
46 | await Task.Delay(3000);
47 | }
48 |
49 | if (!hasResult)
50 | {
51 | yield return Reply("无有效群。");
52 | }
53 | else
54 | {
55 | //SaveLogs(msg, "announcement");
56 | yield return Reply($"已成功发送至{i}个群。");
57 | }
58 | }
59 |
60 | [CommandHandler(AllowedMessageType = MessageType.Private, Authority = MessageAuthority.Root)]
61 | public IResponse Send(MessageContext messageContext,
62 | [Option("g"), Description("要发送的群号。")]
63 | string? groupId = null,
64 | [Option("u"), Description("要发送的用户QQ号。")]
65 | string? userId = null,
66 | [Argument, Description("要发送的信息。")]
67 | string? message = null)
68 | {
69 | var response = ValidateInput(message, out var innerMessage);
70 | if (response != null)
71 | return response;
72 | if (groupId != null && userId != null)
73 | return Reply("请指定唯一一个目标");
74 | if (groupId != null)
75 | return ToChannel(groupId, innerMessage);
76 | if (userId != null)
77 | return ToPrivate(userId, innerMessage);
78 | return Reply(innerMessage);
79 | }
80 |
81 | private static IResponse? ValidateInput(string? message, out string innerMessage)
82 | {
83 | if (message == null)
84 | {
85 | innerMessage = string.Empty;
86 | return Reply("你要说什么……");
87 | }
88 |
89 | innerMessage = Decode(message);
90 | return null;
91 | }
92 |
93 | private static string Decode(string source) =>
94 | source.Replace("\\&", "&tamp;").Replace("\\#91;", "&t#91;").Replace("\\]", "&t#93;").Replace("\\,", "&t#44;")
95 | .Replace("&", "&").Replace("[", "[").Replace("]", "]").Replace(",", ",").
96 | Replace("&tamp;", "&").Replace("&t#91;", "[").Replace("&t#93;", "]").Replace("&t#44;", ",");
97 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/ApiService.cs:
--------------------------------------------------------------------------------
1 | using System.Net.Http;
2 | using Coosu.Api.V2;
3 | using Coosu.Api.V2.ResponseModels;
4 | using daylily.Plugins.Osu.Data;
5 | using Microsoft.Extensions.Logging;
6 | using MilkiBotFramework;
7 | using MilkiBotFramework.Messaging;
8 | using MilkiBotFramework.Plugining;
9 | using MilkiBotFramework.Plugining.Attributes;
10 | using MilkiBotFramework.Plugining.Configuration;
11 | using MilkiBotFramework.Tasking;
12 |
13 | namespace daylily.Plugins.Osu;
14 |
15 | [PluginIdentifier("a1332794-448f-4378-a8b4-209428204af1")]
16 | public class ApiService : ServicePlugin
17 | {
18 | public class ApiResult
19 | {
20 | public ApiResult(string error)
21 | {
22 | Error = error;
23 | }
24 |
25 | public ApiResult(T? result)
26 | {
27 | Result = result;
28 | Success = true;
29 | }
30 |
31 | public bool Success { get; set; }
32 | public T? Result { get; set; }
33 | public string? Error { get; set; }
34 | }
35 |
36 | private readonly BotOptions _botOptions;
37 | private readonly BotTaskScheduler _taskScheduler;
38 | private readonly OsuConfig _config;
39 | private readonly TaskCompletionSource _initialWait;
40 |
41 | internal string UnbindMessage => $"你还没有绑定账号,请私聊我 \"{_botOptions.CommandFlag}setid.osu\" 完成绑定。直接复制引号中的内容即可。";
42 |
43 | public ApiService(IConfiguration configuration, BotOptions botOptions, BotTaskScheduler taskScheduler)
44 | {
45 | _botOptions = botOptions;
46 | _taskScheduler = taskScheduler;
47 | _config = configuration.Instance;
48 | var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
49 | _initialWait = new TaskCompletionSource();
50 | cts.Token.Register(() =>
51 | {
52 | _initialWait.TrySetResult();
53 | cts.Dispose();
54 | });
55 | }
56 |
57 | public UserToken? PublicToken { get; private set; }
58 |
59 | internal async Task> TryAccessPublicApi(Func> func)
60 | {
61 | try
62 | {
63 | if (PublicToken == null)
64 | await _initialWait.Task;
65 | if (PublicToken == null)
66 | return new ApiResult("PublicToken未初始化");
67 | return new ApiResult(await func.Invoke(new OsuClientV2(PublicToken)));
68 | }
69 | catch (HttpRequestException ex)
70 | {
71 | if (ex.Message.Contains("401"))
72 | {
73 | return new ApiResult("osu授权出错,请重新绑定osu ID");
74 | }
75 |
76 | if (ex.Message.Contains("404"))
77 | {
78 | return new ApiResult("无结果,请检查参数");
79 | }
80 |
81 | return new ApiResult("osu!api访问出错:" + ex.Message);
82 | }
83 | }
84 |
85 | internal async Task<(bool, TokenBase? token, IResponse? unbindMessage)> TryGetToken(
86 | MessageContext messageContext, OsuDbContext dbContext)
87 | {
88 | var osuToken = await dbContext.Tokens.FindAsync(long.Parse(messageContext.MessageUserIdentity.UserId));
89 | if (osuToken == null)
90 | {
91 | return (false, null, Reply(UnbindMessage));
92 | }
93 |
94 | return (true, ConvertToken(osuToken), null);
95 | }
96 |
97 | protected override async Task OnInitialized()
98 | {
99 | _taskScheduler.AddTask("RefreshOsuToken", k => k
100 | .ByInterval(TimeSpan.FromHours(12))
101 | .AtStartup()
102 | .WithoutLogging()
103 | .Do(RefreshTokenTask));
104 | }
105 |
106 | private void RefreshTokenTask(TaskContext context, CancellationToken token)
107 | {
108 | var authClientPub = new AuthorizationClient();
109 | try
110 | {
111 | var result = authClientPub.GetPublicToken(_config.ClientId, _config.ClientSecret).Result;
112 | this.PublicToken = result;
113 | context.Logger.LogInformation($"已刷新Osu!PublicToken,将在{DateTime.Now.AddSeconds(result.ExpiresIn)}过期");
114 | }
115 | finally
116 | {
117 | _initialWait.TrySetResult();
118 | }
119 | }
120 |
121 | private static TokenBase ConvertToken(OsuToken osuToken)
122 | {
123 | if (osuToken.IsPublic)
124 | return new PublicToken
125 | {
126 | AccessToken = osuToken.AccessToken,
127 | CreateTime = osuToken.CreateTime,
128 | ExpiresIn = osuToken.ExpiresIn,
129 | TokenType = osuToken.TokenType
130 | };
131 | return new UserToken
132 | {
133 | AccessToken = osuToken.AccessToken,
134 | CreateTime = osuToken.CreateTime,
135 | ExpiresIn = osuToken.ExpiresIn,
136 | TokenType = osuToken.TokenType,
137 | RefreshToken = osuToken.RefreshToken
138 | };
139 | }
140 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/AuthenticationController.cs:
--------------------------------------------------------------------------------
1 | using Coosu.Api.V2;
2 | using Coosu.Api.V2.ResponseModels;
3 | using daylily.Plugins.Osu.Data;
4 | using daylily.Utils;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.Mvc;
7 | using Microsoft.Extensions.Logging;
8 | using MilkiBotFramework.Event;
9 | using MilkiBotFramework.Plugining.Configuration;
10 |
11 | namespace daylily.Plugins.Osu
12 | {
13 | [Route("api/authentication")]
14 | [ApiController]
15 | // ReSharper disable once InconsistentNaming
16 | #pragma warning disable IDE1006 // 命名样式
17 | public class AuthenticationController : ControllerBase
18 | #pragma warning restore IDE1006 // 命名样式
19 | {
20 | private readonly ILogger _logger;
21 | private readonly EventBus _eventBus;
22 | private readonly OsuConfig _config;
23 |
24 | public AuthenticationController(ILogger logger,
25 | IConfiguration configuration,
26 | EventBus eventBus)
27 | {
28 | _logger = logger;
29 | _eventBus = eventBus;
30 | _config = configuration.Instance;
31 | }
32 |
33 | // GET api/
34 | [HttpGet]
35 | public async Task Get(string code, string state)
36 | {
37 | string qq;
38 | string sessionToken;
39 | try
40 | {
41 | var union = EncryptUtil.DecryptAes256UseMd5(state, _config.QQAesKey, _config.QQAesIV);
42 | var split = union.Split('|');
43 | qq = split[0];
44 | var ticks = long.Parse(split[1]);
45 | sessionToken = split[2];
46 | var time = new DateTime(ticks);
47 | if (DateTime.Now - time > TimeSpan.FromMinutes(5))
48 | {
49 | await _eventBus.PublishAsync(new OsuTokenReceivedEvent(sessionToken, "链接已过期,但这本不应发生.."));
50 | return Content("链接已过期。");
51 | }
52 | }
53 | catch (Exception ex)
54 | {
55 | //await _eventBus.PublishAsync(new OsuTokenReceivedEvent(guid, "QQ号获取错误,请重试.."));
56 | #if DEBUG
57 | throw;
58 | #endif
59 | _logger.LogError(ex, "QQ号获取出错,请重试。");
60 | HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
61 | return Content("QQ号获取错误,请重试。");
62 | }
63 |
64 | UserToken result;
65 | try
66 | {
67 | var authClient = new AuthorizationClient();
68 | result = await authClient.GetUserToken(_config.ClientId, new Uri(_config.ServerRedirectUri),
69 | _config.ClientSecret, code);
70 | if (result == null)
71 | throw new Exception("token result为null");
72 | }
73 | catch (Exception ex)
74 | {
75 | await _eventBus.PublishAsync(new OsuTokenReceivedEvent(sessionToken, "token获取出错,请重试.."));
76 | #if DEBUG
77 | throw;
78 | #endif
79 | _logger.LogError(ex, "token获取出错,请重试。");
80 | HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
81 | return Content("token获取出错,请重试。");
82 | }
83 |
84 | var client = new OsuClientV2(result);
85 | var user = await client.User.GetOwnData();
86 |
87 | await _eventBus.PublishAsync(new OsuTokenReceivedEvent(sessionToken, qq, user, result));
88 | return Redirect($"https://osu.ppy.sh/users/{user.Id}");
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/BeatmapStats/BeatmapStatsControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Windows.Media;
3 | using System.Windows.Media.Imaging;
4 | using LiveCharts;
5 | using LiveCharts.Configurations;
6 | using LiveCharts.Wpf;
7 | using Microsoft.Extensions.Logging;
8 | using MilkiBotFramework;
9 | using MilkiBotFramework.Connecting;
10 | using MilkiBotFramework.Imaging.Wpf;
11 | using MilkiBotFramework.Plugining.Configuration;
12 | using Image = SixLabors.ImageSharp.Image;
13 |
14 | namespace daylily.Plugins.Osu.BeatmapStats;
15 |
16 | ///
17 | /// BeatmapStatsControl.xaml 的交互逻辑
18 | ///
19 | public partial class BeatmapStatsControl : WpfDrawingControl
20 | {
21 | private readonly ILogger _logger;
22 | private readonly BotOptions _botOptions;
23 | private readonly LightHttpClient _lightHttpClient;
24 | private readonly BeatmapStatsVm _viewModel;
25 |
26 | public BeatmapStatsControl(
27 | ILogger logger,
28 | BotOptions botOptions,
29 | LightHttpClient lightHttpClient,
30 | object viewModel,
31 | Image? sourceImage = null)
32 | : base(viewModel, sourceImage)
33 | {
34 | _logger = logger;
35 | _botOptions = botOptions;
36 | _lightHttpClient = lightHttpClient;
37 | _viewModel = (BeatmapStatsVm)ViewModel;
38 | Initialized += async (_, _) =>
39 | {
40 | await ResetData();
41 | };
42 | InitializeComponent();
43 | }
44 |
45 | private async Task ResetData()
46 | {
47 | var firstOrDefault = _viewModel.Stats;
48 | var startTime = firstOrDefault.Min(k => k.Timestamp);
49 | var endTime = firstOrDefault.Max(k => k.Timestamp);
50 | //var endTime = startTime.AddDays(1);
51 | var i = 90;
52 | var lidu = (endTime - startTime) / (i * 3f);
53 | //var lidu = TimeSpan.FromDays(1);
54 | var dayConfig = Mappers.Xy>()
55 | .X(dateModel => dateModel.Timestamp.Ticks / lidu.Ticks)
56 | .Y(dateModel => dateModel.Value);
57 |
58 | var series = new SeriesCollection(dayConfig)
59 | {
60 | new LineSeries
61 | {
62 | Title = "Favorite Count",
63 | Values = new ChartValues>(firstOrDefault
64 | .Where(k => k.Timestamp >= startTime && k.Timestamp <= endTime)
65 | .Distinct(new TickComparer(lidu))
66 | .Distinct(new FavoriteComparer())
67 | .Select(k => new DateModel()
68 | {
69 | Timestamp = k.Timestamp,
70 | Value = (int) k.FavoriteCount
71 | })),
72 | Fill = Brushes.Transparent,
73 | PointGeometrySize = 6,
74 | ScalesYAt = 0,
75 | Stroke = Brushes.ForestGreen
76 | },
77 | new LineSeries
78 | {
79 | Title = "Play Count",
80 | Values = new ChartValues>(firstOrDefault
81 | .Where(k => k.Timestamp >= startTime && k.Timestamp <= endTime)
82 | .Distinct(new TickComparer(lidu))
83 | .Distinct(new PlayCountComparer())
84 | .Select(k => new DateModel()
85 | {
86 | Timestamp = k.Timestamp,
87 | Value = (int) k.PlayCount
88 | })),
89 |
90 | Fill = Brushes.Transparent,
91 | ScalesYAt = 1,
92 | PointGeometrySize = 0,
93 | Stroke = Brushes.CornflowerBlue
94 | },
95 | };
96 |
97 | _viewModel.AxisYCollection = new AxesCollection
98 | {
99 | new Axis
100 | {
101 | Title = "Favorite Count", Foreground = Brushes.ForestGreen, FontSize = 15,
102 | LabelFormatter = NumberLabelFormatter
103 | },
104 | new Axis
105 | {
106 | Title = "Play Count", Foreground = Brushes.CornflowerBlue, FontSize = 15,
107 | LabelFormatter = NumberLabelFormatter
108 | }
109 | };
110 | _viewModel.AxisYCollection[0].Separator.StrokeThickness = 0;
111 | //_viewModel.AxisYCollection[1].Separator.StrokeThickness = 0;
112 |
113 | _viewModel.Formatter = value => new DateTime((long)(value * lidu.Ticks)).ToString("yyyy-M-d H:mm");
114 | _viewModel.Series = series;
115 |
116 | _viewModel.SubmittedDateLocal = _viewModel.Beatmapset?.SubmittedDate.ToLocalTime();
117 | _viewModel.LastUpdatedLocal = _viewModel.Beatmapset?.LastUpdated.ToLocalTime();
118 | _viewModel.RankedDateLocal = _viewModel.Beatmapset?.RankedDate?.ToLocalTime();
119 | var link = _viewModel.Beatmapset?.Covers.List2X;
120 |
121 | try
122 | {
123 | var guid = Path.GetRandomFileName();
124 | if (!Directory.Exists(_botOptions.CacheImageDir))
125 | Directory.CreateDirectory(_botOptions.CacheImageDir);
126 | var path = await _lightHttpClient.SaveImageFromUrlAsync(link, _botOptions.CacheImageDir, guid);
127 |
128 | var bi = new BitmapImage();
129 |
130 | // Begin initialization.
131 | bi.BeginInit();
132 |
133 | // Set properties.
134 | bi.CacheOption = BitmapCacheOption.OnLoad;
135 | bi.UriSource = new Uri(path);
136 |
137 | // End initialization.
138 | bi.EndInit();
139 | bi.Freeze(); //Important to freeze it, otherwise it will still have minor leaks
140 |
141 | _viewModel.List2XSource = bi;
142 | await Task.Delay(1);
143 | }
144 | catch (Exception ex)
145 | {
146 | _logger.LogWarning("下载osu!图片时出错:" + ex.Message);
147 | }
148 |
149 | await FinishDrawing();
150 | }
151 | private string NumberLabelFormatter(double value)
152 | {
153 | return Convert.ToInt32(value).ToString("N0");
154 | }
155 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/BeatmapStats/BeatmapStatsVm.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Media;
2 | using Coosu.Api.V2.ResponseModels;
3 | using daylily.Plugins.Osu.Data;
4 | using LiveCharts;
5 | using LiveCharts.Wpf;
6 | using MilkiBotFramework.Data;
7 |
8 | namespace daylily.Plugins.Osu.BeatmapStats;
9 |
10 | internal class BeatmapStatsVm : ViewModelBase
11 | {
12 | private Func _formatter;
13 | private SeriesCollection _series;
14 | private AxesCollection _axisYCollection;
15 | private ImageSource _list2XSource;
16 | private DateTimeOffset? _submittedDateLocal;
17 | private DateTimeOffset? _lastUpdatedLocal;
18 | private DateTimeOffset? _rankedDateLocal;
19 |
20 | public Func Formatter
21 | {
22 | get => _formatter;
23 | set => this.RaiseAndSetIfChanged(ref _formatter, value);
24 | }
25 |
26 | public SeriesCollection Series
27 | {
28 | get => _series;
29 | set => this.RaiseAndSetIfChanged(ref _series, value);
30 | }
31 |
32 | public AxesCollection AxisYCollection
33 | {
34 | get => _axisYCollection;
35 | set => this.RaiseAndSetIfChanged(ref _axisYCollection, value);
36 | }
37 |
38 | public ImageSource List2XSource
39 | {
40 | get => _list2XSource;
41 | set => this.RaiseAndSetIfChanged(ref _list2XSource, value);
42 | }
43 |
44 | public DateTimeOffset? SubmittedDateLocal
45 | {
46 | get => _submittedDateLocal;
47 | set => this.RaiseAndSetIfChanged(ref _submittedDateLocal, value);
48 | }
49 |
50 | public DateTimeOffset? LastUpdatedLocal
51 | {
52 | get => _lastUpdatedLocal;
53 | set => this.RaiseAndSetIfChanged(ref _lastUpdatedLocal, value);
54 | }
55 |
56 | public DateTimeOffset? RankedDateLocal
57 | {
58 | get => _rankedDateLocal;
59 | set => this.RaiseAndSetIfChanged(ref _rankedDateLocal, value);
60 | }
61 |
62 | public Beatmapset? Beatmapset { get; init; }
63 | public List Stats { get; init; } = null!;
64 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/BeatmapStats/DateModel.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.Plugins.Osu.BeatmapStats;
2 |
3 | public class DateModel
4 | {
5 | public DateTime Timestamp { get; set; }
6 | public T Value { get; set; }
7 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/BeatmapStats/FavoriteComparer.cs:
--------------------------------------------------------------------------------
1 | using daylily.Plugins.Osu.Data;
2 |
3 | namespace daylily.Plugins.Osu.BeatmapStats;
4 |
5 | public class FavoriteComparer : IEqualityComparer
6 | {
7 | public bool Equals(BeatmapStat? x, BeatmapStat? y)
8 | {
9 | if (ReferenceEquals(x, y)) return true;
10 | if (ReferenceEquals(x, null)) return false;
11 | if (ReferenceEquals(y, null)) return false;
12 | if (x.GetType() != y.GetType()) return false;
13 | return x.FavoriteCount == y.FavoriteCount;
14 | }
15 |
16 | public int GetHashCode(BeatmapStat obj)
17 | {
18 | return obj.FavoriteCount.GetHashCode();
19 | }
20 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/BeatmapStats/PlayCountComparer.cs:
--------------------------------------------------------------------------------
1 | using daylily.Plugins.Osu.Data;
2 |
3 | namespace daylily.Plugins.Osu.BeatmapStats;
4 |
5 | public class PlayCountComparer : IEqualityComparer
6 | {
7 | public bool Equals(BeatmapStat? x, BeatmapStat? y)
8 | {
9 | if (ReferenceEquals(x, y)) return true;
10 | if (ReferenceEquals(x, null)) return false;
11 | if (ReferenceEquals(y, null)) return false;
12 | if (x.GetType() != y.GetType()) return false;
13 | return x.PlayCount == y.PlayCount;
14 | }
15 |
16 | public int GetHashCode(BeatmapStat obj)
17 | {
18 | return obj.PlayCount.GetHashCode();
19 | }
20 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/BeatmapStats/TickComparer.cs:
--------------------------------------------------------------------------------
1 | using daylily.Plugins.Osu.Data;
2 |
3 | namespace daylily.Plugins.Osu.BeatmapStats;
4 |
5 | public class TickComparer : IEqualityComparer
6 | {
7 | private readonly TimeSpan _lidu;
8 |
9 | public TickComparer(TimeSpan lidu)
10 | {
11 | _lidu = lidu;
12 | }
13 |
14 | public bool Equals(BeatmapStat? x, BeatmapStat? y)
15 | {
16 | if (ReferenceEquals(x, y)) return true;
17 | if (ReferenceEquals(x, null)) return false;
18 | if (ReferenceEquals(y, null)) return false;
19 | if (x.GetType() != y.GetType()) return false;
20 |
21 | return x.Timestamp.Ticks / _lidu.Ticks == y.Timestamp.Ticks / _lidu.Ticks;
22 | }
23 |
24 | public int GetHashCode(BeatmapStat obj)
25 | {
26 | return (obj.Timestamp.Ticks / _lidu.Ticks).GetHashCode();
27 | }
28 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Data/BeatmapScan.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace daylily.Plugins.Osu.Data;
5 |
6 | [Index(nameof(BeatmapSetId), IsUnique = true)]
7 | public class BeatmapScan
8 | {
9 | [Key]
10 | public int Id { get; set; }
11 | public int BeatmapSetId { get; set; }
12 | public string? Title { get; set; }
13 | public string? Artist { get; set; }
14 | public string? Mapper { get; set; }
15 | public List BeatmapStats { get; set; }
16 | public List BeatmapSubscribes { get; set; }
17 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Data/BeatmapStat.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace daylily.Plugins.Osu.Data;
5 |
6 | public class BeatmapStat
7 | {
8 | [Key]
9 | public int Id { get; set; }
10 | public DateTime Timestamp { get; set; }
11 | public long FavoriteCount { get; set; }
12 | public long PlayCount { get; set; }
13 |
14 | [JsonIgnore]
15 | public BeatmapScan BeatmapScan { get; set; }
16 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Data/BeatmapSubscribe.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using Microsoft.EntityFrameworkCore;
3 |
4 | namespace daylily.Plugins.Osu.Data;
5 |
6 | [Index(nameof(ScribeUserId), nameof(BeatmapScanId), IsUnique = true)]
7 | public class BeatmapSubscribe
8 | {
9 | [Key]
10 | public int Id { get; set; }
11 | public string ScribeUserId { get; set; }
12 | public BeatmapScan BeatmapScan { get; set; }
13 | public int BeatmapScanId { get; set; }
14 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Data/OsuDbContext.cs:
--------------------------------------------------------------------------------
1 | using Coosu.Api.V2.ResponseModels;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.ChangeTracking;
4 | using MilkiBotFramework.Plugining.Database;
5 |
6 | namespace daylily.Plugins.Osu.Data;
7 |
8 | public class OsuDbContext : PluginDbContext
9 | {
10 | public DbSet Tokens { get; set; }
11 | public DbSet OsuUserInfos { get; set; }
12 | public DbSet BeatmapScans { get; set; }
13 | public DbSet BeatmapStats { get; set; }
14 | public DbSet BeatmapSubscribes { get; set; }
15 |
16 | protected override void OnModelCreating(ModelBuilder modelBuilder)
17 | {
18 | modelBuilder.Entity(entity =>
19 | {
20 | entity.Property(k => k.RequestStatus)
21 | .HasConversion(
22 | v => (v == null || v.Length == 0) ? default : string.Join(',', v),
23 | v => v == default
24 | ? Array.Empty()
25 | : v.Split(',', StringSplitOptions.RemoveEmptyEntries)
26 | .Select(Enum.Parse)
27 | .ToArray(),
28 | new ValueComparer(true)
29 | );
30 | entity.Property(k => k.ModeIds)
31 | .HasConversion(
32 | v => v.Count == 0 ? default : string.Join(',', v),
33 | v => v == default
34 | ? new HashSet()
35 | : new HashSet(v.Split(',', StringSplitOptions.RemoveEmptyEntries)
36 | .Select(Enum.Parse)),
37 | new ValueComparer>(true)
38 | );
39 | entity.Property(k => k.Id)
40 | .HasConversion(v => v, v => v);
41 | });
42 | }
43 |
44 | public async Task GetUserIdBySourceId(string qq)
45 | {
46 | var result = (await Tokens.FindAsync(qq))?.UserId;
47 | return result;
48 | }
49 |
50 | public async Task AddOrUpdateToken(string sourceId, long userId, TokenBase token)
51 | {
52 | var osuToken = new OsuToken
53 | {
54 | AccessToken = token.AccessToken,
55 | TokenType = token.TokenType,
56 | CreateTime = token.CreateTime,
57 | ExpiresIn = token.ExpiresIn,
58 | SourceId = sourceId,
59 | UserId = userId
60 | };
61 |
62 | if (token is UserToken userToken)
63 | {
64 | osuToken.RefreshToken = userToken.RefreshToken;
65 | }
66 | else
67 | {
68 | osuToken.IsPublic = true;
69 | }
70 |
71 | var exist = await Tokens.FindAsync(sourceId);
72 | if (exist == null)
73 | {
74 | Tokens.Add(osuToken);
75 | }
76 | else
77 | {
78 | exist.TokenType = token.TokenType;
79 | exist.AccessToken = token.AccessToken;
80 | exist.CreateTime = token.CreateTime;
81 | exist.ExpiresIn = token.ExpiresIn;
82 | exist.IsPublic = osuToken.IsPublic;
83 | exist.RefreshToken = exist.RefreshToken;
84 | exist.UserId = userId;
85 | }
86 |
87 | await SaveChangesAsync();
88 | }
89 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Data/OsuToken.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.ComponentModel.DataAnnotations.Schema;
3 |
4 | namespace daylily.Plugins.Osu.Data;
5 |
6 | [Table("tokens")]
7 | public class OsuToken
8 | {
9 | [Key]
10 | public string SourceId { get; set; }
11 | public string TokenType { get; set; }
12 | public long ExpiresIn { get; set; }
13 | public string AccessToken { get; set; }
14 | public DateTimeOffset? CreateTime { get; set; }
15 | public string RefreshToken { get; set; }
16 | public bool IsPublic { get; set; }
17 | public long? UserId { get; set; }
18 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Data/OsuUserInfo.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace daylily.Plugins.Osu.Data;
5 |
6 | public class OsuUserInfo
7 | {
8 | [Key]
9 | [JsonPropertyName("osuId")]
10 | public int Id { get; set; }
11 |
12 | [JsonPropertyName("username")]
13 | public string Username { get; set; } = null!;
14 |
15 | [JsonIgnore]
16 | public HashSet ModeIds { get; set; } = new();
17 |
18 | [JsonPropertyName("group")]
19 | public Group Group { get; set; }
20 |
21 | [JsonPropertyName("level")]
22 | public Level Level { get; set; }
23 |
24 | [JsonPropertyName("requestStatus")]
25 | public RequestStatus[]? RequestStatus { get; set; }
26 |
27 | [JsonPropertyName("requestLink")]
28 | public string? RequestLink { get; set; }
29 |
30 | [JsonIgnore]
31 | public string? UserPageText { get; set; }
32 | }
33 |
34 | [JsonConverter(typeof(JsonStringEnumConverter))]
35 | public enum Group { Bn, Nat };
36 |
37 | [JsonConverter(typeof(JsonStringEnumConverter))]
38 | public enum Level { Full, Probation };
39 |
40 | [JsonConverter(typeof(JsonStringEnumConverter))]
41 | public enum RequestStatus { Closed, GameChat, GlobalQueue, PersonalQueue };
42 |
43 | [JsonConverter(typeof(JsonStringEnumConverter))]
44 | public enum ModeId { Osu, Catch, Mania, Taiko };
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/MePlugin.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using System.Text;
3 | using Coosu.Api.V2;
4 | using daylily.Plugins.Osu.BeatmapStats;
5 | using daylily.Plugins.Osu.Data;
6 | using daylily.Plugins.Osu.Me;
7 | using Microsoft.Extensions.Logging;
8 | using MilkiBotFramework;
9 | using MilkiBotFramework.Connecting;
10 | using MilkiBotFramework.Imaging;
11 | using MilkiBotFramework.Imaging.Wpf;
12 | using MilkiBotFramework.Messaging;
13 | using MilkiBotFramework.Messaging.RichMessages;
14 | using MilkiBotFramework.Plugining;
15 | using MilkiBotFramework.Plugining.Attributes;
16 |
17 | namespace daylily.Plugins.Osu;
18 |
19 | [PluginIdentifier("1d3dce2d-f52d-42ab-97a4-fcf31e11dfbc", "个人名片", Scope = "osu!")]
20 | [Description("个人名片查询")]
21 | public class MePlugin : BasicPlugin
22 | {
23 | private readonly ILogger _logger;
24 | private readonly ApiService _apiService;
25 | private readonly LightHttpClient _lightHttpClient;
26 | private readonly OsuDbContext _dbContext;
27 |
28 | public MePlugin(ILogger logger,
29 | ApiService apiService,
30 | LightHttpClient lightHttpClient,
31 | OsuDbContext dbContext)
32 | {
33 | _logger = logger;
34 | _apiService = apiService;
35 | _lightHttpClient = lightHttpClient;
36 | _dbContext = dbContext;
37 | }
38 |
39 | [CommandHandler("me.osu")]
40 | public async Task MeOsu(MessageContext messageContext)
41 | {
42 | var osuId = await _dbContext.GetUserIdBySourceId(messageContext.MessageUserIdentity.UserId);
43 | if (osuId == null) return Reply(_apiService.UnbindMessage);
44 | var result = await _apiService.TryAccessPublicApi(async client =>
45 | await client.User.GetUser(osuId.ToString(), GameMode.Osu));
46 | if (!result.Success)
47 | {
48 | return Reply(result.Error!);
49 | }
50 |
51 | var user = result.Result;
52 |
53 | var renderer = new WpfDrawingProcessor((vm, image) =>
54 | new MeControl(_lightHttpClient, vm, image), true);
55 | var vm = new MeOsuControlVm
56 | {
57 | User = user
58 | };
59 | var image = await renderer.ProcessAsync(vm);
60 | return Reply(new MemoryImage(image, ImageType.Png));
61 | }
62 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/OsuTokenReceivedEvent.cs:
--------------------------------------------------------------------------------
1 | using Coosu.Api.V2.ResponseModels;
2 | using MilkiBotFramework.Event;
3 |
4 | namespace daylily.Plugins.Osu;
5 |
6 | public class OsuTokenReceivedEvent : IEventBusEvent
7 | {
8 | public OsuTokenReceivedEvent(string sessionToken, string failReason)
9 | {
10 | SessionToken = sessionToken;
11 | FailReason = failReason;
12 | }
13 |
14 | public OsuTokenReceivedEvent(string sessionToken, string sourceId, User user, UserToken token)
15 | {
16 | SessionToken = sessionToken;
17 | IsSuccess = true;
18 | SourceId = sourceId;
19 | User = user;
20 | Token = token;
21 | }
22 |
23 | public string SessionToken { get; }
24 | public bool IsSuccess { get; }
25 | public string? FailReason { get; }
26 | public string? SourceId { get; }
27 | public User? User { get; }
28 | public UserToken? Token { get; }
29 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/Qqm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using MilkiBotFramework.Messaging;
9 | using MilkiBotFramework.Messaging.RichMessages;
10 | using MilkiBotFramework.Plugining;
11 | using MilkiBotFramework.Plugining.Attributes;
12 |
13 | namespace daylily.Plugins.Osu;
14 |
15 | [PluginIdentifier("8c138573-60fb-44ec-9f2d-b2077d1e757a", "大丘丘病了二丘丘瞧", Scope = "osu!")]
16 | [Description("三丘丘采药四丘丘熬")]
17 | public class Qqm : BasicPlugin
18 | {
19 | private string StickerDir { get; set; }
20 |
21 | [CommandHandler("丘丘萌")]
22 | public async Task QqmImpl(MessageContext messageContext)
23 | {
24 | var files = Directory.EnumerateFiles(StickerDir, "*.jpg")
25 | .Concat(Directory.EnumerateFiles(StickerDir, "*.png"))
26 | .Concat(Directory.EnumerateFiles(StickerDir, "*.gif"))
27 | .ToArray();
28 | if (files.Length > 0)
29 | {
30 | var random = Random.Shared.Next(0, files.Length);
31 | var path = files[random];
32 | return Reply(new FileImage(path));
33 | }
34 |
35 | return Reply("未找到图片");
36 | }
37 |
38 | protected override Task OnInitialized()
39 | {
40 | StickerDir = Path.Combine(PluginHome, "stickers");
41 | Directory.CreateDirectory(StickerDir);
42 | return Task.CompletedTask;
43 | }
44 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/RecentPlayPlugin.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Coosu.Api.V2;
3 | using daylily.Plugins.Osu.Data;
4 | using Microsoft.Extensions.Logging;
5 | using MilkiBotFramework.Messaging;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 |
9 | namespace daylily.Plugins.Osu;
10 |
11 | [PluginIdentifier("611d6a61-1ccc-4c19-92f3-57976d927655", "成绩查询", Scope = "osu!")]
12 | [Description("最近成绩查询")]
13 | public class RecentPlayPlugin : BasicPlugin
14 | {
15 | private readonly ILogger _logger;
16 | private readonly ApiService _apiService;
17 | private readonly OsuDbContext _dbContext;
18 |
19 | public RecentPlayPlugin(ILogger logger, ApiService apiService, OsuDbContext dbContext)
20 | {
21 | _logger = logger;
22 | _apiService = apiService;
23 | _dbContext = dbContext;
24 | }
25 |
26 | [CommandHandler("recent")]
27 | public async Task Recent(MessageContext context,
28 | [Argument, Description("游戏模式. 0:Osu; 1:Taiko; 2:Fruits; 3:Mania")] GameMode? gameMode = null,
29 | [Option("all"), Description("包含Fail成绩")] bool all = false,
30 | [Option("qq", Authority = MessageAuthority.Root), Description("指定的qq号")] string? qq = null,
31 | [Option("id", Authority = MessageAuthority.Root), Description("指定的osu id号")] string? osuId = null,
32 | [Option("user", Authority = MessageAuthority.Root), Description("指定的用户名")] string? userName = null)
33 | {
34 | if (osuId == null)
35 | {
36 | if (qq != null)
37 | {
38 | var id = await _dbContext.GetUserIdBySourceId(qq);
39 | if (id == null) return Reply($"QQ {qq} 未绑定osu!账号。");
40 | osuId = id.Value.ToString();
41 | }
42 | else if (userName != null)
43 | {
44 | var response1 = await _apiService.TryAccessPublicApi(async client => await client.User.GetUser(userName));
45 | if (!response1.Success)
46 | {
47 | return Reply(response1.Error!);
48 | }
49 |
50 | osuId = response1.Result!.Id?.ToString();
51 | }
52 | else
53 | {
54 | var id = await _dbContext.GetUserIdBySourceId(context.MessageUserIdentity.UserId);
55 | if (id == null) return Reply(_apiService.UnbindMessage);
56 | osuId = id.Value.ToString();
57 | }
58 | }
59 |
60 | var response = await _apiService.TryAccessPublicApi(async client => await client.User.GetUserScores(osuId,
61 | ScoreType.Recent, all, gameMode, new Pagination
62 | {
63 | Offset = 0,
64 | Limit = 1
65 | }));
66 | if (!response.Success)
67 | {
68 | return Reply(response.Error!);
69 | }
70 |
71 | if (response.Result!.Length == 0) return Reply("没有成绩");
72 | var score = response.Result[0];
73 | var beatmapset = score.Beatmapset;
74 | var user = score.User;
75 | var reply = $"{user.Username} 在 \"{beatmapset.ArtistUnicode} - {beatmapset.TitleUnicode}\" 中以 {score.Accuracy:P2}、{score.Rank} Rank";
76 | if (score.Mods.Length > 0)
77 | {
78 | reply += "、+" + string.Join("", score.Mods);
79 | }
80 |
81 | if (score.Perfect)
82 | reply += $" 的成绩 FC 了. 🥳\r\n{user.Username}!.jpg";
83 | else if (score.Statistics.CountMiss == 0)
84 | reply += $" 的成绩 断滑条 了. 😏\r\n";
85 | else if (score.Passed)
86 | reply += " 的成绩 PASS 了. 👏👏\r\n";
87 | else
88 | reply += " 的成绩 FAIL 了. 🤷♂️\r\n";
89 | reply += score.Beatmap.Url;
90 | return Reply(reply);
91 | }
92 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/SearchBnPlugins/AllUsersByMode.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 | using daylily.Plugins.Osu.Data;
3 |
4 | namespace daylily.Plugins.Osu.SearchBnPlugins;
5 |
6 | public class AllUsersByMode
7 | {
8 | [JsonPropertyName("_id")]
9 | public ModeId ModeId { get; set; }
10 |
11 | [JsonPropertyName("users")]
12 | public OsuUserInfo[] Users { get; set; } = Array.Empty();
13 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/SearchBnPlugins/RelevantInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.Plugins.Osu.SearchBnPlugins;
4 |
5 | public class RelevantInfo
6 | {
7 | [JsonPropertyName("allUsersByMode")]
8 | public AllUsersByMode[] AllUsersByMode { get; set; } = Array.Empty();
9 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/SearchBnPlugins/SearchBnControl.xaml:
--------------------------------------------------------------------------------
1 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
48 |
49 |
50 |
51 |
52 |
53 |
64 |
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/SearchBnPlugins/SearchBnControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using System.Windows;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 | using System.Xml;
7 | using ICSharpCode.AvalonEdit;
8 | using ICSharpCode.AvalonEdit.Highlighting;
9 | using ICSharpCode.AvalonEdit.Highlighting.Xshd;
10 | using MilkiBotFramework.Imaging.Wpf;
11 | using Image = SixLabors.ImageSharp.Image;
12 |
13 | namespace daylily.Plugins.Osu.SearchBnPlugins;
14 |
15 | ///
16 | /// SearchBnControl.xaml 的交互逻辑
17 | ///
18 | public partial class SearchBnControl : WpfDrawingControl
19 | {
20 | private const string XshdTemplate =
21 | @"
22 |
23 |
24 |
25 | {{rep}}
26 |
27 |
28 | ";
29 |
30 | private readonly HashSet _allKeywords = new()
31 | { '\\', '^', '$', '.', '[', ']', '*', '+', '?', '{', '}', '|', '(', ')', ',' };
32 |
33 | private readonly SearchBnVm _viewModel;
34 |
35 | public SearchBnControl(object viewModel, Image? sourceImage = null) : base(viewModel, sourceImage)
36 | {
37 | _viewModel = (SearchBnVm)viewModel;
38 | InitializeComponent();
39 | }
40 |
41 | protected override Visual GetDrawingVisual(out Size size)
42 | {
43 | size = new Size(MainContainer.ActualWidth, MainContainer.ActualHeight);
44 | return MainContainer;
45 | }
46 |
47 | private void SearchBnControl_OnInitialized(object? sender, EventArgs e)
48 | {
49 | var xshdTemplate = XshdTemplate;
50 |
51 | var keyWord = _viewModel.Keyword;
52 | var sb = new StringBuilder();
53 | foreach (var c in keyWord)
54 | {
55 | if (_allKeywords.Contains(c))
56 | sb.Append('\\');
57 | sb.Append(c);
58 | }
59 |
60 | xshdTemplate = xshdTemplate.Replace("{{rep}}", sb.ToString());
61 |
62 | using var stringReader = new StringReader(xshdTemplate);
63 | using var reader = XmlReader.Create(stringReader);
64 | var xshd = HighlightingLoader.LoadXshd(reader);
65 | var xshdRuleSet = (XshdRuleSet)xshd.Elements[1];
66 | xshdRuleSet.IgnoreCase = true;
67 |
68 | var syntaxHighlighting = HighlightingLoader.Load(xshd, HighlightingManager.Instance);
69 | _viewModel.SyntaxHighlighting = syntaxHighlighting;
70 | }
71 |
72 | private async void SearchBnControl_OnLoaded(object sender, RoutedEventArgs e)
73 | {
74 | for (int i = 0; i < ItemsControl.Items.Count; i++)
75 | {
76 | var uiElement = (ContentPresenter)ItemsControl.ItemContainerGenerator.ContainerFromIndex(i);
77 | if (uiElement.ContentTemplate.FindName("TextEditor", uiElement) is TextEditor tb)
78 | {
79 | tb.Text = _viewModel.Details[i].Value;
80 | tb.SyntaxHighlighting = _viewModel.SyntaxHighlighting;
81 | }
82 | }
83 |
84 | await Task.Delay(16);
85 | await FinishDrawing();
86 | }
87 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/SearchBnPlugins/SearchBnVm.cs:
--------------------------------------------------------------------------------
1 | using daylily.Plugins.Osu.Data;
2 | using ICSharpCode.AvalonEdit.Highlighting;
3 | using MilkiBotFramework.Data;
4 |
5 | namespace daylily.Plugins.Osu.SearchBnPlugins;
6 |
7 | public class SearchBnVm : ViewModelBase
8 | {
9 | private IHighlightingDefinition? _syntaxHighlighting;
10 |
11 | public SearchBnVm(IReadOnlyList> details, string keyword)
12 | {
13 | Details = details;
14 | Keyword = keyword;
15 | }
16 |
17 | public IReadOnlyList> Details { get; }
18 | public string Keyword { get; }
19 |
20 | public IHighlightingDefinition? SyntaxHighlighting
21 | {
22 | get => _syntaxHighlighting;
23 | set => this.RaiseAndSetIfChanged(ref _syntaxHighlighting, value);
24 | }
25 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/SetId.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Coosu.Api.V2;
3 | using daylily.Plugins.Osu.Data;
4 | using daylily.Utils;
5 | using MilkiBotFramework.Event;
6 | using MilkiBotFramework.Messaging;
7 | using MilkiBotFramework.Messaging.RichMessages;
8 | using MilkiBotFramework.Plugining;
9 | using MilkiBotFramework.Plugining.Attributes;
10 | using MilkiBotFramework.Plugining.Configuration;
11 |
12 | namespace daylily.Plugins.Osu;
13 |
14 | [PluginIdentifier("f159fb01-de65-46e0-818c-b38fdc55eea3", "绑定osu!账号", Scope = "osu!")]
15 | public class SetId : BasicPlugin
16 | {
17 | private readonly ApiService _apiService;
18 | private readonly EventBus _eventBus;
19 | private readonly OsuDbContext _dbContext;
20 | private readonly OsuConfig _config;
21 |
22 | public SetId(IConfiguration configuration, ApiService apiService, EventBus eventBus,
23 | OsuDbContext dbContext)
24 | {
25 | _config = configuration.Instance;
26 | _apiService = apiService;
27 | _eventBus = eventBus;
28 | _dbContext = dbContext;
29 | }
30 |
31 | [Description("将osu!账号绑定至QQ")]
32 | [CommandHandler("setid.osu", AllowedMessageType = MessageType.Private)]
33 | public async IAsyncEnumerable SetIdCore(MessageContext context)
34 | {
35 | var userId = context.MessageUserIdentity!.UserId;
36 | var sb = new AuthorizationLinkBuilder(_config.ClientId,
37 | new Uri(_config.ServerRedirectUri));
38 |
39 | var guid = Guid.NewGuid();
40 | var sessionToken = guid.ToString("N");
41 | var state = EncryptUtil.EncryptAes256UseMd5(userId + "|" + DateTime.Now.Ticks + "|" + sessionToken,
42 | _config.QQAesKey,
43 | _config.QQAesIV);
44 |
45 | var uri = sb.BuildAuthorizationLink(state, AuthorizationScope.Identify);
46 | using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(5));
47 | var tcs = new TaskCompletionSource();
48 | cts.Token.Register(() => tcs.SetCanceled());
49 | OsuTokenReceivedEvent @event = null!;
50 | _eventBus.Subscribe(e =>
51 | {
52 | if (e.SessionToken != sessionToken) return;
53 | @event = e;
54 | tcs.TrySetResult();
55 | }, guid);
56 |
57 | yield return Reply("请点击以下链接完成账号授权,5分钟内有效(请勿分享链接):");
58 | await Task.Delay(300);
59 | yield return Reply(new UriText(uri.AbsoluteUri));
60 | bool timeout;
61 | try
62 | {
63 | await tcs.Task;
64 | timeout = false;
65 | }
66 | catch
67 | {
68 | timeout = true;
69 | }
70 |
71 | _eventBus.Unsubscribe(guid);
72 | if (timeout)
73 | {
74 | //yield return Reply("超过5分钟未点击链接,操作已取消..");
75 | yield break;
76 | }
77 |
78 | if (!@event.IsSuccess)
79 | {
80 | yield return Reply(@event.FailReason);
81 | yield break;
82 | }
83 |
84 | await _dbContext.AddOrUpdateToken(@event.SourceId!, @event.User.Id.Value, @event.Token!);
85 | yield return Reply($"你已成功绑定osu!id: {@event.User.Username}");
86 | }
87 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/UserPage/UserPageControl.xaml:
--------------------------------------------------------------------------------
1 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/UserPage/UserPageControl.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text.Json;
3 | using System.Web;
4 | using System.Windows.Media.Imaging;
5 | using System.Windows.Threading;
6 | using MilkiBotFramework.Imaging.Wpf;
7 | using mshtml;
8 | using Image = SixLabors.ImageSharp.Image;
9 |
10 | namespace daylily.Plugins.Osu.UserPage
11 | {
12 | ///
13 | /// UserPageControl.xaml 的交互逻辑
14 | ///
15 | public partial class UserPageControl : WpfDrawingControl
16 | {
17 | private static string? _templateText;
18 |
19 | private readonly string _pluginHome;
20 | private readonly UserPageVm _viewModel;
21 | private readonly DispatcherTimer _dispatcherTimer;
22 |
23 | public UserPageControl(string pluginHome, object viewModel, Image? sourceImage = null)
24 | : base(viewModel, sourceImage)
25 | {
26 | _pluginHome = pluginHome;
27 | _viewModel = (UserPageVm)viewModel;
28 | _dispatcherTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
29 |
30 | InitializeComponent();
31 | }
32 |
33 | public override async Task ProcessOnceAsync()
34 | {
35 | var source = WebPrintScreen.PrintBrowser(WebBrowser);
36 | if (source == null)
37 | throw new Exception("WebBrowser print failed!");
38 | var encoder = new PngBitmapEncoder();
39 | encoder.Frames.Add(BitmapFrame.Create((BitmapSource)source));
40 | var stream = new MemoryStream();
41 | encoder.Save(stream);
42 | stream.Position = 0;
43 | return stream;
44 | }
45 |
46 | private void UserPageControl_OnInitialized(object? sender, EventArgs e)
47 | {
48 | _templateText ??= File.ReadAllText(Path.Combine(_pluginHome, "bbcode", "template.txt"));
49 | _dispatcherTimer.Start();
50 | _dispatcherTimer.Tick += DispatcherTimerTick;
51 |
52 | var html = _templateText.Replace("{{content}}", _viewModel.RawHtml);
53 | var htmlPath = Path.Combine(_pluginHome, $"u{_viewModel.UserId}.html");
54 | File.WriteAllText(htmlPath, html);
55 |
56 | var fullPath = Path.GetFullPath(htmlPath);
57 | var htmlPathUri = HttpUtility.UrlEncode(fullPath.Replace("\\", "/"));
58 | WebBrowser.Navigate(new Uri(@"file:///" + htmlPathUri));
59 | }
60 |
61 | private async void DispatcherTimerTick(object? sender, EventArgs e)
62 | {
63 | dynamic doc = WebBrowser.Document;
64 | if (doc is not IHTMLDocument2 thisDoc)
65 | return;
66 |
67 | var element = thisDoc.all
68 | .OfType()
69 | .FirstOrDefault(k => k?.id == "hidden");
70 | var html = element?.innerText;
71 | if (html == null) return;
72 |
73 | var jDoc = JsonDocument.Parse(html);
74 | var width = jDoc.RootElement.GetProperty("width").GetInt32();
75 | var height = jDoc.RootElement.GetProperty("height").GetInt32();
76 | WebBrowser.Width = width + 9;
77 | WebBrowser.Height = height + 2;
78 | _dispatcherTimer.Stop();
79 | await Task.Delay(100);
80 | await FinishDrawing();
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/UserPage/UserPagePlugin.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel;
2 | using Coosu.Api.V2;
3 | using daylily.Plugins.Osu.Data;
4 | using MilkiBotFramework.Imaging;
5 | using MilkiBotFramework.Imaging.Wpf;
6 | using MilkiBotFramework.Messaging;
7 | using MilkiBotFramework.Messaging.RichMessages;
8 | using MilkiBotFramework.Plugining;
9 | using MilkiBotFramework.Plugining.Attributes;
10 |
11 | namespace daylily.Plugins.Osu.UserPage;
12 |
13 | [PluginIdentifier("20e4039e-41fd-4dee-bcaa-12b43dc7e0a6", "个人介绍页面", Scope = "osu!")]
14 | [Description("自动生成个人介绍页面截图")]
15 | public class UserPagePlugin : BasicPlugin
16 | {
17 | private readonly ApiService _apiService;
18 | private readonly OsuDbContext _dbContext;
19 |
20 | public UserPagePlugin(ApiService apiService, OsuDbContext dbContext)
21 | {
22 | _apiService = apiService;
23 | _dbContext = dbContext;
24 | }
25 |
26 | [CommandHandler("userpage")]
27 | public async Task UserPageHandler(MessageContext context,
28 | [Argument, Description("用户名或用户UID")] string? user = null)
29 | {
30 | if (string.IsNullOrWhiteSpace(user))
31 | {
32 | var osuId = await _dbContext.GetUserIdBySourceId(context.MessageUserIdentity!.UserId);
33 | if (osuId == null) return Reply(_apiService.UnbindMessage);
34 | user = osuId.ToString();
35 | }
36 |
37 | var response =
38 | await _apiService.TryAccessPublicApi(async client => await client.User.GetUser(user!, GameMode.Osu));
39 | if (!response.Success)
40 | return Reply(response.Error!);
41 |
42 | var result = response.Result!;
43 | if (string.IsNullOrWhiteSpace(result.Page?.Raw))
44 | return Reply("该用户没有UserPage");
45 |
46 | var renderer = new WpfDrawingProcessor((vm, image1) =>
47 | new UserPageControl(PluginHome, vm, image1));
48 |
49 | var helpViewModel = new UserPageVm(result.Id!.Value, result.Page.Html);
50 | var image = await renderer.ProcessAsync(helpViewModel);
51 | return Reply(new MemoryImage(image, ImageType.Png));
52 | }
53 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/UserPage/UserPageVm.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.Plugins.Osu.UserPage;
2 |
3 | public sealed class UserPageVm
4 | {
5 | public UserPageVm(long userId, string rawHtml)
6 | {
7 | UserId = userId;
8 | RawHtml = rawHtml;
9 | }
10 |
11 | public long UserId { get; }
12 | public string RawHtml { get; }
13 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Osu/UserPage/WebPrintScreen.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using System.IO;
3 | using System.Runtime.InteropServices;
4 | using System.Windows.Controls;
5 | using System.Windows.Media;
6 | using PixelFormat = System.Drawing.Imaging.PixelFormat;
7 |
8 | namespace daylily.Plugins.Osu.UserPage;
9 |
10 | ///
11 | /// Windows Platform only!
12 | ///
13 | internal static class WebPrintScreen
14 | {
15 | [DllImport("user32.dll")]
16 | // ReSharper disable once IdentifierTypo
17 | private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
18 |
19 | public static ImageSource? PrintBrowser(WebBrowser targetBrowser)
20 | {
21 | var screenWidth = (int)targetBrowser.ActualWidth;
22 | var screenHeight = (int)targetBrowser.ActualHeight;
23 |
24 | var browserHandle = targetBrowser.Handle;
25 |
26 | bool drawResult;
27 | using var bitmap = new Bitmap(screenWidth, screenHeight, PixelFormat.Format16bppRgb555);
28 | using (var graphics = Graphics.FromImage(bitmap))
29 | {
30 | var graphicsHdc = graphics.GetHdc();
31 | try
32 | {
33 | drawResult = PrintWindow(browserHandle, graphicsHdc, 0);
34 | }
35 | finally
36 | {
37 | graphics.ReleaseHdc(graphicsHdc);
38 | graphics.Flush();
39 | }
40 | }
41 |
42 | if (!drawResult)
43 | return null;
44 |
45 | var imageSourceConverter = new ImageSourceConverter();
46 | var stream = new MemoryStream();
47 | bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
48 | return (ImageSource)imageSourceConverter.ConvertFrom(stream)!;
49 | }
50 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Services/CacheManagerService.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using Microsoft.Extensions.Logging;
3 | using MilkiBotFramework;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 | using MilkiBotFramework.Tasking;
7 |
8 | namespace daylily.Plugins.Services
9 | {
10 | [PluginIdentifier("689efebb-9642-4ba1-bad8-8e74e0f91200", "缓存定时清理")]
11 | public sealed class CacheManagerService : ServicePlugin
12 | {
13 | private readonly ILogger _logger;
14 | private readonly BotTaskScheduler _scheduler;
15 | private readonly BotOptions _botOptions;
16 |
17 | public CacheManagerService(ILogger logger, BotOptions botOptions, BotTaskScheduler scheduler)
18 | {
19 | _logger = logger;
20 | _botOptions = botOptions;
21 | _scheduler = scheduler;
22 | }
23 |
24 | protected override async Task OnInitialized()
25 | {
26 | _scheduler.AddTask("清理图片缓存", k => k
27 | .AtStartup()
28 | .ByInterval(TimeSpan.FromHours(0.5))
29 | .Do(DoCleanTask)
30 | );
31 | }
32 |
33 | private void DoCleanTask(TaskContext context, CancellationToken token)
34 | {
35 | _logger.LogInformation("开始清理图片缓存……");
36 | ClearCache(_botOptions.CacheImageDir, _logger);
37 | _logger.LogInformation("清理图片缓存完毕,下次清理时间:" + context.NextTriggerTimes.First());
38 | }
39 |
40 | private static void ClearCache(string path, ILogger logger)
41 | {
42 | var folder = new DirectoryInfo(path);
43 | if (!folder.Exists) return;
44 | var files = folder.EnumerateFiles();
45 | foreach (var file in files)
46 | {
47 | try
48 | {
49 | if (DateTime.Now - file.LastWriteTime > TimeSpan.FromMinutes(30))
50 | file.Delete();
51 | }
52 | catch (Exception ex)
53 | {
54 | logger.LogError(ex, "清理文件时出错");
55 | }
56 | }
57 |
58 | var folders = folder.EnumerateDirectories();
59 | foreach (var f in folders)
60 | {
61 | ClearCache(f.FullName, logger);
62 | try
63 | {
64 | if (DateTime.Now - f.LastWriteTime > TimeSpan.FromMinutes(30))
65 | f.Delete();
66 | }
67 | catch (Exception ex)
68 | {
69 | logger.LogError(ex, "清理目录时出错");
70 | }
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/daylily/Plugins/Services/CliPrintMe.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 | using MilkiBotFramework.Plugining.Loading;
6 |
7 | namespace daylily.Plugins.Services;
8 |
9 | [PluginIdentifier("440f5374-925e-4065-85cd-c06e0175af06", Index = 99)]
10 | public class CliPrintMe : ServicePlugin
11 | {
12 | private readonly ILogger _logger;
13 |
14 | public CliPrintMe(ILoggerFactory loggerFactory)
15 | {
16 | _logger = loggerFactory.CreateLogger("raw_me");
17 | }
18 |
19 | public override async Task BeforeSend(PluginInfo pluginInfo, IResponse response)
20 | {
21 | if (response.Message == null) return true;
22 |
23 | var context = response.MessageContext;
24 | var identity = context?.MessageIdentity;
25 | string session = "未知会话";
26 |
27 | if (context != null && identity != null)
28 | {
29 | if (identity.MessageType == MessageType.Private)
30 | {
31 | var privateInfo = context.PrivateInfo!;
32 | session = "私聊 " + privateInfo.UserId + " - " +
33 | (privateInfo.Remark ?? privateInfo.Nickname ?? privateInfo.UserId);
34 | }
35 | else if (identity.MessageType == MessageType.Channel)
36 | {
37 | var channelInfo = context.ChannelInfo!;
38 | session = identity.SubId == null
39 | ? $"群 {channelInfo.ChannelId} - {channelInfo.Name}"
40 | : $"频道 {channelInfo.ChannelId}.{channelInfo.SubChannelId} - {channelInfo.Name}";
41 | }
42 | }
43 |
44 | var time = DateTime.Now;
45 | var richMessage = response.Message.ToString()!;
46 | var actualMessage = string.Join('\n', richMessage.Split('\n').Select(k => " " + k));
47 |
48 | _logger.LogInformation($"{time} ({session}) 我:\r\n" +
49 | $"{actualMessage}");
50 | return true;
51 | }
52 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Services/FailBindingReplyService.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 |
6 | namespace daylily.Plugins.Services;
7 |
8 | [PluginIdentifier("f3561463-fda7-4d97-a899-50f6ba4a36ac")]
9 | public sealed class FailBindingReplyService : ServicePlugin
10 | {
11 | private readonly BotOptions _botOptions;
12 |
13 | public FailBindingReplyService(BotOptions botOptions)
14 | {
15 | _botOptions = botOptions;
16 | }
17 |
18 | public override async Task OnBindingFailed(BindingException bindingException, MessageContext context)
19 | {
20 | if (bindingException.BindingFailureType == BindingFailureType.AuthenticationFailed)
21 | {
22 | if (context.Authority == MessageAuthority.Admin)
23 | return Reply("此功能需要开发者权限..");
24 | return Reply("此功能需要群内管理员权限..");
25 | }
26 |
27 | if (bindingException.BindingFailureType == BindingFailureType.MessageTypeFailed)
28 | {
29 | if (context.MessageIdentity!.MessageType == MessageType.Channel)
30 | return Reply("此功能仅限私聊..");
31 | return Reply("此功能仅限群聊..");
32 | }
33 |
34 | if (bindingException.BindingFailureType == BindingFailureType.Mismatch)
35 | {
36 | GetCommandInfo(bindingException, out var command, out var info, out var type);
37 | var message = $"指令缺少{type}:{info}。请使用 \"{_botOptions.CommandFlag}help {command}\" 查看说明。";
38 | message = AppendError(bindingException, context, message);
39 |
40 | return Reply(message);
41 | }
42 |
43 | if (bindingException.BindingFailureType == BindingFailureType.ConvertError)
44 | {
45 | GetCommandInfo(bindingException, out var command, out var info, out var type);
46 | var message = $"指令{type}解析出错:{info}。请使用 \"{_botOptions.CommandFlag}help {command}\" 查看说明。";
47 | message = AppendError(bindingException, context, message);
48 |
49 | return Reply(message);
50 | }
51 |
52 | return null;
53 | }
54 |
55 | private static void GetCommandInfo(BindingException bindingException, out string command, out string info, out string type)
56 | {
57 | var parameterInfo = bindingException.BindingSource.ParameterInfo!;
58 | command = bindingException.BindingSource.CommandInfo.Command;
59 | var retType = parameterInfo.ParameterType == StaticTypes.Boolean ? "" : " ...";
60 | info = parameterInfo.IsArgument ? $"\"{parameterInfo.ParameterName}\"" : $"\"-{parameterInfo.Name}{retType}\"";
61 | type = parameterInfo.IsArgument ? "参数" : "选项";
62 | }
63 |
64 | private static string AppendError(BindingException bindingException, MessageContext context, string message)
65 | {
66 | if (context.Authority != MessageAuthority.Root) return message;
67 |
68 | //#if DEBUG
69 | // if (context.MessageIdentity!.MessageType == MessageType.Private)
70 | // {
71 | // message += "调试信息:\r\n" + (bindingException.InnerException ?? bindingException);
72 | // }
73 | // else
74 | //#endif
75 | {
76 | message += "\r\n错误信息:" + (bindingException.InnerException?.Message ?? bindingException.Message);
77 | }
78 |
79 | return message;
80 | }
81 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Services/FailExecuteReplyService.cs:
--------------------------------------------------------------------------------
1 | using daylily.Utils;
2 | using MilkiBotFramework.Messaging;
3 | using MilkiBotFramework.Plugining;
4 | using MilkiBotFramework.Plugining.Attributes;
5 |
6 | namespace daylily.Plugins.Services;
7 |
8 | [PluginIdentifier("c7812045-5b4c-44a3-af71-f0fedf73cc1f")]
9 | public sealed class FailExecuteReplyService : ServicePlugin
10 | {
11 | public override async Task OnPluginException(Exception exception, MessageContext context)
12 | {
13 | if (context.Authority != MessageAuthority.Root)
14 | {
15 | return Reply("指令执行出错!" +
16 | "\r\n错误信息:" + (exception?.Message));
17 | }
18 |
19 | var message = "指令执行出错!" +
20 | "\r\n错误信息:" + (exception?.ToFullTypeMessage());
21 |
22 | return Reply(message);
23 | }
24 | }
--------------------------------------------------------------------------------
/daylily/Plugins/Services/MemoryCheckService.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using Microsoft.Extensions.Logging;
3 | using MilkiBotFramework;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 | using MilkiBotFramework.Tasking;
7 |
8 | namespace daylily.Plugins.Services
9 | {
10 | [PluginIdentifier("11CCA200-61EF-49DF-9E30-C48C3AD69BE5", "内存溢出检测")]
11 | public class MemoryCheckService : ServicePlugin
12 | {
13 | private readonly Bot _bot;
14 | private readonly BotTaskScheduler _taskScheduler;
15 |
16 | public MemoryCheckService(Bot bot, BotTaskScheduler taskScheduler)
17 | {
18 | _bot = bot;
19 | _taskScheduler = taskScheduler;
20 | }
21 |
22 | protected override async Task OnInitialized()
23 | {
24 | var preSize = 0L;
25 |
26 | await Task.Delay(TimeSpan.FromSeconds(3));
27 | _taskScheduler.AddTask("内存溢出检测", k => k
28 | .AtStartup()
29 | .WithoutLogging()
30 | .ByInterval(TimeSpan.FromSeconds(5))
31 | .Do((context, token) =>
32 | {
33 | using var proc = Process.GetCurrentProcess();
34 | var logger = context.Logger;
35 | var mem = proc.PrivateMemorySize64;
36 | if (preSize != 0L)
37 | {
38 | var value = mem - preSize;
39 | if (value > 256 * 1024)
40 | logger.LogDebug($"Memory changed by {SizeSuffix(value)} to {SizeSuffix(mem)} in 3 sec");
41 | }
42 |
43 | preSize = mem;
44 |
45 | var max = 1024L * 1024L * 1024L * 2;
46 | if (mem > max)
47 | {
48 | logger.LogWarning($"Memory reaches max to {SizeSuffix(max)}, the application will shutdown.");
49 | _bot.Stop(-114514);
50 | //Environment.Exit(-114514);
51 | }
52 | else if (mem > max * 0.85d)
53 | {
54 | logger.LogWarning($"Memory reaches 85% max to {SizeSuffix(max)} by {SizeSuffix(mem)}!");
55 | }
56 | })
57 | );
58 | }
59 |
60 | private static readonly string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };
61 |
62 | private static string SizeSuffix(long value, int decimalPlaces = 1)
63 | {
64 | if (value < 0) { return "-" + SizeSuffix(-value, decimalPlaces); }
65 |
66 | int i = 0;
67 | double dValue = value;
68 | while (Math.Round(dValue, decimalPlaces) >= 1000)
69 | {
70 | dValue /= 1024;
71 | i++;
72 | }
73 |
74 | return string.Format("{0:n" + decimalPlaces + "} {1}", dValue, SizeSuffixes[i]);
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/daylily/Plugins/Services/MessageAvoidRepeatService.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 | using daylily.Plugins.Core;
3 | using MilkiBotFramework.Messaging;
4 | using MilkiBotFramework.Plugining;
5 | using MilkiBotFramework.Plugining.Attributes;
6 | using MilkiBotFramework.Plugining.Loading;
7 |
8 | namespace daylily.Plugins.Services
9 | {
10 | [PluginIdentifier("365d4d8b-e5f3-4815-9503-a9416fc28af5", "发言重复过滤")]
11 | public class MessageAvoidRepeatService : ServicePlugin
12 | {
13 | private readonly ConcurrentDictionary> _identityDictionary = new();
14 |
15 | public override async Task BeforeSend(PluginInfo pluginInfo, IResponse response)
16 | {
17 | var id = response.MessageContext?.MessageIdentity;
18 | if (id == null) return true;
19 |
20 | if (response.Message == null) return true;
21 | var message = await response.Message.EncodeAsync();
22 |
23 | var list = _identityDictionary.GetOrAdd(id, _ => new List());
24 |
25 | var dueMessage = list.FirstOrDefault(k => k.Timestamp.AddSeconds(30) < DateTimeOffset.Now);
26 | if (dueMessage != null)
27 | {
28 | var index = list.IndexOf(dueMessage);
29 | list.RemoveRange(0, index + 1);
30 | }
31 |
32 | if (list.Any(k => k.RawMessage == message))
33 | {
34 | if (response.IsForced == true) return true;
35 |
36 | response.Handled();
37 | return false;
38 | }
39 |
40 | var lightMessage = new LightMessage { RawMessage = message, Timestamp = DateTimeOffset.Now };
41 | list.Add(lightMessage);
42 | return true;
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/daylily/Plugins/Services/SensitiveMatchService.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using daylily.ThirdParty.ToolGood.Words;
3 | using Microsoft.Extensions.Logging;
4 | using MilkiBotFramework.Messaging;
5 | using MilkiBotFramework.Messaging.RichMessages;
6 | using MilkiBotFramework.Plugining;
7 | using MilkiBotFramework.Plugining.Attributes;
8 | using MilkiBotFramework.Plugining.Loading;
9 |
10 | namespace daylily.Plugins.Services
11 | {
12 | [PluginIdentifier("13432984-2A24-4691-9C0C-DE98B7B70F9A", "敏感词屏蔽")]
13 | public class SensitiveMatchService : ServicePlugin
14 | {
15 | private readonly ILogger _logger;
16 |
17 | public SensitiveMatchService(ILogger logger)
18 | {
19 | _logger = logger;
20 | }
21 |
22 | private StringSearchEx3? _sharedSearch;
23 |
24 | protected override async Task OnInitialized()
25 | {
26 | var bin = Path.Combine(PluginHome, "sensitive.bin");
27 | var text = Path.Combine(PluginHome, "sensitive.txt");
28 | if (File.Exists(bin))
29 | {
30 | _sharedSearch = new StringSearchEx3();
31 | _sharedSearch.Load(bin);
32 | }
33 | else if (File.Exists(text))
34 | {
35 | var lines = File.ReadAllLines(text);
36 | var stringSearchEx3 = new StringSearchEx3();
37 | stringSearchEx3.SetKeywords(lines);
38 | stringSearchEx3.Save(bin);
39 | }
40 | else
41 | {
42 | _logger.LogWarning("Sensitive file not found: " + new FileInfo(text).FullName);
43 | }
44 | }
45 |
46 | public override async Task BeforeSend(PluginInfo pluginInfo, IResponse response)
47 | {
48 | if (_sharedSearch == null) return true;
49 | if (response.Message == null) return true;
50 |
51 | if (response.Message is Text text)
52 | {
53 | ReplaceMessage(text, _sharedSearch);
54 | }
55 | else if (response.Message is RichMessage richMessage)
56 | {
57 | foreach (var message in richMessage)
58 | {
59 | if (message is Text text1)
60 | {
61 | ReplaceMessage(text1, _sharedSearch);
62 | }
63 | }
64 | }
65 |
66 | return true;
67 | }
68 |
69 | private static void ReplaceMessage(Text text, StringSearchEx3 stringSearchEx3)
70 | {
71 | var txt = stringSearchEx3.Replace(text.Content, '\0');
72 | text.Content = txt.Replace("\0", "_");
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/daylily/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using daylily;
3 | using daylily.ThirdParty.Tuling;
4 | using Microsoft.Extensions.DependencyInjection;
5 | using MilkiBotFramework.Aspnetcore;
6 | using MilkiBotFramework.Imaging.Wpf.Internal;
7 | using MilkiBotFramework.Platforms.GoCqHttp;
8 | using NLog.Extensions.Logging;
9 |
10 | UiThreadHelper.GetApplicationFunc = () =>
11 | {
12 | var application = new App();
13 | var resourceLocater = new Uri("/daylily;component/app.xaml", UriKind.Relative);
14 | Application.LoadComponent(application, resourceLocater);
15 | return application;
16 | };
17 |
18 | return await new AspnetcoreBotBuilder()
19 | .ConfigureServices(k => k.AddSingleton())
20 | .ConfigureLogger(k => k
21 | #if DEBUG
22 | .AddNLog("NLog.debug.config")
23 | #else
24 | .AddNLog("NLog.config")
25 | #endif
26 |
27 | )
28 | .UseGoCqHttp()
29 | .UseCommandLineAnalyzer(new GoCqParameterConverter())
30 | .Build()
31 | .RunAsync();
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceHanSerifCn.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceHanSerifCn.ttf
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceSansPro-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceSansPro-Black.ttf
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceSansPro-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceSansPro-Bold.ttf
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceSansPro-ExtraLight.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceSansPro-ExtraLight.ttf
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceSansPro-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceSansPro-Light.ttf
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceSansPro-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceSansPro-Regular.ttf
--------------------------------------------------------------------------------
/daylily/Resources/Fonts/SourceSansPro-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Milkitic/daylily/e26f58e2a69c33a9097e9a88e68ab8635d55ef9d/daylily/Resources/Fonts/SourceSansPro-Semibold.ttf
--------------------------------------------------------------------------------
/daylily/ThirdParty/Moebooru/Api.cs:
--------------------------------------------------------------------------------
1 | using MilkiBotFramework.Connecting;
2 |
3 | namespace daylily.ThirdParty.Moebooru
4 | {
5 | class Api
6 | {
7 | private const string SafeRating = "s";
8 | private const string QuestionableRating = "q";
9 | private const string ExplicitRating = "e";
10 |
11 | private const string PopularPath = "/post/popular_recent.json";
12 | private string Popular => domain + PopularPath;
13 |
14 | readonly string domain;
15 |
16 | public Api(string domain)
17 | {
18 | this.domain = domain.TrimEnd('/');
19 | }
20 |
21 | public bool EnableR18 { get; set; } = false;
22 |
23 | //private static async Task GetTAsync(string url)
24 | //{
25 | // try
26 | // {
27 | // using (var client = new HttpClient())
28 | // {
29 | // var s = await client.GetStringAsync(url);
30 | // T result = JsonSerializer.Deserialize(s);
31 | // return result;
32 | // }
33 | // }
34 | // catch (Exception) { return default(T); }
35 | //}
36 |
37 | public async Task> PopularRecentAsync(LightHttpClient httpClient)
38 | {
39 | IEnumerable result = await httpClient.HttpGet(Popular);
40 |
41 | if (!EnableR18)
42 | result = result?.Where(p => p.rating == SafeRating);
43 | return result;
44 | }
45 |
46 | internal async Task<(IEnumerable result, string info)> PopularRecentDebugAsync(LightHttpClient httpClient)
47 | {
48 | IEnumerable result = await httpClient.HttpGet(Popular);
49 | var groups = result.GroupBy(p => p.rating);
50 | var infos = new LinkedList();
51 | foreach (var group in groups)
52 | {
53 | infos.AddLast($"{group.Key}: {group.Count()}");
54 | }
55 | string info = string.Join("\r\n", infos);
56 | if (!EnableR18)
57 | result = result.Where(p => p.rating == SafeRating);
58 | return (result, info);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/Moebooru/Post.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | #pragma warning disable IDE1006 // 命名样式
4 | // ReSharper disable InconsistentNaming
5 | namespace daylily.ThirdParty.Moebooru
6 | {
7 | public class Post
8 | {
9 | private const string ProtocolPrefix = "https:";
10 |
11 | public int id { get; set; }
12 | public string tags { get; set; }
13 | public int created_at { get; set; }
14 | public int creator_id { get; set; }
15 | public string author { get; set; }
16 | public int change { get; set; }
17 | public string source { get; set; }
18 | public int score { get; set; }
19 | public string md5 { get; set; }
20 | public int file_size { get; set; }
21 | public string file_url { get; set; }
22 | [JsonIgnore]
23 | public string FileUrl => UrlFormat(file_url);
24 | public bool is_shown_in_index { get; set; }
25 | public string preview_url { get; set; }
26 | [JsonIgnore]
27 | public string PreviewUrl => UrlFormat(preview_url);
28 | public int preview_width { get; set; }
29 | public int preview_height { get; set; }
30 | public int actual_preview_width { get; set; }
31 | public int actual_preview_height { get; set; }
32 | public string sample_url { get; set; }
33 | [JsonIgnore]
34 | public string SampleUrl => UrlFormat(sample_url);
35 | public int sample_width { get; set; }
36 | public int sample_height { get; set; }
37 | public int sample_file_size { get; set; }
38 | public string jpeg_url { get; set; }
39 | [JsonIgnore]
40 | public string JpegUrl => UrlFormat(jpeg_url);
41 | public int jpeg_width { get; set; }
42 | public int jpeg_height { get; set; }
43 | public int jpeg_file_size { get; set; }
44 | public string rating { get; set; }
45 | public bool has_children { get; set; }
46 | public object parent_id { get; set; }
47 | public string status { get; set; }
48 | public int width { get; set; }
49 | public int height { get; set; }
50 | public bool is_held { get; set; }
51 | public string frames_pending_string { get; set; }
52 | public object[] frames_pending { get; set; }
53 | public string frames_string { get; set; }
54 | public object[] frames { get; set; }
55 | public object flag_detail { get; set; }
56 |
57 | private static string UrlFormat(string ori) => ori.StartsWith("//") ? ProtocolPrefix + ori : ori;
58 |
59 | // ReSharper restore InconsistentNaming
60 | #pragma warning restore IDE1006 // 命名样式
61 | }
62 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/Data/PinyinData.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.TinyPinyin.Core.Standard.Data
2 | {
3 | public static class PinyinData
4 | {
5 | public static char MIN_VALUE = (char)19968;
6 |
7 | public static char MAX_VALUE = (char)40869;
8 |
9 | public static String PINYIN_12295 = "LING";
10 |
11 | public static char CHAR_12295 = (char)12295;
12 |
13 | public static int PINYIN_CODE_1_OFFSET = 7000;
14 |
15 | public static int PINYIN_CODE_2_OFFSET = 7000 * 2;
16 |
17 | public static int[] BIT_MASKS = new int[] { 1, 2, 4, 8, 16, 32, 64, 128 };
18 |
19 | public static int PADDING_MASK = 256;
20 |
21 | //CHECKSTYLE:OFF
22 | public static string[] PINYIN_TABLE = new string[]{"", "A", "AI", "AN", "ANG", "AO", "BA", "BAI",
23 | "BAN", "BANG", "BAO", "BEI", "BEN", "BENG", "BI", "BIAN", "BIAO", "BIE", "BIN", "BING",
24 | "BO", "BU", "CA", "CAI", "CAN", "CANG", "CAO", "CE", "CEN", "CENG", "CHA", "CHAI",
25 | "CHAN", "CHANG", "CHAO", "CHE", "CHEN", "CHENG", "CHI", "CHONG", "CHOU", "CHU", "CHUAI",
26 | "CHUAN", "CHUANG", "CHUI", "CHUN", "CHUO", "CI", "CONG", "COU", "CU", "CUAN", "CUI",
27 | "CUN", "CUO", "DA", "DAI", "DAN", "DANG", "DAO", "DE", "DENG", "DI", "DIA", "DIAN",
28 | "DIAO", "DIE", "DING", "DIU", "DONG", "DOU", "DU", "DUAN", "DUI", "DUN", "DUO", "E",
29 | "EI", "EN", "ER", "E^", "FA", "FAN", "FANG", "FEI", "FEN", "FENG", "FO", "FOU", "FU",
30 | "GA", "GAI", "GAN", "GANG", "GAO", "GE", "GEI", "GEN", "GENG", "GONG", "GOU", "GU",
31 | "GUA", "GUAI", "GUAN", "GUANG", "GUI", "GUN", "GUO", "HA", "HAI", "HAN", "HANG", "HAO",
32 | "HE", "HEI", "HEN", "HENG", "HONG", "HOU", "HU", "HUA", "HUAI", "HUAN", "HUANG", "HUI",
33 | "HUN", "HUO", "JI", "JIA", "JIAN", "JIANG", "JIAO", "JIE", "JIN", "JING", "JIONG",
34 | "JIU", "JU", "JUAN", "JUE", "JUN", "KA", "KAI", "KAN", "KANG", "KAO", "KE", "KEN",
35 | "KENG", "KONG", "KOU", "KU", "KUA", "KUAI", "KUAN", "KUANG", "KUI", "KUN", "KUO", "LA",
36 | "LAI", "LAN", "LANG", "LAO", "LE", "LEI", "LENG", "LI", "LIA", "LIAN", "LIANG", "LIAO",
37 | "LIE", "LIN", "LING", "LIU", "LONG", "LOU", "LU", "LUAN", "LUN", "LUO", "LV", "LVE",
38 | "M", "MA", "MAI", "MAN", "MANG", "MAO", "ME", "MEI", "MEN", "MENG", "MI", "MIAN",
39 | "MIAO", "MIE", "MIN", "MING", "MIU", "MO", "MOU", "MU", "NA", "NAI", "NAN", "NANG",
40 | "NAO", "NE", "NEI", "NEN", "NENG", "NG", "NI", "NIAN", "NIANG", "NIAO", "NIE", "NIN",
41 | "NING", "NIU", "NONG", "NOU", "NU", "NUAN", "NUO", "NV", "NVE", "O", "OU", "PA", "PAI",
42 | "PAN", "PANG", "PAO", "PEI", "PEN", "PENG", "PI", "PIAN", "PIAO", "PIE", "PIN", "PING",
43 | "PO", "POU", "PU", "QI", "QIA", "QIAN", "QIANG", "QIAO", "QIE", "QIN", "QING", "QIONG",
44 | "QIU", "QU", "QUAN", "QUE", "QUN", "RAN", "RANG", "RAO", "RE", "REN", "RENG", "RI",
45 | "RONG", "ROU", "RU", "RUAN", "RUI", "RUN", "RUO", "SA", "SAI", "SAN", "SANG", "SAO",
46 | "SE", "SEN", "SENG", "SHA", "SHAI", "SHAN", "SHANG", "SHAO", "SHE", "SHEI", "SHEN",
47 | "SHENG", "SHI", "SHOU", "SHU", "SHUA", "SHUAI", "SHUAN", "SHUANG", "SHUI", "SHUN",
48 | "SHUO", "SI", "SONG", "SOU", "SU", "SUAN", "SUI", "SUN", "SUO", "TA", "TAI", "TAN",
49 | "TANG", "TAO", "TE", "TENG", "TI", "TIAN", "TIAO", "TIE", "TING", "TONG", "TOU", "TU",
50 | "TUAN", "TUI", "TUN", "TUO", "WA", "WAI", "WAN", "WANG", "WEI", "WEN", "WENG", "WO",
51 | "WU", "XI", "XIA", "XIAN", "XIANG", "XIAO", "XIE", "XIN", "XING", "XIONG", "XIU", "XU",
52 | "XUAN", "XUE", "XUN", "YA", "YAN", "YANG", "YAO", "YE", "YI", "YIAO", "YIN", "YING",
53 | "YO", "YONG", "YOU", "YU", "YUAN", "YUE", "YUN", "ZA", "ZAI", "ZAN", "ZANG", "ZAO",
54 | "ZE", "ZEI", "ZEN", "ZENG", "ZHA", "ZHAI", "ZHAN", "ZHANG", "ZHAO", "ZHE", "ZHEI",
55 | "ZHEN", "ZHENG", "ZHI", "ZHONG", "ZHOU", "ZHU", "ZHUA", "ZHUAI", "ZHUAN", "ZHUANG",
56 | "ZHUI", "ZHUN", "ZHUO", "ZI", "ZONG", "ZOU", "ZU", "ZUAN", "ZUI", "ZUN", "ZUO"};
57 | //CHECKSTYLE:ON
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/Engine.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace daylily.ThirdParty.TinyPinyin.Core.Standard
4 | {
5 | public static class Engine
6 | {
7 | public static string[] PinyinFromDict(String wordInDict, List pinyinDictSet)
8 | {
9 | if (pinyinDictSet != null)
10 | {
11 | foreach (IPinyinDict dict in pinyinDictSet)
12 | {
13 | if (dict != null && dict.Words() != null && dict.Words().Contains(wordInDict))
14 | {
15 | return dict.ToPinyin(wordInDict);
16 | }
17 | }
18 | }
19 | throw new ArgumentException("No pinyin dict contains word: " + wordInDict);
20 | }
21 | public static string ToPinyin(string inputStr, string trie, List pinyinDictList, string separator, SegmentationSelector selector)
22 | {
23 | if (inputStr == null || inputStr.Length == 0)
24 | {
25 | return inputStr;
26 | }
27 |
28 |
29 | if (trie == null || selector == null)
30 | {
31 | // 没有提供字典或选择器,按单字符转换输出
32 | var builder1 = new StringBuilder();
33 | for (int i = 0; i < inputStr.Length; i++)
34 | {
35 | builder1.Append(PinyinHelper.GetPinyin(inputStr[i]));
36 | if (i != inputStr.Length - 1)
37 | {
38 | builder1.Append(separator);
39 | }
40 | }
41 | return builder1.ToString();
42 | }
43 | return null;
44 |
45 | //List selectedEmits = selector.select(trie.parseText(inputStr));
46 |
47 | //Collections.sort(selectedEmits, EMIT_COMPARATOR);
48 |
49 | //StringBuffer resultPinyinStrBuf = new StringBuffer();
50 |
51 | //int nextHitIndex = 0;
52 |
53 | //for (int i = 0; i < inputStr.length();)
54 | //{
55 | // // 首先确认是否有以第i个字符作为begin的hit
56 | // if (nextHitIndex < selectedEmits.size() && i == selectedEmits.get(nextHitIndex).getStart())
57 | // {
58 | // // 有以第i个字符作为begin的hit
59 | // String[] fromDicts = pinyinFromDict(selectedEmits.get(nextHitIndex).getKeyword(), pinyinDictList);
60 | // for (int j = 0; j < fromDicts.length; j++)
61 | // {
62 | // resultPinyinStrBuf.append(fromDicts[j].toUpperCase());
63 | // if (j != fromDicts.length - 1)
64 | // {
65 | // resultPinyinStrBuf.append(separator);
66 | // }
67 | // }
68 |
69 | // i = i + selectedEmits.get(nextHitIndex).size();
70 | // nextHitIndex++;
71 | // }
72 | // else
73 | // {
74 | // // 将第i个字符转为拼音
75 | // resultPinyinStrBuf.append(Pinyin.toPinyin(inputStr.charAt(i)));
76 | // i++;
77 | // }
78 |
79 | // if (i != inputStr.length())
80 | // {
81 | // resultPinyinStrBuf.append(separator);
82 | // }
83 | //}
84 |
85 | //return resultPinyinStrBuf.toString();
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/IPinyinDict.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.TinyPinyin.Core.Standard
2 | {
3 | public interface IPinyinDict
4 | {
5 | ///
6 | /// 转换文本为到拼音
7 | ///
8 | ///
9 | ///
10 | string[] ToPinyin(string word);
11 |
12 | List Words();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/PinyinHelper.cs:
--------------------------------------------------------------------------------
1 | using daylily.ThirdParty.TinyPinyin.Core.Standard.Data;
2 |
3 | namespace daylily.ThirdParty.TinyPinyin.Core.Standard
4 | {
5 | public static class PinyinHelper
6 | {
7 | private static List mPinyinDicts = null;
8 |
9 | ///
10 | /// 判断给定字符是否是中文
11 | ///
12 | ///
13 | ///
14 | public static bool IsChinese(char c)
15 | {
16 | return (PinyinData.MIN_VALUE <= c && c <= PinyinData.MAX_VALUE && GetPinyinCode(c) > 0) || PinyinData.CHAR_12295 == c;
17 | }
18 |
19 | ///
20 | /// 获取单个字符的拼音
21 | ///
22 | ///
23 | ///
24 | public static string GetPinyin(char c)
25 | {
26 | if (IsChinese(c))
27 | {
28 | if (c == PinyinData.CHAR_12295)
29 | {
30 | return PinyinData.PINYIN_12295;
31 | }
32 | else
33 | {
34 | return PinyinData.PINYIN_TABLE[GetPinyinCode(c)];
35 | }
36 | }
37 | else
38 | {
39 | return c.ToString();
40 | }
41 | }
42 |
43 | ///
44 | /// 获取文本的拼音
45 | ///
46 | /// 要获取拼音的文本
47 | /// 单个拼音分隔符
48 | ///
49 | public static string GetPinyin(string str, string separator = " ")
50 | {
51 | return Engine.ToPinyin(str, null, null, separator, null);
52 | }
53 |
54 | ///
55 | /// 获取拼音手字母
56 | ///
57 | ///
58 | ///
59 | public static string GetPinyinInitials(string str)
60 | {
61 | var result = GetPinyin(str, "|");
62 | return string.Join("", result.Split('|').Select(x => x.Substring(0, 1)).ToArray());
63 | }
64 |
65 | private static int GetPinyinCode(char c)
66 | {
67 | int offset = c - PinyinData.MIN_VALUE;
68 | if (0 <= offset && offset < PinyinData.PINYIN_CODE_1_OFFSET)
69 | {
70 | return decodeIndex(PinyinCode1.PINYIN_CODE_PADDING, PinyinCode1.PINYIN_CODE, offset);
71 | }
72 | else if (PinyinData.PINYIN_CODE_1_OFFSET <= offset
73 | && offset < PinyinData.PINYIN_CODE_2_OFFSET)
74 | {
75 | return decodeIndex(PinyinCode2.PINYIN_CODE_PADDING, PinyinCode2.PINYIN_CODE,
76 | offset - PinyinData.PINYIN_CODE_1_OFFSET);
77 | }
78 | else
79 | {
80 | return decodeIndex(PinyinCode3.PINYIN_CODE_PADDING, PinyinCode3.PINYIN_CODE,
81 | offset - PinyinData.PINYIN_CODE_2_OFFSET);
82 | }
83 | }
84 |
85 | private static short decodeIndex(byte[] paddings, byte[] indexes, int offset)
86 | {
87 | //CHECKSTYLE:OFF
88 | int index1 = offset / 8;
89 | int index2 = offset % 8;
90 | short realIndex;
91 | realIndex = (short)(indexes[offset] & 0xff);
92 | //CHECKSTYLE:ON
93 | if ((paddings[index1] & PinyinData.BIT_MASKS[index2]) != 0)
94 | {
95 | realIndex = (short)(realIndex | PinyinData.PADDING_MASK);
96 | }
97 | return realIndex;
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/PinyinMapDict.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.TinyPinyin.Core.Standard
2 | {
3 | public abstract class PinyinMapDict : IPinyinDict
4 | {
5 | public abstract Dictionary Mapping();
6 |
7 | public string[] ToPinyin(string word)
8 | {
9 | var mappingResult = Mapping();
10 | if (mappingResult == null)
11 | {
12 | return null;
13 | }
14 | if (mappingResult.TryGetValue(word, out var result))
15 | {
16 | return result;
17 | }
18 | return null;
19 | }
20 |
21 | public List Words()
22 | {
23 | var mappingResult = Mapping();
24 | if (mappingResult == null)
25 | {
26 | return null;
27 | }
28 | return mappingResult.Keys.Select(key => key).ToList();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/SegmentationSelector.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.TinyPinyin.Core.Standard
2 | {
3 | public class SegmentationSelector
4 | {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/TinyPinyin.Core.Standard/readme.txt:
--------------------------------------------------------------------------------
1 | https://github.com/hstarorg/TinyPinyin.Net
2 |
3 | MIT License
--------------------------------------------------------------------------------
/daylily/ThirdParty/ToolGood.Words/TrieNode.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.ToolGood.Words
2 | {
3 | public class TrieNode
4 | {
5 | public int Index;
6 | public int Layer;
7 | public bool End;
8 | public char Char;
9 | public List Results;
10 | public Dictionary m_values;
11 | public TrieNode Failure;
12 | public TrieNode Parent;
13 | public bool IsWildcard;
14 | public int WildcardLayer;
15 | public bool HasWildcard;
16 |
17 |
18 | public TrieNode()
19 | {
20 | m_values = new Dictionary();
21 | Results = new List();
22 | }
23 |
24 | public TrieNode Add(char c)
25 | {
26 | TrieNode node;
27 | if (m_values.TryGetValue(c, out node)) {
28 | return node;
29 | }
30 | node = new TrieNode();
31 | node.Parent = this;
32 | node.Char = c;
33 | m_values[c] = node;
34 | return node;
35 | }
36 |
37 | public void SetResults(int index)
38 | {
39 | if (End == false) {
40 | End = true;
41 | }
42 | Results.Add(index);
43 | }
44 |
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/ToolGood.Words/TrieNodeEx.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.ToolGood.Words
2 | {
3 | class TrieNodeEx
4 | {
5 | internal int Char;
6 | internal bool End;
7 | internal int Index;
8 | internal List Results;
9 | internal Dictionary m_values;
10 | private Int32 minflag = Int32.MaxValue;
11 | private Int32 maxflag = 0;
12 | internal Int32 Next;
13 | public int Count;
14 |
15 | public TrieNodeEx()
16 | {
17 | m_values = new Dictionary();
18 | Results = new List();
19 | }
20 |
21 | public void Add(int c, TrieNodeEx node3)
22 | {
23 | if (minflag > c) { minflag = c; }
24 | if (maxflag < c) { maxflag = c; }
25 | m_values.Add(c, node3);
26 | Count++;
27 | }
28 |
29 | public void SetResults(Int32 text)
30 | {
31 | if (End == false) {
32 | End = true;
33 | }
34 | if (Results.Contains(text) == false) {
35 | Results.Add(text);
36 | }
37 | }
38 |
39 | public bool HasKey(int c)
40 | {
41 | return m_values.ContainsKey(c);
42 | }
43 |
44 | public int Rank(ref Int32 oneStart, ref Int32 start, bool[] seats, bool[] seats2, int[] has)
45 | {
46 | if (maxflag == 0) return 0;
47 | if (minflag == maxflag) {
48 | RankOne(ref oneStart, seats, has);
49 | return 0;
50 | }
51 | var keys = m_values.Select(q => (Int32)q.Key).OrderByDescending(q => q).ToList();
52 | var length = keys.Count - 1;
53 | int[] moves = new int[keys.Count - 1];
54 | for (int i = 1; i < keys.Count; i++) {
55 | moves[i - 1] = maxflag - keys[i];
56 | }
57 |
58 | while (has[start] != 0) { start++; }
59 | var s = start < (Int32)minflag ? (Int32)minflag : start;
60 | var next= s-minflag;
61 | var e = next+maxflag;
62 | while(e0)
69 | {
70 | for (int j = 0; j < length; j++)
71 | {
72 | seats2[position+moves[j]]=true;
73 | }
74 | isok=false;
75 | break;
76 | }
77 | }
78 | if(isok){
79 | SetSeats(next, seats, has);
80 | start += keys.Count / 2;
81 | Array.Clear(seats2, start, e + maxflag - start + 1 );
82 | return next;
83 | }
84 | }
85 | next++;
86 | e++;
87 | }
88 | throw new Exception("");
89 | }
90 |
91 | private void RankOne(ref int start, bool[] seats, int[] has)
92 | {
93 | while (has[start] != 0) { start++; }
94 | var s = start < (Int32)minflag ? (Int32)minflag : start;
95 |
96 | for (Int32 i = s; i < has.Length; i++) {
97 | if (has[i] == 0) {
98 | var next = i - (Int32)minflag;
99 | if (seats[next]) continue;
100 | SetSeats(next, seats, has);
101 | break;
102 | }
103 | }
104 | start++;
105 | }
106 |
107 |
108 | private void SetSeats(Int32 next, bool[] seats, int[] has)
109 | {
110 | Next = next;
111 | seats[next] = true;
112 |
113 | foreach (var item in m_values) {
114 | var position = next + item.Key;
115 | has[position] = item.Value.Index;
116 | }
117 |
118 | }
119 |
120 |
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/ToolGood.Words/readme.txt:
--------------------------------------------------------------------------------
1 | https://github.com/toolgood/ToolGood.Words
2 |
3 | Apache-2.0 License
4 |
5 | 这个修改版精简化了代码,将本项目不必要的类删除。
6 | 同时将StringSearchEx3的指针改为Span
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/InputImage.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 图片信息
7 | ///
8 | public class InputImage
9 | {
10 | ///
11 | /// 图片地址
12 | ///
13 | [JsonPropertyName("url")]
14 | public string Url { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/InputMedia.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 音频信息
7 | ///
8 | public class InputMedia
9 | {
10 | ///
11 | /// 音频地址
12 | ///
13 | [JsonPropertyName("url")]
14 | public string Url { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/InputText.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 文本信息
7 | ///
8 | public class InputText
9 | {
10 | ///
11 | /// 直接输入文本 (1-128字符)
12 | ///
13 | [JsonPropertyName("text")]
14 | public string Text { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/Location.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 地理位置信息
7 | ///
8 | public class Location
9 | {
10 | ///
11 | /// 所在城市
12 | ///
13 | [JsonPropertyName("city")]
14 | public string City { get; set; }
15 |
16 | ///
17 | /// 省份
18 | ///
19 | [JsonPropertyName("province")]
20 | public string Province { get; set; }
21 |
22 | ///
23 | /// 街道
24 | ///
25 | [JsonPropertyName("street")]
26 | public string Street { get; set; }
27 | }
28 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/Perception.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 输入信息
7 | ///
8 | public class Perception
9 | {
10 | ///
11 | /// 文本信息
12 | ///
13 | [JsonPropertyName("inputText")]
14 | public InputText InputText { get; set; }
15 |
16 | ///
17 | /// 图片信息
18 | ///
19 | [JsonPropertyName("inputImage")]
20 | public InputImage InputImage { get; set; }
21 |
22 | ///
23 | /// 音频信息
24 | ///
25 | [JsonPropertyName("inputMedia")]
26 | public InputImage InputMedia { get; set; }
27 |
28 | ///
29 | /// 客户端属性
30 | ///
31 | [JsonPropertyName("selfInfo")]
32 | public SelfInfo SelfInfo { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/Request.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | internal class Request
6 | {
7 | ///
8 | /// 输入类型
9 | ///
10 | [JsonPropertyName("reqType")]
11 | public RequestType RequestType { get; set; }
12 |
13 | ///
14 | /// 输入信息
15 | ///
16 | [JsonPropertyName("perception")]
17 | public Perception Perception { get; set; }
18 |
19 | ///
20 | /// 用户参数
21 | ///
22 | [JsonPropertyName("userInfo")]
23 | public UserInfo UserInfo { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/RequestType.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.Tuling.RequestModel
2 | {
3 | internal enum RequestType
4 | {
5 | ///
6 | /// 文本
7 | ///
8 | Text = 0,
9 | ///
10 | /// 图片
11 | ///
12 | Image,
13 | ///
14 | /// 音频
15 | ///
16 | Voice
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/SelfInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 客户端属性
7 | ///
8 | public class SelfInfo
9 | {
10 | ///
11 | /// 地理位置信息
12 | ///
13 | [JsonPropertyName("location")]
14 | public Location Location { get; set; }
15 | }
16 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/RequestModel/UserInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.RequestModel
4 | {
5 | ///
6 | /// 用户参数
7 | ///
8 | public class UserInfo
9 | {
10 | ///
11 | /// 机器人标识
12 | ///
13 | [JsonPropertyName("apiKey")]
14 | public string ApiKey { get; set; }
15 |
16 | ///
17 | /// 用户唯一标识
18 | ///
19 | [JsonPropertyName("userId")]
20 | public string UserId { get; set; }
21 |
22 | ///
23 | /// 群聊唯一标识
24 | ///
25 | [JsonPropertyName("groupId")]
26 | public string GroupId { get; set; }
27 |
28 | ///
29 | /// 群内用户昵称
30 | ///
31 | [JsonPropertyName("userIdName")]
32 | public string? UserIdName { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/ResponseModel/Intent.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.ResponseModel
4 | {
5 | ///
6 | /// 请求意图
7 | ///
8 | public class Intent
9 | {
10 | ///
11 | /// 输出功能code
12 | ///
13 | [JsonPropertyName("code")]
14 | public long Code { get; set; }
15 |
16 | ///
17 | /// 意图名称
18 | ///
19 | [JsonPropertyName("intentName")]
20 | public string IntentName { get; set; }
21 |
22 | ///
23 | /// 意图动作名称
24 | ///
25 | [JsonPropertyName("actionName")]
26 | public string ActionName { get; set; }
27 |
28 | ///
29 | /// 功能相关参数
30 | ///
31 | [JsonPropertyName("parameters")]
32 | public Parameters Parameters { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/ResponseModel/Parameters.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.ResponseModel
4 | {
5 | ///
6 | /// 功能相关参数
7 | ///
8 | public class Parameters
9 | {
10 | [JsonPropertyName("nearby_place")]
11 | public string NearbyPlace { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/ResponseModel/RequestType.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.ThirdParty.Tuling.ResponseModel
2 | {
3 | internal enum ResultType
4 | {
5 | ///
6 | /// 文本
7 | ///
8 | Text = 0,
9 | ///
10 | /// 图片
11 | ///
12 | Image,
13 | ///
14 | /// 音频
15 | ///
16 | Voice
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/ResponseModel/Response.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.ResponseModel
4 | {
5 | public class Response
6 | {
7 | ///
8 | /// 请求意图
9 | ///
10 | [JsonPropertyName("intent")]
11 | public Intent Intent { get; set; }
12 |
13 | ///
14 | /// 输出结果集
15 | ///
16 | [JsonPropertyName("results")]
17 | public Result[] Results { get; set; }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/ResponseModel/Result.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.ResponseModel
4 | {
5 | ///
6 | /// 输出结果集
7 | ///
8 | public class Result
9 | {
10 | ///
11 | /// ‘组’编号:0为独立输出,大于0时可能包含同组相关内容 (如:音频与文本为一组时说明内容一致)
12 | ///
13 | [JsonPropertyName("groupType")]
14 | public long GroupType { get; set; }
15 |
16 | ///
17 | /// 输出类型
18 | ///
19 | [JsonPropertyName("resultType")]
20 | public string ResultType { get; set; }
21 |
22 | ///
23 | /// 输出值
24 | ///
25 | [JsonPropertyName("values")]
26 | public Values Values { get; set; }
27 | }
28 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/ResponseModel/Values.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace daylily.ThirdParty.Tuling.ResponseModel
4 | {
5 | public class Values
6 | {
7 | [JsonPropertyName("url")]
8 | public Uri? Url { get; set; }
9 |
10 | [JsonPropertyName("text")]
11 | public string? Text { get; set; }
12 | }
13 | }
--------------------------------------------------------------------------------
/daylily/ThirdParty/Tuling/TulingClient.cs:
--------------------------------------------------------------------------------
1 | using daylily.ThirdParty.Tuling.RequestModel;
2 | using daylily.ThirdParty.Tuling.ResponseModel;
3 | using MilkiBotFramework.Connecting;
4 |
5 | namespace daylily.ThirdParty.Tuling
6 | {
7 | public class TulingClient
8 | {
9 | private readonly LightHttpClient _lightHttpClient;
10 |
11 | public TulingClient(LightHttpClient lightHttpClient)
12 | {
13 | _lightHttpClient = lightHttpClient;
14 | }
15 |
16 | private const string RequestUri = "http://openapi.tuling123.com/openapi/api/v2";
17 |
18 | public async Task SendText(string apiKey, string content, string userId, string groupId, string? userName = null)
19 | {
20 | var request = new Request
21 | {
22 | Perception = new Perception
23 | {
24 | InputText = new InputText
25 | {
26 | Text = content
27 | }
28 | },
29 | UserInfo = new UserInfo
30 | {
31 | ApiKey = apiKey,
32 | UserId = userId,
33 | GroupId = groupId,
34 | UserIdName = userName
35 | }
36 | };
37 |
38 | return await _lightHttpClient.HttpPost(RequestUri, request);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/daylily/Utils/EncryptUtil.cs:
--------------------------------------------------------------------------------
1 | using System.Security.Cryptography;
2 | using System.Text;
3 |
4 | namespace daylily.Utils;
5 |
6 | public static class EncryptUtil
7 | {
8 | public static string EncryptAes256UseMd5(string sourceStr, string? key = null, string? iv = null)
9 | {
10 | Span keySpan = stackalloc byte[32];
11 | FillSpan(key, keySpan);
12 | Span ivSpan = stackalloc byte[16];
13 | FillSpan(iv, ivSpan);
14 |
15 | using var aes = GetAesProvider(keySpan, ivSpan);
16 | using var crypto = aes.CreateEncryptor();
17 |
18 | var bytes = Encoding.UTF8.GetBytes(sourceStr);
19 | byte[] encrypted = crypto.TransformFinalBlock(bytes, 0, bytes.Length);
20 |
21 | return Convert.ToBase64String(encrypted);
22 | }
23 |
24 | public static string DecryptAes256UseMd5(string encryptedBase64, string? key = null, string? iv = null)
25 | {
26 | return DecryptAes256UseMd5(Convert.FromBase64String(encryptedBase64), key, iv);
27 | }
28 |
29 | public static string DecryptAes256UseMd5(byte[] encryptedBytes, string? key = null, string? iv = null)
30 | {
31 | Span keySpan = stackalloc byte[32];
32 | FillSpan(key, keySpan);
33 | Span ivSpan = stackalloc byte[16];
34 | FillSpan(iv, ivSpan);
35 |
36 | using var aes = GetAesProvider(keySpan, ivSpan);
37 | using var crypto = aes.CreateDecryptor();
38 |
39 | byte[] decrypted = crypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
40 | return Encoding.UTF8.GetString(decrypted);
41 | }
42 |
43 | private static Aes GetAesProvider(Span keySpan, Span ivSpan)
44 | {
45 | var aes = Aes.Create();
46 | aes.BlockSize = 128;
47 | aes.KeySize = 256;
48 | aes.Key = keySpan.ToArray();
49 | aes.IV = ivSpan.ToArray();
50 | aes.Padding = PaddingMode.PKCS7;
51 | aes.Mode = CipherMode.CBC;
52 | return aes;
53 | }
54 |
55 | private static void FillSpan(string? content, Span span)
56 | {
57 | if (content == null) return;
58 | using var md5 = MD5.Create();
59 | var md5Byte = md5.ComputeHash(Encoding.UTF8.GetBytes(content));
60 | md5Byte.CopyTo(span);
61 | }
62 | }
--------------------------------------------------------------------------------
/daylily/Utils/ExceptionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Text;
2 |
3 | namespace daylily.Utils;
4 |
5 | public static class ExceptionExtensions
6 | {
7 | private const string Normal = "│ ";
8 | private const string Middle = "├─ ";
9 | private const string Last = "└─ ";
10 |
11 | public static string ToFullTypeMessage(this Exception exception)
12 | {
13 | return ExceptionToFullMessage(exception, new StringBuilder(), 0, true, true)!;
14 | }
15 |
16 | public static string ToSimpleTypeMessage(this Exception exception)
17 | {
18 | return ExceptionToFullMessage(exception, new StringBuilder(), 0, true, false)!;
19 | }
20 |
21 | public static string ToMessage(this Exception exception)
22 | {
23 | return ExceptionToFullMessage(exception, new StringBuilder(), 0, true, null)!;
24 | }
25 |
26 | private static string? ExceptionToFullMessage(Exception exception, StringBuilder stringBuilder, int deep,
27 | bool isLastItem, bool? includeFullType)
28 | {
29 | var hasChild = exception.InnerException != null;
30 | if (deep > 0)
31 | {
32 | for (int i = 0; i < deep; i++)
33 | {
34 | if (i == deep - 1)
35 | {
36 | stringBuilder.Append((isLastItem && !hasChild) ? Last : Middle);
37 | }
38 | else
39 | {
40 | stringBuilder.Append(Normal + " ");
41 | }
42 | }
43 | }
44 |
45 | var agg = exception as AggregateException;
46 | if (includeFullType == true)
47 | {
48 | var prefix = agg == null ? exception.GetType().ToString() : "!!AggregateException";
49 | stringBuilder.Append($"{prefix}: {GetTrueExceptionMessage(exception)}");
50 | }
51 | else if (includeFullType == false)
52 | {
53 | var prefix = exception.GetType().Name;
54 | stringBuilder.Append($"{prefix}: {GetTrueExceptionMessage(exception)}");
55 | }
56 | else
57 | {
58 | stringBuilder.Append(GetTrueExceptionMessage(exception));
59 | }
60 |
61 | stringBuilder.AppendLine();
62 | if (!hasChild)
63 | {
64 | return deep == 0 ? stringBuilder.ToString() : null;
65 | }
66 |
67 | if (agg != null)
68 | {
69 | for (int i = 0; i < agg.InnerExceptions.Count; i++)
70 | {
71 | ExceptionToFullMessage(agg.InnerExceptions[i], stringBuilder, deep + 1,
72 | i == agg.InnerExceptions.Count - 1, includeFullType);
73 | }
74 | }
75 | else
76 | {
77 | ExceptionToFullMessage(exception.InnerException!, stringBuilder, deep + 1, true, includeFullType);
78 | }
79 |
80 | return deep == 0 ? stringBuilder.ToString() : null;
81 |
82 | static string GetTrueExceptionMessage(Exception ex)
83 | {
84 | if (ex is AggregateException { InnerException: { } } agg)
85 | {
86 | var complexMessage = agg.Message;
87 | var i = complexMessage.IndexOf(agg.InnerException.Message, StringComparison.Ordinal);
88 | if (i == -1)
89 | return complexMessage;
90 | return complexMessage.Substring(0, i - 2);
91 | }
92 |
93 | return string.IsNullOrWhiteSpace(ex.Message) ? "{Empty Message}" : ex.Message;
94 | }
95 | }
96 | }
--------------------------------------------------------------------------------
/daylily/Utils/StringUtil.cs:
--------------------------------------------------------------------------------
1 | namespace daylily.Utils;
2 |
3 | public static class StringUtil
4 | {
5 | public static string ToLowerSnake(this string sourceString)
6 | {
7 | int additionalLength = 0;
8 |
9 | var span = sourceString.AsSpan();
10 | for (var i = 0; i < span.Length; i++)
11 | {
12 | var c = span[i];
13 | if (c >= 65 && c <= 90 && i != 0)
14 | {
15 | additionalLength++;
16 | }
17 | }
18 |
19 | if (additionalLength == 0)
20 | {
21 | return sourceString;
22 | }
23 |
24 | int j = 0;
25 | return string.Create(sourceString.Length + additionalLength, sourceString, (target, source) =>
26 | {
27 | var span1 = source.AsSpan();
28 | for (var i = 0; i < span1.Length; i++)
29 | {
30 | var c = span1[i];
31 | if (c >= 65 && c <= 90)
32 | {
33 | if (i == 0)
34 | {
35 | target[0] = (char)(c + 32);
36 | continue;
37 | }
38 |
39 | target[i + j] = '_';
40 | j++;
41 | target[i + j] = (char)(c + 32);
42 | continue;
43 | }
44 |
45 | target[i + j] = c;
46 | }
47 | });
48 | }
49 | }
--------------------------------------------------------------------------------
/daylily/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
54 |
62 |
63 |
64 |
65 |
66 |
67 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/daylily/appsettings.yaml:
--------------------------------------------------------------------------------
1 | # go-cqhttp连接设置
2 | Connection:
3 | # 连接方式,支持: Http, WebSocket, ReverseWebSocket
4 | ConnectionType: WebSocket
5 | # 目标接口链接。当 ConnectionType 为 Http, WebSocket 时生效
6 | TargetUri: ws://127.0.0.1:5700
7 | # 服务器绑定Url。当 ConnectionType 为 Http, ReverseWebSocket 时生效
8 | ServerBindUrl: http://0.0.0.0:2333
9 | # 服务器绑定的具体路由。当 ConnectionType 为 Http, ReverseWebSocket 时生效
10 | ServerBindPath: /endpoint
11 | HttpOptions:
12 | # 代理服务器地址
13 | ProxyUrl:
14 | # 默认超时
15 | Timeout: 00:00:08
16 | # 自动重试次数
17 | RetryCount: 3
18 | # Root权限账号
19 | RootAccounts: []
20 | # 插件目录
21 | PluginBaseDir: ./plugins
22 | # 插件资源目录
23 | PluginHomeDir: ./homes
24 | # 插件数据库目录
25 | PluginDatabaseDir: ./databases
26 | # 插件配置目录
27 | PluginConfigurationDir: ./configurations
28 | # 缓存图片目录
29 | CacheImageDir: ./caches/images
30 | # gifsicle插件位置
31 | GifSiclePath:
32 | # ffmpeg插件位置
33 | FfMpegPath:
34 |
--------------------------------------------------------------------------------
/daylily/daylily.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0-windows
6 | true
7 | enable
8 | enable
9 | milkiticyf
10 | app.manifest
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | all
26 | runtime; build; native; contentfiles; analyzers; buildtransitive
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | PreserveNewest
48 |
49 |
50 | PreserveNewest
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------