mirror of
https://github.com/FloatTech/ZeroBot-Plugin.git
synced 2025-12-20 06:20:08 +08:00
添加小说下载 (#553)
* ✨ 添加小说下载 * 🎨 修改readme * 🎨 修lint * 🎨 修改包 * 🎨 使用新版http封装 * 🎨 new line
This commit is contained in:
parent
f1a33a1845
commit
07e6f33b04
@ -1032,6 +1032,12 @@ print("run[CQ:image,file="+j["img"]+"]")
|
|||||||
|
|
||||||
- [x] 小说[xxx]
|
- [x] 小说[xxx]
|
||||||
|
|
||||||
|
- 设置小说配置 zerobot 123456
|
||||||
|
|
||||||
|
- 下载小说30298
|
||||||
|
|
||||||
|
- 注: 建议去https://www.23qb.com/ 注册一个账号, 小说下载有积分限制
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>nsfw图片识别</summary>
|
<summary>nsfw图片识别</summary>
|
||||||
|
|||||||
@ -3,19 +3,21 @@ package novel
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/antchfx/htmlquery"
|
"github.com/antchfx/htmlquery"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
zero "github.com/wdvxdr1123/ZeroBot"
|
zero "github.com/wdvxdr1123/ZeroBot"
|
||||||
"github.com/wdvxdr1123/ZeroBot/message"
|
"github.com/wdvxdr1123/ZeroBot/message"
|
||||||
"github.com/wdvxdr1123/ZeroBot/utils/helper"
|
|
||||||
|
|
||||||
ub "github.com/FloatTech/floatbox/binary"
|
ub "github.com/FloatTech/floatbox/binary"
|
||||||
|
"github.com/FloatTech/floatbox/file"
|
||||||
|
"github.com/FloatTech/floatbox/web"
|
||||||
ctrl "github.com/FloatTech/zbpctrl"
|
ctrl "github.com/FloatTech/zbpctrl"
|
||||||
"github.com/FloatTech/zbputils/control"
|
"github.com/FloatTech/zbputils/control"
|
||||||
"github.com/FloatTech/zbputils/ctxext"
|
"github.com/FloatTech/zbputils/ctxext"
|
||||||
@ -32,26 +34,47 @@ const (
|
|||||||
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36"
|
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.com%2F"
|
loginURL = websiteURL + "/login.php?do=submit&jumpurl=https%3A%2F%2Fwww.23qb.com%2F"
|
||||||
searchURL = websiteURL + "/saerch.php"
|
searchURL = websiteURL + "/saerch.php"
|
||||||
|
downloadURL = websiteURL + "/modules/article/txtarticle.php?id=%v"
|
||||||
|
detailURL = websiteURL + "/book/%v/"
|
||||||
idReg = `/(\d+)/`
|
idReg = `/(\d+)/`
|
||||||
)
|
)
|
||||||
|
|
||||||
var gCurCookieJar *cookiejar.Jar
|
var (
|
||||||
|
cachePath string
|
||||||
|
// apikey 由账号和密码拼接而成, 例: zerobot,123456
|
||||||
|
apikey string
|
||||||
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
control.Register("novel", &ctrl.Options[*zero.Ctx]{
|
engine := control.Register("novel", &ctrl.Options[*zero.Ctx]{
|
||||||
DisableOnDefault: false,
|
DisableOnDefault: false,
|
||||||
Brief: "铅笔小说网搜索",
|
Brief: "铅笔小说网搜索",
|
||||||
Help: "- 小说[xxx]",
|
Help: "- 小说[xxx]\n" +
|
||||||
}).OnRegex("^小说([\u4E00-\u9FA5A-Za-z0-9]{1,25})$").SetBlock(true).Limit(ctxext.LimitByUser).
|
"- 设置小说配置 zerobot 123456\n" +
|
||||||
|
"- 下载小说30298\n" +
|
||||||
|
"建议去https://www.23qb.com/ 注册一个账号, 小说下载有积分限制",
|
||||||
|
PrivateDataFolder: "novel",
|
||||||
|
})
|
||||||
|
cachePath = engine.DataFolder() + "cache/"
|
||||||
|
_ = os.MkdirAll(cachePath, 0755)
|
||||||
|
engine.OnRegex("^小说([\u4E00-\u9FA5A-Za-z0-9]{1,25})$").SetBlock(true).Limit(ctxext.LimitByUser).
|
||||||
Handle(func(ctx *zero.Ctx) {
|
Handle(func(ctx *zero.Ctx) {
|
||||||
ctx.SendChain(message.Text("少女祈祷中......"))
|
ctx.SendChain(message.Text("少女祈祷中......"))
|
||||||
err := login(username, password)
|
key := getAPIKey(ctx)
|
||||||
|
u, p, _ := strings.Cut(key, ",")
|
||||||
|
if u == "" {
|
||||||
|
u = username
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = password
|
||||||
|
}
|
||||||
|
cookie, err := login(u, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.SendChain(message.Text("ERROR: ", err))
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
searchKey := ctx.State["regex_matched"].([]string)[1]
|
searchKey := ctx.State["regex_matched"].([]string)[1]
|
||||||
searchHTML, err := search(searchKey)
|
searchHTML, err := search(searchKey, cookie)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.SendChain(message.Text("ERROR: ", err))
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
return
|
return
|
||||||
@ -93,7 +116,7 @@ func init() {
|
|||||||
ctx.SendChain(message.Text("ERROR: ", err))
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(data))); id.ID() == 0 {
|
if id := ctx.SendChain(message.Image("base64://" + ub.BytesToString(data))); id.ID() == 0 {
|
||||||
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
|
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -124,23 +147,76 @@ func init() {
|
|||||||
ctx.SendChain(message.Text(text))
|
ctx.SendChain(message.Text(text))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
engine.OnRegex(`^设置小说配置\s(.*[^\s$])\s(.+)$`, zero.SuperUserPermission).SetBlock(true).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
regexMatched := ctx.State["regex_matched"].([]string)
|
||||||
|
err := setAPIKey(ctx.State["manager"].(*ctrl.Control[*zero.Ctx]), regexMatched[1]+","+regexMatched[2])
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.SendChain(message.Text("成功设置小说配置\nusername: ", regexMatched[1], "\npassword: ", regexMatched[2]))
|
||||||
|
})
|
||||||
|
engine.OnRegex("^下载小说([0-9]{1,25})$").SetBlock(true).
|
||||||
|
Handle(func(ctx *zero.Ctx) {
|
||||||
|
regexMatched := ctx.State["regex_matched"].([]string)
|
||||||
|
id := regexMatched[1]
|
||||||
|
ctx.SendChain(message.Text("少女祈祷中......"))
|
||||||
|
key := getAPIKey(ctx)
|
||||||
|
u, p, _ := strings.Cut(key, ",")
|
||||||
|
if u == "" {
|
||||||
|
u = username
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = password
|
||||||
|
}
|
||||||
|
cookie, err := login(u, p)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
detailHTML, err := detail(id, cookie)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
doc, err := htmlquery.Parse(strings.NewReader(detailHTML))
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
title := htmlquery.InnerText(htmlquery.FindOne(doc, "//*[@id='bookinfo']/div[@class='bookright']/div[@class='d_title']/h1"))
|
||||||
|
fileName := filepath.Join(cachePath, title+".txt")
|
||||||
|
if file.IsExist(fileName) {
|
||||||
|
ctx.UploadThisGroupFile(filepath.Join(file.BOTPATH, fileName), filepath.Base(fileName), "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := download(id, cookie)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.WriteFile(fileName, ub.StringToBytes(data), 0666)
|
||||||
|
if err != nil {
|
||||||
|
ctx.SendChain(message.Text("ERROR: ", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.UploadThisGroupFile(filepath.Join(file.BOTPATH, fileName), filepath.Base(fileName), "")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func login(username, password string) (err error) {
|
func login(username, password string) (cookie string, err error) {
|
||||||
gCurCookieJar, _ = cookiejar.New(nil)
|
client := &http.Client{}
|
||||||
client := &http.Client{
|
usernameData, err := ub.UTF82GBK(ub.StringToBytes(username))
|
||||||
Jar: gCurCookieJar,
|
|
||||||
}
|
|
||||||
usernameData, err := ub.UTF82GBK(helper.StringToBytes(username))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
usernameGbk := helper.BytesToString(usernameData)
|
usernameGbk := ub.BytesToString(usernameData)
|
||||||
passwordData, err := ub.UTF82GBK(helper.StringToBytes(password))
|
passwordData, err := ub.UTF82GBK(ub.StringToBytes(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
passwordGbk := helper.BytesToString(passwordData)
|
passwordGbk := ub.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)))
|
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 {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -151,38 +227,79 @@ func login(username, password string) (err error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = loginResp.Body.Close()
|
defer loginResp.Body.Close()
|
||||||
|
for _, v := range loginResp.Cookies() {
|
||||||
|
cookie += v.Name + "=" + v.Value + ";"
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func search(searchKey string) (searchHTML string, err error) {
|
func search(searchKey string, cookie string) (searchHTML string, err error) {
|
||||||
searchKeyData, err := ub.UTF82GBK(helper.StringToBytes(searchKey))
|
searchKeyData, err := ub.UTF82GBK(ub.StringToBytes(searchKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
searchKeyGbk := helper.BytesToString(searchKeyData)
|
searchKeyGbk := ub.BytesToString(searchKeyData)
|
||||||
client := &http.Client{
|
data, err := web.RequestDataWithHeaders(web.NewDefaultClient(), searchURL, "POST", func(r *http.Request) error {
|
||||||
Jar: gCurCookieJar,
|
r.Header.Set("Cookie", cookie)
|
||||||
}
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
searchReq, err := http.NewRequest("POST", searchURL, strings.NewReader(fmt.Sprintf("searchkey=%s&searchtype=all", url.QueryEscape(searchKeyGbk))))
|
r.Header.Set("User-Agent", ua)
|
||||||
|
return nil
|
||||||
|
}, strings.NewReader(fmt.Sprintf("searchkey=%s&searchtype=all", url.QueryEscape(searchKeyGbk))))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
searchReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
searchData, err := ub.GBK2UTF8(data)
|
||||||
searchReq.Header.Set("User-Agent", ua)
|
|
||||||
searchResp, err := client.Do(searchReq)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
searchData, err := io.ReadAll(searchResp.Body)
|
searchHTML = ub.BytesToString(searchData)
|
||||||
_ = searchResp.Body.Close()
|
return
|
||||||
if err != nil {
|
}
|
||||||
return
|
|
||||||
}
|
func detail(id string, cookie string) (detailHTML string, err error) {
|
||||||
searchData, err = ub.GBK2UTF8(searchData)
|
data, err := web.RequestDataWithHeaders(web.NewDefaultClient(), fmt.Sprintf(detailURL, id), "GET", func(r *http.Request) error {
|
||||||
if err != nil {
|
r.Header.Set("Cookie", cookie)
|
||||||
return
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
}
|
r.Header.Set("User-Agent", ua)
|
||||||
searchHTML = helper.BytesToString(searchData)
|
return nil
|
||||||
return searchHTML, nil
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
detailData, err := ub.GBK2UTF8(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
detailHTML = ub.BytesToString(detailData)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func download(id string, cookie string) (downloadHTML string, err error) {
|
||||||
|
data, err := web.RequestDataWithHeaders(web.NewDefaultClient(), fmt.Sprintf(downloadURL, id), "GET", func(r *http.Request) error {
|
||||||
|
r.Header.Set("Cookie", cookie)
|
||||||
|
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
r.Header.Set("User-Agent", ua)
|
||||||
|
return nil
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
downloadHTML = ub.BytesToString(data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAPIKey(ctx *zero.Ctx) string {
|
||||||
|
if apikey == "" {
|
||||||
|
m := ctx.State["manager"].(*ctrl.Control[*zero.Ctx])
|
||||||
|
_ = m.Manager.GetExtra(-1, &apikey)
|
||||||
|
logrus.Debugln("[novel] get api key:", apikey)
|
||||||
|
}
|
||||||
|
return apikey
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAPIKey(m *ctrl.Control[*zero.Ctx], key string) error {
|
||||||
|
apikey = key
|
||||||
|
_ = m.Manager.Response(-1)
|
||||||
|
return m.Manager.SetExtra(-1, apikey)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user