|
2 | 2 | package bilibiliparse
|
3 | 3 |
|
4 | 4 | import (
|
| 5 | + "encoding/json" |
5 | 6 | "errors"
|
| 7 | + "net/http" |
6 | 8 | "regexp"
|
7 |
| - "strings" |
| 9 | + "strconv" |
| 10 | + "time" |
8 | 11 |
|
9 | 12 | ctrl "github.com/FloatTech/zbpctrl"
|
10 | 13 | "github.com/FloatTech/zbputils/control"
|
11 |
| - "github.com/antchfx/htmlquery" |
| 14 | + "github.com/FloatTech/zbputils/ctxext" |
| 15 | + "github.com/FloatTech/zbputils/web" |
12 | 16 | zero "github.com/wdvxdr1123/ZeroBot"
|
13 | 17 | "github.com/wdvxdr1123/ZeroBot/message"
|
14 | 18 | )
|
15 | 19 |
|
| 20 | +type result struct { |
| 21 | + Data struct { |
| 22 | + Bvid string `json:"bvid"` |
| 23 | + Aid int `json:"aid"` |
| 24 | + Copyright int `json:"copyright"` |
| 25 | + Pic string `json:"pic"` |
| 26 | + Title string `json:"title"` |
| 27 | + Pubdate int `json:"pubdate"` |
| 28 | + Ctime int `json:"ctime"` |
| 29 | + Rights struct { |
| 30 | + IsCooperation int `json:"is_cooperation"` |
| 31 | + } `json:"rights"` |
| 32 | + Owner struct { |
| 33 | + Mid int `json:"mid"` |
| 34 | + Name string `json:"name"` |
| 35 | + } `json:"owner"` |
| 36 | + Stat struct { |
| 37 | + Aid int `json:"aid"` |
| 38 | + View int `json:"view"` |
| 39 | + Danmaku int `json:"danmaku"` |
| 40 | + Reply int `json:"reply"` |
| 41 | + Favorite int `json:"favorite"` |
| 42 | + Coin int `json:"coin"` |
| 43 | + Share int `json:"share"` |
| 44 | + Like int `json:"like"` |
| 45 | + } `json:"stat"` |
| 46 | + Staff []struct { |
| 47 | + Title string `json:"title"` |
| 48 | + Name string `json:"name"` |
| 49 | + Follower int `json:"follower"` |
| 50 | + } `json:"staff"` |
| 51 | + } `json:"data"` |
| 52 | +} |
| 53 | + |
| 54 | +type owner struct { |
| 55 | + Data struct { |
| 56 | + Card struct { |
| 57 | + Fans int `json:"fans"` |
| 58 | + } `json:"data"` |
| 59 | + } |
| 60 | +} |
| 61 | + |
16 | 62 | const (
|
17 |
| - validRe = "https://www.bilibili.com/video/(BV[0-9a-zA-Z]+)" |
| 63 | + videoapi = "https://api.bilibili.com/x/web-interface/view?" |
| 64 | + cardapi = "http://api.bilibili.com/x/web-interface/card?" |
| 65 | + origin = "https://www.bilibili.com/video/" |
18 | 66 | )
|
19 | 67 |
|
20 |
| -var re = regexp.MustCompile(validRe) |
| 68 | +var ( |
| 69 | + reg = regexp.MustCompile(`https://www.bilibili.com/video/([0-9a-zA-Z]+)`) |
| 70 | + limit = ctxext.NewLimiterManager(time.Second*10, 1) |
| 71 | +) |
21 | 72 |
|
| 73 | +// 插件主体 |
22 | 74 | func init() {
|
23 |
| - engine := control.Register("bilibiliparse", &ctrl.Options[*zero.Ctx]{ |
| 75 | + en := control.Register("bilibiliparse", &ctrl.Options[*zero.Ctx]{ |
24 | 76 | DisableOnDefault: false,
|
25 | 77 | Help: "b站视频链接解析\n" +
|
26 | 78 | "- https://www.bilibili.com/video/BV1xx411c7BF | https://www.bilibili.com/video/av1605 | https://b23.tv/I8uzWCA | https://www.bilibili.com/video/bv1xx411c7BF",
|
27 | 79 | })
|
| 80 | + en.OnRegex(`(av[0-9]+|BV[0-9a-zA-Z]{10}){1}`).SetBlock(true).Limit(limit.LimitByGroup). |
| 81 | + Handle(func(ctx *zero.Ctx) { |
| 82 | + id := ctx.State["regex_matched"].([]string)[1] |
| 83 | + m, err := parse(id) |
| 84 | + if err != nil { |
| 85 | + ctx.SendChain(message.Text("ERROR: ", err)) |
| 86 | + return |
| 87 | + } |
| 88 | + ctx.Send(m) |
| 89 | + }) |
| 90 | + en.OnRegex(`https://www.bilibili.com/video/([0-9a-zA-Z]+)`).SetBlock(true).Limit(limit.LimitByGroup). |
| 91 | + Handle(func(ctx *zero.Ctx) { |
| 92 | + id := ctx.State["regex_matched"].([]string)[1] |
| 93 | + m, err := parse(id) |
| 94 | + if err != nil { |
| 95 | + ctx.SendChain(message.Text("ERROR: ", err)) |
| 96 | + return |
| 97 | + } |
| 98 | + ctx.Send(m) |
| 99 | + }) |
| 100 | + en.OnRegex(`(https://b23.tv/[0-9a-zA-Z]+)`).SetBlock(true).Limit(limit.LimitByGroup). |
| 101 | + Handle(func(ctx *zero.Ctx) { |
| 102 | + url := ctx.State["regex_matched"].([]string)[1] |
| 103 | + realurl, err := getrealurl(url) |
| 104 | + if err != nil { |
| 105 | + ctx.SendChain(message.Text("ERROR: ", err)) |
| 106 | + return |
| 107 | + } |
| 108 | + id, err := cuturl(realurl) |
| 109 | + if err != nil { |
| 110 | + ctx.SendChain(message.Text("ERROR: ", err)) |
| 111 | + return |
| 112 | + } |
| 113 | + m, err := parse(id) |
| 114 | + if err != nil { |
| 115 | + ctx.SendChain(message.Text("ERROR: ", err)) |
| 116 | + return |
| 117 | + } |
| 118 | + ctx.Send(m) |
| 119 | + }) |
| 120 | +} |
28 | 121 |
|
29 |
| - engine.OnRegex("(av[0-9]+|BV[0-9a-zA-Z]+|https://b23.tv/[0-9a-zA-Z]+|bv[0-9a-zA-Z]+){1}").SetBlock(true).Handle(func(ctx *zero.Ctx) { |
30 |
| - bilibiliURL := ctx.State["regex_matched"].([]string)[1] |
31 |
| - if bilibiliURL[0] != 'h' { |
32 |
| - bilibiliURL = "https://www.bilibili.com/video/" + bilibiliURL |
| 122 | +// parse 解析视频数据 |
| 123 | +func parse(id string) (m message.Message, err error) { |
| 124 | + var vid string |
| 125 | + switch id[:2] { |
| 126 | + case "av": |
| 127 | + vid = "aid=" + id[2:] |
| 128 | + case "BV": |
| 129 | + vid = "bvid=" + id |
| 130 | + } |
| 131 | + data, err := web.GetData(videoapi + vid) |
| 132 | + if err != nil { |
| 133 | + return |
| 134 | + } |
| 135 | + var r result |
| 136 | + err = json.Unmarshal(data, &r) |
| 137 | + if err != nil { |
| 138 | + return |
| 139 | + } |
| 140 | + m = make(message.Message, 0, 16) |
| 141 | + m = append(m, message.Text("标题:", r.Data.Title, "\n")) |
| 142 | + if r.Data.Rights.IsCooperation == 1 { |
| 143 | + for i := 0; i < len(r.Data.Staff); i++ { |
| 144 | + if i != len(r.Data.Staff) { |
| 145 | + m = append(m, message.Text(r.Data.Staff[i].Title, ":", r.Data.Staff[i].Name, ",粉丝:", r.Data.Staff[i].Follower, "\n")) |
| 146 | + } else { |
| 147 | + m = append(m, message.Text(r.Data.Staff[i].Title, ":", r.Data.Staff[i].Name, ",粉丝:", r.Data.Staff[i].Follower)) |
| 148 | + } |
33 | 149 | }
|
34 |
| - m, err := parseURL(bilibiliURL) |
| 150 | + } else { |
| 151 | + o, err := getcard(r.Data.Owner.Mid) |
35 | 152 | if err != nil {
|
36 |
| - ctx.SendChain(message.Text("ERROR:", err)) |
37 |
| - return |
| 153 | + return m, err |
38 | 154 | }
|
39 |
| - ctx.Send(m) |
40 |
| - }) |
| 155 | + m = append(m, message.Text("\nUP主:", r.Data.Owner.Name, ",粉丝:", o.Data.Card.Fans, "\n")) |
| 156 | + } |
| 157 | + m = append(m, message.Text("播放:", r.Data.Stat.View, ",弹幕:", r.Data.Stat.Danmaku, "\n"), |
| 158 | + message.Image(r.Data.Pic), |
| 159 | + message.Text("\n点赞:", r.Data.Stat.Like, ",投币:", r.Data.Stat.Coin, "\n收藏:", r.Data.Stat.Favorite, ",分享:", r.Data.Stat.Share, "\n", origin, id)) |
| 160 | + return |
41 | 161 | }
|
42 | 162 |
|
43 |
| -func parseURL(bilibiliURL string) (m message.Message, err error) { |
44 |
| - doc, err := htmlquery.LoadURL(bilibiliURL) |
| 163 | +// getrealurl 获取跳转后的链接 |
| 164 | +func getrealurl(url string) (realurl string, err error) { |
| 165 | + data, err := http.Head(url) |
45 | 166 | if err != nil {
|
46 | 167 | return
|
47 | 168 | }
|
48 |
| - videoURL := htmlquery.FindOne(doc, "/html/head/meta[@itemprop='url']").Attr[2].Val |
49 |
| - if !re.MatchString(videoURL) { |
50 |
| - err = errors.New("parse html error: invalid video url") |
| 169 | + realurl = data.Request.URL.String() |
| 170 | + return |
| 171 | +} |
| 172 | + |
| 173 | +// cuturl 获取aid或者bvid |
| 174 | +func cuturl(url string) (id string, err error) { |
| 175 | + if !reg.MatchString(url) { |
| 176 | + err = errors.New("invalid video url") |
| 177 | + return |
| 178 | + } |
| 179 | + id = reg.FindStringSubmatch(url)[1] |
| 180 | + return |
| 181 | +} |
| 182 | + |
| 183 | +// getcard 获取个人信息 |
| 184 | +func getcard(mid int) (o owner, err error) { |
| 185 | + data, err := web.GetData(cardapi + "mid=" + strconv.Itoa(mid)) |
| 186 | + if err != nil { |
51 | 187 | return
|
52 | 188 | }
|
53 |
| - m = make(message.Message, 0, 9) |
54 |
| - title := htmlquery.FindOne(doc, "//*[@id='viewbox_report']/h1/span/text()").Data |
55 |
| - m = append(m, message.Text(title, "\n")) |
56 |
| - upName := strings.TrimSpace(htmlquery.FindOne(doc, "//*[@id='v_upinfo']/div[2]/div[1]/a[1]/text()").Data) |
57 |
| - fanNumber := htmlquery.InnerText(htmlquery.FindOne(doc, "//i[@class='van-icon-general_addto_s']").NextSibling.NextSibling) |
58 |
| - m = append(m, message.Text("UP: ", upName, ", 粉丝: ", fanNumber, "\n")) |
59 |
| - view := htmlquery.FindOne(doc, "//*[@id='viewbox_report']/div/span[@class='view']/text()").Data |
60 |
| - dm := htmlquery.FindOne(doc, "//*[@id='viewbox_report']/div/span[@class='dm']/text()").Data |
61 |
| - m = append(m, message.Text(view, dm, "\n")) |
62 |
| - t := htmlquery.FindOne(doc, "//*[@id='viewbox_report']/div/span[3]/text()").Data |
63 |
| - m = append(m, message.Text(t)) |
64 |
| - image := htmlquery.FindOne(doc, "/html/head/meta[@itemprop='image']").Attr[2].Val |
65 |
| - m = append(m, message.Image(image)) |
66 |
| - like := htmlquery.FindOne(doc, "//*[@id='arc_toolbar_report']/div[1]/span[@class='like']/text()").Data |
67 |
| - coin := htmlquery.FindOne(doc, "//*[@id='arc_toolbar_report']/div[1]/span[@class='coin']/text()").Data |
68 |
| - m = append(m, message.Text("\n点赞: ", strings.TrimSpace(like), ", 投币: ", strings.TrimSpace(coin), "\n")) |
69 |
| - collect := htmlquery.FindOne(doc, "//*[@id='arc_toolbar_report']/div[1]/span[@class='collect']/text()").Data |
70 |
| - share := htmlquery.FindOne(doc, "//*[@id='arc_toolbar_report']/div[1]/span[@class='share']/text()").Data |
71 |
| - m = append(m, message.Text("收藏: ", strings.TrimSpace(collect), ", 分享: ", strings.TrimSpace(share), "\n")) |
72 |
| - play := htmlquery.FindOne(doc, "//*[@id='viewbox_report']/div/span[1]/text()").Data |
73 |
| - danmaku := htmlquery.FindOne(doc, "//*[@id='viewbox_report']/div/span[2]/text()").Data |
74 |
| - m = append(m, message.Text(strings.TrimSpace(play), "播放, ", strings.TrimSpace(danmaku), "弹幕\n")) |
75 |
| - m = append(m, message.Text(videoURL)) |
| 189 | + err = json.Unmarshal(data, &o) |
76 | 190 | return
|
77 | 191 | }
|
0 commit comments