一步步打造QQ群发消息群发器
最近为了做公众号号推广,吸粉,然后加了几百个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
多写原创文章,多分享,帮助他人,快乐自己。
欢迎编程界的朋友,一起学习,一起交流。
关注公众号“程序员之事”获取更多资源。