using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Threading.Tasks;
using Excel = Microsoft.Office.Interop.Excel;
namespace TableMagic.Skills
{
public class ExcelMailSkill : ISkill
{
public string Name => "ExcelMail";
public string Description => "邮件技能,支持邮件群发、邮件模板和附件发送";
public List<SkillTool> GetTools()
{
return new List<SkillTool>
{
new SkillTool
{
Name = "send_email",
Description = "发送单封邮件。当用户要求发送邮件时使用此工具。",
Parameters = new Dictionary<string, object>
{
{ "type", "object" },
{ "properties", new Dictionary<string, object>
{
{ "to", new { type = "string", description = "收件人邮箱地址" } },
{ "subject", new { type = "string", description = "邮件主题" } },
{ "body", new { type = "string", description = "邮件正文" } },
{ "isHtml", new { type = "boolean", description = "是否HTML格式(默认false)" } },
{ "attachments", new { type = "string", description = "附件路径列表(JSON数组格式,可选)" } },
{ "cc", new { type = "string", description = "抄送人邮箱(可选)" } },
{ "bcc", new { type = "string", description = "密送人邮箱(可选)" } }
}
}
},
RequiredParameters = new List<string> { "to", "subject", "body" }
},
new SkillTool
{
Name = "batch_send_emails",
Description = "根据Excel数据批量发送邮件。Excel中需包含收件人、主题、正文列。当用户要求群发邮件、批量发送邮件时使用此工具。",
Parameters = new Dictionary<string, object>
{
{ "type", "object" },
{ "properties", new Dictionary<string, object>
{
{ "emailColumn", new { type = "string", description = "收件人邮箱列名" } },
{ "subjectColumn", new { type = "string", description = "邮件主题列名" } },
{ "bodyColumn", new { type = "string", description = "邮件正文列名" } },
{ "attachmentColumn", new { type = "string", description = "附件路径列名(可选)" } },
{ "subjectTemplate", new { type = "string", description = "主题模板(使用{列名}替换,可选)" } },
{ "bodyTemplate", new { type = "string", description = "正文模板(使用{列名}替换,可选)" } },
{ "sheetName", new { type = "string", description = "工作表名称(可选)" } },
{ "startRow", new { type = "integer", description = "起始行(默认2)" } },
{ "endRow", new { type = "integer", description = "结束行(可选,默认到最后一行)" } }
}
}
},
RequiredParameters = new List<string> { "emailColumn" }
},
new SkillTool
{
Name = "configure_smtp",
Description = "配置SMTP邮件服务器设置。",
Parameters = new Dictionary<string, object>
{
{ "type", "object" },
{ "properties", new Dictionary<string, object>
{
{ "host", new { type = "string", description = "SMTP服务器地址" } },
{ "port", new { type = "integer", description = "SMTP端口(默认587)" } },
{ "username", new { type = "string", description = "发件人邮箱" } },
{ "password", new { type = "string", description = "邮箱密码或授权码" } },
{ "enableSsl", new { type = "boolean", description = "是否启用SSL(默认true)" } },
{ "displayName", new { type = "string", description = "发件人显示名称(可选)" } }
}
}
},
RequiredParameters = new List<string> { "host", "username", "password" }
},
new SkillTool
{
Name = "test_smtp_connection",
Description = "测试SMTP服务器连接是否正常。",
Parameters = new Dictionary<string, object>
{
{ "type", "object" },
{ "properties", new Dictionary<string, object>() }
},
RequiredParameters = new List<string>()
},
new SkillTool
{
Name = "preview_email",
Description = "预览邮件内容(不发送)。",
Parameters = new Dictionary<string, object>
{
{ "type", "object" },
{ "properties", new Dictionary<string, object>
{
{ "emailColumn", new { type = "string", description = "收件人邮箱列名" } },
{ "subjectTemplate", new { type = "string", description = "主题模板" } },
{ "bodyTemplate", new { type = "string", description = "正文模板" } },
{ "previewRow", new { type = "integer", description = "预览行号(默认2)" } }
}
}
},
RequiredParameters = new List<string> { "emailColumn" }
}
};
}
public async Task<SkillResult> ExecuteToolAsync(string toolName, Dictionary<string, object> arguments)
{
try
{
switch (toolName)
{
case "send_email":
return await SendEmailAsync(arguments);
case "batch_send_emails":
return await BatchSendEmailsAsync(arguments);
case "configure_smtp":
return ConfigureSmtp(arguments);
case "test_smtp_connection":
return await TestSmtpConnectionAsync();
case "preview_email":
return PreviewEmail(arguments);
default:
return new SkillResult { Success = false, Error = $"Tool {toolName} not implemented in ExcelMailSkill" };
}
}
catch (Exception ex)
{
return new SkillResult { Success = false, Error = ex.Message };
}
}
private static SmtpConfig _smtpConfig;
private async Task<SkillResult> SendEmailAsync(Dictionary<string, object> arguments)
{
return await Task.Run(() =>
{
if (_smtpConfig == null)
return new SkillResult { Success = false, Error = "请先配置SMTP服务器" };
var to = arguments["to"].ToString();
var subject = arguments["subject"].ToString();
var body = arguments["body"].ToString();
var isHtml = arguments.ContainsKey("isHtml") && Convert.ToBoolean(arguments["isHtml"]);
var cc = arguments.ContainsKey("cc") ? arguments["cc"].ToString() : null;
var bcc = arguments.ContainsKey("bcc") ? arguments["bcc"].ToString() : null;
var attachments = arguments.ContainsKey("attachments")
? Newtonsoft.Json.JsonConvert.DeserializeObject<List<string>>(arguments["attachments"].ToString())
: null;
try
{
using (var message = new MailMessage())
{
message.From = new MailAddress(_smtpConfig.Username, _smtpConfig.DisplayName);
message.To.Add(to);
if (!string.IsNullOrEmpty(cc))
message.CC.Add(cc);
if (!string.IsNullOrEmpty(bcc))
message.Bcc.Add(bcc);
message.Subject = subject;
message.Body = body;
message.IsBodyHtml = isHtml;
if (attachments != null)
{
foreach (var attachment in attachments)
{
if (File.Exists(attachment))
message.Attachments.Add(new Attachment(attachment));
}
}
using (var client = new SmtpClient(_smtpConfig.Host, _smtpConfig.Port))
{
client.EnableSsl = _smtpConfig.EnableSsl;
client.Credentials = new NetworkCredential(_smtpConfig.Username, _smtpConfig.Password);
client.Send(message);
}
}
return new SkillResult { Success = true, Content = $"邮件发送成功:{to}" };
}
catch (Exception ex)
{
return new SkillResult { Success = false, Error = $"邮件发送失败:{ex.Message}" };
}
});
}
private async Task<SkillResult> BatchSendEmailsAsync(Dictionary<string, object> arguments)
{
if (_smtpConfig == null)
return new SkillResult { Success = false, Error = "请先配置SMTP服务器" };
var emailColumn = arguments["emailColumn"].ToString();
var subjectColumn = arguments.ContainsKey("subjectColumn") ? arguments["subjectColumn"].ToString() : null;
var bodyColumn = arguments.ContainsKey("bodyColumn") ? arguments["bodyColumn"].ToString() : null;
var attachmentColumn = arguments.ContainsKey("attachmentColumn") ? arguments["attachmentColumn"].ToString() : null;
var subjectTemplate = arguments.ContainsKey("subjectTemplate") ? arguments["subjectTemplate"].ToString() : null;
var bodyTemplate = arguments.ContainsKey("bodyTemplate") ? arguments["bodyTemplate"].ToString() : null;
var sheetName = arguments.ContainsKey("sheetName") ? arguments["sheetName"].ToString() : null;
var startRow = arguments.ContainsKey("startRow") ? Convert.ToInt32(arguments["startRow"]) : 2;
var endRow = arguments.ContainsKey("endRow") ? Convert.ToInt32(arguments["endRow"]) : 0;
return await Task.Run(() =>
{
var workbook = ThisAddIn.app.ActiveWorkbook;
var sheet = string.IsNullOrEmpty(sheetName) ? workbook.ActiveSheet : workbook.Worksheets[sheetName];
var usedRange = sheet.UsedRange;
int lastRow = endRow > 0 ? endRow : usedRange.Rows.Count;
int lastCol = usedRange.Columns.Count;
var columnMap = new Dictionary<string, int>();
for (int c = 1; c <= lastCol; c++)
{
var colName = sheet.Cells[1, c].Text?.ToString();
if (!string.IsNullOrEmpty(colName))
columnMap[colName] = c;
}
if (!columnMap.ContainsKey(emailColumn))
return new SkillResult { Success = false, Error = $"未找到邮箱列: {emailColumn}" };
int emailColIdx = columnMap[emailColumn];
int subjectColIdx = subjectColumn != null && columnMap.ContainsKey(subjectColumn) ? columnMap[subjectColumn] : 0;
int bodyColIdx = bodyColumn != null && columnMap.ContainsKey(bodyColumn) ? columnMap[bodyColumn] : 0;
int attachmentColIdx = attachmentColumn != null && columnMap.ContainsKey(attachmentColumn) ? columnMap[attachmentColumn] : 0;
int successCount = 0;
int failCount = 0;
var errors = new List<string>();
for (int r = startRow; r <= lastRow; r++)
{
var email = sheet.Cells[r, emailColIdx].Text?.ToString();
if (string.IsNullOrEmpty(email)) continue;
string subject = subjectTemplate ?? "";
string body = bodyTemplate ?? "";
if (!string.IsNullOrEmpty(subjectTemplate))
{
subject = ReplaceTemplate(subjectTemplate, sheet, r, columnMap);
}
else if (subjectColIdx > 0)
{
subject = sheet.Cells[r, subjectColIdx].Text?.ToString() ?? "";
}
if (!string.IsNullOrEmpty(bodyTemplate))
{
body = ReplaceTemplate(bodyTemplate, sheet, r, columnMap);
}
else if (bodyColIdx > 0)
{
body = sheet.Cells[r, bodyColIdx].Text?.ToString() ?? "";
}
var attachmentPath = attachmentColIdx > 0 ? sheet.Cells[r, attachmentColIdx].Text?.ToString() : null;
try
{
using (var message = new MailMessage())
{
message.From = new MailAddress(_smtpConfig.Username, _smtpConfig.DisplayName);
message.To.Add(email);
message.Subject = subject;
message.Body = body;
message.IsBodyHtml = true;
if (!string.IsNullOrEmpty(attachmentPath) && File.Exists(attachmentPath))
message.Attachments.Add(new Attachment(attachmentPath));
using (var client = new SmtpClient(_smtpConfig.Host, _smtpConfig.Port))
{
client.EnableSsl = _smtpConfig.EnableSsl;
client.Credentials = new NetworkCredential(_smtpConfig.Username, _smtpConfig.Password);
client.Send(message);
}
}
successCount++;
}
catch (Exception ex)
{
failCount++;
errors.Add($"行{r} ({email}): {ex.Message}");
}
}
var result = $"批量邮件发送完成\n成功: {successCount} 封\n失败: {failCount} 封";
if (errors.Count > 0)
result += $"\n失败详情:\n{string.Join("\n", errors.Take(10))}";
return new SkillResult { Success = failCount == 0, Content = result };
});
}
private SkillResult ConfigureSmtp(Dictionary<string, object> arguments)
{
_smtpConfig = new SmtpConfig
{
Host = arguments["host"].ToString(),
Port = arguments.ContainsKey("port") ? Convert.ToInt32(arguments["port"]) : 587,
Username = arguments["username"].ToString(),
Password = arguments["password"].ToString(),
EnableSsl = !arguments.ContainsKey("enableSsl") || Convert.ToBoolean(arguments["enableSsl"]),
DisplayName = arguments.ContainsKey("displayName") ? arguments["displayName"].ToString() : ""
};
return new SkillResult { Success = true, Content = $"SMTP配置成功\n服务器: {_smtpConfig.Host}:{_smtpConfig.Port}\n发件人: {_smtpConfig.Username}" };
}
private async Task<SkillResult> TestSmtpConnectionAsync()
{
if (_smtpConfig == null)
return new SkillResult { Success = false, Error = "请先配置SMTP服务器" };
return await Task.Run(() =>
{
try
{
using (var client = new SmtpClient(_smtpConfig.Host, _smtpConfig.Port))
{
client.EnableSsl = _smtpConfig.EnableSsl;
client.Credentials = new NetworkCredential(_smtpConfig.Username, _smtpConfig.Password);
client.Send(new MailMessage(_smtpConfig.Username, _smtpConfig.Username, "测试连接", "这是一封测试邮件"));
}
return new SkillResult { Success = true, Content = "SMTP连接测试成功" };
}
catch (Exception ex)
{
return new SkillResult { Success = false, Error = $"SMTP连接测试失败:{ex.Message}" };
}
});
}
private SkillResult PreviewEmail(Dictionary<string, object> arguments)
{
var emailColumn = arguments["emailColumn"].ToString();
var subjectTemplate = arguments.ContainsKey("subjectTemplate") ? arguments["subjectTemplate"].ToString() : "";
var bodyTemplate = arguments.ContainsKey("bodyTemplate") ? arguments["bodyTemplate"].ToString() : "";
var previewRow = arguments.ContainsKey("previewRow") ? Convert.ToInt32(arguments["previewRow"]) : 2;
var workbook = ThisAddIn.app.ActiveWorkbook;
var sheet = workbook.ActiveSheet;
var usedRange = sheet.UsedRange;
int lastCol = usedRange.Columns.Count;
var columnMap = new Dictionary<string, int>();
for (int c = 1; c <= lastCol; c++)
{
var colName = sheet.Cells[1, c].Text?.ToString();
if (!string.IsNullOrEmpty(colName))
columnMap[colName] = c;
}
var email = columnMap.ContainsKey(emailColumn)
? sheet.Cells[previewRow, columnMap[emailColumn]].Text?.ToString()
: "";
var subject = ReplaceTemplate(subjectTemplate, sheet, previewRow, columnMap);
var body = ReplaceTemplate(bodyTemplate, sheet, previewRow, columnMap);
return new SkillResult
{
Success = true,
Content = $"邮件预览(第{previewRow}行)\n收件人: {email}\n主题: {subject}\n正文:\n{body}"
};
}
private string ReplaceTemplate(string template, Excel.Worksheet sheet, int row, Dictionary<string, int> columnMap)
{
var result = template;
foreach (var kvp in columnMap)
{
var value = sheet.Cells[row, kvp.Value].Text?.ToString() ?? "";
result = result.Replace($"{{{kvp.Key}}}", value);
}
return result;
}
private class SmtpConfig
{
public string Host { get; set; }
public int Port { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public bool EnableSsl { get; set; }
public string DisplayName { get; set; }
}
}
}