ZeroBot-Plugin/plugin/movies/main.go
2025-04-23 18:35:34 +09:00

436 lines
11 KiB
Go

// Package movies 电影查询
package movies
import (
"encoding/json"
"image"
"net/http"
"os"
"path/filepath"
"sort"
"strconv"
"sync"
"time"
"github.com/FloatTech/floatbox/file"
"github.com/FloatTech/floatbox/web"
"github.com/FloatTech/gg"
"github.com/FloatTech/imgfactory"
"github.com/FloatTech/rendercard"
ctrl "github.com/FloatTech/zbpctrl"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/img/text"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
)
const (
apiURL = "https://m.maoyan.com/ajax/"
ua = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Mobile Safari/537.36"
)
var (
mu sync.RWMutex
todayPic = make([][]byte, 2)
lasttime time.Time
en = control.AutoRegister(&ctrl.Options[*zero.Ctx]{
DisableOnDefault: false,
Brief: "电影查询",
Help: "- 今日电影\n" +
"- 预售电影",
PrivateDataFolder: "movies",
})
)
func init() {
en.OnFullMatch("今日电影").SetBlock(true).Handle(func(ctx *zero.Ctx) {
if todayPic != nil && time.Since(lasttime) < 12*time.Hour {
ctx.SendChain(message.ImageBytes(todayPic[0]))
return
}
lasttime = time.Now()
movieComingList, err := getMovieList("今日电影")
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
if len(movieComingList) == 0 {
ctx.SendChain(message.Text("没有今日电影"))
return
}
pic, err := drawOnListPic(movieComingList)
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
todayPic[0] = pic
ctx.SendChain(message.ImageBytes(pic))
})
en.OnFullMatch("预售电影").SetBlock(true).Handle(func(ctx *zero.Ctx) {
if todayPic[1] != nil && time.Since(lasttime) < 12*time.Hour {
ctx.SendChain(message.ImageBytes(todayPic[1]))
return
}
lasttime = time.Now()
movieComingList, err := getMovieList("预售电影")
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
if len(movieComingList) == 0 {
ctx.SendChain(message.Text("没有预售电影"))
return
}
pic, err := drawComListPic(movieComingList)
if err != nil {
ctx.SendChain(message.Text("[ERROR]:", err))
return
}
todayPic[1] = pic
ctx.SendChain(message.ImageBytes(pic))
})
}
type movieInfo struct {
ID int64 `json:"id"` // 电影ID
Img string `json:"img"` // 海报
Nm string `json:"nm"` // 名称
Dir string `json:"dir"` // 导演
Star string `json:"star"` // 演员
OriLang string `json:"oriLang"` // 原语言
Cat string `json:"cat"` // 类型
Version string `json:"version"` // 电影格式
Rt string `json:"rt"` // 上映时间
ShowInfo string `json:"showInfo"` // 今日上映信息
ComingTitle string `json:"comingTitle"` // 预售信息
Sc float64 `json:"sc"` // 评分
Wish int64 `json:"wish"` // 观看人数
Watched int64 `json:"watched"` // 观看数
}
type movieOnList struct {
MovieList []movieInfo `json:"movieList"`
}
type comingList struct {
MovieList []movieInfo `json:"coming"`
}
type movieShow struct {
MovieInfo movieInfo `json:"detailMovie"`
}
type cardInfo struct {
Avatar image.Image
TopLeftText string
BottomLeftText []string
RightText string
Rank string
}
func getMovieList(mode string) (movieList []movieInfo, err error) {
var data []byte
if mode == "今日电影" {
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"movieOnInfoList", "", "GET", ua, nil)
if err != nil {
return
}
var parsed movieOnList
err = json.Unmarshal(data, &parsed)
if err != nil {
return
}
movieList = parsed.MovieList
} else {
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"comingList?token=", "", "GET", ua, nil)
if err != nil {
return
}
var parsed comingList
err = json.Unmarshal(data, &parsed)
if err != nil {
return
}
movieList = parsed.MovieList
}
if len(movieList) == 0 {
return
}
for i, info := range movieList {
movieID := strconv.FormatInt(info.ID, 10)
data, err = web.RequestDataWith(web.NewDefaultClient(), apiURL+"detailmovie?movieId="+movieID, "", "GET", ua, nil)
if err != nil {
return
}
var movieInfo movieShow
err = json.Unmarshal(data, &movieInfo)
if err != nil {
return
}
if mode != "今日电影" {
movieInfo.MovieInfo.ComingTitle = movieList[i].ComingTitle
}
movieList[i] = movieInfo.MovieInfo
}
// 整理数据,进行排序
sort.Slice(movieList, func(i, j int) bool {
if movieList[i].Sc != movieList[j].Sc {
return movieList[i].Sc > movieList[j].Sc
}
if mode == "今日电影" {
return movieList[i].Watched > movieList[j].Watched
}
return movieList[i].Wish > movieList[j].Wish
})
return movieList, nil
}
func drawOnListPic(lits []movieInfo) (data []byte, err error) {
rankinfo := make([]*cardInfo, len(lits))
wg := &sync.WaitGroup{}
wg.Add(len(lits))
for i := 0; i < len(lits); i++ {
go func(i int) {
info := lits[i]
defer wg.Done()
img, err := avatar(&info)
if err != nil {
return
}
movieType := "2D"
if info.Version != "" {
movieType = info.Version
}
watched := ""
switch {
case info.Watched > 100000000:
watched = strconv.FormatFloat(float64(info.Watched)/100000000, 'f', 2, 64) + "亿"
case info.Watched > 10000:
watched = strconv.FormatFloat(float64(info.Watched)/10000, 'f', 2, 64) + "万"
default:
watched = strconv.FormatInt(info.Watched, 10)
}
rankinfo[i] = &cardInfo{
TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")",
BottomLeftText: []string{
"导演:" + info.Dir,
"演员:" + info.Star,
"标签:" + info.Cat,
"语言: " + info.OriLang + " 类型: " + movieType,
"上映时间: " + info.Rt,
},
RightText: watched + "人已看",
Avatar: img,
Rank: strconv.FormatFloat(info.Sc, 'f', 1, 64),
}
}(i)
}
wg.Wait()
fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
if err != nil {
return
}
img, err := drawRankingCard(fontbyte, "今日电影", rankinfo)
if err != nil {
return
}
data, err = imgfactory.ToBytes(img)
return
}
func drawComListPic(lits []movieInfo) (data []byte, err error) {
rankinfo := make([]*cardInfo, len(lits))
wg := &sync.WaitGroup{}
wg.Add(len(lits))
for i := 0; i < len(lits); i++ {
go func(i int) {
info := lits[i]
defer wg.Done()
img, err := avatar(&info)
if err != nil {
return
}
movieType := "2D"
if info.Version != "" {
movieType = info.Version
}
wish := ""
switch {
case info.Wish > 100000000:
wish = strconv.FormatFloat(float64(info.Wish)/100000000, 'f', 2, 64) + "亿"
case info.Wish > 10000:
wish = strconv.FormatFloat(float64(info.Wish)/10000, 'f', 2, 64) + "万"
default:
wish = strconv.FormatInt(info.Wish, 10)
}
rankinfo[i] = &cardInfo{
TopLeftText: info.Nm + " (" + strconv.FormatInt(info.ID, 10) + ")",
BottomLeftText: []string{
"导演:" + info.Dir,
"演员:" + info.Star,
"标签:" + info.Cat,
"语言: " + info.OriLang + " 类型: " + movieType,
"上映时间: " + info.Rt + " 播放时间: " + info.ComingTitle,
},
RightText: wish + "人期待",
Avatar: img,
Rank: strconv.Itoa(i + 1),
}
}(i)
}
wg.Wait()
fontbyte, err := file.GetLazyData(text.GlowSansFontFile, control.Md5File, true)
if err != nil {
return
}
img, err := drawRankingCard(fontbyte, "预售电影", rankinfo)
if err != nil {
return
}
data, err = imgfactory.ToBytes(img)
return
}
func drawRankingCard(fontdata []byte, title string, rankinfo []*cardInfo) (img image.Image, err error) {
line := len(rankinfo)
const lineh = 130
const w = 800
h := 64 + (lineh+14)*line + 20 - 14
canvas := gg.NewContext(w, h)
canvas.SetRGBA255(255, 255, 255, 255)
canvas.Clear()
cardh, cardw := lineh, 770
cardspac := 14
hspac, wspac := 64.0, 16.0
r := 16.0
wg := &sync.WaitGroup{}
wg.Add(line)
cardimgs := make([]image.Image, line)
for i := 0; i < line; i++ {
go func(i int) {
defer wg.Done()
card := gg.NewContext(w, cardh)
card.NewSubPath()
card.MoveTo(wspac+float64(cardh)/2, 0)
card.LineTo(wspac+float64(cardw)-r, 0)
card.DrawArc(wspac+float64(cardw)-r, r, r, gg.Radians(-90), gg.Radians(0))
card.LineTo(wspac+float64(cardw), float64(cardh)-r)
card.DrawArc(wspac+float64(cardw)-r, float64(cardh)-r, r, gg.Radians(0), gg.Radians(90))
card.LineTo(wspac+float64(cardh)/2, float64(cardh))
card.DrawArc(wspac+r, float64(cardh)-r, r, gg.Radians(90), gg.Radians(180))
card.LineTo(wspac, r)
card.DrawArc(wspac+r, r, r, gg.Radians(180), gg.Radians(270))
card.ClosePath()
card.ClipPreserve()
avatar := rankinfo[i].Avatar
PicH := cardh - 20
picW := int(float64(avatar.Bounds().Dx()) * float64(PicH) / float64(avatar.Bounds().Dy()))
card.DrawImageAnchored(imgfactory.Size(avatar, picW, PicH).Image(), int(wspac)+10+picW/2, cardh/2, 0.5, 0.5)
card.ResetClip()
card.SetRGBA255(0, 0, 0, 127)
card.Stroke()
card.SetRGBA255(240, 210, 140, 200)
card.DrawRoundedRectangle(wspac+float64(cardw-8-250), (float64(cardh)-50)/2, 250, 50, 25)
card.Fill()
card.SetRGB255(rendercard.RandJPColor())
card.DrawRoundedRectangle(wspac+float64(cardw-8-60), (float64(cardh)-50)/2, 60, 50, 25)
card.Fill()
cardimgs[i] = card.Image()
}(i)
}
canvas.SetRGBA255(0, 0, 0, 255)
err = canvas.ParseFontFace(fontdata, 32)
if err != nil {
return
}
canvas.DrawStringAnchored(title, w/2, 64/2, 0.5, 0.5)
err = canvas.ParseFontFace(fontdata, 22)
if err != nil {
return
}
wg.Wait()
for i := 0; i < line; i++ {
canvas.DrawImageAnchored(cardimgs[i], w/2, int(hspac)+((cardh+cardspac)*i), 0.5, 0)
canvas.DrawStringAnchored(rankinfo[i].TopLeftText, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*3/16), 0, 0.5)
}
// canvas.SetRGBA255(63, 63, 63, 255)
err = canvas.ParseFontFace(fontdata, 14)
if err != nil {
return
}
for i := 0; i < line; i++ {
for j, text := range rankinfo[i].BottomLeftText {
canvas.DrawStringAnchored(text, wspac+10+80+10, hspac+float64((cardspac+cardh)*i+cardh*6/16)+float64(j*16), 0, 0.5)
}
}
canvas.SetRGBA255(0, 0, 0, 255)
err = canvas.ParseFontFace(fontdata, 20)
if err != nil {
return
}
for i := 0; i < line; i++ {
canvas.DrawStringAnchored(rankinfo[i].RightText, w-wspac-8-60-8, hspac+float64((cardspac+cardh)*i+cardh/2), 1, 0.5)
}
canvas.SetRGBA255(255, 255, 255, 255)
err = canvas.ParseFontFace(fontdata, 28)
if err != nil {
return
}
for i := 0; i < line; i++ {
canvas.DrawStringAnchored(rankinfo[i].Rank, w-wspac-8-30, hspac+float64((cardspac+cardh)*i+cardh/2), 0.5, 0.5)
}
img = canvas.Image()
return
}
// avatar 获取电影海报,图片大且多,存本地增加响应速度
func avatar(movieInfo *movieInfo) (pic image.Image, err error) {
mu.Lock()
defer mu.Unlock()
aimgfile := filepath.Join(en.DataFolder(), movieInfo.Nm+"("+strconv.FormatInt(movieInfo.ID, 10)+").jpg")
if file.IsNotExist(aimgfile) {
err = file.DownloadTo(movieInfo.Img, aimgfile)
if err != nil {
return urlToImg(movieInfo.Img)
}
}
f, err := os.Open(filepath.Join(file.BOTPATH, aimgfile))
if err != nil {
return urlToImg(movieInfo.Img)
}
defer f.Close()
pic, _, err = image.Decode(f)
return
}
func urlToImg(url string) (img image.Image, err error) {
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
img, _, err = image.Decode(resp.Body)
return
}