zoukankan      html  css  js  c++  java
  • 一步步打造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>", "
    ");
                        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 startend 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 startend 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


  • 相关阅读:
    学习笔记9(必做)
    团队作业(四):描述设计
    改进ls的实现(课下作业)
    stat命令的实现-mysate(必做)
    反汇编测试
    学习笔记8(必做)
    初次使用git上传代码到码云
    团队作业(三):确定分工
    学习笔记7(必做)
    缓冲区溢出漏洞实验
  • 原文地址:https://www.cnblogs.com/jonlan/p/12866640.html
Copyright © 2011-2022 走看看