[init] firika bot init

This commit is contained in:
Dubi906w 2024-09-04 23:10:58 +08:00
commit c064aa7be8
41 changed files with 1859 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
Doubx690i.FirikaBot.Wpf/bin
Doubx690i.FirikaBot.Wpf/.vs
Doubx690i.FirikaBot.Wpf/obj
Doubx690i.FirikaBot.Server/bin
Doubx690i.FirikaBot.Server/WebPages/.idea
Doubx690i.FirikaBot.Server/.vs
Doubx690i.FirikaBot.Server/obj
.vs
.idea

View File

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CefSharp.OffScreen.NETCore" Version="127.3.50" />
<PackageReference Include="EmbedIO" Version="3.5.2" />
<PackageReference Include="Flurl.Http" Version="4.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Websocket.Client" Version="5.1.2" />
</ItemGroup>
<ItemGroup>
<None Update="firika.config.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Services\BotCommandParserServices\" />
<Folder Include="Services\CefSharpRendererServices\" />
<Folder Include="Services\ScribanTemplateServices\" />
<Folder Include="WebPages\" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Doubx690i.FirikaBot.Server.Models;
using Newtonsoft.Json;
using static Swan.Terminal;
namespace Doubx690i.FirikaBot.Server.Helpers {
internal static class FirikaConfigHelper {
/// <summary>
/// 在Server的运行目录下寻找指定名称的配置文件
/// </summary>
/// <param name="configFileName">配置文件名称</param>
/// <returns>配置</returns>
public async static Task<FirikaConfigModel> ReadConfigFromFile(string configFileName = "firika.config.json") {
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
FirikaConfigModel? config = new FirikaConfigModel();
try {
if (File.Exists(baseDirectory + configFileName)) {
try {
string text = await File.ReadAllTextAsync(baseDirectory + configFileName);
config = JsonConvert.DeserializeObject<FirikaConfigModel>(text, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.Auto,
NullValueHandling = NullValueHandling.Ignore,
});
}
catch (Exception ex) {
Trace.WriteLine(ex);
}
}
}
catch (Exception ex) {
Trace.WriteLine(ex);
}
return config??new FirikaConfigModel();
}
/// <summary>
/// 保存配置到文件中
/// </summary>
/// <param name="config"></param>
/// <param name="configFileName"></param>
public async static Task SaveConfigToFile(FirikaConfigModel config, string configFileName = "firika.config.json") {
var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
try {
string serializedJson = JsonConvert.SerializeObject(config);
await File.WriteAllTextAsync(baseDirectory + configFileName, serializedJson);
}
catch (Exception ex) {
Trace.WriteLine(ex);
}
}
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Flurl;
using Newtonsoft.Json;
namespace Doubx690i.FirikaBot.Server.Models {
internal class FirikaConfigModel {
public enum BotProtocolTypes {
Firika_LLOneBot,
Firika_TelegramBot
}
public class FirikaGeneralConfig {
[JsonProperty("master_qq")]
public int MasterAdminQQ { get; set; } = 1684342110;
[JsonProperty("bot_qq")]
public int BotQQ { get; set; } = 1874424309;
}
public class FirikaHttpServerConfig {
[JsonProperty("url")]
public string _ServerExposeUrl { get; set; } = "http://0.0.0.0:8596";
public Url ServerExposeUrl => new Url(_ServerExposeUrl);
[JsonProperty("token")]
public string ServerToken { get; set; } = "test1145141919810";
}
public class FirikaBotProtocolConfig {
[JsonProperty("type")]
public string _ProtocolType { get; set; } = "llonebot";
public BotProtocolTypes ProtocolType => _ProtocolType == "llonebot"
? BotProtocolTypes.Firika_LLOneBot
: BotProtocolTypes.Firika_TelegramBot;
[JsonProperty("http_url")]
public string _HttpServerUrl { get; set; } = "http://localhost:3000";
public Url HttpServerUrl => new Url(_HttpServerUrl);
[JsonProperty("ws_url")]
public string _WebsocketServerUrl { get; set; } = "ws://localhost:3001";
public Url WebsocketServerUrl => new Url(_WebsocketServerUrl);
}
public class FirikaFunctionConfig {
[JsonProperty("name")]
public string? Name { get; set; } = null;
[JsonProperty("enabled")]
public bool IsEnabled { get; set; } = true;
}
[JsonProperty("general")]
public FirikaGeneralConfig General { get; set; } = new FirikaGeneralConfig();
[JsonProperty("http_server")]
public FirikaHttpServerConfig HttpServer { get; set; } = new FirikaHttpServerConfig();
[JsonProperty("protocols")]
public List<FirikaBotProtocolConfig> Protocols { get; set; } = new List<FirikaBotProtocolConfig>();
[JsonProperty("functions")]
public List<FirikaFunctionConfig> Functions { get; set; } = new List<FirikaFunctionConfig>();
}
}

View File

@ -0,0 +1,52 @@
using System.Diagnostics;
using System.Net.WebSockets;
using System.Runtime.Loader;
using Doubx690i.FirikaBot.Server.Helpers;
using Doubx690i.FirikaBot.Server.Models;
using Doubx690i.FirikaBot.Server.Protocols.LLOneBot;
namespace Doubx690i.FirikaBot.Server {
internal class Program {
public static readonly ManualResetEvent ExitEvent = new(false);
static async Task Main(string[] args) {
Console.WriteLine("[INFO]FirikaBot by Doubx690i 启动中...");
// 初始化配置
var config = await FirikaConfigHelper.ReadConfigFromFile();
Console.WriteLine("[INFO]获取到配置,将连接 LLOneBot的WebSocket服务器");
// Websocket监听
var websocket = new LLOneBotWebsocketClient(config.Protocols
.Where(protocol => protocol.ProtocolType == FirikaConfigModel.BotProtocolTypes.Firika_LLOneBot).Single()
._WebsocketServerUrl);
// osu-akatsuki 插件
websocket.ChatMessageReceived += (sender, eventArgs) => {
Console.WriteLine("有新消息咯!");
};
// 监听关闭事件
AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => {
Console.WriteLine("Exiting process...");
ExitEvent.Set();
};
AssemblyLoadContext.Default.Unloading += context => {
Console.WriteLine("Unloading process...");
ExitEvent.Set();
};
Console.CancelKeyPress += (sender, eventArgs) => {
Console.WriteLine("Disposing Websocket Client...");
websocket.Client.Stop(WebSocketCloseStatus.NormalClosure, "normal closure").Wait();
websocket.Client.Dispose();
Console.WriteLine("Disposed Websocket Client.");
Console.WriteLine("Canceling process...");
eventArgs.Cancel = true;
ExitEvent.Set();
};
ExitEvent.WaitOne();
}
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Models;
namespace Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Interfaces {
internal interface ILLOneBotEventBase {
LLOneBotEventType EventType { get; }
Int64 SelfID { get; }
Int64 TimeStamp { get; }
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Models;
using Newtonsoft.Json.Linq;
namespace Doubx690i.FirikaBot.Server.Protocols.LLOneBot {
public static class LLOneBotEventModelFacotry {
public static LLOneBotMessageEvent createMessageEventFromRawJson(JObject json) {
var isGm = (string)json.SelectToken("message_type", errorWhenNoMatch: false) == "group" &&
(string)json.SelectToken("sub_type", errorWhenNoMatch: false) == "normal";
var _selfId = (Int64)json.SelectToken("self_id", errorWhenNoMatch: false);
var _time = (Int64)json.SelectToken("time", errorWhenNoMatch: false);
var _messageType = (string)json.SelectToken("message_type", errorWhenNoMatch: false);
var _submessagetype = (string)json.SelectToken("sub_type", errorWhenNoMatch: false);
var _messageId = (Int64)json.SelectToken("message_id", errorWhenNoMatch: false);
var _groupId = isGm ? (Int64)json.SelectToken("group_id", errorWhenNoMatch: false):0;
var _userId = (Int64)json.SelectToken("user_id", errorWhenNoMatch: false);
var _nickname = (string)json.SelectToken("sender.nickname", errorWhenNoMatch: false);
var _card = isGm ? (string)(json.SelectToken("sender.card", errorWhenNoMatch: false)??""):"";
var _sex = (string)(json.SelectToken("sender.sex", errorWhenNoMatch: false)??"");
var _role = isGm ? (string)(json.SelectToken("sender.role", errorWhenNoMatch: false)??"") : "";
var _age = (Int64)(json.SelectToken("sender.age", errorWhenNoMatch: false)??0);
var _isAnonymous = isGm ? (bool)(json.SelectToken("anonymous", errorWhenNoMatch: false)??false) : false;
var me = new LLOneBotMessageEvent(
"message", _selfId, _time, _messageType, _submessagetype, _messageId, _groupId,
new LLOneBotSenderModel(
isGm,
_userId,
_nickname,
_card,
_sex,
_role,
_age
),
_isAnonymous,
// TODO 支持消息段
(string)json.SelectToken("message", errorWhenNoMatch: false)
);
return me;
}
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Models;
using Flurl;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Websocket.Client;
namespace Doubx690i.FirikaBot.Server.Protocols.LLOneBot {
public class LLOneBotWebsocketClient {
public string _WebsocketUrl { get; set; } = "ws://localhost:3001";
public Url WebsocketUrl => new Url(_WebsocketUrl);
public WebsocketClient Client { get; set; }
public class ChatMessageReceivedEventArgs : EventArgs {
private LLOneBotMessageEvent _event { get; set; }
public LLOneBotMessageEvent Event => _event;
public ChatMessageReceivedEventArgs(LLOneBotMessageEvent e) {
_event = e;
}
}
public event EventHandler<ChatMessageReceivedEventArgs> ChatMessageReceived;
public LLOneBotWebsocketClient(string url) {
_WebsocketUrl = url;
Client = new WebsocketClient(WebsocketUrl.ToUri());
Client.ReconnectTimeout = TimeSpan.FromSeconds(90);
Client.ReconnectionHappened.Subscribe(info => {
if (info.Type == ReconnectionType.Initial) {
Console.WriteLine("[INFO]Websocket 初始化连接成功!");
}
});
Client.MessageReceived.Subscribe(msg => {
var bodyJson = JObject.Parse(msg.Text??"{}");
string rawPostType;
Int64 rawSelfID, rawTime;
try {
rawPostType = (string)bodyJson.SelectToken("post_type", errorWhenNoMatch: true);
rawSelfID = (Int64)bodyJson.SelectToken("self_id", errorWhenNoMatch: true);
rawTime = (Int64)bodyJson.SelectToken("time", errorWhenNoMatch: true);
}
catch (JsonException e) {
Console.WriteLine($"[ERROR][{DateTime.Now}]遇到了无法解析的Websocket消息");
Console.WriteLine($"------ JsonException 错误信息:{e.Message}");
Console.WriteLine($"------ 报文数据:{msg.Text}");
throw;
}
Console.WriteLine($"[INFO][{DateTime.Now}]解析到消息:------ 类型:{rawPostType}QQ号{rawSelfID},时间:{rawTime}");
if (rawPostType == "message") {
var ev = LLOneBotEventModelFacotry.createMessageEventFromRawJson(bodyJson);
ChatMessageReceived?.Invoke(null, new ChatMessageReceivedEventArgs(ev));
}
});
Client.Start().Wait();
Console.WriteLine("[INFO]Websocket 客户端服务启动成功!");
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Models {
public enum LLOneBotEventType {
Message,
Notice,
Request,
MetaEvent
}
/// <summary>
/// 定义该消息的类型
/// </summary>
public enum LLOneBotMessageSourceType {
/// <summary>
/// 好友消息,就是私聊的消息
/// </summary>
FriendMessage,
OtherPrivateMessage,
/// <summary>
/// 群聊成员的消息
/// </summary>
GroupMessage,
/// <summary>
/// 匿名群消息
/// </summary>
AnonymousGroupMessage,
/// <summary>
/// 群聊的系统消息如「管理员已禁止群内匿名聊天管理员修改群名称为“XXX”」
/// </summary>
GroupSystemNoticeMessage
}
public enum LLOneBotUserSex {
Male,
FeMale,
Unknown,
}
/// <summary>
/// 定义群组成员在群聊中的角色
/// </summary>
public enum LLOneBotGroupMemberRole {
/// <summary>
/// 群主
/// </summary>
Owner,
/// <summary>
/// 管理员
/// </summary>
Administrator,
Member
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Interfaces;
namespace Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Models {
public class LLOneBotMessageEvent : ILLOneBotEventBase {
private string eventType { get; set; } = "message";
public LLOneBotEventType EventType => eventType switch {
"meta_event" => LLOneBotEventType.MetaEvent,
"message" => LLOneBotEventType.Message,
"request" => LLOneBotEventType.Request,
_ => LLOneBotEventType.Notice
};
private Int64 selfId { get; set; }
public Int64 SelfID => selfId;
private Int64 time { get; set; }
public Int64 TimeStamp => time;
public DateTime Time {
get {
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
dateTime = dateTime.AddSeconds( time ).ToLocalTime();
return dateTime;
}
}
private string messageType { get; set; }
private string subMessageType { get; set; }
public LLOneBotMessageSourceType MessageSourceType => messageType=="private"&&subMessageType=="friend" ? LLOneBotMessageSourceType.FriendMessage :
messageType=="private"&&subMessageType=="other" ? LLOneBotMessageSourceType.OtherPrivateMessage :
messageType=="group"&&subMessageType=="normal" ? LLOneBotMessageSourceType.GroupMessage :
messageType=="group"&&subMessageType=="anonymous" ? LLOneBotMessageSourceType.AnonymousGroupMessage :
LLOneBotMessageSourceType.GroupSystemNoticeMessage;
private Int64 messageId { get; set; }
public Int64 MessageID => messageId;
private Int64 groupId { get; set; }
public Int64 GroupID => groupId;
private LLOneBotSenderModel sender { get; set; }
public LLOneBotSenderModel Sender => sender;
private bool isAnonymous { get; set; }
public bool IsAnonymous => isAnonymous;
public string message { get; set; }
public string Message => message;
public LLOneBotMessageEvent(string _eventType, Int64 _selfId, Int64 _time, string _messageType, string _subMessageType,
Int64 _messageId, Int64 _groupId, LLOneBotSenderModel senderModel, bool _isAnonymous, string _message) {
eventType = _eventType;
selfId = _selfId;
time = _time;
messageType = _messageType;
subMessageType = _subMessageType;
messageId = _messageId;
groupId = _groupId;
sender = senderModel;
isAnonymous = _isAnonymous;
message = _message;
}
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Server.Protocols.LLOneBot.Models {
public class LLOneBotSenderModel {
private bool isGroupMember { get; set; }
public bool IsGroupMember => isGroupMember;
private Int64 userId { get; set; }
public Int64 UserID => userId;
private string nickname { get; set; }
public string NickName => nickname;
private string card { get; set; }
public string NickNameInGroup => isGroupMember ? card : throw new Exception("非群成员无法获取该成员!");
private string sex { get; set; }
public LLOneBotUserSex Gender => sex == "male" ? LLOneBotUserSex.Male :
sex == "female" ? LLOneBotUserSex.FeMale : LLOneBotUserSex.Unknown;
private string role { get; set; }
public LLOneBotGroupMemberRole MemberRole => isGroupMember ? role == "owner" ? LLOneBotGroupMemberRole.Owner :
role == "admin" ? LLOneBotGroupMemberRole.Administrator : LLOneBotGroupMemberRole.Member : throw new Exception("非群成员无法获取该成员!");
private Int64 age { get; set; }
public Int64 Age => age;
public LLOneBotSenderModel(bool _isGroupMember, Int64 _userId, string _nickname, string _card, string _sex,
string _role, Int64 _age) {
isGroupMember = _isGroupMember;
nickname = _nickname;
card = _card;
sex = _sex;
role = _role;
age = _age;
userId = _userId;
}
/// <summary>
/// 返回该发送者的IDQQ号
/// </summary>
/// <returns></returns>
public override string ToString() {
return userId.ToString();
}
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Server.Services.OsuAkatsukiServices {
internal class OsuAkatsukiRankMapService {
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Server.Services.OsuAkatsukiServices {
internal class OsuAkatsukiScoresService {
}
}

View File

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Server.Services.OsuAkatsukiServices {
internal class OsuAkatsukiUserService {
}
}

View File

@ -0,0 +1,505 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Akatsuki_UserInfo</title>
<style>
*{
--level-tier-iron: #bab3ab, #bab3ab;
--level-tier-bronze: #b88f7a, #855c47;
--level-tier-silver: #e0e0eb, #a3a3c2;
--level-tier-gold: #f0e4a8, #e0c952;
--level-tier-platinum: #a8f0ef, #52e0df;
--level-tier-rhodium: #d9f8d3, #a0cf96;
--level-tier-radiant: #97dcff, #ed82ff;
--level-tier-lustrous: #ffe600, #ed82ff;
}
.userinfo_card {
background-color: #18181b;
width: 430px;
display: flex;
flex-direction: column;
}
body {
margin: 0;
user-select: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
.userinfo_titlebar {
display: flex;
flex-direction: row;
padding: 16pt 18pt;
align-items: center;
justify-content: start;
}
.userinfo_userinfo {
display: flex;
flex-direction: row;
padding: 16pt 18pt;
padding-top: 0;
align-items: start;
justify-content: start;
}
.userinfo_clan_area {
display: flex;
flex-direction: row;
align-items: center;
justify-content: start;
}
.userinfo_avatar {
width: 78pt;
height: 78pt;
border-radius: 6pt;
}
.userinfo_avatar_onlineStatus {
position: relative;
}
.userinfo_avatar_onlineStatus .userinfo_online_badge{
position: absolute;
bottom: -6pt;
right: -6pt;
width: 13pt;
height: 13pt;
border-radius: 16pt;
border: 2.5pt solid #18181b;
}
.userinfo_avatar_onlineStatus .userinfo_online_badge.online_online {
background: #16a34a;
}
.userinfo_avatar_onlineStatus .userinfo_online_badge.online_offline {
background: #52525b;
}
.userinfo_avatar img {
width: 78pt;
height: 78pt;
border-radius: 6pt;
}
.userinfo_userinfo_area {
display: flex;
flex: 1 1 0%;
flex-direction: column;
margin-left: 12pt;
}
.userinfo_title_area {
display: flex;
flex: 1 1 0%;
flex-direction: column;
margin-left: 6pt;
}
.userinfo_title {
font-size: 1.125rem;
line-height: 1.5rem;
color: white;
font-weight: 700;
}
.userinfo_username {
font-size: 2rem;
line-height: 1;
color: white;
font-weight: 700;
}
.userinfo_akatsuki {
justify-self: end;
}
.userinfo_clanID {
font-size: 1rem; /* 16px */
line-height: 1.5rem; /* 24px */
font-weight: 700;
color: #06b6d4;
}
.userinfo_clanName {
font-size: 1rem; /* 16px */
line-height: 1.5rem; /* 24px */
font-weight: 700;
color: white;
opacity: 0.8;
}
.userinfo_lastSeen {
font-size: 0.875rem; /* 14px */
line-height: 1.25rem; /* 20px */
color: white;
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
margin-top: 4pt;
}
.userinfo_registeredAt {
font-size: 0.875rem; /* 14px */
line-height: 1.25rem; /* 20px */
color: white;
display: flex;
flex-direction: row;
justify-content: start;
align-items: center;
margin-top: 2pt;
}
.userinfo_rankStatus {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
column-gap: 0.75rem;
padding: 16pt 18pt;
margin-top: -20pt;
}
.userinfo_rankScoresStatus {
display: grid;
grid-template-columns: repeat(8, minmax(0, 1fr));
column-gap: 0.5rem;
padding: 16pt 18pt;
margin-top: -24pt;
}
.userinfo_rankScoresStatus .userinfo_ScoreRank {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
row-gap: 2.5pt;
font-size: 0.9rem;
color: white;
justify-content: center;
}
.userinfo_globalRank, .userinfo_countryRank, .userinfo_ppRank {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
column-gap: 6pt;
font-size: 1.45rem;
color: white;
font-weight: 700;
justify-content: center;
}
.userinfo_ppRank .userinfo_ppRank_pp {
margin-left: -3pt;
font-size: 1rem;
opacity: 0.8;
align-self: end;
padding-bottom: 1.5pt;
}
.userinfo_countryFlag {
border-radius: 3pt;
}
.userinfo_rankingIcon {
width: 21pt;
height: 21pt;
}
.userinfo_detailsCard {
padding: 16pt 18pt;
display: flex;
flex-direction: column;
row-gap: 6pt;
background: #27272a;
}
.userinfo_detailItem {
display: flex;
width: 100%;
flex-direction: row;
align-items: center;
column-gap: 4pt;
font-size: 1rem; /* 16px */
line-height: 1.5rem; /* 24px */
color: white;
}
.userinfo_detailItem .userinfo_detailItemTitle {
margin-bottom: 0.5pt;
font-weight: 700;
flex: 1 1 0%;
}
.userinfo_detailItem .userinfo_detailItemNumber {
margin-bottom: 0.5pt;
margin-right: 3pt;
font-size: 1.03rem;
line-height: 1.5rem;
font-family: 'JetBrains Mono SemiBold',monospace;
}
.userinfo_levelAndFollowers {
display: flex;
flex-direction: column;
row-gap: 4pt;
flex: 1 1 0%;
align-items: stretch;
}
.userinfo_followers {
display: flex;
flex-direction: row;
align-items: center;
column-gap: 4pt;
font-size: 0.9rem; /* 16px */
line-height: 1.5rem; /* 24px */
color: white;
align-self: center;
}
.userinfo_followers .userinfo_followersTitle {
margin-bottom: 0.5pt;
font-weight: 700;
flex: 1 1 0%;
}
.userinfo_followers .userinfo_followersNumber {
margin-bottom: 0.5pt;
margin-left: 6pt;
font-size: 1.02rem;
line-height: 1.5rem;
font-family: 'JetBrains Mono SemiBold',monospace;
}
.userinfo_levelCard {
padding: 6pt 18pt;
padding-top: 12pt;
border-top: #52525b66 solid 1pt;
border-bottom: #52525b66 solid 1pt;
margin-top: 4pt;
display: flex;
flex-direction: row;
column-gap: 12pt;
align-items: center;
background: #09090b77;
}
.userinfo_levelCard .userinfo_levelBadge {
width: 32pt;
height: 32pt;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
.userinfo_levelCard .userinfo_level_icon {
width: 38pt;
height: 38pt;
position: absolute;
z-index: 10;
clip-path: path("m 25,2.7 a 9.3,9.3 0 0 1 4.7,1.2 l 11.3,6.5 a 9.3,9.3 0 0 1 4.6,8.1 v 13.0 a 9.3,9.3 0 0 1 -4.6,8.1 L 29.7,46.1 a 9.3,9.3 0 0 1 -9.3,0 L 9.1,39.6 A 9.3,9.3 0 0 1 4.4,31.5 v -13.0 A 9.3,9.3 0 0 1 9.1,10.4 L 20.4,3.9 A 9.3,9.3 0 0 1 25,2.7 M 25,0.0 A 11.9,11.9 0 0 0 19.0,1.6 L 7.7,8.1 A 12.0,12.0 0 0 0 1.7,18.5 v 13.0 a 12.0,12.0 0 0 0 6.0,10.4 l 11.3,6.5 a 12.0,12.0 0 0 0 12.0,0 l 11.3,-6.5 a 12.0,12.0 0 0 0 6.0,-10.4 v -13.0 A 12.0,12.0 0 0 0 42.3,8.1 L 31.0,1.6 A 11.9,11.9 0 0 0 25.0,0 Z");
background: linear-gradient(var(--level-color));
}
.userinfo_levelCard .userinfo_level {
font-size: 1.1rem; /* 16px */
line-height: 1.5rem; /* 24px */
font-weight: bold;
color: white;
margin-right: 0.1pt;
margin-bottom: 1pt;
}
.userinfo_levelCard .userinfo_levelProgressBar {
flex: 1 1 0%;
height: 24px;
background: #27272a;
border-radius: 4pt;
display: flex;
flex-direction: row;
}
.userinfo_levelProgressBar .userinfo_levelProgress {
display: flex;
justify-content: end;
align-items: center;
background: #dc2626;
height: 24px;
width: 35%;
border-radius: 4pt;
font-size: 0.875rem; /* 14px */
line-height: 1.25rem; /* 20px */
font-weight: bold;
font-family: 'JetBrains Mono ExtraBold',monospace;
color: #fecaca;
padding-right: 7pt;
}
.copyright {
position: relative;
padding: 4pt 18pt;
height: 46pt;
display: flex;
flex-direction: column;
row-gap: 2pt;
align-items: center;
justify-content: center;
color: white;
font-size: 0.875rem; /* 14px */
line-height: 1.25rem; /* 20px */
overflow: clip;
font-weight: 700;
}
.copyright span:nth-child(2) {
font-size: 0.75rem; /* 12px */
line-height: 1rem; /* 16px */
font-weight: normal;
opacity: 0.7;
}
</style>
</head>
<body>
<div class="userinfo_card">
<div class="userinfo_titlebar">
<svg width="24" height="24" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.00012 1C6.34327 1 5.00012 2.34315 5.00012 4C5.00012 5.65685 6.34327 7 8.00012 7C9.65698 7 11.0001 5.65685 11.0001 4C11.0001 2.34315 9.65698 1 8.00012 1ZM6.50012 8C4.00712 8 2.00012 10.007 2.00012 12.5V14C2.00012 15 3.00012 15 3.00012 15H13.0001C13.0001 15 14.0001 15 14.0001 14V12.5C14.0001 10.007 11.9931 8 9.50012 8H6.50012Z" fill="white"/>
</svg>
<div class="userinfo_title_area">
<div class="userinfo_title">个人信息</div>
</div>
<svg class="userinfo_akatsuki" xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 0 1551 320" fill="none">
<g clip-path="url(#clip0_65_192)">
<path d="M217.624 106.117V312.698H164.463V299.641C146.743 313.009 128.246 319.693 108.971 319.693C78.8158 319.693 53.1681 308.967 32.0282 287.516C10.8883 266.066 0.318359 239.952 0.318359 209.174C0.318359 178.708 10.8883 152.749 32.0282 131.299C53.1681 109.848 78.8158 99.1225 108.971 99.1225C128.246 99.1225 146.743 105.806 164.463 119.174V106.117H217.624ZM111.303 147.154C94.2044 147.154 80.3702 152.905 69.8002 164.408C59.2303 175.599 53.9453 190.522 53.9453 209.174C53.9453 227.516 59.0748 242.594 69.3339 254.408C79.9039 265.91 93.4272 271.661 109.904 271.661C126.381 271.661 139.748 265.91 150.007 254.408C160.267 242.594 165.396 227.516 165.396 209.174C165.396 191.143 160.267 176.376 150.007 164.874C140.059 153.06 127.158 147.154 111.303 147.154Z" fill="white"/>
<path d="M465.215 106.117L376.148 192.853L470.811 312.698H405.526L339.775 228.294L320.189 247.879V312.698H267.029V1.66138H320.189V179.796L395.267 106.117H465.215Z" fill="white"/>
<path d="M685.312 106.117V312.698H632.151V299.641C614.431 313.009 595.933 319.693 576.659 319.693C546.503 319.693 520.856 308.967 499.716 287.516C478.576 266.066 468.006 239.952 468.006 209.174C468.006 178.708 478.576 152.749 499.716 131.299C520.856 109.848 546.503 99.1225 576.659 99.1225C595.933 99.1225 614.431 105.806 632.151 119.174V106.117H685.312ZM578.99 147.154C561.892 147.154 548.058 152.905 537.488 164.408C526.918 175.599 521.633 190.522 521.633 209.174C521.633 227.516 526.762 242.594 537.021 254.408C547.591 265.91 561.115 271.661 577.591 271.661C594.068 271.661 607.436 265.91 617.695 254.408C627.954 242.594 633.083 227.516 633.083 209.174C633.083 191.143 627.954 176.376 617.695 164.874C607.747 153.06 594.845 147.154 578.99 147.154Z" fill="white"/>
<path d="M822.829 154.148H791.119V246.014C791.119 253.786 791.741 259.071 792.984 261.869C794.539 264.667 797.337 266.066 801.378 266.066C806.352 266.066 814.591 264.511 826.093 261.402V311.299C813.658 314.718 803.399 316.428 795.316 316.428C776.974 316.428 762.829 310.366 752.881 298.242C742.932 286.117 737.958 268.708 737.958 246.014V154.148H716.508V106.117H737.958V61.8168L791.119 40.8324V106.117H822.829V154.148Z" fill="white"/>
<path d="M1002.61 148.086L961.108 168.604C955.201 159.9 950.071 153.993 945.719 150.884C941.367 147.775 936.082 146.221 929.864 146.221C923.336 146.221 918.051 147.775 914.009 150.884C910.279 153.993 908.413 158.034 908.413 163.009C908.413 167.361 909.812 170.781 912.61 173.268C915.719 175.444 923.18 178.708 934.994 183.06L953.647 190.522C971.056 197.361 983.802 205.444 991.885 214.77C999.968 224.097 1004.01 235.444 1004.01 248.812C1004.01 268.708 996.548 285.496 981.626 299.174C966.704 312.853 948.362 319.693 926.6 319.693C888.051 319.693 861.16 298.242 845.926 255.34L889.294 238.086C895.823 251.143 901.73 260.003 907.014 264.667C912.299 269.33 918.983 271.661 927.066 271.661C934.216 271.661 939.968 269.796 944.32 266.066C948.672 262.024 950.849 256.739 950.849 250.211C950.849 245.858 949.139 242.439 945.719 239.952C942.61 237.465 935.615 234.045 924.735 229.692L903.75 221.765C872.973 210.262 857.584 191.143 857.584 164.408C857.584 145.755 864.424 130.211 878.103 117.775C892.092 105.34 909.502 99.1225 930.331 99.1225C946.496 99.1225 960.331 103.009 971.833 110.781C983.336 118.553 993.595 130.988 1002.61 148.086Z" fill="white"/>
<path d="M1217.94 312.698H1164.78V301.972C1149.86 313.786 1133.85 319.693 1116.75 319.693C1090.95 319.693 1069.65 310.522 1052.86 292.18C1037.63 276.014 1030.01 250.988 1030.01 217.102V106.117H1083.18V213.838C1083.18 222.231 1083.49 229.382 1084.11 235.288C1085.04 240.884 1086.59 246.791 1088.77 253.009C1091.26 259.226 1095.14 263.889 1100.43 266.998C1105.71 270.107 1112.55 271.661 1120.95 271.661C1130.58 271.661 1138.36 270.107 1144.26 266.998C1150.48 263.579 1154.99 258.76 1157.79 252.542C1160.58 246.014 1162.45 239.641 1163.38 233.423C1164.32 226.895 1164.78 218.812 1164.78 209.174V106.117H1217.94V312.698Z" fill="white"/>
<path d="M1465.46 106.117L1376.39 192.853L1471.06 312.698H1405.77L1340.02 228.294L1320.43 247.879V312.698H1267.27V1.66138H1320.43V179.796L1395.51 106.117H1465.46Z" fill="white"/>
<path d="M1492.24 106.117H1545.4V312.698H1492.24V106.117ZM1519.75 3.06035C1528.45 3.06035 1536.07 6.3246 1542.6 12.8531C1549.13 19.3816 1552.39 26.9982 1552.39 35.7028C1552.39 44.7184 1549.13 52.4904 1542.6 59.0189C1536.07 65.2365 1528.3 68.3453 1519.28 68.3453C1509.96 68.3453 1502.03 65.2365 1495.5 59.0189C1488.97 52.4904 1485.71 44.563 1485.71 35.2365C1485.71 26.221 1488.97 18.6044 1495.5 12.3868C1502.03 6.16916 1510.11 3.06035 1519.75 3.06035Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_65_192">
<rect width="1550.68" height="318.352" fill="white" transform="translate(0.318359 0.823975)"/>
</clipPath>
</defs>
</svg>
</div>
<div class="userinfo_userinfo">
<div class="userinfo_avatar">
<img src="https://a.akatsuki.gg/133898"/>
<div class="userinfo_avatar_onlineStatus">
<div class="userinfo_online_badge online_offline"></div>
</div>
</div>
<div class="userinfo_userinfo_area">
<div class="userinfo_username">kriastans</div>
<div class="userinfo_clan_area">
<div class="userinfo_clanID">[WDNMD]</div>
<div class="userinfo_clanName">WorldNietaMad</div>
</div>
<div class="userinfo_lastSeen">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 2pt;margin-top: 0.25pt;"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 12l3 2" /><path d="M12 7v5" /></svg>
<span>最近上线于 3 天前</span>
</div>
<div class="userinfo_registeredAt">
<svg xmlns="http://www.w3.org/2000/svg" width="17.5" height="17.5" viewBox="0 0 24 24" fill="currentColor" style="margin-right: 2pt;margin-top: 0.25pt;"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 2a5 5 0 1 1 -5 5l.005 -.217a5 5 0 0 1 4.995 -4.783z" /><path d="M14 14a5 5 0 0 1 5 5v1a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-1a5 5 0 0 1 5 -5h4z" /></svg>
<span>账号注册于 2023年1月27日</span>
</div>
</div>
</div>
<div class="userinfo_rankStatus">
<div class="userinfo_globalRank">
<svg width="22" height="22" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.89258 4.56315e-05C3.51581 4.56315e-05 -0.000389968 3.62328 3.24395e-08 8.00005C3.24395e-08 12.4183 3.58172 16 8 16C12.4183 16 16 12.4183 16 8.00005C16 3.58177 12.4183 4.56315e-05 8 4.56315e-05C7.9643 -0.000194368 7.9283 4.56315e-05 7.89258 4.56315e-05ZM5.92773 2.46098C6.16157 2.48178 6.36353 2.63305 6.44922 2.85161C6.46212 3.07848 6.2893 3.27312 6.0625 3.28715C5.83525 3.28724 5.65134 3.47201 5.65234 3.69926V4.88286C5.65301 5.32431 5.29497 5.68235 4.85352 5.68169C4.41359 5.6821 4.05705 6.03863 4.05664 6.47856C4.05622 6.91925 3.6985 7.2761 3.25781 7.27544C2.81712 7.27586 2.46028 7.63357 2.46094 8.07426C2.46136 8.51419 2.81788 8.87072 3.25781 8.87114C3.69927 8.87047 4.05731 9.22851 4.05664 9.66997V12.5254C2.77371 11.3911 2.03847 9.76134 2.03711 8.04887C2.04011 6.55761 2.60027 5.12131 3.60742 4.02153L3.6582 3.97072C3.89273 3.72143 4.14822 3.49273 4.42188 3.28714L4.46112 3.25784C4.9104 2.92497 5.40396 2.65674 5.92773 2.46098ZM10.459 2.89262H11.0488C11.53 3.17575 11.9691 3.52484 12.3535 3.92973C12.3193 3.93673 12.2843 3.93974 12.248 3.93974H10.459C10.1688 3.93974 9.93555 3.70646 9.93555 3.41631C9.93555 3.12615 10.1688 2.89262 10.459 2.89262ZM8.40625 3.76567C8.72709 3.76568 8.98438 4.02491 8.98438 4.34575V4.98051C8.98437 5.30135 8.72709 5.56059 8.40625 5.56059C8.08542 5.56059 7.82617 5.30135 7.82617 4.98051V4.34575C7.82617 4.02491 8.08542 3.76568 8.40625 3.76567ZM10.6953 4.43559H12.7832C13.5434 5.43863 13.9678 6.65588 13.9961 7.91411C13.8728 8.01784 13.7178 8.0764 13.5566 8.08012L12.4277 8.07613C11.755 8.07521 11.2091 8.6202 11.209 9.29292V10.6406C11.2093 11.0811 10.8527 11.4387 10.4121 11.4394C9.97017 11.4404 9.61133 11.0825 9.61133 10.6406V9.76949C9.70283 9.45886 9.52533 9.13284 9.21484 9.04097C8.8125 9.04095 8.48635 8.71479 8.48633 8.31245V7.07417C8.45063 6.60275 8.80395 6.19169 9.27539 6.1562C9.66755 6.15689 9.98594 5.83938 9.98633 5.44722V5.14449C9.98672 4.75309 10.3039 4.43598 10.6953 4.43559Z" fill="white"/>
</svg>
<span>#1424</span>
</div>
<div class="userinfo_countryRank">
<img src="https://flagicons.lipis.dev/flags/4x3/hk.svg" class="userinfo_countryFlag" height="22">
<span>#64</span>
</div>
<div class="userinfo_ppRank">
<svg width="23" height="23" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 29.2766C18.4057 29.2766 20.6411 28.6752 22.7064 27.4723C24.7035 26.2922 26.2922 24.7035 27.4723 22.7064C28.6752 20.6411 29.2766 18.4057 29.2766 16C29.2766 13.5943 28.6752 11.3589 27.4723 9.29362C26.2922 7.29645 24.7035 5.7078 22.7064 4.52766C20.6411 3.32482 18.4057 2.7234 16 2.7234C13.5943 2.7234 11.3589 3.32482 9.29362 4.52766C7.29645 5.7078 5.7078 7.29645 4.52766 9.29362C3.32482 11.3589 2.7234 13.5943 2.7234 16C2.7234 18.4057 3.32482 20.6411 4.52766 22.7064C5.7078 24.7035 7.29645 26.2922 9.29362 27.4723C11.3589 28.6752 13.5943 29.2766 16 29.2766ZM16 32C13.7986 32 11.7333 31.5858 9.80426 30.7574C7.87518 29.9291 6.17305 28.7773 4.69787 27.3021C3.24539 25.895 2.09929 24.2099 1.25957 22.2468C0.419858 20.2837 0 18.2014 0 16C0 13.7986 0.414184 11.7333 1.24255 9.80426C2.07092 7.87518 3.2227 6.17305 4.69787 4.69787C6.10496 3.24539 7.79007 2.09929 9.75319 1.25957C11.7163 0.419858 13.7986 0 16 0C18.2014 0 20.2667 0.414184 22.1957 1.24255C24.1248 2.07092 25.827 3.2227 27.3021 4.69787C28.7546 6.10496 29.9007 7.79007 30.7404 9.75319C31.5801 11.7163 32 13.7986 32 16C32 18.2014 31.5858 20.2667 30.7574 22.1957C29.9291 24.1248 28.7773 25.827 27.3021 27.3021C25.895 28.7546 24.2099 29.9007 22.2468 30.7404C20.2837 31.5801 18.2014 32 16 32ZM16 24.1021C14.5475 24.1021 13.1972 23.739 11.9489 23.0128C10.7007 22.2865 9.71348 21.2993 8.98724 20.0511C8.26099 18.8028 7.89787 17.4525 7.89787 16C7.89787 14.5475 8.26099 13.1972 8.98724 11.9489C9.71348 10.7007 10.7007 9.71348 11.9489 8.98724C13.1972 8.26099 14.5475 7.89787 16 7.89787C17.4525 7.89787 18.8028 8.26099 20.0511 8.98724C21.2993 9.71348 22.2865 10.7007 23.0128 11.9489C23.739 13.1972 24.1021 14.5475 24.1021 16C24.1021 17.4525 23.739 18.8028 23.0128 20.0511C22.2865 21.2993 21.2993 22.2865 20.0511 23.0128C18.8028 23.739 17.4525 24.1021 16 24.1021Z" fill="white"/>
</svg>
<span>1372</span>
<span class="userinfo_ppRank_pp">PP</span>
</div>
</div>
<div class="userinfo_rankScoresStatus">
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-XH-small@2x.png" class="userinfo_rankingIcon">
<span style="font-weight: 700;">232</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-X-small@2x.png" class="userinfo_rankingIcon">
<span style="font-weight: 700;color:#fde047;filter: brightness(1.5)">13</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-SH-small@2x.png" class="userinfo_rankingIcon">
<span>252</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-S-small@2x.png" class="userinfo_rankingIcon">
<span style="color:#fde047;filter: brightness(1.5)">1145</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-A-small@2x.png" class="userinfo_rankingIcon">
<span style="color:#22c55e;filter: brightness(1.5)">513</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-B-small@2x.png" class="userinfo_rankingIcon">
<span style="color:#3b82f6;filter: brightness(1.5)">45</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-C-small@2x.png" class="userinfo_rankingIcon">
<span style="color:#a855f7;filter: brightness(2)">24</span>
</div>
<div class="userinfo_ScoreRank">
<img src="../resources/ranking-D-small@2x.png" class="userinfo_rankingIcon">
<span style="color:#dc2626;filter: brightness(2)">13</span>
</div>
</div>
<div class="userinfo_levelCard">
<div class="userinfo_levelAndFollowers">
<div class="userinfo_levelProgressBar">
<div class="userinfo_levelProgress">35%</div>
</div>
<div class="userinfo_followers">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-users"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 7m-4 0a4 4 0 1 0 8 0a4 4 0 1 0 -8 0" /><path d="M3 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /><path d="M16 3.13a4 4 0 0 1 0 7.75" /><path d="M21 21v-2a4 4 0 0 0 -3 -3.85" /></svg>
<span class="userinfo_followersTitle">粉丝数</span>
<span class="userinfo_followersNumber">97</span>
</div>
</div>
<div class="userinfo_levelBadge">
<div class="userinfo_level_icon" style="--level-color: var(--level-tier-platinum);"></div>
<div class="userinfo_level">999</div>
</div>
</div>
<div class="userinfo_detailsCard">
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M12 5v2" /><path d="M12 10v1" /><path d="M12 14v1" /><path d="M12 18v1" /><path d="M7 3v2" /><path d="M17 3v2" /><path d="M15 10.5v3a1.5 1.5 0 0 0 3 0v-3a1.5 1.5 0 0 0 -3 0z" /><path d="M6 9h1.5a1.5 1.5 0 0 1 0 3h-.5h.5a1.5 1.5 0 0 1 0 3h-1.5" /></svg>
<span class="userinfo_detailItemTitle">Ranked 谱面总分</span>
<span class="userinfo_detailItemNumber">1,109,198,726</span>
</div>
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-viewfinder"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 3l0 4" /><path d="M12 21l0 -3" /><path d="M3 12l4 0" /><path d="M21 12l-3 0" /><path d="M12 12l0 .01" /></svg>
<span class="userinfo_detailItemTitle">总体准确度</span>
<span class="userinfo_detailItemNumber">96.28%</span>
</div>
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="currentColor" class="icon icon-tabler icons-tabler-filled icon-tabler-circle-number-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10 -10 10s-10 -4.477 -10 -10s4.477 -10 10 -10zm.994 5.886c-.083 -.777 -1.008 -1.16 -1.617 -.67l-.084 .077l-2 2l-.083 .094a1 1 0 0 0 0 1.226l.083 .094l.094 .083a1 1 0 0 0 1.226 0l.094 -.083l.293 -.293v5.586l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-8l-.006 -.114z" /></svg>
<span class="userinfo_detailItemTitle">总osu!游玩次数</span>
<span class="userinfo_detailItemNumber">23,132</span>
</div>
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /><path d="M12 5v2" /><path d="M12 10v1" /><path d="M12 14v1" /><path d="M12 18v1" /><path d="M7 3v2" /><path d="M17 3v2" /><path d="M15 10.5v3a1.5 1.5 0 0 0 3 0v-3a1.5 1.5 0 0 0 -3 0z" /><path d="M6 9h1.5a1.5 1.5 0 0 1 0 3h-.5h.5a1.5 1.5 0 0 1 0 3h-1.5" /></svg>
<span class="userinfo_detailItemTitle">所有谱面总分</span>
<span class="userinfo_detailItemNumber">4,967,232,187</span>
</div>
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-click"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 12l3 0" /><path d="M12 3l0 3" /><path d="M7.8 7.8l-2.2 -2.2" /><path d="M16.2 7.8l2.2 -2.2" /><path d="M7.8 16.2l-2.2 2.2" /><path d="M12 12l9 3l-4 2l-2 4l-3 -9" /></svg>
<span class="userinfo_detailItemTitle">总命中次数</span>
<span class="userinfo_detailItemNumber">8,920</span>
</div>
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="currentColor" class="icon icon-tabler icons-tabler-filled icon-tabler-clock-hour-4"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M17 3.34a10 10 0 1 1 -15 8.66l.005 -.324a10 10 0 0 1 14.995 -8.336m-5 2.66a1 1 0 0 0 -1 1v5.026l.009 .105l.02 .107l.04 .129l.048 .102l.046 .078l.042 .06l.069 .08l.088 .083l.083 .062l3 2a1 1 0 1 0 1.11 -1.664l-2.555 -1.704v-4.464a1 1 0 0 0 -.883 -.993z" /></svg>
<span class="userinfo_detailItemTitle">游玩时间</span>
<span class="userinfo_detailItemNumber">12d 2h 32m 54s</span>
</div>
<div class="userinfo_detailItem">
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="23" viewBox="0 0 24 24" fill="currentColor" class="icon icon-tabler icons-tabler-filled icon-tabler-circle-number-1"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 2c5.523 0 10 4.477 10 10s-4.477 10 -10 10s-10 -4.477 -10 -10s4.477 -10 10 -10zm.994 5.886c-.083 -.777 -1.008 -1.16 -1.617 -.67l-.084 .077l-2 2l-.083 .094a1 1 0 0 0 0 1.226l.083 .094l.094 .083a1 1 0 0 0 1.226 0l.094 -.083l.293 -.293v5.586l.007 .117a1 1 0 0 0 1.986 0l.007 -.117v-8l-.006 -.114z" /></svg>
<span class="userinfo_detailItemTitle">最大连击数</span>
<span class="userinfo_detailItemNumber">1390</span>
</div>
</div>
<div class="copyright">
<span>Powered By FirikaBot 2024</span>
<span>© Copyright 2024 Doubx690i 版权所有</span>
<img src="../resources/bocchi.webp" style="width: 48pt;position: absolute;bottom: -2pt;left: 12pt;">
</div>
</div>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -0,0 +1,23 @@
{
"general": {
"master_qq": 1684342110,
"bot_qq": 1874424309
},
"http_server": {
"url": "http://0.0.0.0:8596",
"token": "testsdcfsdgvdfdfvv"
},
"protocols": [
{
"type": "llonebot",
"http_url": "http://localhost:3000",
"ws_url": "ws://localhost:3001"
}
],
"functions": [
{
"name": "help",
"enabled": true
}
]
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>

View File

@ -0,0 +1,15 @@
<Application x:Class="Doubx690i.FirikaBot.Wpf.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Doubx690i.FirikaBot.Wpf"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ui:ThemeResources/>
<ui:XamlControlsResources/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
namespace Doubx690i.FirikaBot.Wpf {
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application {
}
}

View File

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Wpf.Core.Interfaces {
public interface IFirikaInterfaceBase {
object ConvertToJsonObj();
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Doubx690i.FirikaBot.Wpf.Core.Interfaces;
namespace Doubx690i.FirikaBot.Wpf.Core.Models {
public class SendPrivateMessageModel : IFirikaInterfaceBase {
public int UserQQ { get; set; }
public string Message { get; set; }
public bool IsAutoEscape { get; set; }
public class SendPrivateMessageJsonModel {
public int user_id { get; set; }
public string message { get; set; }
public bool auto_escape { get; set; } = false;
}
public object ConvertToJsonObj() {
return new SendPrivateMessageJsonModel() {
user_id = UserQQ,
message = Message,
auto_escape = IsAutoEscape,
};
}
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<OutputType>WinExe</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWPF>true</UseWPF>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Flurl.Http" Version="4.0.2" />
<PackageReference Include="iNKORE.UI.WPF" Version="1.2.7.1" />
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.9.30" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34221.43
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Doubx690i.FirikaBot.Wpf", "Doubx690i.FirikaBot.Wpf.csproj", "{64FB7D49-AAF5-44AC-9710-A435AD52DBD5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Doubx690i.FirikaBot.Server", "..\Doubx690i.FirikaBot.Server\Doubx690i.FirikaBot.Server.csproj", "{AA303F6D-F679-497C-8A01-6A68C28C2820}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{64FB7D49-AAF5-44AC-9710-A435AD52DBD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{64FB7D49-AAF5-44AC-9710-A435AD52DBD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{64FB7D49-AAF5-44AC-9710-A435AD52DBD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{64FB7D49-AAF5-44AC-9710-A435AD52DBD5}.Release|Any CPU.Build.0 = Release|Any CPU
{AA303F6D-F679-497C-8A01-6A68C28C2820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA303F6D-F679-497C-8A01-6A68C28C2820}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA303F6D-F679-497C-8A01-6A68C28C2820}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA303F6D-F679-497C-8A01-6A68C28C2820}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9C6362B9-A1C4-4874-964E-4254E223027E}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Doubx690i.FirikaBot.Wpf.Helpers {
public static class FileBuildTimeHelper {
public struct _IMAGE_FILE_HEADER
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
};
public static DateTimeOffset? GetBuildDateTime(Assembly assembly)
{
var path = assembly.Location;
if (File.Exists(path))
{
var buffer = new byte[Math.Max(Marshal.SizeOf(typeof(_IMAGE_FILE_HEADER)), 4)];
using (var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read))
{
fileStream.Position = 0x3C;
fileStream.Read(buffer, 0, 4);
fileStream.Position = BitConverter.ToUInt32(buffer, 0); // COFF header offset
fileStream.Read(buffer, 0, 4); // "PE\0\0"
fileStream.Read(buffer, 0, buffer.Length);
}
var pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
var coffHeader = (_IMAGE_FILE_HEADER)Marshal.PtrToStructure(pinnedBuffer.AddrOfPinnedObject(), typeof(_IMAGE_FILE_HEADER));
return DateTimeOffset.FromUnixTimeSeconds(coffHeader.TimeDateStamp);
}
finally
{
pinnedBuffer.Free();
}
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,53 @@
<Window x:Class="Doubx690i.FirikaBot.Wpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Doubx690i.FirikaBot.Wpf"
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
mc:Ignorable="d"
ui:ThemeManager.RequestedTheme="Light"
Background="White"
ResizeMode="CanMinimize"
Title="Doubx690i.FirikaBot.Wpf" Height="650" Width="850">
<Grid Margin="16,9" Name="MainWinGrid">
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Left" Orientation="Vertical">
<TextBlock FontWeight="Bold" Foreground="Black" FontSize="24">Doubx690i.FirikaBot.Wpf</TextBlock>
<TextBlock Opacity="0.5" Foreground="Black" FontSize="12">© Copyright 2024 Doubx690i</TextBlock>
<TextBlock Opacity="0.5" Foreground="Black" FontSize="12">
<Run Text="构建日期:"/>
<Run Text="{Binding BundleBuildVersion, Mode=OneWay}"></Run>
</TextBlock>
</StackPanel>
<StackPanel VerticalAlignment="Top" HorizontalAlignment="Right" Orientation="Vertical">
<TextBlock TextWrapping="Wrap" Width="389" FlowDirection="RightToLeft" Opacity="0.75" Foreground="Black" FontSize="12">FirikaBot.Wpf 是一个由 Doubx690i 开发的集osu!查询和git通知推送和其他各种实用功能于一体的 无头 NTQQ Bot 项目</TextBlock>
<TextBlock TextWrapping="Wrap" Width="389" FlowDirection="RightToLeft" Opacity="0.75" Foreground="Red" FontSize="12">QQ 机器人协议实现LLOneBot</TextBlock>
</StackPanel>
<StackPanel HorizontalAlignment="Left" Width="356" Margin="0,78,0,0" Orientation="Vertical">
<TextBlock FontWeight="Bold" Foreground="Black" Margin="0,0,0,16" FontSize="16">协议设置</TextBlock>
<TextBlock Foreground="Black" FontSize="13" Margin="0,0,0,2">LLOneBot 服务地址</TextBlock>
<TextBlock Foreground="Black" Opacity="0.5" FontSize="11" Margin="0,0,0,4">请确保LLOneBot正在正常运行不能多开NTQQ</TextBlock>
<TextBox Margin="0,0,0,12" Text="{Binding LLOneBotUrl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Foreground="Black" FontSize="13" Margin="0,0,0,2">超级管理员 QQ号</TextBlock>
<TextBlock Foreground="Black" Opacity="0.5" FontSize="11" Margin="0,0,0,4">超级管理员拥有管理机器人的权限,英文逗号分割</TextBlock>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<ui:NumberBox Maximum="9999999999" Width="148" Minimum="10000" AcceptsExpression="False" Text="{Binding MasterQQNumber, Mode=TwoWay}"/>
<Button Margin="4,0,0,0" Content="发送测试消息"/>
</StackPanel>
<TextBlock Foreground="Black" FontSize="13" Margin="0,0,0,2">LLOneBot HTTP 服务端口</TextBlock>
<TextBlock Foreground="Black" Opacity="0.5" FontSize="11" Margin="0,0,0,4">设置HTTP端口用于后备调用API</TextBlock>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<ui:NumberBox Maximum="65536" Width="96" Minimum="80" Text="{Binding LLOneBotHttpPort, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button IsEnabled="{Binding TestHttpPortConnectivityButtonEnabled, Mode=OneWay}" Command="{Binding TestHttpPortConnectivityCommand}" Margin="4,0,0,0" Style="{StaticResource {x:Static ui:ThemeKeys.AccentButtonStyleKey}}" Content="测试端口连通性"/>
<Button Command="{Binding SendTestMessageToMasterQQCommand}" Margin="4,0,0,0" Style="{StaticResource {x:Static ui:ThemeKeys.AccentButtonStyleKey}}" Content="发送测试消息"/>
</StackPanel>
<TextBlock Foreground="Black" FontSize="13" Margin="0,0,0,2">LLOneBot Websocket 客户端端口</TextBlock>
<TextBlock Foreground="Black" Opacity="0.5" FontSize="11" Margin="0,0,0,4">设置Websocket端口用于监听事件和在生产环境调用API</TextBlock>
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
<ui:NumberBox Maximum="65536" Width="96" Minimum="80" Text="{Binding LLOneBotWsPort, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<Button Margin="4,0,0,0" Content="WS 日志"/>
<Button Margin="4,0,0,0" Style="{StaticResource {x:Static ui:ThemeKeys.AccentButtonStyleKey}}" Content="发送测试消息"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Doubx690i.FirikaBot.Wpf.ViewModels;
namespace Doubx690i.FirikaBot.Wpf {
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window {
public MainWindowViewModel _MainWindowViewModel;
public MainWindow() {
InitializeComponent();
// 初始化 MainWindow 的 ViewModel
_MainWindowViewModel = new MainWindowViewModel();
MainWinGrid.DataContext = _MainWindowViewModel;
}
}
}

View File

@ -0,0 +1,55 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// 有关程序集的一般信息由以下
// 控制。更改这些特性值可修改
// 与程序集关联的信息。
[assembly: AssemblyTitle("Doubx690i.FirikaBot.Wpf")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Doubx690i.FirikaBot.Wpf")]
[assembly: AssemblyCopyright("Copyright © 2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// 将 ComVisible 设置为 false 会使此程序集中的类型
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
//请将此类型的 ComVisible 特性设置为 true。
[assembly: ComVisible(false)]
//若要开始生成可本地化的应用程序,请设置
//.csproj 文件中的 <UICulture>CultureYouAreCodingWith</UICulture>
//在 <PropertyGroup> 中。例如,如果你使用的是美国英语。
//使用的是美国英语,请将 <UICulture> 设置为 en-US。 然后取消
//对以下 NeutralResourceLanguage 特性的注释。 更新
//以下行中的“en-US”以匹配项目文件中的 UICulture 设置。
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //主题特定资源词典所处位置
//(未在页面中找到资源时使用,
//或应用程序资源字典中找到时使用)
ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
//(未在页面中找到资源时使用,
//、应用程序或任何主题专用资源字典中找到时使用)
)]
// 程序集的版本信息由下列四个值组成:
//
// 主版本
// 次版本
// 生成号
// 修订号
//
//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值
//通过使用 "*",如下所示:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,62 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本: 4.0.30319.42000
//
// 对此文件的更改可能导致不正确的行为,如果
// 重新生成代码,则所做更改将丢失。
// </auto-generated>
//------------------------------------------------------------------------------
namespace Doubx690i.FirikaBot.Wpf.Properties {
/// <summary>
/// 强类型资源类,用于查找本地化字符串等。
/// </summary>
// 此类是由 StronglyTypedResourceBuilder
// 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
// 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
// (以 /str 作为命令选项),或重新生成 VS 项目。
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// 返回此类使用的缓存 ResourceManager 实例。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if ((resourceMan == null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Doubx690i.FirikaBot.Wpf.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// 重写当前线程的 CurrentUICulture 属性,对
/// 使用此强类型资源类的所有资源查找执行重写。
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Doubx690i.FirikaBot.Wpf.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Doubx690i.FirikaBot.Wpf.Core.Models;
using Doubx690i.FirikaBot.Wpf.Helpers;
using Flurl;
using Flurl.Http;
using Ookii.Dialogs.Wpf;
using Url = Flurl.Url;
namespace Doubx690i.FirikaBot.Wpf.ViewModels {
public class MainWindowViewModel : INotifyPropertyChanged {
public MainWindowViewModel() {
TestHttpPortConnectivityCommand = new RelayCommand(TestHttpPortConnectivity);
SendTestMessageToMasterQQCommand = new RelayCommand(SendTestMessageToMasterQQ);
}
public async void SendTestMessageToMasterQQ(object o) {
SendTestMessageToMasterQQButtonEnabled = false;
Url url;
var pmModel = new SendPrivateMessageModel() {
Message = "Hello,World!",
UserQQ = _masterQQNumber,
};
int responseCode = 200;
try {
url = new Url(_llOneBotUrl);
url.Scheme = "http";
url.Port = _llOneBotHttpPort;
var response = await url
.AppendPathSegment("send_private_msg")
.PostJsonAsync(pmModel.ConvertToJsonObj());
responseCode = response.StatusCode;
}
catch (Exception e) {
Trace.WriteLine(e);
(new Thread(() => {
if (TaskDialog.OSSupportsTaskDialogs) {
using (TaskDialog dialog = new TaskDialog()) {
dialog.WindowTitle = "Doubx690i.FirikaBot.Wpf";
dialog.MainIcon = TaskDialogIcon.Error;
dialog.MainInstruction = "错误";
dialog.Content = e.ToString();
TaskDialogButton okButton = new TaskDialogButton(ButtonType.Ok);
dialog.Buttons.Add(okButton);
dialog.ShowDialog();
}
}
})).Start();
SendTestMessageToMasterQQButtonEnabled = true;
return;
}
SendTestMessageToMasterQQButtonEnabled = true;
if (TaskDialog.OSSupportsTaskDialogs) {
using( TaskDialog dialog = new TaskDialog() ) {
dialog.WindowTitle = "Doubx690i.FirikaBot.Wpf";
dialog.MainInstruction = "状态码";
dialog.MainIcon = TaskDialogIcon.Information;
dialog.Content = responseCode.ToString();
TaskDialogButton okButton = new TaskDialogButton(ButtonType.Ok);
dialog.Buttons.Add(okButton);
dialog.ShowDialog();
}
}
}
public async void TestHttpPortConnectivity(object o) {
TestHttpPortConnectivityButtonEnabled = false;
Url url;
string result = "";
try {
url = new Url(_llOneBotUrl);
url.Scheme = "http";
url.Port = _llOneBotHttpPort;
result = await url.ToString().GetStringAsync();
}
catch (Exception e) {
Trace.WriteLine(e);
(new Thread(() => {
if (TaskDialog.OSSupportsTaskDialogs) {
using (TaskDialog dialog = new TaskDialog()) {
dialog.WindowTitle = "Doubx690i.FirikaBot.Wpf";
dialog.MainIcon = TaskDialogIcon.Error;
dialog.MainInstruction = "错误";
dialog.Content = e.ToString();
TaskDialogButton okButton = new TaskDialogButton(ButtonType.Ok);
dialog.Buttons.Add(okButton);
dialog.ShowDialog();
}
}
})).Start();
TestHttpPortConnectivityButtonEnabled = true;
return;
}
TestHttpPortConnectivityButtonEnabled = true;
if (TaskDialog.OSSupportsTaskDialogs) {
using( TaskDialog dialog = new TaskDialog() ) {
dialog.WindowTitle = "Doubx690i.FirikaBot.Wpf";
dialog.MainInstruction = "输出信息";
dialog.MainIcon = TaskDialogIcon.Information;
dialog.Content = result;
TaskDialogButton okButton = new TaskDialogButton(ButtonType.Ok);
dialog.Buttons.Add(okButton);
dialog.ShowDialog();
}
}
}
private string GetBundleBuildTime() {
var buildTime = FileBuildTimeHelper.GetBuildDateTime(System.Reflection.Assembly.GetExecutingAssembly());
if (buildTime != null) {
var bt = ((DateTimeOffset)buildTime).LocalDateTime;
var m = bt.Month.ToString().PadLeft(2, '0');
var d = bt.Day.ToString().PadLeft(2, '0');
var h = bt.Hour.ToString().PadLeft(2, '0');
var min = bt.Minute.ToString().PadLeft(2, '0');
var s = bt.Second.ToString().PadLeft(2, '0');
return $"build-{bt.Year}-{m}-{d}-{h}:{min}:{s}";
}
return "error";
}
#region
public string BundleBuildVersion => GetBundleBuildTime();
private string _llOneBotUrl { get; set; } = "http://localhost";
public string LLOneBotUrl {
get => _llOneBotUrl;
set {
_llOneBotUrl = value;
OnPropertyChanged("LLOneBotUrl");
}
}
private int _llOneBotHttpPort { get; set; } = 3000;
public int LLOneBotHttpPort {
get => _llOneBotHttpPort;
set {
_llOneBotHttpPort = value;
OnPropertyChanged("LLOneBotHttpPort");
}
}
private int _llOneBotWsPort { get; set; } = 3001;
public int LLOneBotWsPort {
get => _llOneBotWsPort;
set {
_llOneBotWsPort = value;
OnPropertyChanged("LLOneBotWsPort");
}
}
private int _masterQQNumber { get; set; } = 1684342110;
public int MasterQQNumber {
get => _masterQQNumber;
set {
_masterQQNumber = value;
OnPropertyChanged("MasterQQNumber");
}
}
private bool _testHttpPortConnectivityButtonEnabled { get; set; } = true;
public bool TestHttpPortConnectivityButtonEnabled {
get => _testHttpPortConnectivityButtonEnabled;
set {
_testHttpPortConnectivityButtonEnabled = value;
OnPropertyChanged("TestHttpPortConnectivityButtonEnabled");
}
}
private bool _sendTestMessageToMasterQQButtonEnabled { get; set; } = true;
public bool SendTestMessageToMasterQQButtonEnabled {
get => _sendTestMessageToMasterQQButtonEnabled;
set {
_sendTestMessageToMasterQQButtonEnabled = value;
OnPropertyChanged("SendTestMessageToMasterQQButtonEnabled");
}
}
public ICommand TestHttpPortConnectivityCommand { get; private set; }
public ICommand SendTestMessageToMasterQQCommand { get; private set; }
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name) {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public class RelayCommand : ICommand {
private Action<object> execute;
public RelayCommand(Action<object> executeAction) {
execute = executeAction;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter) {
execute(parameter);
}
}
}
}