Add plugin setutime

This commit is contained in:
Yiwen-Chan 2021-02-14 19:15:47 +08:00
parent 2ad5102297
commit e667720694
10 changed files with 1301 additions and 0 deletions

152
setutime/pic_searcher.go Normal file
View File

@ -0,0 +1,152 @@
package setutime
import (
utils "bot/setutime/utils"
zero "github.com/wdvxdr1123/ZeroBot"
)
func init() {
zero.RegisterPlugin(picSearch{}) // 注册插件
}
type picSearch struct{} // pixivSearch 搜索P站插图
func (_ picSearch) GetPluginInfo() zero.PluginInfo { // 返回插件信息
return zero.PluginInfo{
Author: "kanri",
PluginName: "PicSearch",
Version: "0.0.1",
Details: "以图搜图",
}
}
func (_ picSearch) Start() { // 插件主体
// TODO 根据PID搜图
zero.OnRegex(`搜图(\d+)`).SetBlock(true).SetPriority(30).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
id := utils.Str2Int(state["regex_matched"].([]string)[1])
zero.Send(event, "少女祈祷中......")
// TODO 获取P站插图信息
illust := &utils.Illust{}
if err := illust.IllustInfo(id); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
// TODO 下载P站插图
if _, err := illust.PixivPicDown(CACHEPATH); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
// TODO 发送搜索结果
zero.Send(event, illust.DetailPic)
return zero.FinishResponse
})
// TODO 通过回复以图搜图
zero.OnRegex(`\[CQ:reply,id=(.*?)\](.*)搜索图片`).SetBlock(true).SetPriority(32).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var pics []string // 图片搜索池子
// TODO 获取回复的上文图片链接
id := utils.Str2Int(state["regex_matched"].([]string)[1])
for _, elem := range zero.GetMessage(id).Elements {
if elem.Type == "image" {
pics = append(pics, elem.Data["url"])
}
}
// TODO 没有收到图片则向用户索取
if len(pics) == 0 {
zero.Send(event, "请发送多张图片!")
next := matcher.FutureEvent("message", zero.CheckUser(event.UserID))
recv, cancel := next.Repeat()
for e := range recv { // 循环获取channel发来的信息
if len(e.Message) == 1 && e.Message[0].Type == "text" {
cancel() // 如果是纯文本则退出索取
break
}
for _, elem := range e.Message {
if elem.Type == "image" { // 将信息中的图片添加到搜索池子
pics = append(pics, elem.Data["url"])
}
}
if len(pics) >= 5 {
cancel() // 如果是图片数量大于等于5则退出索取
break
}
}
}
if len(pics) == 0 {
zero.Send(event, "没有收到图片,搜图结束......")
return zero.FinishResponse
}
// TODO 开始搜索图片
zero.Send(event, "少女祈祷中......")
for _, pic := range pics {
if text, err := utils.SauceNaoSearch(pic); err == nil {
zero.Send(event, text) // 返回SauceNAO的结果
continue
} else {
utils.SendError(event, err)
}
if text, err := utils.Ascii2dSearch(pic); err == nil {
zero.Send(event, text) // 返回Ascii2d的结果
continue
} else {
utils.SendError(event, err)
}
}
return zero.FinishResponse
})
// TODO 通过命令以图搜图
zero.OnKeywordGroup([]string{"以图识图", "以图搜图", "搜索图片"}).SetBlock(true).SetPriority(33).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var pics []string // 图片搜索池子
// TODO 获取信息中图片链接
for _, elem := range event.Message {
if elem.Type == "image" {
pics = append(pics, elem.Data["url"])
}
}
// TODO 没有收到图片则向用户索取
if len(pics) == 0 {
zero.Send(event, "请发送多张图片!")
next := matcher.FutureEvent("message", zero.CheckUser(event.UserID))
recv, cancel := next.Repeat()
for e := range recv { // 循环获取channel发来的信息
if len(e.Message) == 1 && e.Message[0].Type == "text" {
cancel() // 如果是纯文本则退出索取
break
}
for _, elem := range e.Message {
if elem.Type == "image" { // 将信息中的图片添加到搜索池子
pics = append(pics, elem.Data["url"])
}
}
if len(pics) >= 5 {
cancel() // 如果是图片数量大于等于5则退出索取
break
}
}
}
if len(pics) == 0 {
zero.Send(event, "没有收到图片,搜图结束......")
return zero.FinishResponse
}
// TODO 开始搜索图片
zero.Send(event, "少女祈祷中......")
for _, pic := range pics {
if text, err := utils.SauceNaoSearch(pic); err == nil {
zero.Send(event, text) // 返回SauceNAO的结果
continue
} else {
utils.SendError(event, err)
}
if text, err := utils.Ascii2dSearch(pic); err == nil {
zero.Send(event, text) // 返回Ascii2d的结果
continue
} else {
utils.SendError(event, err)
}
}
return zero.FinishResponse
})
}

339
setutime/setu_geter.go Normal file
View File

@ -0,0 +1,339 @@
package setutime
import (
"errors"
"fmt"
utils "bot/setutime/utils"
zero "github.com/wdvxdr1123/ZeroBot"
)
type setuGet struct{} // setuGet 来份色图
func (_ setuGet) GetPluginInfo() zero.PluginInfo { // 返回插件信息
return zero.PluginInfo{
Author: "kanri",
PluginName: "SetuGet",
Version: "0.0.1",
Details: "来份色图",
}
}
var (
BOTPATH = utils.PathExecute() // 当前bot运行目录
DATAPATH = BOTPATH + "data/SetuTime/" // 数据目录
DBPATH = DATAPATH + "SetuTime.db" // 数据库路径
CACHEPATH = DATAPATH + "cache/" // 缓冲图片路径
DB = utils.Sqlite{DBPath: DBPATH} // 涩图数据库
pool = utils.PicsCache{Max: 10} // 图片缓冲池子
)
func init() {
zero.RegisterPlugin(setuGet{}) // 注册插件
utils.CACHE_GROUP = 868047498 // 图片缓冲群
utils.CACHEPATH = CACHEPATH // 缓冲图片路径
utils.CreatePath(DBPATH)
utils.CreatePath(CACHEPATH)
ecy := &ecy{}
setu := &setu{}
scenery := &scenery{}
DB.DBCreate(ecy)
DB.DBCreate(setu)
DB.DBCreate(scenery)
}
// ecy 二次元
type ecy struct {
utils.Illust
}
// setu 涩图
type setu struct {
utils.Illust
}
// scenery 风景
type scenery struct {
utils.Illust
}
func (_ setuGet) Start() { // 插件主体
zero.OnFullMatchGroup([]string{"来份涩图", "setu", "来份色图"}).SetBlock(true).SetPriority(20).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var (
type_ = "setu"
illust = &setu{}
)
// TODO 池子无图片则立刻下载
length := illust.Len(type_, &pool)
if length == 0 {
zero.Send(event, "[SetuTime] 正在填充弹药......")
if err := DB.DBSelect(illust, "ORDER BY RANDOM() limit 1"); err != nil {
utils.SendError(event, err) // 查询出一张图片
return zero.FinishResponse
}
if err := illust.Add(type_, &pool); err != nil {
utils.SendError(event, err) // 向缓冲池添加一张图片
return zero.FinishResponse
}
}
// TODO 补充池子
go func() {
times := utils.Min(pool.Max-length, 2)
for i := 0; i < times; i++ {
if err := DB.DBSelect(illust, "ORDER BY RANDOM() limit 1"); err != nil {
utils.SendError(event, err) // 查询出一张图片
}
if err := illust.Add(type_, &pool); err != nil {
utils.SendError(event, err) // 向缓冲池添加一张图片
}
}
}()
// TODO 从缓冲池里抽一张
hash := illust.Get(type_, &pool)
if utils.XML {
if id := zero.Send(event, illust.BigPic(hash)); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
}
} else {
if id := zero.Send(event, illust.NormalPic()); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
}
}
return zero.FinishResponse
})
zero.OnFullMatchGroup([]string{"二次元", "ecy", "来份二次元"}).SetBlock(true).SetPriority(20).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var (
type_ = "ecy"
illust = &ecy{}
)
// TODO 池子无图片则立刻下载
length := illust.Len(type_, &pool)
if length == 0 {
zero.Send(event, "[SetuTime] 正在填充弹药......")
if err := DB.DBSelect(illust, "ORDER BY RANDOM() limit 1"); err != nil {
utils.SendError(event, err) // 查询出一张图片
return zero.FinishResponse
}
if err := illust.Add(type_, &pool); err != nil {
utils.SendError(event, err) // 向缓冲池添加一张图片
return zero.FinishResponse
}
}
// TODO 补充池子
go func() {
times := utils.Min(pool.Max-length, 2)
for i := 0; i < times; i++ {
if err := DB.DBSelect(illust, "ORDER BY RANDOM() limit 1"); err != nil {
utils.SendError(event, err) // 查询出一张图片
}
if err := illust.Add(type_, &pool); err != nil {
utils.SendError(event, err) // 向缓冲池添加一张图片
}
}
}()
// TODO 从缓冲池里抽一张
hash := illust.Get(type_, &pool)
if utils.XML {
if id := zero.Send(event, illust.BigPic(hash)); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
}
} else {
if id := zero.Send(event, illust.NormalPic()); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
}
}
return zero.FinishResponse
})
zero.OnFullMatchGroup([]string{"风景", "来份风景"}).SetBlock(true).SetPriority(20).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var (
type_ = "scenery"
illust = &scenery{}
)
// TODO 池子无图片则立刻下载
length := illust.Len(type_, &pool)
if length == 0 {
zero.Send(event, "[SetuTime] 正在填充弹药......")
if err := DB.DBSelect(illust, "ORDER BY RANDOM() limit 1"); err != nil {
utils.SendError(event, err) // 查询出一张图片
return zero.FinishResponse
}
if err := illust.Add(type_, &pool); err != nil {
utils.SendError(event, err) // 向缓冲池添加一张图片
return zero.FinishResponse
}
}
// TODO 补充池子
go func() {
times := utils.Min(pool.Max-length, 2)
for i := 0; i < times; i++ {
if err := DB.DBSelect(illust, "ORDER BY RANDOM() limit 1"); err != nil {
utils.SendError(event, err) // 查询出一张图片
}
if err := illust.Add(type_, &pool); err != nil {
utils.SendError(event, err) // 向缓冲池添加一张图片
}
}
}()
// TODO 从缓冲池里抽一张
hash := illust.Get(type_, &pool)
if utils.XML {
if id := zero.Send(event, illust.BigPic(hash)); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
}
} else {
if id := zero.Send(event, illust.NormalPic()); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
}
}
return zero.FinishResponse
})
zero.OnRegex(`添加涩图(\d+)`, zero.SuperUserPermission).SetBlock(true).SetPriority(21).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var illust = &setu{}
zero.Send(event, "少女祈祷中......")
// TODO 查询P站插图信息
id := utils.Str2Int(state["regex_matched"].([]string)[1])
if err := illust.IllustInfo(id); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
// TODO 下载插画
if _, err := illust.PixivPicDown(CACHEPATH); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
if id := zero.Send(event, illust.DetailPic()); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
return zero.FinishResponse
}
// TODO 添加插画到对应的数据库table
if err := DB.DBInsert(illust); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
zero.Send(event, "添加成功")
return zero.FinishResponse
})
zero.OnRegex(`添加二次元(\d+)`, zero.SuperUserPermission).SetBlock(true).SetPriority(21).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var illust = &ecy{}
zero.Send(event, "少女祈祷中......")
// TODO 查询P站插图信息
id := utils.Str2Int(state["regex_matched"].([]string)[1])
if err := illust.IllustInfo(id); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
// TODO 下载插画
if _, err := illust.PixivPicDown(CACHEPATH); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
if id := zero.Send(event, illust.DetailPic()); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
return zero.FinishResponse
}
// TODO 添加插画到对应的数据库table
if err := DB.DBInsert(illust); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
zero.Send(event, "添加成功")
return zero.FinishResponse
})
zero.OnRegex(`添加风景(\d+)`, zero.SuperUserPermission).SetBlock(true).SetPriority(21).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var illust = &scenery{}
zero.Send(event, "少女祈祷中......")
// TODO 查询P站插图信息
id := utils.Str2Int(state["regex_matched"].([]string)[1])
if err := illust.IllustInfo(id); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
// TODO 下载插画
if _, err := illust.PixivPicDown(CACHEPATH); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
if id := zero.Send(event, illust.DetailPic()); id == 0 {
utils.SendError(event, errors.New("可能被风控了"))
return zero.FinishResponse
}
// TODO 添加插画到对应的数据库table
if err := DB.DBInsert(illust); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
zero.Send(event, "添加成功")
return zero.FinishResponse
})
zero.OnRegex(`删除涩图(\d+)`, zero.SuperUserPermission).SetBlock(true).SetPriority(22).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var illust = &setu{}
// TODO 查询数据库
id := utils.Str2Int(state["regex_matched"].([]string)[1])
if err := DB.DBDelete(illust, fmt.Sprintf("WHERE pid=%d", id)); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
zero.Send(event, "删除成功")
return zero.FinishResponse
})
zero.OnRegex(`删除二次元(\d+)`, zero.SuperUserPermission).SetBlock(true).SetPriority(22).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var illust = &ecy{}
// TODO 查询数据库
id := utils.Str2Int(state["regex_matched"].([]string)[1])
if err := DB.DBDelete(illust, fmt.Sprintf("WHERE pid=%d", id)); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
zero.Send(event, "删除成功")
return zero.FinishResponse
})
zero.OnRegex(`删除风景(\d+)`, zero.SuperUserPermission).SetBlock(true).SetPriority(22).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
var illust = &scenery{}
// TODO 查询数据库
id := utils.Str2Int(state["regex_matched"].([]string)[1])
if err := DB.DBDelete(illust, fmt.Sprintf("WHERE pid=%d", id)); err != nil {
utils.SendError(event, err)
return zero.FinishResponse
}
zero.Send(event, "删除成功")
return zero.FinishResponse
})
// TODO 查询数据库涩图数量
zero.OnFullMatchGroup([]string{"setu -s", "setu --status"}, zero.SuperUserPermission).SetBlock(true).SetPriority(23).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
setu, _ := DB.DBNum(&setu{})
ecy, _ := DB.DBNum(&ecy{})
scenery, _ := DB.DBNum(&scenery{})
zero.Send(event, fmt.Sprintf("[SetuTime] \n风景%d \n二次元%d \n涩图%d", scenery, ecy, setu))
return zero.FinishResponse
})
// TODO 开xml模式
zero.OnFullMatchGroup([]string{"setu -x", "setu --xml"}, zero.AdminPermission).SetBlock(true).SetPriority(24).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
utils.XML = true
zero.Send(event, "[SetuTime] XML->ON")
return zero.FinishResponse
})
// TODO 关xml模式
zero.OnFullMatchGroup([]string{"setu -p", "setu --pic"}, zero.AdminPermission).SetBlock(true).SetPriority(24).
Handle(func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response {
utils.XML = false
zero.Send(event, "[SetuTime] XML->OFF")
return zero.FinishResponse
})
}

100
setutime/utils/ascii2d.go Normal file
View File

@ -0,0 +1,100 @@
package utils
import (
"errors"
"fmt"
"net/http"
"net/url"
"strings"
xpath "github.com/antchfx/htmlquery"
)
// Ascii2dSearch Ascii2d 以图搜图
// 第一个参数 返回错误
// 第二个参数 返回的信息
func Ascii2dSearch(pic string) (text string, err error) {
var (
api = "https://ascii2d.net/search/uri"
)
transport := http.Transport{
DisableKeepAlives: true,
}
client := &http.Client{
Transport: &transport,
}
// TODO 包装请求参数
data := url.Values{}
data.Set("uri", pic) // 图片链接
fromData := strings.NewReader(data.Encode())
// TODO 网络请求
req, _ := http.NewRequest("POST", api, fromData)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0")
resp, err := client.Do(req)
if err != nil {
return "", err
}
// TODO 色合检索改变到特征检索
var bovwUrl = strings.ReplaceAll(resp.Request.URL.String(), "color", "bovw")
bovwReq, _ := http.NewRequest("POST", bovwUrl, nil)
bovwReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
bovwReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36")
bovwResp, err := client.Do(bovwReq)
if err != nil {
return "", err
}
defer bovwResp.Body.Close()
// TODO 解析XPATH
doc, err := xpath.Parse(resp.Body)
if err != nil {
return "", err
}
// TODO 取出每个返回的结果
list := xpath.Find(doc, `//div[@class="row item-box"]`)
var link string
// TODO 遍历取出第一个返回的PIXIV结果
for _, n := range list {
linkPath := xpath.Find(n, `//div[2]/div[3]/h6/a[1]`)
picPath := xpath.Find(n, `//div[1]/img`)
if len(linkPath) != 0 && len(picPath) != 0 {
link = xpath.SelectAttr(linkPath[0], "href")
if strings.Contains(link, "www.pixiv.net") {
break
}
}
}
// TODO 链接取出PIXIV id
var index = strings.LastIndex(link, "/")
if link == "" || index == -1 {
return "", errors.New("Ascii2d not found")
}
var id = Str2Int(link[index+1:])
if id == 0 {
return "", errors.New("convert to pid error")
}
// TODO 根据PID查询插图信息
var illust = &Illust{}
if err := illust.IllustInfo(id); err != nil {
return "", err
}
if illust.AgeLimit != "all-age" {
return "", errors.New("Ascii2d not found")
}
// TODO 返回插图信息文本
return fmt.Sprintf(
`[SetuTime] emmm大概是这个
标题%s
插画ID%d
画师%s
画师ID%d
直链https://pixivel.moe/detail?id=%d`,
illust.Title,
illust.Pid,
illust.UserName,
illust.UserId,
illust.Pid,
), nil
}

View File

@ -0,0 +1,60 @@
package utils
import (
"crypto/md5"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"strings"
zero "github.com/wdvxdr1123/ZeroBot"
)
// urlCache 缓存并返回缓存路径
func (this *Illust) PixivPicDown(path string) (savePath string, err error) {
url := this.ImageUrls
pid := this.Pid
url = strings.ReplaceAll(url, "i.pximg.net", "i.pixiv.cat")
url = strings.ReplaceAll(url, "img-original", "img-master")
url = strings.ReplaceAll(url, "_p0", "_p0_master1200")
url = strings.ReplaceAll(url, ".png", ".jpg")
// TODO 文件名为url的hash值
savePath = path + Int2Str(pid) + ".jpg"
// TODO 文件存在或文件大小大于10kb
if PathExists(savePath) && FileSize(savePath) > 10240 {
return savePath, nil
}
zero.SendGroupMessage(CACHE_GROUP, "正在下载"+url)
// TODO 模拟QQ客户端请求
client := &http.Client{}
reqest, _ := http.NewRequest("GET", url, nil)
reqest.Header.Add("User-Agent", "QQ/8.2.0.1296 CFNetwork/1126")
reqest.Header.Add("Net-Type", "Wifi")
resp, err := client.Do(reqest)
if err != nil {
return "", err
}
fmt.Println(resp.StatusCode)
if code := resp.StatusCode; code != 200 {
return "", errors.New(fmt.Sprintf("Download failed, code %d", code))
}
defer resp.Body.Close()
// TODO 写入文件
data, _ := ioutil.ReadAll(resp.Body)
f, _ := os.OpenFile(savePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
defer f.Close()
f.Write(data)
return savePath, err
}
func PicHash(path string) string {
data, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return strings.ToUpper(fmt.Sprintf("%x", md5.Sum(data)))
}

163
setutime/utils/pic_pool.go Normal file
View File

@ -0,0 +1,163 @@
package utils
import (
"errors"
"fmt"
"strings"
"sync"
zero "github.com/wdvxdr1123/ZeroBot"
)
var (
CACHEPATH string // 图片缓存路径
CACHE_GROUP int64 // 图片缓存群用于上传图片到tx服务器
)
// PicsCache 图片缓冲池
type PicsCache struct {
Lock sync.Mutex
Max int
ECY []string
IECY []Illust
SETU []string
ISETU []Illust
SCENERY []string
ISCENERY []Illust
}
// Len 返回当前缓冲池的图片数量
func (this *Illust) Len(type_ string, pool *PicsCache) (length int) {
switch type_ {
case "ecy":
return len(pool.ECY)
case "setu":
return len(pool.SETU)
case "scenery":
return len(pool.SCENERY)
}
return 0
}
// Add 添加图片到缓冲池,返回错误
func (this Illust) Add(type_ string, pool *PicsCache) (err error) {
// TODO 下载图片
path, err := this.PixivPicDown(CACHEPATH)
if err != nil {
return err
}
hash := PicHash(path)
// TODO 发送到缓存群以上传tx服务器
if id := zero.SendGroupMessage(CACHE_GROUP, "[CQ:image,file=file:///"+path+"]"); id == 0 {
return errors.New("send failed")
}
// TODO 把hash和插图信息添加到缓冲池
pool.Lock.Lock()
defer pool.Lock.Unlock()
switch type_ {
case "ecy":
pool.ECY = append(pool.ECY, hash)
pool.IECY = append(pool.IECY, this)
case "setu":
pool.SETU = append(pool.SETU, hash)
pool.ISETU = append(pool.ISETU, this)
case "scenery":
pool.SCENERY = append(pool.SCENERY, hash)
pool.ISCENERY = append(pool.ISCENERY, this)
}
return nil
}
// Get 从缓冲池里取出一张返回hashillust值中Pid和UserName会被改变
func (this *Illust) Get(type_ string, pool *PicsCache) (hash string) {
pool.Lock.Lock()
defer pool.Lock.Unlock()
switch type_ {
case "ecy":
if len(pool.ECY) > 0 {
hash := pool.ECY[0]
this.Pid = pool.IECY[0].Pid
this.Title = pool.IECY[0].Title
this.UserName = pool.IECY[0].UserName
pool.ECY = pool.ECY[1:]
pool.IECY = pool.IECY[1:]
return hash
}
case "setu":
if len(pool.SETU) > 0 {
hash := pool.SETU[0]
this.Pid = pool.ISETU[0].Pid
this.Title = pool.ISETU[0].Title
this.UserName = pool.ISETU[0].UserName
pool.SETU = pool.SETU[1:]
pool.ISETU = pool.ISETU[1:]
return hash
}
case "scenery":
if len(pool.SCENERY) > 0 {
hash := pool.SCENERY[0]
this.Pid = pool.ISCENERY[0].Pid
this.Title = pool.ISCENERY[0].Title
this.UserName = pool.ISCENERY[0].UserName
pool.SCENERY = pool.SCENERY[1:]
pool.ISCENERY = pool.ISCENERY[1:]
return hash
}
default:
//
}
return ""
}
func GetCQcodePicLink(text string) (url string) {
text = strings.ReplaceAll(text, "{", "")
text = strings.ReplaceAll(text, "{", "")
text = strings.ReplaceAll(text, "-", "")
if index := strings.Index(text, "."); index != -1 {
if hash := text[:index]; len(hash) == 32 {
return fmt.Sprintf("http://gchat.qpic.cn/gchatpic_new//--%s/0", hash)
}
}
return ""
}
// BigPic 返回一张XML大图CQ码
func (this *Illust) BigPic(hash string) string {
return fmt.Sprintf(`[CQ:xml,data=<?xml version='1.0'
encoding='UTF-8' standalone='yes' ?><msg serviceID="5"
templateID="12345" action="" brief="不够涩!"
sourceMsgId="0" url="" flag="0" adverSign="0" multiMsgFlag="0">
<item layout="0" advertiser_id="0" aid="0"><image uuid="%s.jpg" md5="%s"
GroupFiledid="2235033681" filesize="81322" local_path="%s.jpg"
minWidth="200" minHeight="200" maxWidth="500" maxHeight="1000" />
</item><source name="%s⭐(id:%d author:%s)" icon=""
action="" appid="-1" /></msg>]`,
hash,
hash,
hash,
this.Title,
this.Pid,
this.UserName,
)
}
// NormalPic 返回一张普通图CQ码
func (this *Illust) NormalPic() string {
return fmt.Sprintf(`[CQ:image,file=file:///%s%d.jpg]`, CACHEPATH, this.Pid)
}
// DetailPic 返回一张带详细信息的图片CQ码
func (this *Illust) DetailPic() string {
return fmt.Sprintf(`[SetuTime] %s 标题%s
插画ID%d
画师%s
画师ID%d
直链https://pixivel.moe/detail?id=%d`,
this.NormalPic(),
this.Title,
this.Pid,
this.UserName,
this.UserId,
this.Pid,
)
}

View File

@ -0,0 +1,94 @@
package utils
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"strings"
"github.com/tidwall/gjson"
)
// Illust 插画信息
type Illust struct {
Pid int64 `db:"pid"`
Title string `db:"title"`
Caption string `db:"caption"`
Tags string `db:"tags"`
ImageUrls string `db:"image_urls"`
AgeLimit string `db:"age_limit"`
CreatedTime string `db:"created_time"`
UserId int64 `db:"user_id"`
UserName string `db:"user_name"`
}
// IllustInfo 根据p站插画id返回插画信息Illust
func (this *Illust) IllustInfo(id int64) (err error) {
api := fmt.Sprintf("https://pixiv.net/ajax/illust/%d", id)
transport := http.Transport{
DisableKeepAlives: true,
// TODO 绕过sni审查
TLSClientConfig: &tls.Config{
ServerName: "-",
InsecureSkipVerify: true,
},
// TODO 更改dns
Dial: func(network, addr string) (net.Conn, error) {
return net.Dial("tcp", "210.140.131.223:443")
},
}
client := &http.Client{
Transport: &transport,
}
// TODO 网络请求
req, err := http.NewRequest("GET", api, nil)
if err != nil {
return err
}
req.Header.Set("Host", "pixiv.net")
req.Header.Set("Referer", "pixiv.net")
req.Header.Set("Accept", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0")
resp, err := client.Do(req)
if err != nil {
return err
}
if code := resp.StatusCode; code != 200 {
return errors.New(fmt.Sprintf("Search illust's info failed, status %d", code))
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
json := gjson.ParseBytes(body).Get("body")
// TODO 如果有"R-18"tag则判断为R-18暂时
var ageLimit = "all-age"
for _, tag := range json.Get("tags.tags.#.tag").Array() {
if tag.Str == "R-18" {
ageLimit = "r18"
break
}
}
// TODO 解决json返回带html格式
var caption = strings.ReplaceAll(json.Get("illustComment").Str, "<br />", "\n")
if index := strings.Index(caption, "<"); index != -1 {
caption = caption[:index]
}
// TODO 解析返回插画信息
this.Pid = json.Get("illustId").Int()
this.Title = json.Get("illustTitle").Str
this.Caption = caption
this.Tags = fmt.Sprintln(json.Get("tags.tags.#.tag").Array())
this.ImageUrls = json.Get("urls.original").Str
this.AgeLimit = ageLimit
this.CreatedTime = json.Get("createDate").Str
this.UserId = json.Get("userId").Int()
this.UserName = json.Get("userName").Str
return nil
}

View File

@ -0,0 +1,84 @@
package utils
import (
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/tidwall/gjson"
)
// SauceNaoSearch SauceNao 以图搜图 需要链接 返回错误和信息
func SauceNaoSearch(pic string) (text string, err error) {
var (
api = "https://saucenao.com/search.php"
apiKey = "2cc2772ca550dbacb4c35731a79d341d1a143cb5"
minSimilarity = 70.0 // 返回图片结果的最小相似度
)
transport := http.Transport{
DisableKeepAlives: true,
}
client := &http.Client{
Transport: &transport,
}
// TODO 包装请求参数
data := url.Values{}
data.Set("url", pic) // 图片链接
data.Set("api_key", apiKey) // api_key
data.Set("db", "5") // 只搜索Pixiv
data.Set("numres", "1") // 返回一个结果
data.Set("output_type", "2") // 返回JSON格式数据
fromData := strings.NewReader(data.Encode())
// TODO 网络请求
req, err := http.NewRequest("POST", api, fromData)
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0")
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if code := resp.StatusCode; code != 200 {
// 如果返回不是200则立刻抛出错误
return "", errors.New(fmt.Sprintf("SauceNAO not found, code %d", code))
}
content := gjson.ParseBytes(body)
if status := content.Get("header.status").Int(); status != 0 {
// 如果json信息返回status不为0则立刻抛出错误
return "", errors.New(fmt.Sprintf("SauceNAO not found, status %d", status))
}
if content.Get("results.0.header.similarity").Float() < minSimilarity {
return "", errors.New("SauceNAO not found")
}
// TODO 正常发送
return fmt.Sprintf(
`[SetuTime] 我有把握是这个[CQ:image,file=%s]相似度%s%%
标题%s
插画ID%d
画师%s
画师ID%d
直链https://pixivel.moe/detail?id=%d`,
content.Get("results.0.header.thumbnail").Str,
content.Get("results.0.header.similarity").Str,
content.Get("results.0.data.title").Str,
content.Get("results.0.data.pixiv_id").Int(),
content.Get("results.0.data.member_name").Str,
content.Get("results.0.data.member_id").Int(),
content.Get("results.0.data.pixiv_id").Int(),
), nil
}

224
setutime/utils/sqlite.go Normal file
View File

@ -0,0 +1,224 @@
package utils
import (
"database/sql"
"errors"
"fmt"
"reflect"
_ "github.com/mattn/go-sqlite3"
)
type Sqlite struct {
DB *sql.DB
DBPath string
}
// DBCreate 根据结构体生成数据库tabletag为"id"为主键,自增
func (db *Sqlite) DBCreate(objptr interface{}) (err error) {
if db.DB == nil {
database, err := sql.Open("sqlite3", db.DBPath)
if err != nil {
return err
}
db.DB = database
}
table := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s (", Struct2name(objptr))
for i, column := range strcut2columns(objptr) {
table += fmt.Sprintf(" %s %s NULL", column, column2type(objptr, column))
if i+1 != len(strcut2columns(objptr)) {
table += ","
} else {
table += " );"
}
}
if _, err := db.DB.Exec(table); err != nil {
return err
}
return nil
}
// DBInsert 根据结构体插入一条数据
func (db *Sqlite) DBInsert(objptr interface{}) (err error) {
defer func() {
if err := recover(); err != nil {
panic(err)
}
}()
rows, err := db.DB.Query("SELECT * FROM " + Struct2name(objptr))
if err != nil {
return err
}
defer rows.Close()
columns, _ := rows.Columns()
index := -1
names := "("
insert := "("
for i, column := range columns {
if column == "id" {
index = i
continue
}
if i != len(columns)-1 {
names += column + ","
insert += "?,"
} else {
names += column + ")"
insert += "?)"
}
}
stmt, err := db.DB.Prepare("INSERT INTO " + Struct2name(objptr) + names + " values " + insert)
if err != nil {
return err
}
value := []interface{}{}
if index == -1 {
value = append(value, struct2values(objptr, columns)...)
} else {
value = append(value, append(struct2values(objptr, columns)[:index], struct2values(objptr, columns)[index+1:]...)...)
}
_, err = stmt.Exec(value...)
if err != nil {
return err
}
return nil
}
// DBSelect 根据结构体查询对应的表cmd可为"WHERE id = 0 "
func (db *Sqlite) DBSelect(objptr interface{}, cmd string) (err error) {
rows, err := db.DB.Query(fmt.Sprintf("SELECT * FROM %s %s", Struct2name(objptr), cmd))
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
columns, err := rows.Columns()
if err != nil {
return err
}
err = rows.Scan(struct2addrs(objptr, columns)...)
if err != nil {
return err
}
return nil
}
return errors.New("Database no such elem")
}
// DBDelete 删除struct对应表的一行返回错误
func (db *Sqlite) DBDelete(objptr interface{}, cmd string) (err error) {
stmt, err := db.DB.Prepare(fmt.Sprintf("DELETE FROM %s %s", Struct2name(objptr), cmd))
if err != nil {
return err
}
_, err = stmt.Exec()
if err != nil {
return err
}
return nil
}
// DBNum 查询struct对应表的行数,返回行数以及错误
func (db *Sqlite) DBNum(objptr interface{}) (num int, err error) {
rows, err := db.DB.Query(fmt.Sprintf("SELECT * FROM %s", Struct2name(objptr)))
if err != nil {
return num, err
}
defer rows.Close()
for rows.Next() {
num++
}
return num, nil
}
// strcut2columns 反射得到结构体的 tag 数组
func strcut2columns(objptr interface{}) []string {
var columns []string
elem := reflect.ValueOf(objptr).Elem()
// TODO 判断第一个元素是否为匿名字段
if elem.Type().Field(0).Anonymous {
elem = elem.Field(0)
}
for i, flen := 0, elem.Type().NumField(); i < flen; i++ {
columns = append(columns, elem.Type().Field(i).Tag.Get("db"))
}
return columns
}
// Struct2name 反射得到结构体的名字
func Struct2name(objptr interface{}) string {
return reflect.ValueOf(objptr).Elem().Type().Name()
}
// column2type 反射得到结构体对应 tag 的 数据库数据类型
func column2type(objptr interface{}, column string) string {
type_ := ""
elem := reflect.ValueOf(objptr).Elem()
// TODO 判断第一个元素是否为匿名字段
if elem.Type().Field(0).Anonymous {
elem = elem.Field(0)
}
for i, flen := 0, elem.Type().NumField(); i < flen; i++ {
if column == elem.Type().Field(i).Tag.Get("db") {
type_ = elem.Field(i).Type().String()
}
}
if column == "id" {
return "INTEGER PRIMARY KEY"
}
switch type_ {
case "int64":
return "INT"
case "string":
return "TEXT"
default:
return "TEXT"
}
}
// struct2addrs 反射得到结构体对应数据库字段的属性地址
func struct2addrs(objptr interface{}, columns []string) []interface{} {
var addrs []interface{}
elem := reflect.ValueOf(objptr).Elem()
// TODO 判断第一个元素是否为匿名字段
if elem.Type().Field(0).Anonymous {
elem = elem.Field(0)
}
for _, column := range columns {
for i, flen := 0, elem.Type().NumField(); i < flen; i++ {
if column == elem.Type().Field(i).Tag.Get("db") {
addrs = append(addrs, elem.Field(i).Addr().Interface())
}
}
}
return addrs
}
// struct2values 反射得到结构体对应数据库字段的属性值
func struct2values(objptr interface{}, columns []string) []interface{} {
var values []interface{}
elem := reflect.ValueOf(objptr).Elem()
// TODO 判断第一个元素是否为匿名字段
if elem.Type().Field(0).Anonymous {
elem = elem.Field(0)
}
for _, column := range columns {
for i, flen := 0, elem.Type().NumField(); i < flen; i++ {
if column == elem.Type().Field(i).Tag.Get("db") {
switch elem.Field(i).Type().String() {
case "int64":
values = append(values, elem.Field(i).Int())
case "string":
values = append(values, elem.Field(i).String())
default:
values = append(values, elem.Field(i).String())
}
}
}
}
return values
}

5
setutime/utils/switch.go Normal file
View File

@ -0,0 +1,5 @@
package utils
var (
XML = true // 图片XML开关默认开启
)

80
setutime/utils/utils.go Normal file
View File

@ -0,0 +1,80 @@
package utils
import (
"fmt"
"os"
"strconv"
"strings"
zero "github.com/wdvxdr1123/ZeroBot"
)
// Str2Int string --> int64
func Str2Int(str string) int64 {
val, _ := strconv.ParseInt(str, 10, 64)
return val
}
// Int2Str int64 --> string
func Int2Str(val int64) string {
str := strconv.FormatInt(val, 10)
return str
}
// PathExecute 返回当前运行目录
func PathExecute() string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
return dir + "/"
}
// CreatePath 生成路径或文件所对应的目录
func CreatePath(path string) {
length := len(path)
switch {
case path[length:] != "/":
path = path[:strings.LastIndex(path, "/")]
case path[length:] != "\\":
path = path[:strings.LastIndex(path, "\\")]
default:
//
}
if !PathExists(path) {
err := os.MkdirAll(path, 0644)
if err != nil {
panic(err)
}
}
}
// PathExists 判断路径或文件是否存在
func PathExists(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
// FileSize 获取文件大小
func FileSize(file string) int64 {
if fi, err := os.Stat(file); err == nil {
return fi.Size()
}
return 0
}
// Min 返回两数最小值
func Min(a, b int) int {
switch {
default:
return a
case a > b:
return b
case a < b:
return a
}
}
func SendError(event zero.Event, err error) {
zero.Send(event, fmt.Sprintf("ERROR: %v", err))
}