极简设计 · 开源可定制
WinExe
net8.0-windows
enable
true
Assets\favicon.ico
Always
Always
Always
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Xml.Linq;
using System.Windows.Threading;
using System.Security.Cryptography;
namespace XiaoPush3_0
{
public partial class MainWindow : Window
{
private const string UserAuthFile = "user_auth.json";
private const string IconMapFile = "icon_mapping.json";
private string uid;
private string regCode;
private bool uid_read_type;
private static readonly HttpClient httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
private readonly string baseUrl = "https://localhost/api/";
private readonly List messagesList = new List();
private readonly int keepTime = 8000; private Dictionary iconMap;
private const string DefaultIconPath = "./Assets/logo.png";
private class UserAuthData
{
public string Uid { get; set; }
public string RegCode { get; set; }
}
private class IconMappingData : Dictionary { }
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
Closed += OnClosed;
}
private async void OnLoaded(object sender, RoutedEventArgs e)
{
try
{
Left = SystemParameters.PrimaryScreenWidth - Width;
Top = 0;
var hwnd = new WindowInteropHelper(this).Handle;
SetWindowExTransparent(hwnd);
LoadIconMapFromJson();
AddMessage("XiaoPush", "Nice To Meet You");
await StartProcess();
}
catch (Exception ex)
{
MessageBox.Show($"启动失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void OnClosed(object sender, EventArgs e)
{
foreach (var message in messagesList)
{
CleanupMessage(message);
}
messagesList.Clear();
MessagesCanvas.Children.Clear();
}
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hwnd, int index);
private void SetWindowExTransparent(IntPtr hwnd)
{
const int WS_EX_TRANSPARENT = 0x00000020;
const int WS_EX_TOOLWINDOW = 0x00000080;
const int GWL_EXSTYLE = -20;
try
{
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW);
}
catch (Exception ex)
{
Console.WriteLine($"设置窗口样式失败: {ex.Message}");
}
}
private void LoadIconMapFromJson()
{
try
{
var jsonOptions = new JsonSerializerOptions { WriteIndented = true }; if (!File.Exists(IconMapFile))
{
iconMap = new IconMappingData
{
{ "com.tencent.mm", "./Assets/wechat.png" },
{ "com.tencent.mobileqq", "./Assets/qq.png" }
};
string defaultJson = JsonSerializer.Serialize(iconMap, jsonOptions);
File.WriteAllText(IconMapFile, defaultJson);
return;
}
string iconJson = File.ReadAllText(IconMapFile);
iconMap = JsonSerializer.Deserialize(iconJson) ?? new IconMappingData();
}
catch (Exception ex)
{
Console.WriteLine($"加载图标映射失败: {ex.Message},使用默认配置");
iconMap = new Dictionary
{
{ "com.tencent.mm", "./Assets/wechat.png" },
{ "com.tencent.qq", "./Assets/logo.png" }
};
}
}
private (string uid, bool type) LoadOrGenerateUid()
{
try
{
if (File.Exists(UserAuthFile))
{
string authJson = File.ReadAllText(UserAuthFile);
var authData = JsonSerializer.Deserialize(authJson); if (authData != null && !string.IsNullOrEmpty(authData.Uid))
{
uid = authData.Uid.Trim();
regCode = authData.RegCode ?? string.Empty; return (uid, true);
}
}
string newUid = GenerateUuidV7();
regCode = string.Empty;
SaveUserAuthData(newUid, regCode);
return (newUid, false);
}
catch (Exception ex)
{
Console.WriteLine($"UID加载失败: {ex.Message},生成新UID");
string newUid = GenerateUuidV7();
regCode = string.Empty;
SaveUserAuthData(newUid, regCode);
return (newUid, false);
}
}
private void SaveUserAuthData(string uid, string regCode)
{
try
{
var authData = new UserAuthData
{
Uid = uid,
RegCode = regCode ?? string.Empty
};
var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
string authJson = JsonSerializer.Serialize(authData, jsonOptions);
File.WriteAllText(UserAuthFile, authJson);
}
catch (Exception ex)
{
Console.WriteLine($"保存用户认证数据失败: {ex.Message}");
}
}
private void SaveUid(string uid)
{
SaveUserAuthData(uid, regCode);
}
private async Task StartProcess()
{
try
{
(uid, uid_read_type) = LoadOrGenerateUid(); if (!uid_read_type)
{
string newRegCode = await RegisterUid(uid); if (newRegCode == "re-register")
{
uid = GenerateUuidV7();
newRegCode = await RegisterUid(uid);
}
if (!string.IsNullOrEmpty(newRegCode) && newRegCode != "re-register")
{
regCode = newRegCode;
SaveUserAuthData(uid, regCode); AddMessage("注册码", regCode); await PollLogin();
}
else
{
AddMessage("注册失败", "无法连接到服务器,请检查网络");
}
}
else
{
await PollLogin();
}
}
catch (Exception ex)
{
Console.WriteLine($"启动流程失败: {ex.Message}");
AddMessage("启动失败", ex.Message);
}
}
private string GenerateUuidV7()
{
try
{
long unixTimeMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); byte[] uuidBytes = new byte[16]; uuidBytes[0] = (byte)(unixTimeMs >> 40);
uuidBytes[1] = (byte)(unixTimeMs >> 32);
uuidBytes[2] = (byte)(unixTimeMs >> 24);
uuidBytes[3] = (byte)(unixTimeMs >> 16);
uuidBytes[4] = (byte)(unixTimeMs >> 8);
uuidBytes[5] = (byte)unixTimeMs; uuidBytes[6] = 0x70; uuidBytes[8] = 0x80; using (var rng = RandomNumberGenerator.Create())
{
byte[] randomByte = new byte[1];
rng.GetBytes(randomByte);
uuidBytes[6] |= (byte)(randomByte[0] & 0x0F); rng.GetBytes(uuidBytes.AsSpan(7, 1));
rng.GetBytes(uuidBytes.AsSpan(9, 7));
}
return string.Format("{0:X2}{1:X2}{2:X2}{3:X2}-{4:X2}{5:X2}-{6:X2}{7:X2}-{8:X2}{9:X2}-{10:X2}{11:X2}{12:X2}{13:X2}{14:X2}{15:X2}",
uuidBytes[0], uuidBytes[1], uuidBytes[2], uuidBytes[3],
uuidBytes[4], uuidBytes[5],
uuidBytes[6], uuidBytes[7],
uuidBytes[8], uuidBytes[9],
uuidBytes[10], uuidBytes[11], uuidBytes[12], uuidBytes[13], uuidBytes[14], uuidBytes[15]).ToLower();
}
catch (Exception ex)
{
Console.WriteLine($"UUID v7生成失败: {ex.Message},使用后备GUID");
return Guid.NewGuid().ToString().ToLower();
}
}
private async Task RegisterUid(string uid)
{
try
{
var content = new FormUrlEncodedContent(new[] { new KeyValuePair("uid", uid) });
var response = await httpClient.PostAsync(baseUrl + "register.php", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"注册请求失败: {ex.Message}");
return null;
}
catch (TaskCanceledException)
{
Console.WriteLine("注册请求超时");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"注册错误: {ex.Message}");
return null;
}
}
private async Task PollLogin()
{
int maxRetries = 30;
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
AddMessage("XiaoPush", "身份认证中!");
string result = await LoginUid(uid); if (result == "online")
{
AddMessage("XiaoPush", "身份认证已通过!");
await StartLongPolling();
return;
}
else if (result == "not-whitelisted")
{
if (!string.IsNullOrEmpty(regCode))
{
AddMessage("注册码", regCode);
}
Console.WriteLine($"未在白名单中,重试中... ({retryCount + 1}/{maxRetries})");
}
else
{
AddMessage("注册码?", result);
}
retryCount++;
}
catch (Exception ex)
{
AddMessage("XiaoPush", "身份认证失败");
Console.WriteLine($"登录轮询错误: {ex.Message}");
retryCount++;
}
await Task.Delay(10000);
}
AddMessage("登录失败", "无法连接到服务,请检查网络或联系管理员");
}
private async Task LoginUid(string uid)
{
try
{
var content = new FormUrlEncodedContent(new[] { new KeyValuePair("uid", uid) });
var response = await httpClient.PostAsync(baseUrl + "login.php", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"登录请求失败: {ex.Message}");
return null;
}
catch (TaskCanceledException)
{
Console.WriteLine("登录请求超时");
return null;
}
catch (Exception ex)
{
Console.WriteLine($"登录错误: {ex.Message}");
return null;
}
}
private async Task StartLongPolling()
{
Dispatcher.Invoke(() =>
{
messagesList.Clear();
MessagesCanvas.Children.Clear();
});
AddMessage("XiaoPush", "Xiao与您同行..."); int failCount = 0;
while (true)
{
try
{
if (!this.IsLoaded) break;
string jsonStr = await LongPollUid(uid); if (!string.IsNullOrEmpty(jsonStr))
{
try
{
var msg = JsonSerializer.Deserialize(jsonStr);
if (msg != null && !string.IsNullOrEmpty(msg.message_title))
{
AddMessage(msg.message_title, msg.message_content, msg.package_name);
}
}
catch (JsonException ex)
{
Console.WriteLine($"JSON解析失败: {ex.Message}");
}
}
failCount = 0;
}
catch (Exception ex)
{
Console.WriteLine($"长轮询错误: {ex.Message}");
failCount++; if (failCount >= 3)
{
string attackResult = await CheckAttack(uid);
if (!string.IsNullOrEmpty(attackResult) && attackResult != "normal")
{
Console.WriteLine($"黑名单解封时间: {attackResult}");
AddMessage("网络异常", "检测到异常请求,等待恢复...");
await Task.Delay(60000);
failCount = 0;
}
}
await Task.Delay(5000);
}
}
}
private async Task LongPollUid(string uid)
{
try
{
var content = new FormUrlEncodedContent(new[] { new KeyValuePair("uid", uid) });
var response = await httpClient.PostAsync(baseUrl + "longpolling.php", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"长轮询请求失败: {ex.Message}");
return null;
}
catch (TaskCanceledException)
{
return string.Empty;
}
catch (Exception ex)
{
Console.WriteLine($"长轮询错误: {ex.Message}");
return null;
}
}
private async Task CheckAttack(string uid)
{
try
{
var content = new FormUrlEncodedContent(new[] { new KeyValuePair("uid", uid) });
var response = await httpClient.PostAsync(baseUrl + "attacktest.php", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
Console.WriteLine($"攻击检测请求失败: {ex.Message}");
return null;
}
}
private void AddMessage(string title, string content, string packageName = null)
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(() => AddMessage(title, content, packageName));
return;
}
var message = new Border
{
Width = 280,
Height = 70,
Background = new SolidColorBrush(Color.FromArgb(192, 255, 255, 255)),
BorderBrush = Brushes.White,
BorderThickness = new Thickness(2),
CornerRadius = new CornerRadius(18),
Margin = new Thickness(10, 0, 10, 0),
Opacity = 0.1,
RenderTransform = new ScaleTransform(0.1, 0.1),
Effect = new DropShadowEffect
{
Color = Colors.Black,
Direction = 320,
ShadowDepth = 5,
Opacity = 0.3,
BlurRadius = 10
}
}; var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(70) });
grid.ColumnDefinitions.Add(new ColumnDefinition()); var img = new Image
{
Source = LoadBitmapImage(GetIconPath(packageName)),
Height = 44,
Width = 44,
Margin = new Thickness(10)
};
Grid.SetColumn(img, 0); var stack = new StackPanel
{
Orientation = Orientation.Vertical,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center
}; var titleText = new TextBlock
{
Text = title,
FontSize = 16,
FontWeight = FontWeights.Bold,
FontFamily = new FontFamily("Segoe UI"),
MaxWidth = 180,
TextTrimming = TextTrimming.CharacterEllipsis,
Foreground = Brushes.Black,
TextAlignment = TextAlignment.Left
}; var contentText = new TextBlock
{
Text = content,
FontSize = 14,
MaxWidth = 180,
TextTrimming = TextTrimming.CharacterEllipsis,
Foreground = Brushes.Gray,
FontFamily = new FontFamily("Segoe UI"),
}; stack.Children.Add(titleText);
stack.Children.Add(contentText);
Grid.SetColumn(stack, 1); grid.Children.Add(img);
grid.Children.Add(stack);
message.Child = grid; Canvas.SetTop(message, -110);
Canvas.SetRight(message, 0); MessagesCanvas.Children.Add(message);
messagesList.Add(message); AnimateMessageIn(message);
RefreshMessages();
RecycleMessage(message);
}
private void AnimateMessageIn(FrameworkElement message)
{
var config_duation_time = 1;
if (!(message.RenderTransform is ScaleTransform))
{
message.RenderTransform = new ScaleTransform();
message.RenderTransformOrigin = new Point(0.5, 0.5);
}
var enterStoryboard = new Storyboard(); var slideIn = new DoubleAnimation
{
From = -100,
To = 10,
Duration = TimeSpan.FromSeconds(config_duation_time),
EasingFunction = new ElasticEase
{
Oscillations = 2,
Springiness = 5,
EasingMode = EasingMode.EaseOut
}
};
Storyboard.SetTarget(slideIn, message);
Storyboard.SetTargetProperty(slideIn, new PropertyPath(Canvas.TopProperty)); var fadeIn = new DoubleAnimation
{
From = 0.1,
To = 1,
Duration = TimeSpan.FromSeconds(config_duation_time),
EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseOut }
};
Storyboard.SetTarget(fadeIn, message);
Storyboard.SetTargetProperty(fadeIn, new PropertyPath(OpacityProperty)); var scaleXAnim = new DoubleAnimation
{
From = 0.1,
To = 1,
Duration = TimeSpan.FromSeconds(config_duation_time),
EasingFunction = new ElasticEase { Oscillations = 2, Springiness = 5, EasingMode = EasingMode.EaseOut }
};
var scaleYAnim = new DoubleAnimation
{
From = 0.1,
To = 1,
Duration = TimeSpan.FromSeconds(config_duation_time),
EasingFunction = new ElasticEase { Oscillations = 2, Springiness = 5, EasingMode = EasingMode.EaseOut }
}; Storyboard.SetTarget(scaleXAnim, message);
Storyboard.SetTargetProperty(scaleXAnim, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleX)"));
Storyboard.SetTarget(scaleYAnim, message);
Storyboard.SetTargetProperty(scaleYAnim, new PropertyPath("(UIElement.RenderTransform).(ScaleTransform.ScaleY)")); enterStoryboard.Children.Add(slideIn);
enterStoryboard.Children.Add(fadeIn);
enterStoryboard.Children.Add(scaleXAnim);
enterStoryboard.Children.Add(scaleYAnim); enterStoryboard.Begin();
}
private string GetIconPath(string packageName)
{
if (!string.IsNullOrEmpty(packageName) && iconMap.TryGetValue(packageName, out var path))
{
return path;
}
return DefaultIconPath;
}
private BitmapImage LoadBitmapImage(string imagePath)
{
try
{
if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath))
{
imagePath = DefaultIconPath;
}
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(imagePath, UriKind.RelativeOrAbsolute);
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
bitmap.Freeze();
return bitmap;
}
catch (Exception ex)
{
Console.WriteLine($"加载图片失败 '{imagePath}': {ex.Message}");
return new BitmapImage();
}
}
private void RefreshMessages()
{
if (!Dispatcher.CheckAccess())
{
Dispatcher.Invoke(RefreshMessages);
return;
}
var RefreshMessagesConfigDurationTime = 0.3;
for (int i = 0; i < Math.Min(5, messagesList.Count); i++)
{
var element = messagesList[i];
var storyboard = new Storyboard(); double targetTop = i * 75;
var marginAnimation = new ThicknessAnimation
{
To = new Thickness(0, targetTop, 10, 0),
Duration = TimeSpan.FromSeconds(RefreshMessagesConfigDurationTime),
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut }
};
Storyboard.SetTarget(marginAnimation, element);
Storyboard.SetTargetProperty(marginAnimation, new PropertyPath(FrameworkElement.MarginProperty));
storyboard.Children.Add(marginAnimation); double targetOpacity = Math.Max(0, 1.0 - i * 0.01);
var opacityAnimation = new DoubleAnimation
{
To = targetOpacity,
Duration = TimeSpan.FromSeconds(RefreshMessagesConfigDurationTime),
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
};
Storyboard.SetTarget(opacityAnimation, element);
Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(UIElement.OpacityProperty));
storyboard.Children.Add(opacityAnimation); double targetScale = Math.Max(0, 1.0 - i * 0.01);
if (!(element.RenderTransform is ScaleTransform))
{
element.RenderTransform = new ScaleTransform(1, 1);
element.RenderTransformOrigin = new Point(0.5, 0.5);
}
var scaleXAnimation = new DoubleAnimation
{
To = targetScale,
Duration = TimeSpan.FromSeconds(RefreshMessagesConfigDurationTime),
EasingFunction = new BackEase { Amplitude = 0.1, EasingMode = EasingMode.EaseOut }
};
Storyboard.SetTarget(scaleXAnimation, element);
Storyboard.SetTargetProperty(scaleXAnimation, new PropertyPath("RenderTransform.ScaleX"));
storyboard.Children.Add(scaleXAnimation); var scaleYAnimation = new DoubleAnimation
{
To = targetScale,
Duration = TimeSpan.FromSeconds(RefreshMessagesConfigDurationTime),
EasingFunction = new BackEase { Amplitude = 0.1, EasingMode = EasingMode.EaseOut }
};
Storyboard.SetTarget(scaleYAnimation, element);
Storyboard.SetTargetProperty(scaleYAnimation, new PropertyPath("RenderTransform.ScaleY"));
storyboard.Children.Add(scaleYAnimation); element.SetValue(Panel.ZIndexProperty, 5 - i); var rightAnimation = new DoubleAnimation
{
To = 0,
Duration = TimeSpan.FromSeconds(RefreshMessagesConfigDurationTime),
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseInOut }
};
Storyboard.SetTarget(rightAnimation, element);
Storyboard.SetTargetProperty(rightAnimation, new PropertyPath(Canvas.RightProperty));
storyboard.Children.Add(rightAnimation); storyboard.Begin();
}
}
private async void RecycleMessage(FrameworkElement message)
{
try
{
await Task.Delay(keepTime);
if (!this.IsLoaded) return; await Dispatcher.InvokeAsync(() =>
{
if (!messagesList.Contains(message) || !MessagesCanvas.Children.Contains(message))
return; TransformGroup transformGroup = new TransformGroup();
ScaleTransform scaleTransform = (message.RenderTransform as ScaleTransform) ?? new ScaleTransform(1, 1);
TranslateTransform translateTransform = new TranslateTransform(0, 0);
transformGroup.Children.Add(scaleTransform);
transformGroup.Children.Add(translateTransform);
message.RenderTransform = transformGroup;
message.RenderTransformOrigin = new Point(0.5, 0); var fadeOut = new DoubleAnimation(message.Opacity, 0, TimeSpan.FromSeconds(0.5))
{
EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseIn }
}; var currentScale = scaleTransform.ScaleX;
var scaleAnim = new DoubleAnimation(currentScale, 0, TimeSpan.FromSeconds(0.5))
{
EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseIn }
}; var moveUpAnim = new DoubleAnimation(0, -20, TimeSpan.FromSeconds(0.5))
{
EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseIn },
FillBehavior = FillBehavior.HoldEnd
}; message.BeginAnimation(OpacityProperty, fadeOut);
scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnim);
scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnim);
translateTransform.BeginAnimation(TranslateTransform.YProperty, moveUpAnim); messagesList.Remove(message); Task.Delay(500).ContinueWith(async _ =>
{
if (this.IsLoaded)
{
await Dispatcher.InvokeAsync(() =>
{
if (MessagesCanvas.Children.Contains(message))
{
CleanupMessage(message);
MessagesCanvas.Children.Remove(message);
}
});
}
}); RefreshMessages();
});
}
catch (Exception ex)
{
Console.WriteLine($"回收消息时出错: {ex.Message}");
}
}
private void CleanupMessage(FrameworkElement message)
{
try
{
message.BeginAnimation(UIElement.OpacityProperty, null); if (message.RenderTransform is ScaleTransform scaleTransform)
{
scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, null);
scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, null);
}
message.RenderTransform = null; if (message is ContentControl contentControl)
{
contentControl.Content = null;
}
else if (message is Panel panel)
{
panel.Children.Clear();
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
catch (Exception ex)
{
Console.WriteLine($"清理消息资源时出错: {ex.Message}");
}
if (message is Border border && border.Child is Grid grid)
{
foreach (var child in grid.Children)
{
if (child is Image image)
{
image.Source = null;
}
}
}
}
}
public class MessageItem
{
public string package_name { get; set; }
public string message_title { get; set; }
public string message_content { get; set; }
}
}
using System.Windows;
namespace XiaoPush3_0
{
public partial class App : Application
{
}
}