最近为了做公众号号推广,吸粉,然后加了几百个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, );
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 > )
{
var random = new Random();
var index = random.Next(, 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 * * );
}
} #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 = ;//
break;
case Speed.Slow:
this.Interval = ;//
break;
default:
this.Interval = ;//
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 <= )
{
set.ReplyInterval = ;
}
else if (set.ReplyInterval > )
{
set.ReplyInterval = ;
} 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();
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();
StringBuilder sbClassName = new StringBuilder(); WinAPI.GetWindowText(hwnd, sbWindowText, sbWindowText.Capacity);
WinAPI.GetClassName(hwnd, sbClassName, sbClassName.Capacity); if (sbWindowText.Length > )
{
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 > )
{
return GroupList;
}
else
{
WinAPI.EnumWindowsCallBack callback = new WinAPI.EnumWindowsCallBack(WindCallback);
WinAPI.EnumWindows(callback, );
} return GroupList;
} /// <summary>
/// 刷新群列表
/// </summary>
/// <returns></returns>
public static List<Group> ReloadGroupList()
{
GroupList.Clear(); WinAPI.EnumWindowsCallBack callback = new WinAPI.EnumWindowsCallBack(WindCallback);
WinAPI.EnumWindows(callback, ); 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 < ) return; using (Graphics g = Graphics.FromImage(imageDest))
{
Font font = new System.Drawing.Font("Arial", , FontStyle.Regular);
//g.Clear(Color.White); Image imageWater = new Bitmap(, ); using (Graphics gWater = Graphics.FromImage(imageWater))
{
gWater.Clear(Color.Gray); Brush brush1 = new SolidBrush(Color.Red);
gWater.DrawString("xxxxxxxxx", font, brush1, , );
gWater.DrawString("xxxxxxxxx", font, brush1, , );
} //将旋转后的图片画到画布上
ImageAttributes imageAtt = GetAlphaImgAttr();
g.DrawImage(imageWater, new Rectangle(, imageDest.Height - , imageWater.Width, imageWater.Height),
, , 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 < || opcity > )
{
throw new ArgumentOutOfRangeException("opcity 值为 0~100");
} //颜色矩阵
float[][] matrixItems =
{
new float[]{,,,,},
new float[]{,,,,},
new float[]{,,,,},
new float[]{,,,(float)opcity / ,},
new float[]{,,,,}
};
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[]; #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 < && 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 > - ? html.IndexOf('>', htmlOpenIdx) + : -;
int htmlCloseIdx = html.LastIndexOf("</html", StringComparison.OrdinalIgnoreCase); if (fragmentStartIdx < && fragmentEndIdx < )
{
int bodyOpenIdx = html.IndexOf("<body", StringComparison.OrdinalIgnoreCase);
int bodyOpenEndIdx = bodyOpenIdx > - ? html.IndexOf('>', bodyOpenIdx) + : -; if (htmlOpenEndIdx < && bodyOpenEndIdx < )
{
// 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 < )
sb.Append("<html>");
else
sb.Append(html, , htmlOpenEndIdx); if (bodyOpenEndIdx > -)
sb.Append(html, htmlOpenEndIdx > - ? htmlOpenEndIdx : , bodyOpenEndIdx - (htmlOpenEndIdx > - ? htmlOpenEndIdx : )); sb.Append(StartFragment);
fragmentStart = GetByteCount(sb); var innerHtmlStart = bodyOpenEndIdx > - ? bodyOpenEndIdx : (htmlOpenEndIdx > - ? htmlOpenEndIdx : );
var innerHtmlEnd = bodyCloseIdx > - ? bodyCloseIdx : (htmlCloseIdx > - ? 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 < )
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 < )
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 < )
sb.Append("</html>");
} // Back-patch offsets (scan only the header part for performance)
sb.Replace("<<<<<<<<1", Header.Length.ToString("D9"), , Header.Length);
sb.Replace("<<<<<<<<2", GetByteCount(sb).ToString("D9"), , Header.Length);
sb.Replace("<<<<<<<<3", fragmentStart.ToString("D9"), , Header.Length);
sb.Replace("<<<<<<<<4", fragmentEnd.ToString("D9"), , 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 = , int end = -)
{
int count = ;
end = end > - ? end : sb.Length;
for (int i = start; i < end; i++)
{
_byteCount[] = sb[i];
count += Encoding.Default.GetByteCount(_byteCount);
}
return count;
}
}
}

新建窗体项目AssistWFA

由于界面部分代码比较多,很多代码都是系统自动生成的,没有必要贴代码。
我把截图贴上去:

主界面

设置界面

群列表界面

问候语界面

本群发器的特点:

  • 简单高效
  • 稳定
  • 配置参数少

功能使用注意:

  • 要打开所以群窗口,不可以合并窗口
  • 不支持锁屏挂机

就这么多,这是根据自己的需求编写的,功能比较简单,够用就行,关键是稳定,不掉线。
有不明的地方或者需要源码的朋友可以联系我,微信:xiaoqiu20121212

一步步打造QQ群发消息群发器的更多相关文章

  1. C#编写一款qq消息群发器

    先上软件成品图 功能编写大概分为以下几个部分了: 获取QQ分组 发送消息 先来讲发送消息吧,实现还是比较简单 //这段主要是用来打开会话窗口的(只能列表中的好友进行会话的) System.Diagno ...

  2. [3] 微信公众号开发 - 结合UEditor实现图文消息群发功能

    0.写在前面的话 如何实现微信平台后台管理中的,图文消息发送功能? 大概的过程如下: 通过类似表单的形式,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中 需要推送的时候,将不同的文章 ...

  3. 微信公众号开发 [03] 结合UEditor实现图文消息群发功能

    0.写在前面的话 如何实现微信平台后台管理中的,图文消息发送功能? 大概的过程如下: 通过类似表单的形式,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中 需要推送的时候,将不同的文章 ...

  4. httpClient实现微信公众号消息群发

    1.实现功能 向关注了微信公众号的微信用户群发消息.(可以是所有的用户,也可以是提供了微信openid的微信用户集合) 2.基本步骤 前提: 已经有认证的公众号或者测试公众账号 发送消息步骤: 发送一 ...

  5. C#实现微信公众号群发消息(解决一天只能发一次的限制)

    经过几天研究网上的代码和谢灿大神的帮忙,今天终于用C#实现了微信公众号群发消息,现在整理一下. 总体思路:1.首先必须要在微信公众平台上申请一个公众号. 2.然后进行模拟登陆.(由于我对http传输原 ...

  6. node实现微信扫码群发消息《附上github代码》

    本篇文章就是为大家介绍一下我是如何用node去实现扫码群发功能,源代码地址在最后面 获取登录二维码 -> 扫码登录服务端           首先介绍一下主要流程,并附上关键代码 1.获取UUI ...

  7. Python自制微信机器人:群发消息、自动接收好友

    运营公众号也有半年了,今年5月份开始的,之前一直用一款windows工具来运营自动接受好友请求.群发文章.自动回复等操作,但颇有不便. 举几个场景: 突然在外面看到一篇文章很好,临时写了一篇,想群发一 ...

  8. php 实现微信模拟登陆、获取用户列表及群发消息功能示例

    本文实例讲述了php实现微信模拟登陆.获取用户列表及群发消息功能.分享给大家供大家参考,具体如下: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ...

  9. 【微信公众号开发】根据openId群发消息

    根据开发文档可知,只要使用POST方式提交固定格式的json字符串到那个地址即可.这里我写的是最简单的文本 第一步:建立对应的实体类. package cn.sp.bean; import java. ...

随机推荐

  1. 全网最全C#实习面试题目

    整个内容是我在春招面试时候整理的一些题目,里面涵盖有网上搬运的(由于当时没有记录来源,如果有转载没标注来源,请与我联系),还有我面试到的.整个排版很乱,后期我会一步一步整理.整个内容大概快有两万字.整 ...

  2. L - Neko does Maths CodeForces - 1152C 数论(gcd)

    题目大意:输入两个数 a,b,输出一个k使得lcm(a+k,b+k)尽可能的小,如果有多个K,输出最小的. 题解: 假设gcd(a+k,b+k)=z; 那么(a+k)%z=(b+k)%z=0. a%z ...

  3. 数据结构与算法--堆(heap)与栈(stack)的区别

    堆和栈的区别 在C.C++编程中,经常需要操作的内存可分为以下几个类别: 栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈. 堆区(heap ...

  4. 记录在腾讯云上搭建Ubuntu服务器

    为了能让更多的比赛题复现,只好自己去手动搭建服务器 各种奇葩的操作以及很多的由于升级之后出现的问题变成了一个个坑. 写下这篇博客以此来记录我踩过的坑. 第一步 购买一个服务器,当然我购买的是学生版本的 ...

  5. testNG 预期异常、忽略测试、超时测试

    通过@Test 注解的参数值实现如下的几种测试 一.通过 @Test(expectedExceptions=异常类名) 参数实现到达 预期指定的异常效果 @Test(expectedException ...

  6. 统计字符串中每种字符出现的评率(HashMap中getOrDefault(K, V)方法的使用)

    为了统计字符串中每种字符出现的频率,使用HashMap这种数据结构.其中,字符作为Key,出现的频率作为Value. 基本算法为: 1. 将字符串分成字符数组 2. (1)如果HashMap中的Key ...

  7. python os模块获取指定目录下的文件列表

    bath_path = r"I:\ner_results\ner_results" dir_list1 = os.listdir(bath_path) for dir1 in di ...

  8. Python数据分析入门与实践 学习

    pandas是一个Python语言的软件包,在我们使用Python语言进行机器学习编程的时候,这是一个非常常用的基础编程库.本文是对它的一个入门教程.pandas提供了快速,灵活和富有表现力的数据结构 ...

  9. 前端基础-HTML(2)

    1. 什么是标签以及标签的分类: 在HTML页面中,带有“< >”符号的元素被称为HTML标签,如上节提到的 <HTML>.<head>.<body>都 ...

  10. unset变量释放内存不起作用

    unset()函数只能在变量值占用内存空间超过256字节时才会释放内存空间. 只有当指向该变量的所有变量(如引用变量)都被销毁后,才会释放内存.