feat:添加b站推送功能 (#106)

* feat:添加b站推送,写了一半

* feat:添加b站推送(后续看情况,替换掉chromepd)

* fix:修复一直推送直播的bug

* fix:添加缓存路径

* feat:添加推送列表,修复重复发消息的bug

* feat:发现禁用有用,删除多余指令

* fix:修改格式

* fix:sleep无效

* fix:修改直播card

* fix:增加长度

* fix:修改readme

* fix:修lint,并去掉cron

* fix:修lint

* fix:去掉日志

Co-authored-by: Guohuiyuan <haibaraguo@yeahka.com>
This commit is contained in:
himawari
2022-01-14 22:17:24 +08:00
committed by GitHub
parent af8e6a63b9
commit cb1e4bcdb7
8 changed files with 672 additions and 2 deletions

View File

@@ -0,0 +1,448 @@
// Package bilibilipush b站推送
package bilibilipush
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/FloatTech/zbputils/control"
"github.com/FloatTech/zbputils/ctxext"
"github.com/FloatTech/zbputils/file"
"github.com/FloatTech/zbputils/txt2img"
"github.com/FloatTech/zbputils/web"
"github.com/chromedp/chromedp"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
zero "github.com/wdvxdr1123/ZeroBot"
"github.com/wdvxdr1123/ZeroBot/message"
"github.com/wdvxdr1123/ZeroBot/utils/helper"
"io"
"io/ioutil"
"net/http"
"strconv"
"time"
)
const (
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36"
referer = "https://www.bilibili.com/"
infoURL = "https://api.bilibili.com/x/space/acc/info?mid=%d"
userDynamicsURL = "https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history?host_uid=%d&offset_dynamic_id=0&need_top=0"
liveListURL = "https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids"
tURL = "https://t.bilibili.com/"
liveURL = "https://live.bilibili.com/"
prio = 10
serviceName = "bilibilipush"
)
var (
lastTime = map[int64]int64{}
typeMsg = map[int64]string{
0: "发布了新动态",
1: "转发了一条动态",
2: "发布了新动态",
4: "发布了新动态",
8: "发布了新投稿",
16: "发布了短视频",
64: "发布了新专栏",
256: "发布了新音频",
}
liveStatus = map[int64]int{}
uidErrorMsg = map[int]string{
0: "输入的uid有效",
-400: "uid不存在注意uid不是房间号",
-402: "uid不存在注意uid不是房间号",
-412: "操作过于频繁IP暂时被风控请半小时后再尝试",
}
upMap = map[int64]string{}
)
func init() {
go bilibiliPushDaily()
en := control.Register(serviceName, &control.Options{
DisableOnDefault: false,
Help: "bilibilipush\n" +
"- 添加订阅[uid]\n" +
"- 取消订阅[uid]\n" +
"- 取消动态订阅[uid]\n" +
"- 取消直播订阅[uid]\n" +
"- 推送列表",
})
en.OnRegex(`^添加订阅(\d+)$`, ctxext.UserOrGrpAdmin).SetBlock(true).SetPriority(prio).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
var name string
var ok bool
if name, ok = upMap[buid]; !ok {
var status int
status, name = checkBuid(buid)
if status != 0 {
msg, ok := uidErrorMsg[status]
if !ok {
msg = "未知错误,请私聊反馈给" + zero.BotConfig.NickName[0]
}
ctx.SendChain(message.Text(msg))
return
}
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := subscribe(buid, gid); err != nil {
log.Errorln("[bilibilipush]:", err)
} else {
ctx.SendChain(message.Text("已添加" + name + "的订阅"))
}
})
en.OnRegex(`^取消订阅(\d+)$`, ctxext.UserOrGrpAdmin).SetBlock(true).SetPriority(prio).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
var name string
var ok bool
if name, ok = upMap[buid]; !ok {
var status int
status, name = checkBuid(buid)
if status != 0 {
msg, ok := uidErrorMsg[status]
if !ok {
msg = "未知错误,请私聊反馈给" + zero.BotConfig.NickName[0]
}
ctx.SendChain(message.Text(msg))
return
}
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := unsubscribe(buid, gid); err != nil {
log.Errorln("[bilibilipush]:", err)
} else {
ctx.SendChain(message.Text("已取消" + name + "的订阅"))
}
})
en.OnRegex(`^取消动态订阅(\d+)$`, ctxext.UserOrGrpAdmin).SetBlock(true).SetPriority(prio).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
var name string
var ok bool
if name, ok = upMap[buid]; !ok {
var status int
status, name = checkBuid(buid)
if status != 0 {
msg, ok := uidErrorMsg[status]
if !ok {
msg = "未知错误,请私聊反馈给" + zero.BotConfig.NickName[0]
}
ctx.SendChain(message.Text(msg))
return
}
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := unsubscribeDynamic(buid, gid); err != nil {
log.Errorln("[bilibilipush]:", err)
} else {
ctx.SendChain(message.Text("已取消" + name + "的动态订阅"))
}
})
en.OnRegex(`^取消直播订阅(\d+)$`, ctxext.UserOrGrpAdmin).SetBlock(true).SetPriority(prio).Handle(func(ctx *zero.Ctx) {
buid, _ := strconv.ParseInt(ctx.State["regex_matched"].([]string)[1], 10, 64)
var name string
var ok bool
if name, ok = upMap[buid]; !ok {
var status int
status, name = checkBuid(buid)
if status != 0 {
msg, ok := uidErrorMsg[status]
if !ok {
msg = "未知错误,请私聊反馈给" + zero.BotConfig.NickName[0]
}
ctx.SendChain(message.Text(msg))
return
}
}
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
if err := unsubscribeLive(buid, gid); err != nil {
log.Errorln("[bilibilipush]:", err)
} else {
ctx.SendChain(message.Text("已取消" + name + "的直播订阅"))
}
})
en.OnFullMatch("推送列表", ctxext.UserOrGrpAdmin).SetBlock(true).SetPriority(prio).Handle(func(ctx *zero.Ctx) {
gid := ctx.Event.GroupID
if gid == 0 {
gid = -ctx.Event.UserID
}
bpl := bdb.getAllPushByGroup(gid)
fmt.Println(bpl)
msg := "--------推送列表--------"
for _, v := range bpl {
if _, ok := upMap[v.BilibiliUID]; !ok {
bdb.updateAllUp()
fmt.Println(upMap)
}
msg += fmt.Sprintf("\nuid:%-12d 动态:", v.BilibiliUID)
if v.DynamicDisable == 0 {
msg += "●"
} else {
msg += "○"
}
msg += " 直播:"
if v.LiveDisable == 0 {
msg += "●"
} else {
msg += "○"
}
msg += " up主" + upMap[v.BilibiliUID]
}
data, err := txt2img.RenderToBase64(msg, txt2img.FontFile, 600, 20)
if err != nil {
log.Errorln("[bilibilipush]:", err)
}
if id := ctx.SendChain(message.Image("base64://" + helper.BytesToString(data))); id == 0 {
ctx.SendChain(message.Text("ERROR: 可能被风控了"))
}
})
}
func bilibiliPushDaily() {
t := time.NewTicker(time.Second * 10)
defer t.Stop()
for range t.C {
log.Println("-----bilibilipush拉取推送信息-----")
sendDynamic()
sendLive()
}
}
func checkBuid(buid int64) (status int, name string) {
data, err := web.ReqWith(fmt.Sprintf(infoURL, buid), "GET", referer, ua)
if err != nil {
log.Errorln("[bilibilipush]:", err)
}
status = int(gjson.Get(helper.BytesToString(data), "code").Int())
name = gjson.Get(helper.BytesToString(data), "data.name").String()
if status == 0 {
bdb.insertBilibiliUp(buid, name)
upMap[buid] = name
}
return
}
// subscribe 订阅
func subscribe(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"live_disable": 0,
"dynamic_disable": 0,
}
err = bdb.insertOrUpdateLiveAndDynamic(bpMap)
return
}
// unsubscribe 取消订阅
func unsubscribe(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"live_disable": 1,
"dynamic_disable": 1,
}
err = bdb.insertOrUpdateLiveAndDynamic(bpMap)
return
}
func unsubscribeDynamic(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"dynamic_disable": 1,
}
err = bdb.insertOrUpdateLiveAndDynamic(bpMap)
return
}
func unsubscribeLive(buid, groupid int64) (err error) {
bpMap := map[string]interface{}{
"bilibili_uid": buid,
"group_id": groupid,
"live_disable": 1,
}
err = bdb.insertOrUpdateLiveAndDynamic(bpMap)
return
}
func getUserDynamicCard(buid int64) (cardList []gjson.Result) {
data, err := web.ReqWith(fmt.Sprintf(userDynamicsURL, buid), "GET", referer, ua)
if err != nil {
log.Errorln("[bilibilipush]:", err)
}
cardList = gjson.Get(helper.BytesToString(data), "data.cards").Array()
return
}
func getLiveList(uids ...int64) string {
m := make(map[string]interface{})
m["uids"] = uids
b, _ := json.Marshal(m)
client := &http.Client{}
// 提交请求
request, err := http.NewRequest("POST", liveListURL, bytes.NewBuffer(b))
if err != nil {
log.Errorln("[bilibilipush]:", err)
}
request.Header.Add("Referer", referer)
request.Header.Add("User-Agent", ua)
response, err := client.Do(request)
if err != nil {
log.Errorln("[bilibilipush]:", err)
}
defer response.Body.Close()
data, err := io.ReadAll(response.Body)
if err != nil {
log.Errorln("[bilibilipush]:", err)
}
return helper.BytesToString(data)
}
func sendDynamic() {
uids := bdb.getAllBuidByDynamic()
for _, buid := range uids {
cardList := getUserDynamicCard(buid)
if len(cardList) == 0 {
return
}
t, ok := lastTime[buid]
if !ok {
lastTime[buid] = cardList[0].Get("desc.timestamp").Int()
return
}
for i := len(cardList) - 1; i >= 0; i-- {
ct := cardList[i].Get("desc.timestamp").Int()
if ct > t && ct > time.Now().Unix()-600 {
lastTime[buid] = ct
m, ok := control.Lookup(serviceName)
if ok {
groupList := bdb.getAllGroupByBuidAndDynamic(buid)
cID := cardList[i].Get("desc.dynamic_id").String()
cType := cardList[i].Get("desc.type").Int()
cName := cardList[i].Get("desc.user_profile.info.uname").String()
screenshotFile := cachePath + cID + ".png"
initDynamicScreenshot(cID)
var msg []message.MessageSegment
msg = append(msg, message.Text(cName+typeMsg[cType]))
msg = append(msg, message.Image("file:///"+file.BOTPATH+"/"+screenshotFile))
msg = append(msg, message.Text(tURL+cID))
zero.RangeBot(func(id int64, ctx *zero.Ctx) bool {
for _, gid := range groupList {
if m.IsEnabledIn(gid) {
if gid > 0 {
ctx.SendGroupMessage(gid, msg)
} else if gid < 0 {
ctx.SendPrivateMessage(-gid, msg)
} else {
log.Errorln("[bilibilipush]:gid为0")
}
}
}
return true
})
}
}
}
}
}
func sendLive() {
uids := bdb.getAllBuidByLive()
gjson.Get(getLiveList(uids...), "data").ForEach(func(key, value gjson.Result) bool {
newStatus := int(value.Get("live_status").Int())
if newStatus == 2 {
newStatus = 0
}
if _, ok := liveStatus[key.Int()]; !ok {
liveStatus[key.Int()] = newStatus
return true
}
oldStatus := liveStatus[key.Int()]
if newStatus != oldStatus && newStatus == 1 {
liveStatus[key.Int()] = newStatus
m, ok := control.Lookup(serviceName)
if ok {
groupList := bdb.getAllGroupByBuidAndLive(key.Int())
roomID := value.Get("short_id").Int()
if roomID == 0 {
roomID = value.Get("room_id").Int()
}
lURL := liveURL + strconv.FormatInt(roomID, 10)
lName := value.Get("uname").String()
lTitle := value.Get("title").String()
lCover := value.Get("cover_from_user").String()
if lCover == "" {
lCover = value.Get("keyframe").String()
}
var msg []message.MessageSegment
msg = append(msg, message.Text(lName+" 正在直播:\n"))
msg = append(msg, message.Text(lTitle))
msg = append(msg, message.Image(lCover))
msg = append(msg, message.Text(lURL))
zero.RangeBot(func(id int64, ctx *zero.Ctx) bool {
for _, gid := range groupList {
if m.IsEnabledIn(gid) {
if gid > 0 {
ctx.SendGroupMessage(gid, msg)
} else if gid < 0 {
ctx.SendPrivateMessage(-gid, msg)
} else {
log.Errorln("[bilibilipush]:gid为0")
}
}
}
return true
})
}
}
return true
})
}
func initDynamicScreenshot(dynamicID string) {
screenshotFile := cachePath + dynamicID + ".png"
if file.IsNotExist(screenshotFile) {
var imageBuf []byte
dynamicURL := tURL + dynamicID
ctx, cancel := chromedp.NewContext(context.Background(), chromedp.WithDebugf(log.Printf))
defer cancel()
if err := chromedp.Run(ctx, chromedp.Tasks{
chromedp.Navigate(dynamicURL),
chromedp.SetAttributeValue(`div.unlogin-popover-avatar`, "style", "display:none;", chromedp.ByQuery),
chromedp.SetAttributeValue(`div.bb-comment`, "style", "display:none;", chromedp.ByQuery),
chromedp.Screenshot(`.card`, &imageBuf, chromedp.NodeVisible, chromedp.ByQuery),
}); err != nil {
log.Errorln("[bilibilipush]:", err)
}
if err := ioutil.WriteFile(screenshotFile, imageBuf, 0644); err != nil {
log.Errorln("[bilibilipush]:", err)
}
}
}

View File

@@ -0,0 +1,28 @@
package bilibilipush
import (
"github.com/FloatTech/zbputils/process"
log "github.com/sirupsen/logrus"
"os"
)
const (
cachePath = dbpath + "cache/"
dbpath = "data/bilibilipush/"
dbfile = dbpath + "push.db"
)
// bdb bilibili推送数据库
var bdb *bilibilipushdb
// 加载数据库
func init() {
go func() {
process.SleepAbout1sTo2s()
_ = os.MkdirAll(dbpath, 0755)
os.RemoveAll(cachePath)
_ = os.MkdirAll(cachePath, 0755)
bdb = initialize(dbfile)
log.Println("[bilibilipush]加载bilibilipush数据库")
}()
}

View File

@@ -0,0 +1,158 @@
package bilibilipush
import (
"encoding/json"
"github.com/jinzhu/gorm"
_ "github.com/logoove/sqlite" // import sql
"os"
)
// bilibilipushdb bili推送数据库
type bilibilipushdb gorm.DB
type bilibilipush struct {
ID int64 `gorm:"column:id;primary_key" json:"id"`
BilibiliUID int64 `gorm:"column:bilibili_uid;index:idx_buid_gid" json:"bilibili_uid"`
GroupID int64 `gorm:"column:group_id;index:idx_buid_gid" json:"group_id"`
LiveDisable int64 `gorm:"column:live_disable;default:0" json:"live_disable"`
DynamicDisable int64 `gorm:"column:dynamic_disable;default:0" json:"dynamic_disable"`
}
// TableName ...
func (bilibilipush) TableName() string {
return "bilibili_push"
}
type bilibiliup struct {
BilibiliUID int64 `gorm:"column:bilibili_uid;primary_key"`
Name string `gorm:"column:name"`
}
// TableName ...
func (bilibiliup) TableName() string {
return "bilibili_up"
}
// initialize 初始化ScoreDB数据库
func initialize(dbpath string) *bilibilipushdb {
var err error
if _, err = os.Stat(dbpath); err != nil || os.IsNotExist(err) {
// 生成文件
f, err := os.Create(dbpath)
if err != nil {
return nil
}
defer f.Close()
}
gdb, err := gorm.Open("sqlite3", dbpath)
if err != nil {
panic(err)
}
gdb.AutoMigrate(&bilibilipush{}).AutoMigrate(&bilibiliup{})
return (*bilibilipushdb)(gdb)
}
// open ...
func open(dbpath string) (*bilibilipushdb, error) {
db, err := gorm.Open("sqlite3", dbpath)
if err != nil {
return nil, err
}
return (*bilibilipushdb)(db), nil
}
// close ...
func (bdb *bilibilipushdb) close() error {
db := (*gorm.DB)(bdb)
return db.Close()
}
// insertOrUpdateLiveAndDynamic 插入或更新数据库
func (bdb *bilibilipushdb) insertOrUpdateLiveAndDynamic(bpMap map[string]interface{}) (err error) {
db := (*gorm.DB)(bdb)
bp := bilibilipush{}
data, _ := json.Marshal(&bpMap)
_ = json.Unmarshal(data, &bp)
if err = db.Debug().Model(&bilibilipush{}).First(&bp, "bilibili_uid = ? and group_id = ?", bp.BilibiliUID, bp.GroupID).Error; err != nil {
if gorm.IsRecordNotFoundError(err) {
err = db.Debug().Model(&bilibilipush{}).Create(&bp).Error
}
} else {
err = db.Debug().Model(&bilibilipush{}).Where("bilibili_uid = ? and group_id = ?", bp.BilibiliUID, bp.GroupID).Update(bpMap).Error
}
return
}
func (bdb *bilibilipushdb) getAllBuidByLive() (buidList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Debug().Model(&bilibilipush{}).Find(&bpl, "live_disable = 0")
temp := make(map[int64]bool)
for _, v := range bpl {
_, ok := temp[v.BilibiliUID]
if !ok {
buidList = append(buidList, v.BilibiliUID)
temp[v.BilibiliUID] = true
}
}
return
}
func (bdb *bilibilipushdb) getAllBuidByDynamic() (buidList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Debug().Model(&bilibilipush{}).Find(&bpl, "dynamic_disable = 0")
temp := make(map[int64]bool)
for _, v := range bpl {
_, ok := temp[v.BilibiliUID]
if !ok {
buidList = append(buidList, v.BilibiliUID)
temp[v.BilibiliUID] = true
}
}
return
}
func (bdb *bilibilipushdb) getAllGroupByBuidAndLive(buid int64) (groupList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Debug().Model(&bilibilipush{}).Find(&bpl, "bilibili_uid = ? and live_disable = 0", buid)
for _, v := range bpl {
groupList = append(groupList, v.GroupID)
}
return
}
func (bdb *bilibilipushdb) getAllGroupByBuidAndDynamic(buid int64) (groupList []int64) {
db := (*gorm.DB)(bdb)
var bpl []bilibilipush
db.Debug().Model(&bilibilipush{}).Find(&bpl, "bilibili_uid = ? and dynamic_disable = 0", buid)
for _, v := range bpl {
groupList = append(groupList, v.GroupID)
}
return
}
func (bdb *bilibilipushdb) getAllPushByGroup(groupID int64) (bpl []bilibilipush) {
db := (*gorm.DB)(bdb)
db.Debug().Model(&bilibilipush{}).Find(&bpl, "group_id = ? and (live_disable = 0 or dynamic_disable = 0)", groupID)
return
}
func (bdb *bilibilipushdb) insertBilibiliUp(buid int64, name string) {
db := (*gorm.DB)(bdb)
bu := bilibiliup{
BilibiliUID: buid,
Name: name,
}
db.Debug().Model(&bilibiliup{}).Create(bu)
}
func (bdb *bilibilipushdb) updateAllUp() {
db := (*gorm.DB)(bdb)
var bul []bilibiliup
db.Debug().Model(&bilibiliup{}).Find(&bul)
for _, v := range bul {
upMap[v.BilibiliUID] = v.Name
}
}