diff --git a/dot.sln.DotSettings b/dot.sln.DotSettings index 24a4bdd..e0c09a2 100644 --- a/dot.sln.DotSettings +++ b/dot.sln.DotSettings @@ -1,8 +1,4 @@ - True - True - True - True False 1 1 @@ -16,7 +12,6 @@ <Policy Inspect="True" Prefix="_" Suffix="" Style="AA_BB" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> <TypePattern> @@ -57,5 +52,4 @@ </Entry> </TypePattern> </Patterns> - \ No newline at end of file diff --git a/src/Color/MouseHook.cs b/src/Color/MouseHook.cs deleted file mode 100644 index 0d410f5..0000000 --- a/src/Color/MouseHook.cs +++ /dev/null @@ -1,82 +0,0 @@ -// ReSharper disable ClassNeverInstantiated.Global - -#if NET7_0_WINDOWS -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace Dot.Color; - -internal sealed class MouseHook : IDisposable -{ - private const int _WH_MOUSE_LL = 14; - private const int _WM_LBUTTONDOWN = 0x0201; - private const int _WM_MOUSEMOVE = 0x0200; - private readonly nint _hookId; - private bool _disposed; - - public MouseHook() - { - _hookId = SetHook(HookCallback); - } - - ~MouseHook() - { - Dispose(false); - } - - public event MouseEventHandler MouseEvent; - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private static nint SetHook(Func proc) - { - using var curProcess = Process.GetCurrentProcess(); - using var curModule = curProcess.MainModule!; - return Win32.SetWindowsHookEx(_WH_MOUSE_LL, proc, Win32.GetModuleHandle(curModule.ModuleName), 0); - } - - private void Dispose(bool disposing) - { - if (_disposed) { - return; - } - - if (disposing) { - // - } - - if (_hookId != default) { - Win32.UnhookWindowsHookEx(_hookId); - } - - _disposed = true; - } - - private nint HookCallback(int nCode, nint wParam, nint lParam) - { - if (nCode < 0 || (wParam != _WM_MOUSEMOVE && wParam != _WM_LBUTTONDOWN)) { - return Win32.CallNextHookEx(_hookId, nCode, wParam, lParam); - } - - var hookStruct = (Msllhookstruct)Marshal.PtrToStructure(lParam, typeof(Msllhookstruct))!; - MouseEvent?.Invoke(null, new MouseEventArgs( // - wParam == _WM_MOUSEMOVE ? MouseButtons.None : MouseButtons.Left // - , 0 // - , hookStruct.X // - , hookStruct.Y // - , 0)); - return Win32.CallNextHookEx(_hookId, nCode, wParam, lParam); - } - - [StructLayout(LayoutKind.Explicit)] - private readonly struct Msllhookstruct - { - [FieldOffset(0)] public readonly int X; - [FieldOffset(4)] public readonly int Y; - } -} -#endif \ No newline at end of file diff --git a/src/Color/WinMain.cs b/src/Color/WinMain.cs index 78dfe70..82c5d76 100644 --- a/src/Color/WinMain.cs +++ b/src/Color/WinMain.cs @@ -1,6 +1,7 @@ #if NET7_0_WINDOWS using System.Runtime.InteropServices; using System.Runtime.Versioning; +using Dot.Native; using TextCopy; namespace Dot.Color; diff --git a/src/Native/MouseHook.cs b/src/Native/MouseHook.cs new file mode 100644 index 0000000..15a8d57 --- /dev/null +++ b/src/Native/MouseHook.cs @@ -0,0 +1,73 @@ +// ReSharper disable ClassNeverInstantiated.Global + +#if NET7_0_WINDOWS +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Dot.Native; + +internal sealed class MouseHook : IDisposable +{ + private readonly nint _hookId; + private bool _disposed; + + public MouseHook() + { + _hookId = SetHook(HookCallback); + } + + ~MouseHook() + { + Dispose(false); + } + + public event MouseEventHandler MouseMoveEvent; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private static nint SetHook(Win32.HookProc lpfn) + { + using var process = Process.GetCurrentProcess(); + using var module = process.MainModule; + return Win32.SetWindowsHookExA(Win32.WH_MOUSE_LL, lpfn, module!.BaseAddress, 0); + } + + private void Dispose(bool disposing) + { + if (_disposed) { + return; + } + + if (disposing) { + // + } + + if (_hookId != default) { + Win32.UnhookWindowsHookExA(_hookId); + } + + _disposed = true; + } + + private nint HookCallback(int nCode, nint wParam, nint lParam) + { + if (wParam == Win32.WM_MOUSEMOVE) { + var hookStruct = (Msllhookstruct)Marshal.PtrToStructure(lParam, typeof(Msllhookstruct))!; + MouseMoveEvent?.Invoke(null, new MouseEventArgs(MouseButtons.None, 0, hookStruct.X, hookStruct.Y, 0)); + } + + return Win32.CallNextHookEx(_hookId, nCode, wParam, lParam); + } + + [StructLayout(LayoutKind.Explicit)] + private readonly struct Msllhookstruct + { + [FieldOffset(0)] public readonly int X; + [FieldOffset(4)] public readonly int Y; + } +} +#endif \ No newline at end of file diff --git a/src/Win32.cs b/src/Native/Win32.cs similarity index 65% rename from src/Win32.cs rename to src/Native/Win32.cs index c9b6e6a..3f91e6c 100644 --- a/src/Win32.cs +++ b/src/Native/Win32.cs @@ -3,17 +3,23 @@ using System.Runtime.InteropServices; -namespace Dot; +namespace Dot.Native; internal static partial class Win32 { - public const int SW_HIDE = 0; - public const int WM_CHANGECBCHAIN = 0x030D; - public const int WM_DRAWCLIPBOARD = 0x308; + public const int CS_DROP_SHADOW = 0x20000; + public const int GCL_STYLE = -26; + public const int SW_HIDE = 0; + public const int WH_MOUSE_LL = 14; + public const int WM_CHANGECBCHAIN = 0x030D; + public const int WM_DRAWCLIPBOARD = 0x308; + public const int WM_LBUTTONDOWN = 0x0201; + public const int WM_MOUSEMOVE = 0x0200; + private const string _GDI32_DLL = "gdi32.dll"; + private const string _KERNEL32_DLL = "kernel32.dll"; + private const string _USER32_DLL = "user32.dll"; - private const string _GDI32_DLL = "gdi32.dll"; - private const string _KERNEL32_DLL = "kernel32.dll"; - private const string _USER32_DLL = "user32.dll"; + public delegate nint HookProc(int nCode, nint wParam, nint lParam); [LibraryImport(_USER32_DLL)] internal static partial nint CallNextHookEx(nint hhk, int nCode, nint wParam, nint lParam); @@ -22,6 +28,9 @@ internal static partial class Win32 [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool ChangeClipboardChain(nint hWndRemove, nint hWndNewNext); + [LibraryImport(_USER32_DLL)] + internal static partial int GetClassLongA(nint hWnd, int nIndex); + [LibraryImport(_KERNEL32_DLL)] internal static partial nint GetConsoleWindow(); @@ -29,7 +38,7 @@ internal static partial class Win32 internal static partial nint GetDesktopWindow(); [LibraryImport(_KERNEL32_DLL, StringMarshalling = StringMarshalling.Utf16)] - internal static partial nint GetModuleHandle(string lpModuleName); + internal static partial nint GetModuleHandleA(string lpModuleName); [LibraryImport(_GDI32_DLL)] internal static partial uint GetPixel(nint dc, int x, int y); @@ -43,6 +52,9 @@ internal static partial class Win32 [LibraryImport(_USER32_DLL)] internal static partial int SendMessageA(nint hwnd, uint wMsg, nint wParam, nint lParam); + [LibraryImport(_USER32_DLL)] + internal static partial int SetClassLongA(nint hWnd, int nIndex, int dwNewLong); + [LibraryImport(_USER32_DLL)] internal static partial int SetClipboardViewer(nint hWnd); @@ -50,8 +62,7 @@ internal static partial class Win32 internal static partial void SetLocalTime(Systemtime st); [LibraryImport(_USER32_DLL)] - internal static partial nint SetWindowsHookEx(int idHook, Func lpfn, nint hMod - , uint dwThreadId); + internal static partial nint SetWindowsHookExA(int idHook, HookProc lpfn, nint hMod, uint dwThreadId); [LibraryImport(_USER32_DLL)] [return: MarshalAs(UnmanagedType.Bool)] @@ -59,7 +70,7 @@ internal static partial class Win32 [LibraryImport(_USER32_DLL)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool UnhookWindowsHookEx(nint hhk); + internal static partial bool UnhookWindowsHookExA(nint hhk); [StructLayout(LayoutKind.Explicit)] internal ref struct Systemtime diff --git a/src/Time/Main.cs b/src/Time/Main.cs index dace916..e224413 100644 --- a/src/Time/Main.cs +++ b/src/Time/Main.cs @@ -1,6 +1,7 @@ // ReSharper disable ClassNeverInstantiated.Global using System.Net.Sockets; +using Dot.Native; namespace Dot.Time; diff --git a/src/Tran/BaiduSignCracker.cs b/src/Tran/BaiduSignCracker.cs new file mode 100644 index 0000000..6f90da0 --- /dev/null +++ b/src/Tran/BaiduSignCracker.cs @@ -0,0 +1,60 @@ +using NSExt.Extensions; + +namespace Dot.Tran; + +internal static class BaiduSignCracker +{ + private const int _MAGIC_NUMBER_1 = 320305; + private const int _MAGIC_NUMBER_2 = 131321201; + + public static string Sign(string text) + { + var e = new List(text.Length); + for (var i = 0; i < text.Length; i++) { + var k = (int)text[i]; + switch (k) { + case < 128: + e.Add(k); + break; + case < 2048: + e.Add((k >> 6) | 192); + break; + default: { + if ((k & 64512) == 55296 && i + 1 < text.Length && (text[i + 1] & 64512) == 56320) { + k = 65536 + ((k & 1023) << 10) + (text[++i] & 1023); + e.Add((k >> 18) | 240); + e.Add(((k >> 12) & 63) | 128); + } + else { + e.Add((k >> 12) | 224); + e.Add(((k >> 6) & 63) | 128); + e.Add((k & 63) | 128); + } + + break; + } + } + } + + var ret = e.Aggregate(_MAGIC_NUMBER_1, (accumulator, source) => Compute(accumulator + source, "+-a^+6")); + ret = Compute(ret, "+-3^+b+-f"); + ret ^= _MAGIC_NUMBER_2; + var longRet = ret < 0 ? 1L + (ret & int.MaxValue) + int.MaxValue : ret; + longRet %= 1_000_000; + return $"{longRet}.{longRet ^ _MAGIC_NUMBER_1}"; + } + + private static int Compute(int number, string password) + { + unchecked { + for (var i = 0; i < password.Length - 2; i += 3) { + var c = password[i + 2]; + var moveBit = c >= 'a' ? c - 87 : c.ToString().Int32(); + var d = password[i + 1] == '+' ? number >>> moveBit : number << moveBit; + number = password[i] == '+' ? (number + d) & (int)uint.MaxValue : number ^ d; + } + } + + return number; + } +} \ No newline at end of file diff --git a/src/Tran/Dto/BaiduTranslateResultDto.cs b/src/Tran/Dto/BaiduTranslateResultDto.cs new file mode 100644 index 0000000..da1031f --- /dev/null +++ b/src/Tran/Dto/BaiduTranslateResultDto.cs @@ -0,0 +1,62 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable ClassNeverInstantiated.Global + +#pragma warning disable IDE1006,SA1300 +namespace Dot.Tran.Dto; + +internal sealed record BaiduTranslateResultDto +{ + public sealed record DataItem + { + public string dst { get; set; } + + public int prefixWrap { get; set; } + + public string result { get; set; } + + public string src { get; set; } + } + + public sealed record PhoneticItem + { + public string src_str { get; set; } + + public string trg_str { get; set; } + } + + public sealed record Root + { + public string errmsg { get; set; } + + public int errno { get; set; } + + public int error { get; set; } + + public string errShowMsg { get; set; } + + public string from { get; set; } + + public long logid { get; set; } + + public string query { get; set; } + + public string to { get; set; } + + public Trans_result trans_result { get; set; } + } + + public sealed record Trans_result + { + public List data { get; set; } + + public string from { get; set; } + + public List phonetic { get; set; } + + public int status { get; set; } + + public string to { get; set; } + + public int type { get; set; } + } +} \ No newline at end of file diff --git a/src/Tran/FrmMain.cs b/src/Tran/FrmMain.cs index b164407..f8b9365 100644 --- a/src/Tran/FrmMain.cs +++ b/src/Tran/FrmMain.cs @@ -1,37 +1,38 @@ #if NET7_0_WINDOWS +using System.Net; +using System.Text.RegularExpressions; +using Dot.Native; +using Dot.Tran.Dto; using NSExt.Extensions; -using Colors = System.Drawing.Color; -using Padding = System.Windows.Forms.Padding; +using TextCopy; using Size = System.Drawing.Size; namespace Dot.Tran; -internal sealed class FrmMain : Form +internal sealed partial class FrmMain : Form { - private const int _HIDING_MIL_SECS = 1000; // 鼠标离开超过此时间后隐藏窗体 - private const string _TOKEN_URL = "https://fanyi.qq.com/api/reauth12f"; - private const string _URL = "https://fanyi.qq.com/api/translate"; - private static ManualResetEvent _mre = new(false); // 隐藏窗体侦测线程信号 + private const int _HIDING_MIL_SECS = 5000; // 隐藏窗体时间(秒 + private const int _RETRY_WAIT_MIL_SEC = 1000; // 重试等待时间(秒) + private const string _TRANSLATE_API_URL = "https://fanyi.baidu.com/v2transapi"; + private const string _TRANSLATE_HOME_URL = "https://fanyi.baidu.com"; + private const double _WINDOW_OPACITY = .5; // 窗体透明度 + private static ManualResetEvent _mre = new(false); // 隐藏窗体侦测线程信号 + private readonly HttpClient _httpClient = new(); + private readonly Label _labelDest = new(); // 显示翻译内容的文本框 - private readonly Colors _bgColor // 背景颜色 - = Colors.FromArgb(0xff, 0x1e, 0x1e, 0x1e); - - private readonly Colors _foreColor = Colors.White; // 字体颜色 - - private readonly HttpClient _httpClient = new(); - private readonly RichTextBox _richTextSource = new(); // 显示剪贴板内容的富文本框 - private readonly TextBox _textDest = new(); // 显示翻译内容的文本框 - private readonly Padding _windowPadding = new(10, 10, 10, 10); // 窗体边距 - private readonly Size _windowSize = new(640, 360); // 窗体大小 - private DateTime? _mouseLeaveTime; // 鼠标离开窗体时间 - private nint _nextHwnd; // 下一个剪贴板监视链对象句柄 + private readonly MouseHook _mouseHook = new(); + private readonly Size _mouseMargin = new(10, 10); + private bool _disposed; + private DateTime? _latestActiveTime; // 窗体最后激活时间 + private nint _nextHwnd; // 下一个剪贴板监视链对象句柄 + private string _token = "ae72ebad4113270fd26ada5125301268"; public FrmMain() { InitForm(); - - InitTextSource(); - InitTextDest(); + InitHook(); + InitLabelDest(); + InitHttpClient(); _nextHwnd = Win32.SetClipboardViewer(Handle); @@ -40,35 +41,27 @@ internal sealed class FrmMain : Form ~FrmMain() { - _mre?.Dispose(); - _richTextSource?.Dispose(); + Dispose(false); } - protected override void OnClosed(EventArgs e) + protected override void Dispose(bool disposing) { - base.OnClosed(e); + base.Dispose(disposing); - // 结束定时隐藏窗体线程 - _mre = null; - - // 从剪贴板监视链移除本窗体 - Win32.ChangeClipboardChain(Handle, _nextHwnd); - } - - protected override void OnMouseEnter(EventArgs e) - { - base.OnMouseEnter(e); - _mouseLeaveTime = null; - } - - protected override void OnMouseLeave(EventArgs e) - { - base.OnMouseLeave(e); - if (ClientRectangle.Contains(PointToClient(MousePosition))) { + if (_disposed) { return; } - _mouseLeaveTime = DateTime.Now; + if (disposing) { + _mre = null; // 结束定时隐藏窗体线程 + _mre?.Dispose(); + _httpClient?.Dispose(); + _labelDest?.Dispose(); + _mouseHook?.Dispose(); + } + + Win32.ChangeClipboardChain(Handle, _nextHwnd); // 从剪贴板监视链移除本窗体 + _disposed = true; } protected override void WndProc(ref Message m) @@ -84,6 +77,7 @@ internal sealed class FrmMain : Form SendToNext(m); break; case Win32.WM_CHANGECBCHAIN: + Console.WriteLine(Win32.WM_CHANGECBCHAIN); if (m.WParam == _nextHwnd) { _nextHwnd = m.LParam; } @@ -98,61 +92,44 @@ internal sealed class FrmMain : Form } } + [GeneratedRegex("token: '(\\w+)'")] + private static partial Regex MyRegex(); + /// /// 显示剪贴板内容. /// private void DisplayClipboardData() { - var clipData = Clipboard.GetDataObject(); - if (clipData is null) { + var clipText = ClipboardService.GetText(); + if (clipText.NullOrWhiteSpace()) { return; } - if (clipData.GetDataPresent(DataFormats.Rtf)) { - _richTextSource.Rtf = clipData.GetData(DataFormats.Rtf) as string; - } - else if (clipData.GetDataPresent(DataFormats.Text)) { - _richTextSource.Text = clipData.GetData(DataFormats.Text) as string; - } - else { - return; - } + _labelDest.Text = Str.Translating; + Task.Run(() => { + var translateText = _labelDest.Text = TranslateText(clipText); + Invoke(() => { _labelDest.Text = translateText; }); + }); - var mousePos = Cursor.Position; - mousePos.Offset(-_windowPadding.Left, -_windowPadding.Top); - Location = mousePos; - Visible = true; - _mouseLeaveTime = null; + var point = Cursor.Position; + point.Offset(new Point(_mouseMargin)); + Location = point; + Show(); + _latestActiveTime = DateTime.Now; _mre.Set(); } - private TokenStruct GetTranslateToken() - { - using var rsp = _httpClient.PostAsync(_TOKEN_URL, new StringContent(string.Empty)).Result; - var rspStr = rsp.Content.ReadAsStringAsync().Result; - return rspStr.Object(); - } - - private int HalfHeight() - { - return (_windowSize.Height - _windowPadding.Top - _windowPadding.Bottom) / 2 - _windowPadding.Top / 2; - } - private async Task HideWindow() { while (_mre is not null) { while (Visible) { await Task.Delay(100); - if (_mouseLeaveTime is null || - !((DateTime.Now - _mouseLeaveTime.Value).TotalMilliseconds > _HIDING_MIL_SECS)) { + if (_latestActiveTime is null || + !((DateTime.Now - _latestActiveTime.Value).TotalMilliseconds > _HIDING_MIL_SECS)) { continue; } - Invoke(() => { - Visible = false; - _richTextSource.Clear(); - _textDest.Clear(); - }); + Invoke(Hide); } _mre.WaitOne(); @@ -161,73 +138,74 @@ internal sealed class FrmMain : Form private void InitForm() { - BackColor = _bgColor; + AutoSize = true; + AutoSizeMode = AutoSizeMode.GrowAndShrink; FormBorderStyle = FormBorderStyle.None; - Padding = _windowPadding; - Size = _windowSize; + Opacity = _WINDOW_OPACITY; TopMost = true; Visible = false; } - private void InitTextDest() + private void InitHook() { - _textDest.BackColor = _bgColor; - _textDest.BorderStyle = BorderStyle.None; - _textDest.Dock = DockStyle.Bottom; - _textDest.ForeColor = _foreColor; - _textDest.Height = HalfHeight(); - _textDest.Margin = new Padding(0, _windowPadding.Top / 2, 0, 0); - _textDest.MouseEnter += (_, e) => OnMouseEnter(e); - _textDest.Multiline = true; - _textDest.ScrollBars = ScrollBars.None; - Controls.Add(_textDest); + _mouseHook.MouseMoveEvent += (_, e) => { + var point = new Point(e.X, e.Y); + point.Offset(new Point(_mouseMargin)); + Location = point; + }; } - private void InitTextSource() + private void InitHttpClient() { - _richTextSource.TextChanged += (sender, e) => { - if (_richTextSource.Text.NullOrWhiteSpace()) { - return; - } + _httpClient.DefaultRequestHeaders.Add( // + "User-Agent" + , " Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"); + } - _textDest.Text = Str.Translating; - _textDest.Text = TranslateText(_richTextSource.Text); - }; - _richTextSource.BackColor = _bgColor; - _richTextSource.BorderStyle = BorderStyle.None; - _richTextSource.Dock = DockStyle.Top; - _richTextSource.ForeColor = _foreColor; - _richTextSource.Height = HalfHeight(); - _richTextSource.Margin = new Padding(0, 0, 0, _windowPadding.Top / 2); - _richTextSource.MouseEnter += (_, e) => OnMouseEnter(e); - _richTextSource.ScrollBars = RichTextBoxScrollBars.None; - Controls.Add(_richTextSource); + private void InitLabelDest() + { + _labelDest.BorderStyle = BorderStyle.None; + _labelDest.Dock = DockStyle.Fill; + _labelDest.AutoSize = true; + Controls.Add(_labelDest); } private string TranslateText(string sourceText) { - var token = GetTranslateToken(); - var content = new FormUrlEncodedContent(new List> { - new("source", "auto") - , new("target", "zh") - , new("qtv", token.Qtv.Url()) - , new("qtk", token.Qtk.Url()) - , new("sourceText", sourceText.Url()) - }); - var rsp = _httpClient.PostAsync(_URL, content).Result; - var ret = rsp.Content.ReadAsStringAsync().Result; + while (true) { + var hash = sourceText.Length > 30 + ? string.Concat(sourceText.AsSpan()[..10], sourceText.AsSpan(sourceText.Length / 2 - 5, 10) + , sourceText.AsSpan()[^10..]) + : sourceText; + var sign = BaiduSignCracker.Sign(hash); + var content = new FormUrlEncodedContent(new List> { + new("from", "auto") + , new("to", "zh") + , new("query", sourceText) + , new("simple_means_flag", "3") + , new("sign", sign) + , new("token", _token) + , new("domain", "common") + }); - return ret; - } + var rsp = _httpClient.PostAsync(_TRANSLATE_API_URL, content).Result; + var rspObj = rsp.Content.ReadAsStringAsync().Result.Object(); + if (rspObj.error == 0) { + return string.Join(Environment.NewLine, rspObj.trans_result.data.Select(x => x.dst)); + } - private readonly struct TokenStruct - { - // ReSharper disable UnusedAutoPropertyAccessor.Local - public string Qtk { get; init; } + Console.Error.WriteLine(rspObj.Json().UnicodeDe()); + Console.Error.WriteLine(rsp.Headers.Json()); - public string Qtv { get; init; } - - // ReSharper restore UnusedAutoPropertyAccessor.Local + //cookie or token invalid + Task.Delay(_RETRY_WAIT_MIL_SEC).Wait(); + var cookie = string.Join( + ';', rsp.Headers.First(x => x.Key == "Set-Cookie").Value.Select(x => x.Split(';').First())); + _httpClient.DefaultRequestHeaders.Remove(nameof(Cookie)); + _httpClient.DefaultRequestHeaders.Add(nameof(Cookie), cookie); + var html = _httpClient.GetStringAsync(_TRANSLATE_HOME_URL).Result; + _token = MyRegex().Match(html).Groups[1].Value; + } } } #endif \ No newline at end of file diff --git a/src/Tran/Main.cs b/src/Tran/Main.cs index e4713e5..a00f763 100644 --- a/src/Tran/Main.cs +++ b/src/Tran/Main.cs @@ -9,7 +9,10 @@ internal sealed class Main : ToolBase