From fd85ce0923aaed4a2be6b8b432e72965aad5084b Mon Sep 17 00:00:00 2001 From: himawari <54976075+guohuiyuan@users.noreply.github.com> Date: Sun, 19 Dec 2021 16:05:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E9=93=85=E7=AC=94?= =?UTF-8?q?=E5=B0=8F=E8=AF=B4=E6=90=9C=E7=B4=A2=E6=8F=92=E4=BB=B6=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:添加铅笔小说搜索插件 * fix:小修改help * fix:微调语句 * fix:替换编码库 * fix:不修改go.sum --- README.md | 4 +- go.mod | 2 + main.go | 1 + plugin_novel/qianbi.go | 187 +++++++++++++++++++++++++++++++++++++++++ utils/encode/encode.go | 29 +++++++ 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 plugin_novel/qianbi.go create mode 100644 utils/encode/encode.go diff --git a/README.md b/README.md index 232d845c..f614397f 100644 --- a/README.md +++ b/README.md @@ -211,10 +211,12 @@ zerobot -h -t token -u url [-d|w] [-g 监听地址:端口] qq1 qq2 qq3 ... - [x] vtb语录 - [x] 随机vtb - **书评** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_book_review"` - - [x] 书评[关键字] + - [x] 书评[xxx] - [x] 随机书评 - **coser** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_coser" ` - [x] coser +- **novel** `import _ "github.com/FloatTech/ZeroBot-Plugin/plugin_novel" ` + - [x] 小说[xxx] - **TODO...** ## 使用方法 diff --git a/go.mod b/go.mod index 3cf4dd66..74f6095e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/FloatTech/AnimeAPI v1.1.11 github.com/FloatTech/ZeroBot-Plugin-Gif v0.2.4 github.com/FloatTech/bot-manager v1.0.1-0.20211112011524-85b9895271ed + github.com/antchfx/htmlquery v1.2.3 github.com/corona10/goimagehash v1.0.3 github.com/fogleman/gg v1.3.0 github.com/fumiama/cron v1.3.0 @@ -24,4 +25,5 @@ require ( github.com/tidwall/gjson v1.12.1 github.com/wdvxdr1123/ZeroBot v1.4.1 golang.org/x/image v0.0.0-20211028202545-6944b10bf410 + golang.org/x/text v0.3.6 ) diff --git a/main.go b/main.go index b2daa870..af4151a7 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( _ "github.com/FloatTech/ZeroBot-Plugin/plugin_minecraft" // MCSManager _ "github.com/FloatTech/ZeroBot-Plugin/plugin_moyu" // 摸鱼 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_music" // 点歌 + _ "github.com/FloatTech/ZeroBot-Plugin/plugin_novel" // 铅笔小说网搜索 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_omikuji" // 浅草寺求签 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_reborn" // 投胎 _ "github.com/FloatTech/ZeroBot-Plugin/plugin_shindan" // 测定 diff --git a/plugin_novel/qianbi.go b/plugin_novel/qianbi.go new file mode 100644 index 00000000..436c4dd3 --- /dev/null +++ b/plugin_novel/qianbi.go @@ -0,0 +1,187 @@ +package plugin_novel + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/cookiejar" + "net/url" + "regexp" + "strings" + "time" + + "github.com/FloatTech/ZeroBot-Plugin/control" + "github.com/FloatTech/ZeroBot-Plugin/utils/encode" + "github.com/antchfx/htmlquery" + log "github.com/sirupsen/logrus" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/extension/rate" + "github.com/wdvxdr1123/ZeroBot/message" + "github.com/wdvxdr1123/ZeroBot/utils/helper" +) + +const ( + websiteURL = "https://www.23qb.net" + websiteTitle = "铅笔小说" + errorTitle = "出现错误!" + username = "zerobot" + password = "123456" + submit = "%26%23160%3B%B5%C7%26%23160%3B%26%23160%3B%C2%BC%26%23160%3B" + ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" + loginURL = websiteURL + "/login.php?do=submit&jumpurl=https%3A%2F%2Fwww.23qb.net%2F" + searchURL = websiteURL + "/saerch.php" + idReg = `/(\d+)/` +) + +var ( + gCurCookies []*http.Cookie + gCurCookieJar *cookiejar.Jar + engine = control.Register("novel", &control.Options{ + DisableOnDefault: false, + Help: "铅笔小说网搜索\n- 小说[xxx]", + }) + limit = rate.NewManager(time.Minute, 5) +) + +func init() { + engine.OnRegex("^小说([\u4E00-\u9FA5A-Za-z0-9]{1,25})$").SetBlock(true). + Handle(func(ctx *zero.Ctx) { + if !limit.Load(ctx.Event.GroupID).Acquire() { + ctx.SendChain(message.Text("请稍后重试0x0...")) + return + } + ctx.SendChain(message.Text("少女祈祷中......")) + login(username, password) + searchKey := ctx.State["regex_matched"].([]string)[1] + searchHtml := search(searchKey) + var m message.Message + doc, err := htmlquery.Parse(strings.NewReader(searchHtml)) + if err != nil { + log.Println("err:", err) + } + htmlTitle := htmlquery.InnerText(htmlquery.FindOne(doc, "/html/head/title")) + if htmlTitle == websiteTitle { + list, err := htmlquery.QueryAll(doc, "//dl[@id='nr']") + if err != nil { + log.Println("err:", err) + } + if len(list) != 0 { + for _, v := range list { + bookName := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[1]/h3/a[1]")) + category := htmlquery.InnerText(htmlquery.FindOne(v, "/dt/span[1]")) + author := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[2]/span[1]")) + status := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[2]/span[2]")) + wordNumbers := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[2]/span[3]")) + description := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[3]")) + updateTime := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[1]/h3/span[1]")) + latestChapter := htmlquery.InnerText(htmlquery.FindOne(v, "/dd[4]/a[1]")) + + reg := regexp.MustCompile(idReg) + id := reg.FindStringSubmatch(htmlquery.SelectAttr(htmlquery.FindOne(v, "/dt/a[1]"), "href"))[1] + + webpageURL := websiteURL + "/book/" + id + "/" + downloadURL := websiteURL + "/modules/article/txtarticle.php?id=" + id + text := fmt.Sprintf("书名:%s\n类型:%s\n作者:%s\n状态:%s\n字数:%s\n简介:%s\n更新时间:%s\n最新章节:%s\n网页链接:%s\n下载地址:%s\n", bookName, category, author, status, wordNumbers, description, updateTime, latestChapter, webpageURL, downloadURL) + m = append(m, + message.CustomNode( + zero.BotConfig.NickName[0], + ctx.Event.SelfID, + text), + ) + } + if id := ctx.SendGroupForwardMessage( + ctx.Event.GroupID, + m).Get("message_id").Int(); id == 0 { + ctx.SendChain(message.Text("ERROR: 可能被风控了")) + } + } else { + text := htmlquery.InnerText(htmlquery.FindOne(doc, "//div[@id='tipss']")) + text = strings.Replace(text, " ", "", -1) + text = strings.Replace(text, "本站", websiteURL, -1) + ctx.SendChain(message.Text(text)) + } + } else if htmlTitle == errorTitle { + ctx.SendChain(message.Text(errorTitle)) + text := htmlquery.InnerText(htmlquery.FindOne(doc, "//div[@style='text-align: center;padding:10px']")) + text = strings.Replace(text, " ", "", -1) + ctx.SendChain(message.Text(text)) + } else { + bookName := htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:book_name']"), "content") + category := htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:category']"), "content") + author := htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:author']"), "content") + status := htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:status']"), "content") + description := htmlquery.InnerText(htmlquery.FindOne(doc, "//div[@id='bookintro']/p")) + updateTime := htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:update_time']"), "content") + latestChapter := htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:latest_chapter_name']"), "content") + + reg := regexp.MustCompile(idReg) + id := reg.FindStringSubmatch(htmlquery.SelectAttr(htmlquery.FindOne(doc, "//meta[@property='og:novel:read_url']"), "content"))[1] + webpageURL := websiteURL + "/book/" + id + "/" + downloadURL := websiteURL + "/modules/article/txtarticle.php?id=" + id + text := fmt.Sprintf("书名:%s\n类型:%s\n作者:%s\n状态:%s\n简介:%s\n更新时间:%s\n最新章节:%s\n网页链接:%s\n下载地址:%s\n", bookName, category, author, status, description, updateTime, latestChapter, webpageURL, downloadURL) + ctx.SendChain(message.Text(text)) + } + + }) +} + +func login(username, password string) { + gCurCookies = nil + gCurCookieJar, _ = cookiejar.New(nil) + client := &http.Client{ + Jar: gCurCookieJar, + } + usernameData, err := encode.Utf8ToGbk(helper.StringToBytes(username)) + if err != nil { + log.Println("err:", err) + } + usernameGbk := helper.BytesToString(usernameData) + passwordData, err := encode.Utf8ToGbk(helper.StringToBytes(password)) + if err != nil { + log.Println("err:", err) + } + passwordGbk := helper.BytesToString(passwordData) + loginReq, err := http.NewRequest("POST", loginURL, strings.NewReader(fmt.Sprintf("username=%s&password=%s&usecookie=315360000&action=login&submit=%s", url.QueryEscape(usernameGbk), url.QueryEscape(passwordGbk), submit))) + if err != nil { + log.Println("err:", err) + } + loginReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + loginReq.Header.Set("User-Agent", ua) + loginResp, err := client.Do(loginReq) + if err != nil { + log.Println("err:", err) + } + defer loginResp.Body.Close() + gCurCookies = gCurCookieJar.Cookies(loginReq.URL) + +} + +func search(searchKey string) (searchHtml string) { + searchKeyData, err := encode.Utf8ToGbk(helper.StringToBytes(searchKey)) + if err != nil { + log.Println("err:", err) + } + searchKeyGbk := helper.BytesToString(searchKeyData) + client := &http.Client{ + Jar: gCurCookieJar, + } + searchReq, err := http.NewRequest("POST", searchURL, strings.NewReader(fmt.Sprintf("searchkey=%s&searchtype=all", url.QueryEscape(searchKeyGbk)))) + if err != nil { + log.Println("err:", err) + } + searchReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + searchReq.Header.Set("User-Agent", ua) + searchResp, err := client.Do(searchReq) + if err != nil { + log.Println("err:", err) + } + defer searchResp.Body.Close() + searchData, err := ioutil.ReadAll(searchResp.Body) + if err != nil { + log.Printf("get response for url=%s got error=%s\n", searchURL, err.Error()) + } + gCurCookies = gCurCookieJar.Cookies(searchReq.URL) + searchData, err = encode.GbkToUtf8(searchData) + searchHtml = helper.BytesToString(searchData) + return searchHtml +} diff --git a/utils/encode/encode.go b/utils/encode/encode.go new file mode 100644 index 00000000..9b31d1d1 --- /dev/null +++ b/utils/encode/encode.go @@ -0,0 +1,29 @@ +package encode + +import ( + "bytes" + "io/ioutil" + + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" +) + +// GBK 转 UTF-8 +func GbkToUtf8(s []byte) ([]byte, error) { + reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewDecoder()) + d, e := ioutil.ReadAll(reader) + if e != nil { + return nil, e + } + return d, nil +} + +// UTF-8 转 GBK +func Utf8ToGbk(s []byte) ([]byte, error) { + reader := transform.NewReader(bytes.NewReader(s), simplifiedchinese.GBK.NewEncoder()) + d, e := ioutil.ReadAll(reader) + if e != nil { + return nil, e + } + return d, nil +}