Golang入坑笔记 – 整理了和她的9万条微信聊天记录,每天通过邮件分享给自己

算是第一次接触Go语言,从Runoob看了一中午的基础教程,了解了一些常用语法。最近看到掘金有一位前端大佬发布了两篇 Nodejs定时给女朋友发送暖心消息的教程,决定自己也撸一个差不多的脚本。当然这篇文章很水,只能说是学会了golang的基本使用,通过面相过程的方式做了一个简单的实现。 通过实现这个小玩意儿,算是开始入go坑了吧,请多关照。

写在前面

在本项目中,我使用golang来整理微信聊天记录,然后拼接成HTML,发到自己的邮箱。
这里提供两篇其他的教程,有女朋友的可以学习一下:

1.用Node+wechaty写一个爬虫脚本每天定时给女朋友发微信暖心话
2.用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件

当然,如果你没有女朋友的话,可以自己发到自己的邮箱,本篇采用这种做法。

少废话,先看东西:

技术实现

  1. Golang操作sqlite,获取聊天记录
  2. 使用 net/smtp 发送邮件
  3. 关于获取微信聊天记录内容 (目前在研究一个开源py代码,分析一下微信是如何操作sqlite的)

代码部分

Golang操作sqlite,获取聊天记录

微信的聊天记录是存储在sqlite中的,部分设备拿到没有经过任何加密的.sqlite 文件

目前在数据库中发现了这些表,还有一些以ChatExt2_为前缀的表,后面与Chat_前缀的表一一对应。 每一个Chat_表代表与一个好友的聊天记录,目前还没有找到高效的方法确定表名与好友的关系,(Friend表居然是空的,不知道是不是我打开的方式不对,这里有待研究), 点开Chat_表可以看到和好友的聊天记录,当然这些数据是和手机上看到的数据是一样的,比如你在手机上左滑删除了好友,那么聊天记录在这里面也是找不到的。

这里我用了一个最笨的方法去找到了那个我需要的表,那就是一一遍历表,然后统计出表中的数据条数,最后冒泡排序一下,找到这个记录最多的那个表。(我很确定记录最多的那个表就是我想要的那个表)

这里可以看到 Chat_,ChatExt2_排名最高,但是实际文本内容在Chat_表中。 (93020条数据,其中包括682条’对方已撤回’和’被对方拒收’这样的系统提示)

具体表结构是这样的

  • CreateTime: 发消息时间
  • Desc: 目前只看到两个值 0表示自己发的,1表示对方
  • ImgStatus: 猜测是记录视频,音频,图片和表情包的
  • MsgLocalID: 消息本地ID
  • Message: 消息主体 (基本都是文本和emoji,图片和表情以xml的形式出现,图片音视频以出现,当然里面都是id,没有实际的媒体URL)
  • MsgSvrId: 消息在服务器的ID
  • Status: 目前看到的是2表示自己发的,4表示对方发的/系统提示(已撤回,已拒收),5表示被拒收的消息(没有发过去,只存到了本地数据库)
  • TableVer: 都是1,没发现什么问题
  • Type: 1是文本,47是表情,49是分享卡片,10000是系统消息,别的就不分析了,可以通过Type和Status判断是对方发的还是系统发的

这里我主要是通过传进来两个时间去查询该时间段的消息。

// 记得import "database/sql", _ "github.com/mattn/go-sqlite3" 来操作数据库

// 聊天记录struct
type Message struct {
    create_time int64
    content     string
    status      int
    msg_type    int
}

/**
 * 数据库操作
 */
func getMessages(start, end int64) []Message {
    db, err := sql.Open("sqlite3", DatabasePath)
    checkErr(err)

    sql := "SELECT `CreateTime`, `Message`, `Status`, `Type` FROM '" + DatabaseName + "' WHERE  `CreateTime` BETWEEN " + strconv.FormatInt(start,10) + " AND " + strconv.FormatInt(end,10) + " ORDER BY `CreateTime` ASC"
    rows, err := db.Query(sql)
    checkErr(err)

    data := make([]Message, 0)
    for rows.Next() {
        var message Message
        err = rows.Scan(&message.create_time, &message.content, &message.status, &message.msg_type)
        checkErr(err)

        data = append(data, message)
    }

    db.Close()
    return data
}

时间的生成上我觉得Go有点麻烦,也可能是我姿势不对

/**
 * 时间获取封装
 */
func getTime() (int64, int64) {
    start_time := time.Now().Format(TimeFormat)
    t, _ := time.Parse(TimeFormat, start_time)
    
    start_time_unix := (BeignTime - t.Unix() + SoLikeU + 8 * 3600)  // 记得减去8个小时的时间戳
    end_time_unix := start_time_unix + (26 * 3600)                  // 到第二天凌晨两点吧,因为你也会失眠睡不着
    
    return start_time_unix, end_time_unix
}

这里是想通过获取某一天的0点时刻到第二天的2点 (也就是一天的时间+半夜睡不着的那段时间)

中间BeignTime和SoLikeU都是我自己定义的常量,后续在完整代码提供

这里使用了一个方法去获取当天的0点时刻

timeStr := time.Now().Format("2006-01-02")
fmt.Println("timeStr:", timeStr)
t, _ := time.Parse("2006-01-02", timeStr)
timeNumber := t.Unix()
fmt.Println("timeNumber:", timeNumber)

但是这个方法会返回当天8点而不是0点,所以需要自己手动减去8个小时。

另外go的时间格式化上,并不像其他语言那样用”yyyy-mm-dd hh:mm:ss”, 而是用了一个固定的时间: 2006/1/2 15:04:05, 当初在学习时间格式转化的时候,搜了很多文章发现都是写了这么一个时间,总觉得那些博主不够专业,怎么随随便便的写个时间而不是用ymd,后来索性改成了自己的某个纪念日,结果是有问题的。

所以我觉得时间格式转换对新手很不友好,当然写过之后就记得了 (2006/1/2 15:04:05 分别是1,2,下午3点,4,5)

拼接HTML

当然这里没有什么好讲的,我采用的是最原始的HTML拼接方式,这种方式感觉在前端开发的时候经常用到。

/**
 * HTML生成
 */
func createHTML(data []Message, unix int64) string {
    html_header := "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\"content=\"width=device-width, initial-scale=1.0\"><meta http-equiv=\"X-UA-Compatible\"content=\"ie=edge\"><title>mango</title></head><style>html,body{height:100%;margin:0;padding:0}.header{width:100%;height:100px}.red-span{font-size:24px;color:rgb(221,73,73)}</style><body>"
    html_tip := "<div class=\"header\">" + 
                    "<div style=\"width:100%; margin: 40px auto;font-size:20px; color:#5f5e5e;text-align:center\">" +
                        "<span class=\"red-span\"> " + time.Unix(unix, 0).Format(TimeFormat) + " </span> <br>" + 
                        "<span>你们一共聊了</span>" + 
                        "<span class=\"red-span\">" + strconv.Itoa(len(data)) + "</span>" + 
                        "<span>句</span>" + 
                    "</div>" +
                "</div>"
    html_content := "<div class=\"lite-chatbox\">" 

    for index, msg := range data {
        if index % 10 == 0 {
            html_content += "<div class=\"tips\">" + 
                                "<span>" + time.Unix(msg.create_time, 0).Format("15:04:05") + "</span>" + 
                            "</div>"
        }

        if msg.status == 4 {
            // 表示对方发来的消息
            html_content += "<div class=\"cleft cmsg\">" +
                                "<img class=\"headIcon radius\" ondragstart=\"return false;\"  oncontextmenu=\"return false;\" src=\"" + MAvatar + "\" />" + 
                                    "<span class=\"name\">" + MName + "</span>"
        } else {
            // 自己发送的消息
            html_content += "<div class=\"cright cmsg\">" +
                                "<img class=\"headIcon radius\" ondragstart=\"return false;\"  oncontextmenu=\"return false;\" src=\"" + XzkAvatar + "\" />" + 
                                    "<span class=\"name\">" + XzkName + "</span>"
        }

        html_content += "<span class=\"content\">" + msg.content + "</span>" + "</div>"
    }

    html_bottom := "<div class=\"header\">" + 
                        "<div style=\"width:100%; margin: 40px auto;font-size:20px; color:#5f5e5e;text-align:center\">" +
                            "<span>加油鸭</span><br/>" + 
                            "<span>记得背单词,刷算法,减肥</span><br/>" + 
                        "</div>" +
                    "</div>" +
                "</div>" + 
                "</body></html>"
    html_css := "<style>.lite-chatbox{padding:0;width:100%;position:relative;overflow-y:auto;overflow-x:hidden;font:18px Helvetica,\"PingFang SC\",\"Microsoft YaHei\",sans-serif}.lite-chatbox .cmsg{position:relative;margin:4px 7px;min-height:50px;border:0}.lite-chatbox .cright{text-align:right;margin-left:64px}.lite-chatbox .cleft{text-align:left;margin-right:64px}.lite-chatbox img.headIcon{width:34px;height:34px;top:9px;position:absolute;border:1px solid #c5d4c4}.lite-chatbox .name{color:#8b8b8b;font-size:12px;display:block;line-height:18px}.lite-chatbox .name .htitle{display:inline-block;padding:0 3px;background-color:#ccc;color:#fff;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin-right:4px;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:50px}.lite-chatbox .content{word-break:break-all;word-wrap:break-word;text-align:left;position:relative;display:inline-block;font-size:15px;padding:10px 15px;line-height:20px;min-width:9px;min-height:18px}.lite-chatbox .content img{width:100%;height:auto}.lite-chatbox .content a{color:#0072C1;margin:0 5px;cursor:hand}.lite-chatbox .tips{margin:12px;text-align:center;font-size:12px}.lite-chatbox .tips span{display:inline-block;padding:4px;background-color:#ccc;color:#fff;-moz-border-radius:6px;-webkit-border-radius:6px;border-radius:6px}.lite-chatbox img.radius{-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}.lite-chatbox .cright img.headIcon{right:0}.lite-chatbox .cleft img.headIcon{left:0}.lite-chatbox .cright .name{margin:0 48px 2px 0}.lite-chatbox .cleft .name{margin:0 0 2px 48px}.lite-chatbox .cright .content{margin:0 48px 0 0;-webkit-border-radius:20px 0 20px 20px;border-radius:20px 0 20px 20px;color:#fff;background:-webkit-linear-gradient(70deg,#3FD1E1 0%,#44D7CD 100%);background:linear-gradient(20deg,#3f8fe1cc 0%,#44d7c9 100%);-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,0.15);box-shadow:5px 5px 15px 0 rgba(102,102,102,0.15)}.lite-chatbox .cleft .content{margin:0 0 0 48px;-webkit-border-radius:0 20px 20px 20px;border-radius:0 20px 20px 20px;color:#666;border:1px solid rgba(0,0,0,0.05);background:#FFF;-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,0.1);box-shadow:5px 5px 15px 0 rgba(102,102,102,0.1)}.lite-chatbox .cright .content:after{right:-12px;top:8px}.lite-chatbox .cleft .content:after{left:-12px;top:8px}.lite-chatbox .tips .tips-primary{background-color:#3986c8}.lite-chatbox .tips .tips-success{background-color:#49b649}.lite-chatbox .tips .tips-info{background-color:#5bb6d1}.lite-chatbox .tips .tips-warning{background-color:#eea948}.lite-chatbox .tips .tips-danger{background-color:#e24d48}.lite-chatbox .name .admin{background-color:#72D6A0}.lite-chatbox .name .owner{background-color:#F2BF25}</style>"
            
    html := html_header + html_css +  html_tip + html_content + html_bottom + html_css
    
    return html
}

这里用到了一个前端第三方框架,LiteWebChat_Frame,可以快速搭建聊天界面。

在封装HTML的时候,一定要注意所有的css必须本地化,不可以采用引入的方式去加载css,但是图片是可以的。这跟邮件的某些安全策略有关吧,所以我在中间拼了一个超级长的style。

发送邮件

发送邮件用到了 “net/smtp” 包。

这里基本配置应该可以看得懂,主要是登录账号,发送邮件

最重要的是设置content_type, 只有设置为Content-Type: text/html; charset=UTF-8 才可以发送这种html模板的邮件。

另外有一个大坑。不知道在什么时候开始,很多邮箱登录都需要使用授权码登录而不是密码。这个在之前某一篇PHP笔记中也提到过,大家可以去邮箱的设置中心查看授权码。这里我用163邮箱测试。

最后选择163而不是qq的原因是,QQ可能对资源加载做了更多的限制,我在HTML中插入的超长css代码被过滤掉了,最后显示的是一个乱版的邮件,而163没有任何问题。


// import "net/smtp"

/**
 * 发送邮件
 */
func sendMail(html string) {
    auth := smtp.PlainAuth("", "需要登录的邮箱", "授权码", "smtp.163.com")
    to := []string{"发送的邮箱"}
    nickname := "昵称"
    user := "显示的邮箱"
    subject := "邮件标题"
    content_type := "Content-Type: text/html; charset=UTF-8"
    body := html
    msg := []byte("To: " + strings.Join(to, ",") + "\r\nFrom: " + nickname +
        "<" + user + ">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
    err := smtp.SendMail("smtp.163.com:25", auth, user, to, msg)
    checkErr(err)
    
    fmt.Println("邮件发送成功")
}

总结

对Golang总结:

第一次写Golang,发现有些地方确实简单,例如 := 定义变量,但是和Python无需事先声明变量比,新手可能很容易忘记 : 而只写 =, 其次元组很方便,可以接受多个返回值,第一次接触不借助中间值对ab进行交换也是在go中学到的(a, b = b, a)

go也提供了很多好用的包,这个需要慢慢学习。

时间格式化上可能对新手有些懵逼,后续自己可以手动封装一个时间管理util。

当然语言只是工具,了解了语法之后,简单的上手还是很容易的,接触过Python,Swift后,觉得go的语法也很容易接受

对项目总结

该项目只是通过面相过程的方式去实现了一个小小的功能,没有任何的优化,所以后续我需要去深入了解GO的使用。

同时给自己埋下两个坑吧:
1. 学会Go的高级用法
2. 既然已经有了聊天记录资源,是否可以通过机器学习等技术训练一个对话机器人。

完整代码 (代码low,请见谅)

package main

import (
    "fmt"
    "time"
    "strings"
    "strconv"
    "net/smtp"
    "io/ioutil"
    "database/sql"
    _ "github.com/mattn/go-sqlite3"
)

// 聊天记录struct
type Message struct {
    create_time int64
    content     string
    status      int
    msg_type    int
}

const (
    DatabasePath    = "/Users/xzk/Downloads/Applications/com.tencent.xin/Documents/4fa8684bdeae2190a5963a404b0cf007/DB/MM.sqlite"
    DatabaseName    = "Chat_1a6c5c5787382c86b99ba9f72c0732dc"
    TimeFormat      = "2006/1/2"
    BeignTime       = 1551369600    // 19.3.1 00:00:00
    SoLikeU         = 1525795200    // 2018/5/9 18:18:21
    
    MName       = "💖对方昵称,用在HTML"
    MAvatar     = "对方的头像,用在HTML"
    XzkName         = "我的昵称,用在HTML"
    XzkAvatar       = "我的头像,用在HTML"

    // html输出路径,做了存储
    FilePath        = "/Users/xzk/Desktop/xzk/m/"
    
)

func main() {

    now := time.Now().Format("2006/1/2 15:04:05")
    start_time, end_time := getTime()

    println("现在是: ", now);

    println("今天整理的是: [ ", time.Unix(start_time, 0).Format("2006/1/2 15:04:05"), " - " , time.Unix(end_time, 0).Format("2006/1/2 15:04:05"), " ] 的故事")

    data := getMessages(start_time, end_time)
    // for _, msg:= range data {
    //  fmt.Println(msg)
    // }

    html := createHTML(data, start_time)
    saveAndSend(html, start_time)

    fmt.Println("Bye~")
}

/**
 * 时间获取封装
 */
func getTime() (int64, int64) {
    start_time := time.Now().Format(TimeFormat)
    t, _ := time.Parse(TimeFormat, start_time)
    
    start_time_unix := (BeignTime - t.Unix() + SoLikeU + 8 * 3600)  // 记得减去8个小时的时间戳
    end_time_unix := start_time_unix + (26 * 3600)                  // 到第二天凌晨两点吧,因为你也会失眠睡不着
    
    return start_time_unix, end_time_unix
}

/**
 * 数据库操作
 */
func getMessages(start, end int64) []Message {
    db, err := sql.Open("sqlite3", DatabasePath)
    checkErr(err)

    sql := "SELECT `CreateTime`, `Message`, `Status`, `Type` FROM '" + DatabaseName + "' WHERE  `CreateTime` BETWEEN " + strconv.FormatInt(start,10) + " AND " + strconv.FormatInt(end,10) + " ORDER BY `CreateTime` ASC"
    rows, err := db.Query(sql)
    checkErr(err)

    data := make([]Message, 0)
    for rows.Next() {
        var message Message
        err = rows.Scan(&message.create_time, &message.content, &message.status, &message.msg_type)
        checkErr(err)

        data = append(data, message)
    }

    db.Close()
    return data
}

/**
 * HTML生成
 */
func createHTML(data []Message, unix int64) string {
    html_header := "<!DOCTYPE html><html lang=\"en\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\"content=\"width=device-width, initial-scale=1.0\"><meta http-equiv=\"X-UA-Compatible\"content=\"ie=edge\"><title>mango</title></head><style>html,body{height:100%;margin:0;padding:0}.header{width:100%;height:100px}.red-span{font-size:24px;color:rgb(221,73,73)}</style><body>"
    html_tip := "<div class=\"header\">" + 
                    "<div style=\"width:100%; margin: 40px auto;font-size:20px; color:#5f5e5e;text-align:center\">" +
                        "<span class=\"red-span\"> " + time.Unix(unix, 0).Format(TimeFormat) + " </span> <br>" + 
                        "<span>你们一共聊了</span>" + 
                        "<span class=\"red-span\">" + strconv.Itoa(len(data)) + "</span>" + 
                        "<span>句</span>" + 
                    "</div>" +
                "</div>"
    html_content := "<div class=\"lite-chatbox\">" 

    for index, msg := range data {
        if index % 10 == 0 {
            html_content += "<div class=\"tips\">" + 
                                "<span>" + time.Unix(msg.create_time, 0).Format("15:04:05") + "</span>" + 
                            "</div>"
        }

        if msg.status == 4 {
            // 表示对方发来的消息
            html_content += "<div class=\"cleft cmsg\">" +
                                "<img class=\"headIcon radius\" ondragstart=\"return false;\"  oncontextmenu=\"return false;\" src=\"" + MAvatar + "\" />" + 
                                    "<span class=\"name\">" + MName + "</span>"
        } else {
            // 自己发送的消息
            html_content += "<div class=\"cright cmsg\">" +
                                "<img class=\"headIcon radius\" ondragstart=\"return false;\"  oncontextmenu=\"return false;\" src=\"" + XzkAvatar + "\" />" + 
                                    "<span class=\"name\">" + XzkName + "</span>"
        }

        html_content += "<span class=\"content\">" + msg.content + "</span>" + "</div>"
    }

    html_bottom := "<div class=\"header\">" + 
                        "<div style=\"width:100%; margin: 40px auto;font-size:20px; color:#5f5e5e;text-align:center\">" +
                            "<span>加油鸭</span><br/>" + 
                            "<span>记得背单词,刷算法,减肥</span><br/>" + 
                        "</div>" +
                    "</div>" +
                "</div>" + 
                "</body></html>"
    html_css := "<style>.lite-chatbox{padding:0;width:100%;position:relative;overflow-y:auto;overflow-x:hidden;font:18px Helvetica,\"PingFang SC\",\"Microsoft YaHei\",sans-serif}.lite-chatbox .cmsg{position:relative;margin:4px 7px;min-height:50px;border:0}.lite-chatbox .cright{text-align:right;margin-left:64px}.lite-chatbox .cleft{text-align:left;margin-right:64px}.lite-chatbox img.headIcon{width:34px;height:34px;top:9px;position:absolute;border:1px solid #c5d4c4}.lite-chatbox .name{color:#8b8b8b;font-size:12px;display:block;line-height:18px}.lite-chatbox .name .htitle{display:inline-block;padding:0 3px;background-color:#ccc;color:#fff;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;margin-right:4px;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;max-width:50px}.lite-chatbox .content{word-break:break-all;word-wrap:break-word;text-align:left;position:relative;display:inline-block;font-size:15px;padding:10px 15px;line-height:20px;min-width:9px;min-height:18px}.lite-chatbox .content img{width:100%;height:auto}.lite-chatbox .content a{color:#0072C1;margin:0 5px;cursor:hand}.lite-chatbox .tips{margin:12px;text-align:center;font-size:12px}.lite-chatbox .tips span{display:inline-block;padding:4px;background-color:#ccc;color:#fff;-moz-border-radius:6px;-webkit-border-radius:6px;border-radius:6px}.lite-chatbox img.radius{-moz-border-radius:100%;-webkit-border-radius:100%;border-radius:100%}.lite-chatbox .cright img.headIcon{right:0}.lite-chatbox .cleft img.headIcon{left:0}.lite-chatbox .cright .name{margin:0 48px 2px 0}.lite-chatbox .cleft .name{margin:0 0 2px 48px}.lite-chatbox .cright .content{margin:0 48px 0 0;-webkit-border-radius:20px 0 20px 20px;border-radius:20px 0 20px 20px;color:#fff;background:-webkit-linear-gradient(70deg,#3FD1E1 0%,#44D7CD 100%);background:linear-gradient(20deg,#3f8fe1cc 0%,#44d7c9 100%);-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,0.15);box-shadow:5px 5px 15px 0 rgba(102,102,102,0.15)}.lite-chatbox .cleft .content{margin:0 0 0 48px;-webkit-border-radius:0 20px 20px 20px;border-radius:0 20px 20px 20px;color:#666;border:1px solid rgba(0,0,0,0.05);background:#FFF;-webkit-box-shadow:5px 5px 15px 0 rgba(102,102,102,0.1);box-shadow:5px 5px 15px 0 rgba(102,102,102,0.1)}.lite-chatbox .cright .content:after{right:-12px;top:8px}.lite-chatbox .cleft .content:after{left:-12px;top:8px}.lite-chatbox .tips .tips-primary{background-color:#3986c8}.lite-chatbox .tips .tips-success{background-color:#49b649}.lite-chatbox .tips .tips-info{background-color:#5bb6d1}.lite-chatbox .tips .tips-warning{background-color:#eea948}.lite-chatbox .tips .tips-danger{background-color:#e24d48}.lite-chatbox .name .admin{background-color:#72D6A0}.lite-chatbox .name .owner{background-color:#F2BF25}</style>"
            
    html := html_header + html_css +  html_tip + html_content + html_bottom + html_css
    
    return html
}

/**
 * 存储生成的网站
 */
func saveAndSend(html string, unix int64) {
    file_path := FilePath + time.Unix(unix, 0).Format("2006-01-02") + ".html"
    println(file_path)
    _, err := ioutil.ReadFile(file_path)
    if err != nil {
        // 文件不存在, 写入文件存档
        bytes := []byte(html)
        err := ioutil.WriteFile(file_path, bytes, 0644)
        checkErr(err)

        fmt.Println("文件已经保存,开始发送邮件")

        // 发送邮件
        sendMail(html)
    } else {
        // 文件已经存在,可能已经发送过email
        fmt.Println(file_path + " 文件已经存在,可能已发送过邮件")
    }
}

/**
 * 发送邮件
 */
func sendMail(html string) {
    auth := smtp.PlainAuth("", "xxx@163.com", "xxx", "smtp.163.com")
    to := []string{"xxx@163.com"}
    nickname := ""
    user := "xxx@163.com"
    subject := "xxxx"
    content_type := "Content-Type: text/html; charset=UTF-8"
    body := html
    msg := []byte("To: " + strings.Join(to, ",") + "\r\nFrom: " + nickname +
        "<" + user + ">\r\nSubject: " + subject + "\r\n" + content_type + "\r\n\r\n" + body)
    err := smtp.SendMail("smtp.163.com:25", auth, user, to, msg)
    checkErr(err)
    
    fmt.Println("邮件发送成功")
}

/**
 * 错误检测
 */
func checkErr(err error) {
    if err != nil {
        panic(err)
    }
}

写在尾部: 我想学习如何通过这些聊天记录来训练一个聊天机器人,但是作为开发狗对机器学习或者其他技术并不了解,希望懂的big brother给指条明路。

SoLikeU