最近為了做公眾號號推廣,吸粉,然後加了幾百個QQ群,感覺QQ群的群發效果還是不錯的,一天能撈到100個粉絲左右,好的時候也有200個,少的時候幾十個,但是由於太多的群了,手工一個個點選開來群發,幾百個群,要花費大量的時間,所以想到了QQ群發工具,然後就在百度上搜尋,最後發現網上很多QQ群發器根本用不了,本來想買一個的,因為太垃圾了,就不買了,而自己又是個程式設計師,乾脆花點時間自己編寫一個工具,經過兩天奮戰,終於把這個工具寫好了,經測試,感覺還挺穩定的,這樣我就可以解放雙手了,現在我要把這編寫的程式碼分享給大家。
新建專案AssistLib
新建相關實體類
AssistEventArgs.cs
using System; using System.Collections.Generic; using System.Text; namespace AssistLib.Models { public class SendMessageEventArgs : EventArgs { public object State { get; set; } } }
Group.cs
using System; using System.Collections.Generic; using System.Text; namespace AssistLib.Models { public class Group { /// <summary> /// 視窗控制程式碼 /// </summary> public IntPtr Hwnd { get; set; } /// <summary> /// 視窗類名 /// </summary> public string ClassName { get; set; } /// <summary> /// 群名稱 /// </summary> public string Name { get; set; } /// <summary> /// 是否傳送 /// </summary> public bool IsSend { get; set; } } }
Settings.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace AssistLib.Models { /// <summary> /// 設定 /// </summary> public class Settings { /// <summary> /// 傳送內容 /// </summary> public string Content { get; set; } /// <summary> /// 圖片路徑 /// </summary> public string ImagePath { get; set; } /// <summary> /// 是否隨機待問候語 /// </summary> public bool IsGreeting { get; set; } /// <summary> /// 是否重複迴圈傳送 /// </summary> public bool IsReply { get; set; } /// <summary> /// 如果重複迴圈傳送,隔多久迴圈(分鐘) /// </summary> public int ReplyInterval { get; set; } /// <summary> /// 傳送速度 /// </summary> public Speed SendSpeed { get; set; } /// <summary> /// 傳送鍵型別 /// </summary> public SendKeyType KeyType { get; set; } } /// <summary> /// 傳送速度 /// </summary> public enum Speed : byte { /// <summary> /// 300毫秒 /// </summary> Fast, /// <summary> /// 1000毫秒 /// </summary> Middle, /// <summary> /// 3000毫秒 /// </summary> Slow } /// <summary> /// QQ傳送鍵型別 /// </summary> public enum SendKeyType : byte { Enter, CtrlEnter } }
新建群發器類Assist.cs
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using AssistLib.Models; using Newtonsoft.Json; namespace AssistLib { public class Assist { /// <summary> /// 取消任務告知源 /// </summary> static CancellationTokenSource cts = new CancellationTokenSource(); static ManualResetEvent resetEvent = new ManualResetEvent(true); static ManualResetEvent resetEvent2 = new ManualResetEvent(false); static Task task = null; public Assist() { //初始化資料 InitData(); } #region 屬性 /// <summary> /// 群列表 /// </summary> public List<Group> GroupList = new List<Group>(); /// <summary> /// 問候語列表 /// </summary> public List<string> GreetingsList = new List<string>(); /// <summary> /// 群發間隔時間(毫秒) /// </summary> public int Interval { get; set; } public Settings SendSettings { get; set; } #endregion #region 方法 /// <summary> /// 取消 /// </summary> public static void TaskCancel() { cts.Cancel(); //終止執行緒阻塞 resetEvent.Set(); resetEvent2.Set(); } /// <summary> /// 是否已經取消 /// </summary> /// <returns></returns> public static bool TaskIsCancellationRequested() { return cts.IsCancellationRequested; } /// <summary> /// 暫停 /// </summary> public static void TaskReset() { resetEvent.Reset(); //執行緒阻塞啟用 resetEvent2.Reset(); } /// <summary> /// 繼續 /// </summary> public static void TaskSet() { resetEvent.Set(); //執行緒阻塞啟用 resetEvent2.Reset(); } public Task GetTask() { return task; } /// <summary> /// 傳送內容 /// </summary> /// <param name="hwnd"></param> private void SendMessage(IntPtr hwnd) { WinAPI.ShowWindow(hwnd, 9); WinAPI.SetActiveWindow(hwnd); WinAPI.SetForegroundWindow(hwnd); WinAPI.SetFocus(hwnd); SendKeys.SendWait("^v"); if (SendSettings.KeyType == SendKeyType.Enter) { SendKeys.SendWait("{ENTER}"); } else { SendKeys.SendWait("^{ENTER}"); } } /// <summary> /// 將內容複製到貼上板上 /// </summary> private void CopyToClipboard() { var message = ""; //圖片 if (File.Exists(SendSettings.ImagePath)) { message += string.Format(@" <IMG src=""file:///{0}"" > <br /> ", SendSettings.ImagePath); } //內容 if (!string.IsNullOrWhiteSpace(SendSettings.Content)) { message += SendSettings.Content + " <br> "; } //問候語 if (SendSettings.IsGreeting && GreetingsList.Count > 0) { var random = new Random(); var index = random.Next(0, GreetingsList.Count); message += GreetingsList[index] + " <br> "; } Console.WriteLine(message); //設定貼上板內容 if (!string.IsNullOrWhiteSpace(message)) { if (File.Exists(SendSettings.ImagePath)) { ClipboardHelper.CopyToClipboard(message, ""); } else { message = message.Replace("<br>", "\n"); Clipboard.SetText(message, TextDataFormat.UnicodeText); } } } /// <summary> /// 傳送訊息 /// </summary> public void SendMessage() { cts = new CancellationTokenSource(); resetEvent = new ManualResetEvent(true); resetEvent2 = new ManualResetEvent(false); Logger logger = new Logger(); var logMsg = ""; task = Task.Factory.StartNew(() => { var tcs = new TaskCompletionSource<object>(); var thread = new Thread(() => { try { logMsg = SendMessage(logger, logMsg); tcs.SetResult(new object()); } catch (Exception e) { tcs.SetException(e); } }); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); }, cts.Token); } private string SendMessage(Logger logger, string logMsg) { #region 業務邏輯 var arg = new SendMessageEventArgs(); //取消執行緒 while (true) { foreach (var g in GroupList) { //取消執行緒 if (cts.IsCancellationRequested) { logMsg = string.Format("【傳送取消】{0}", g.Name); logger.Write(logMsg); if (OnSendFinished != null) { arg.State = logMsg; OnSendFinished(g, arg); } break; } //阻塞執行緒 resetEvent.WaitOne(); if (!g.IsSend) continue; if (!WinAPI.IsWindowVisible(g.Hwnd)) { logMsg = string.Format("{0}【傳送失敗,可能已關閉】", g.Name); logger.Write(logMsg); if (OnSendFaild != null) { arg.State = logMsg; OnSendFaild(g, arg); } continue; } CopyToClipboard(); SendMessage(g.Hwnd); logMsg = string.Format("【傳送成功】{0}", g.Name); logger.Write(logMsg); if (OnSending != null) { arg.State = logMsg; OnSending(g, arg); } resetEvent2.WaitOne(this.Interval); } //取消執行緒 if (cts.IsCancellationRequested) { logMsg = string.Format("【傳送取消,群迴圈取消傳送】"); logger.Write(logMsg); if (OnSendFinished != null) { arg.State = logMsg; OnSendFinished(null, arg); } break; } //不迴圈,跳出傳送 if (!SendSettings.IsReply) { logMsg = string.Format("【傳送完畢!】"); logger.Write(logMsg); if (OnSendFinished != null) { arg.State = logMsg; OnSendFinished(null, arg); } break; } else { resetEvent2.WaitOne(SendSettings.ReplyInterval * 60 * 1000); } } #endregion return logMsg; } /// <summary> /// 初始化資料 /// </summary> public void InitData() { var setting = AssistTool.GetSettings(); if (setting == null) return; SendSettings = setting; this.GreetingsList = AssistTool.GetGreetings(); switch (setting.SendSpeed) { case Speed.Middle: this.Interval = 1000;// break; case Speed.Slow: this.Interval = 3000;// break; default: this.Interval = 300;// break; } if (!File.Exists(SendSettings.ImagePath)) { SendSettings.ImagePath = string.Empty; } this.GroupList = AssistTool.GetGroupList(); } #endregion public event SendEventHandler OnSending; public event SendEventHandler OnSendFinished; public event SendEventHandler OnSendFaild; public delegate void SendEventHandler(Group sender, SendMessageEventArgs e); } }
新建群發器工具類AssistTool.cs
using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; using AssistLib.Models; using Newtonsoft.Json; namespace AssistLib { public class AssistTool { private static List<Group> GroupList = new List<Group>(); /// <summary> /// 儲存引數 /// </summary> public static void SaveSettings(Settings settings) { string path = Directory.GetCurrentDirectory(); string setPath = Path.Combine(path, "Settings.json"); if (!File.Exists(setPath)) { File.Create(setPath).Close(); } using (StreamWriter writer = new StreamWriter(setPath, false, Encoding.UTF8)) { var json = JsonConvert.SerializeObject(settings); writer.Write(json); } } /// <summary> /// 獲取設定引數 /// </summary> /// <returns></returns> public static Settings GetSettings() { Settings set = new Settings(); string path = Directory.GetCurrentDirectory(); string setPath = Path.Combine(path, "Settings.json"); if (!File.Exists(setPath)) { return null; } using (StreamReader sr = new StreamReader(setPath)) { var json = sr.ReadToEnd(); set = JsonConvert.DeserializeObject<Settings>(json); } if (set.ReplyInterval <= 0) { set.ReplyInterval = 1; } else if (set.ReplyInterval > 600) { set.ReplyInterval = 600; } return set; } /// <summary> /// 儲存問候語 /// </summary> public static void SaveGreetings(string content) { string path = Directory.GetCurrentDirectory(); string setPath = Path.Combine(path, "greetings.txt"); if (!File.Exists(setPath)) { File.Create(setPath).Close(); } using (StreamWriter writer = new StreamWriter(setPath, false, Encoding.UTF8)) { writer.Write(content); } } /// <summary> /// 獲取問候語 /// </summary> /// <returns></returns> public static List<string> GetGreetings() { List<string> list = new List<string>(); string path = Directory.GetCurrentDirectory(); string setPath = Path.Combine(path, "greetings.txt"); if (!File.Exists(setPath)) { return list; } using (StreamReader sr = new StreamReader(setPath)) { var text = ""; while (!string.IsNullOrWhiteSpace(text = sr.ReadLine())) { list.Add(text); } } return list; } /// <summary> /// 讀取問候語的內容 /// </summary> /// <returns></returns> public static string GetGreetingsText() { var content = ""; string path = Directory.GetCurrentDirectory(); string setPath = Path.Combine(path, "greetings.txt"); if (!File.Exists(setPath)) { return string.Empty; } using (StreamReader sr = new StreamReader(setPath)) { content = sr.ReadToEnd(); } return content; } /// <summary> /// 列舉視窗 /// </summary> /// <param name="hwnd"></param> /// <param name="lParam"></param> /// <returns></returns> private static bool WindCallback(IntPtr hwnd, int lParam) { StringBuilder classname = new StringBuilder(256); WinAPI.GetClassName(hwnd, classname, classname.Capacity); string leiname = classname.ToString(); IntPtr pHwnd = WinAPI.GetParent(hwnd); if (pHwnd == IntPtr.Zero && WinAPI.IsWindowVisible(hwnd) == true && leiname == "TXGuiFoundation") { StringBuilder sbWindowText = new StringBuilder(256); StringBuilder sbClassName = new StringBuilder(256); WinAPI.GetWindowText(hwnd, sbWindowText, sbWindowText.Capacity); WinAPI.GetClassName(hwnd, sbClassName, sbClassName.Capacity); if (sbWindowText.Length > 0) { var g = new Group() { Hwnd = hwnd, Name = sbWindowText.ToString(), ClassName = sbClassName.ToString(), IsSend = true }; GroupList.Add(g); } } return true; } /// <summary> /// 獲取群視窗 /// </summary> public static List<Group> GetGroupList() { if (GroupList.Count > 0) { return GroupList; } else { WinAPI.EnumWindowsCallBack callback = new WinAPI.EnumWindowsCallBack(WindCallback); WinAPI.EnumWindows(callback, 0); } return GroupList; } /// <summary> /// 重新整理群列表 /// </summary> /// <returns></returns> public static List<Group> ReloadGroupList() { GroupList.Clear(); WinAPI.EnumWindowsCallBack callback = new WinAPI.EnumWindowsCallBack(WindCallback); WinAPI.EnumWindows(callback, 0); return GroupList; } /// <summary> /// 繪圖,廣告聯絡方式 /// </summary> public static void CreateContactPic(string imagePath) { if (!File.Exists(imagePath)) return; string path = Directory.GetCurrentDirectory(); string destPath = Path.Combine(path, "send.jpg"); Image imageDest = Image.FromFile(imagePath); //圖片太小 if (imageDest.Height < 32) return; using (Graphics g = Graphics.FromImage(imageDest)) { Font font = new System.Drawing.Font("Arial", 9, FontStyle.Regular); //g.Clear(Color.White); Image imageWater = new Bitmap(220, 32); using (Graphics gWater = Graphics.FromImage(imageWater)) { gWater.Clear(Color.Gray); Brush brush1 = new SolidBrush(Color.Red); gWater.DrawString("xxxxxxxxx", font, brush1, 2, 2); gWater.DrawString("xxxxxxxxx", font, brush1, 2, 17); } //將旋轉後的圖片畫到畫布上 ImageAttributes imageAtt = GetAlphaImgAttr(70); g.DrawImage(imageWater, new Rectangle(2, imageDest.Height - 42, imageWater.Width, imageWater.Height), 0, 0, imageWater.Width, imageWater.Height, GraphicsUnit.Pixel, imageAtt); imageDest.Save(destPath); imageWater.Dispose(); imageDest.Dispose(); } } /// <summary> /// 獲取一個帶有透明度的ImageAttributes /// </summary> /// <param name="opcity"></param> /// <returns></returns> public static ImageAttributes GetAlphaImgAttr(int opcity) { if (opcity < 0 || opcity > 100) { throw new ArgumentOutOfRangeException("opcity 值為 0~100"); } //顏色矩陣 float[][] matrixItems = { new float[]{1,0,0,0,0}, new float[]{0,1,0,0,0}, new float[]{0,0,1,0,0}, new float[]{0,0,0,(float)opcity / 100,0}, new float[]{0,0,0,0,1} }; ColorMatrix colorMatrix = new ColorMatrix(matrixItems); ImageAttributes imageAtt = new ImageAttributes(); imageAtt.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap); return imageAtt; } /// <summary> /// 獲取聯絡方式的圖片路徑 /// </summary> /// <returns></returns> public static string GetContactPicPath() { string path = Directory.GetCurrentDirectory(); string imagePath = Path.Combine(path, "send.jpg"); return imagePath; } } }
新建日誌類Logger.cs
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; namespace AssistLib { /// <summary> /// 日誌類 /// </summary> public class Logger { /// <summary> /// 日誌檔案路徑 /// </summary> public string LogFile { get; set; } public Logger() { string path = Directory.GetCurrentDirectory(); string logPath = Path.Combine(path, "logs"); if (!Directory.Exists(logPath)) { Directory.CreateDirectory(logPath); } string filePath = Path.Combine(logPath, DateTime.Now.ToString("yyyyMMdd") + ".txt"); if (!File.Exists(filePath)) { File.Create(filePath).Close(); } this.LogFile = filePath; } /// <summary> /// 寫日誌 /// </summary> /// <param name="log"></param> public void Write(string log) { using (StreamWriter writer = new StreamWriter(this.LogFile,true,Encoding.UTF8)) { writer.WriteLine("{0}:{1}", DateTime.Now, log); } } } }
新建WinAPI.cs
using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; namespace AssistLib { public class WinAPI { [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; public POINT(int x, int y) { this.X = x; this.Y = y; } } [StructLayout(LayoutKind.Sequential)] public struct Rect { public int Left; public int Top; public int Right; public int Bottom; } //移動滑鼠 public const int MOUSEEVENTF_MOVE = 0x0001; //模擬滑鼠左鍵按下 public const int MOUSEEVENTF_LEFTDOWN = 0x0002; //模擬滑鼠左鍵抬起 public const int MOUSEEVENTF_LEFTUP = 0x0004; //模擬滑鼠右鍵按下 public const int MOUSEEVENTF_RIGHTDOWN = 0x0008; //模擬滑鼠右鍵抬起 public const int MOUSEEVENTF_RIGHTUP = 0x0010; //模擬滑鼠中鍵按下 public const int MOUSEEVENTF_MIDDLEDOWN = 0x0020; //模擬滑鼠中鍵抬起 public const int MOUSEEVENTF_MIDDLEUP = 0x0040; //標示是否採用絕對座標 public const int MOUSEEVENTF_ABSOLUTE = 0x8000; public const int SW_SHOW = 0x0005; public delegate bool EnumWindowsCallBack(IntPtr hwnd, int lParam); [DllImport("user32.dll", EntryPoint = "SendMessage")] public static extern IntPtr SendMessage(IntPtr hwnd, int wMsg, int wParam, int lParam); [DllImport("user32.dll", EntryPoint = "FindWindow")] public extern static IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", EntryPoint = "SetForegroundWindow")] public extern static bool SetForegroundWindow(IntPtr hwnd); [DllImport("user32.dll", EntryPoint = "FindWindowEx")] public static extern IntPtr FindWindowEx(IntPtr parent, IntPtr childe, string strclass, string strname); [DllImport("user32.dll", EntryPoint = "GetWindowText")] public static extern IntPtr GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll", EntryPoint = "GetClassName")] public static extern IntPtr GetClassName(IntPtr hWnd, StringBuilder lpString, int nMaxCont); [DllImport("user32.dll", EntryPoint = "EnumWindows")] public static extern IntPtr EnumWindows(EnumWindowsCallBack callback, int lParam); [DllImport("user32.dll", EntryPoint = "GetParent")] public static extern IntPtr GetParent(IntPtr hwnd); [DllImport("user32.dll", EntryPoint = "IsWindowVisible")] public static extern bool IsWindowVisible(IntPtr hwnd); [DllImport("user32.dll", EntryPoint = "SetFocus")] public static extern bool SetFocus(IntPtr hwnd); [DllImport("user32.dll", EntryPoint = "SetActiveWindow")] public static extern IntPtr SetActiveWindow(IntPtr hwnd); [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndlnsertAfter, int X, int Y, int cx, int cy, uint Flags); [DllImport("user32")] public static extern int mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo); [DllImport("user32")] public static extern bool SetCursorPos(int X, int Y); [DllImport("user32.dll", EntryPoint = "ShowWindow", CharSet = CharSet.Auto)] public static extern int ShowWindow(IntPtr hwnd, int nCmdShow); [DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT lpPoint); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hwnd, out Rect lpRect); } }
新建貼上板幫助類ClipboardHelper.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms; namespace AssistLib { /// <summary> /// Helper to encode and set HTML fragment to clipboard.<br/> /// See http://theartofdev.com/2014/06/12/setting-htmltext-to-clipboard-revisited/.<br/> /// <seealso cref="CreateDataObject"/>. /// </summary> /// <remarks> /// The MIT License (MIT) Copyright (c) 2014 Arthur Teplitzki. /// </remarks> public static class ClipboardHelper { #region Fields and Consts /// <summary> /// The string contains index references to other spots in the string, so we need placeholders so we can compute the offsets. <br/> /// The <![CDATA[<<<<<<<]]>_ strings are just placeholders. We'll back-patch them actual values afterwards. <br/> /// The string layout (<![CDATA[<<<]]>) also ensures that it can't appear in the body of the html because the <![CDATA[<]]> <br/> /// character must be escaped. <br/> /// </summary> private const string Header = @"Version:0.9 StartHTML:<<<<<<<<1 EndHTML:<<<<<<<<2 StartFragment:<<<<<<<<3 EndFragment:<<<<<<<<4"; //StartSelection:<<<<<<<<3 //EndSelection:<<<<<<<<4"; /// <summary> /// html comment to point the beginning of html fragment /// </summary> public const string StartFragment = "<!--StartFragment-->"; /// <summary> /// html comment to point the end of html fragment /// </summary> public const string EndFragment = @"<!--EndFragment-->"; /// <summary> /// Used to calculate characters byte count in UTF-8 /// </summary> private static readonly char[] _byteCount = new char[1]; #endregion /// <summary> /// Create <see cref="DataObject"/> with given html and plain-text ready to be used for clipboard or drag and drop.<br/> /// Handle missing <![CDATA[<html>]]> tags, specified start\end segments and Unicode characters. /// </summary> /// <remarks> /// <para> /// Windows Clipboard works with UTF-8 Unicode encoding while .NET strings use with UTF-16 so for clipboard to correctly /// decode Unicode string added to it from .NET we needs to be re-encoded it using UTF-8 encoding. /// </para> /// <para> /// Builds the CF_HTML header correctly for all possible HTMLs<br/> /// If given html contains start/end fragments then it will use them in the header: /// <code><![CDATA[<html><body><!--StartFragment-->hello <b>world</b><!--EndFragment--></body></html>]]></code> /// If given html contains html/body tags then it will inject start/end fragments to exclude html/body tags: /// <code><![CDATA[<html><body>hello <b>world</b></body></html>]]></code> /// If given html doesn't contain html/body tags then it will inject the tags and start/end fragments properly: /// <code><![CDATA[hello <b>world</b>]]></code> /// In all cases creating a proper CF_HTML header:<br/> /// <code> /// <![CDATA[ /// Version:1.0 /// StartHTML:000000177 /// EndHTML:000000329 /// StartFragment:000000277 /// EndFragment:000000295 /// StartSelection:000000277 /// EndSelection:000000277 /// <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> /// <html><body><!--StartFragment-->hello <b>world</b><!--EndFragment--></body></html> /// ]]> /// </code> /// See format specification here: http://msdn.microsoft.com/library/default.asp?url=/workshop/networking/clipboard/htmlclipboard.asp /// </para> /// </remarks> /// <param name="html">a html fragment</param> /// <param name="plainText">the plain text</param> public static DataObject CreateDataObject(string html, string plainText) { html = html ?? String.Empty; var htmlFragment = GetHtmlDataString(html); // re-encode the string so it will work correctly (fixed in CLR 4.0) if (Environment.Version.Major < 4 && html.Length != Encoding.UTF8.GetByteCount(html)) htmlFragment = Encoding.UTF8.GetString(Encoding.UTF8.GetBytes(htmlFragment)); //特別注意,這裡要做轉換儲存,否則亂碼,貼上板不支援UTF8儲存 var htmlFragmentBytes = Encoding.UTF8.GetBytes(htmlFragment); var htmlFragmentData = Encoding.Default.GetString(htmlFragmentBytes); var dataObject = new DataObject(); dataObject.SetData(DataFormats.Html, htmlFragmentData); dataObject.SetData(DataFormats.Text, plainText); dataObject.SetData(DataFormats.UnicodeText, plainText); return dataObject; } /// <summary> /// Clears clipboard and sets the given HTML and plain text fragment to the clipboard, providing additional meta-information for HTML.<br/> /// See <see cref="CreateDataObject"/> for HTML fragment details.<br/> /// </summary> /// <example> /// ClipboardHelper.CopyToClipboard("Hello <b>World</b>", "Hello World"); /// </example> /// <param name="html">a html fragment</param> /// <param name="plainText">the plain text</param> public static void CopyToClipboard(string html, string plainText) { var dataObject = CreateDataObject(html, plainText); Clipboard.SetDataObject(dataObject, true); } /// <summary> /// Generate HTML fragment data string with header that is required for the clipboard. /// </summary> /// <param name="html">the html to generate for</param> /// <returns>the resulted string</returns> private static string GetHtmlDataString(string html) { var sb = new StringBuilder(); sb.AppendLine(Header); sb.AppendLine(@"<!DOCTYPE HTML PUBLIC ""-//W3C//DTD HTML 4.0 Transitional//EN"">"); // if given html already provided the fragments we won't add them int fragmentStart, fragmentEnd; int fragmentStartIdx = html.IndexOf(StartFragment, StringComparison.OrdinalIgnoreCase); int fragmentEndIdx = html.LastIndexOf(EndFragment, StringComparison.OrdinalIgnoreCase); // if html tag is missing add it surrounding the given html (critical) int htmlOpenIdx = html.IndexOf("<html", StringComparison.OrdinalIgnoreCase); int htmlOpenEndIdx = htmlOpenIdx > -1 ? html.IndexOf('>', htmlOpenIdx) + 1 : -1; int htmlCloseIdx = html.LastIndexOf("</html", StringComparison.OrdinalIgnoreCase); if (fragmentStartIdx < 0 && fragmentEndIdx < 0) { int bodyOpenIdx = html.IndexOf("<body", StringComparison.OrdinalIgnoreCase); int bodyOpenEndIdx = bodyOpenIdx > -1 ? html.IndexOf('>', bodyOpenIdx) + 1 : -1; if (htmlOpenEndIdx < 0 && bodyOpenEndIdx < 0) { // the given html doesn't contain html or body tags so we need to add them and place start/end fragments around the given html only sb.Append("<html><body>"); sb.Append(StartFragment); fragmentStart = GetByteCount(sb); sb.Append(html); fragmentEnd = GetByteCount(sb); sb.Append(EndFragment); sb.Append("</body></html>"); } else { // insert start/end fragments in the proper place (related to html/body tags if exists) so the paste will work correctly int bodyCloseIdx = html.LastIndexOf("</body", StringComparison.OrdinalIgnoreCase); if (htmlOpenEndIdx < 0) sb.Append("<html>"); else sb.Append(html, 0, htmlOpenEndIdx); if (bodyOpenEndIdx > -1) sb.Append(html, htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0, bodyOpenEndIdx - (htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0)); sb.Append(StartFragment); fragmentStart = GetByteCount(sb); var innerHtmlStart = bodyOpenEndIdx > -1 ? bodyOpenEndIdx : (htmlOpenEndIdx > -1 ? htmlOpenEndIdx : 0); var innerHtmlEnd = bodyCloseIdx > -1 ? bodyCloseIdx : (htmlCloseIdx > -1 ? htmlCloseIdx : html.Length); sb.Append(html, innerHtmlStart, innerHtmlEnd - innerHtmlStart); fragmentEnd = GetByteCount(sb); sb.Append(EndFragment); if (innerHtmlEnd < html.Length) sb.Append(html, innerHtmlEnd, html.Length - innerHtmlEnd); if (htmlCloseIdx < 0) sb.Append("</html>"); } } else { // handle html with existing start\end fragments just need to calculate the correct bytes offset (surround with html tag if missing) if (htmlOpenEndIdx < 0) sb.Append("<html>"); int start = GetByteCount(sb); sb.Append(html); fragmentStart = start + GetByteCount(sb, start, start + fragmentStartIdx) + StartFragment.Length; fragmentEnd = start + GetByteCount(sb, start, start + fragmentEndIdx); if (htmlCloseIdx < 0) sb.Append("</html>"); } // Back-patch offsets (scan only the header part for performance) sb.Replace("<<<<<<<<1", Header.Length.ToString("D9"), 0, Header.Length); sb.Replace("<<<<<<<<2", GetByteCount(sb).ToString("D9"), 0, Header.Length); sb.Replace("<<<<<<<<3", fragmentStart.ToString("D9"), 0, Header.Length); sb.Replace("<<<<<<<<4", fragmentEnd.ToString("D9"), 0, Header.Length); return sb.ToString(); } /// <summary> /// Calculates the number of bytes produced by encoding the string in the string builder in UTF-8 and not .NET default string encoding. /// </summary> /// <param name="sb">the string builder to count its string</param> /// <param name="start">optional: the start index to calculate from (default - start of string)</param> /// <param name="end">optional: the end index to calculate to (default - end of string)</param> /// <returns>the number of bytes required to encode the string in UTF-8</returns> private static int GetByteCount(StringBuilder sb, int start = 0, int end = -1) { int count = 0; end = end > -1 ? end : sb.Length; for (int i = start; i < end; i++) { _byteCount[0] = sb[i]; count += Encoding.Default.GetByteCount(_byteCount); } return count; } } }
新建窗體專案AssistWFA
由於介面部分程式碼比較多,很多程式碼都是系統自動生成的,沒有必要貼程式碼。
我把截圖貼上去:
主介面
設定介面
群列表介面
問候語介面
本群發器的特點:
- 簡單高效
- 穩定
- 配置引數少
功能使用注意:
- 要開啟所以群視窗,不可以合併視窗
- 不支援鎖屏掛機
就這麼多,這是根據自己的需求編寫的,功能比較簡單,夠用就行,關鍵是穩定,不掉線。
有不明的地方或者需要原始碼的朋友可以聯絡我,微信:xiaoqiu20121212