显示
case 后有函数调用的为支持,为nil的为待完善,注释掉的调用为未启用
-var Msg_map = map[string]func(replayF, string) {
+//Msg类型数据处理方法map
+var Msg_map = map[string]func(replyF, string) {
+ "PK_BATTLE_PRE":nil,//人气pk
+ "PK_BATTLE_START":nil,//人气pk
+ "PK_BATTLE_PROCESS":nil,//人气pk
+ "PK_BATTLE_END":nil,//人气pk
+ "PK_BATTLE_RANK_CHANGE":nil,//人气pk
+ "PK_BATTLE_SETTLE_USER":nil,//人气pk
+ "PK_BATTLE_SETTLE_V2":nil,//人气pk
+ "PK_BATTLE_SETTLE":nil,//人气pk
+ "SYS_MSG":nil,//系统消息
"ROOM_SKIN_MSG":nil,
"GUARD_ACHIEVEMENT_ROOM":nil,
"ANCHOR_LOT_START":nil,//天选之人开始
"HOUR_RANK_AWARDS":nil,
"ROOM_RANK":nil,
"ROOM_SHIELD":nil,
- "USER_TOAST_MSG":nil,
- "WIN_ACTIVITY":nil,
- "GUARD_BUY":replayF.guard_buy,//大航海购买
- "WELCOME_GUARD":replayF.welcome_guard,//大航海进入
- "DANMU_MSG":replayF.danmu,//弹幕
- "ROOM_CHANGE":replayF.room_change,//房间信息分区改变
- "ROOM_SILENT_OFF":replayF.roomsilent,//禁言结束
- "ROOM_SILENT_ON":replayF.roomsilent,//禁言开始
- "SEND_GIFT":replayF.send_gift,//礼物
- "ROOM_BLOCK_MSG":replayF.room_block_msg,//封禁
- "PREPARING":replayF.preparing,//下播
- "LIVE":replayF.live,//开播
- "SUPER_CHAT_MESSAGE":nil,//replayF.super_chat_message,//打赏
- "SUPER_CHAT_MESSAGE_JPN":replayF.super_chat_message,//打赏
- "PANEL":replayF.panel,//排行榜
- "ENTRY_EFFECT":nil,//replayF.entry_effect,//进入特效
- "ROOM_REAL_TIME_MESSAGE_UPDATE":nil,//replayF.roominfo,//粉丝数
+ "USER_TOAST_MSG":replyF.user_toast_msg,//大航海购买信息
+ "WIN_ACTIVITY":replyF.win_activity,//活动
+ "SPECIAL_GIFT":replyF.special_gift,//节奏风暴
+ "GUARD_BUY":nil,//replyF.guard_buy,//大航海购买
+ "WELCOME_GUARD":replyF.welcome_guard,//大航海进入
+ "DANMU_MSG":replyF.danmu,//弹幕
+ "ROOM_CHANGE":replyF.room_change,//房间信息分区改变
+ "ROOM_SILENT_OFF":replyF.roomsilent,//禁言结束
+ "ROOM_SILENT_ON":replyF.roomsilent,//禁言开始
+ "SEND_GIFT":replyF.send_gift,//礼物
+ "ROOM_BLOCK_MSG":replyF.room_block_msg,//封禁
+ "PREPARING":replyF.preparing,//下播
+ "LIVE":replyF.live,//开播
+ "SUPER_CHAT_MESSAGE":nil,//replyF.super_chat_message,//SC
+ "SUPER_CHAT_MESSAGE_JPN":replyF.super_chat_message,//SC
+ "PANEL":replyF.panel,//排行榜
+ "ENTRY_EFFECT":nil,//replyF.entry_effect,//进入特效
+ "ROOM_REAL_TIME_MESSAGE_UPDATE":nil,//replyF.roominfo,//粉丝数
}
```
以下内容可能过时,点击查看[当前支持功能](https://github.com/qydysky/bili_danmu/blob/master/Reply/F.go#L16)
obs https://obsproject.com/download
obs-websocket https://github.com/Palakis/obs-websocket/releases
*/
+ "Ass":true,//Ass弹幕生成,由于时间对应关系,仅开启流保存时生效
"Autoban":true,//自动封禁(仅提示,未完成)
"Jiezou":true,//带节奏预警,提示弹幕礼仪
"Danmuji":true,//反射型弹幕机,回应弹幕
go run main.go -r=此处填房间ID
```
以下内容可能过时,以实际运行为准
+- 命令窗口(以下为截取)
```
$ go run main.go
输入房间号: 213
INFO: 2020/09/16 16:48:11 [bili_danmu.go 测试] [连接 wss://tx-sh-live-comet-01.chat.bilibili.com/sub]
INFO: 2020/09/16 16:48:11 [bili_danmu.go 测试] [已连接到房间 213]
INFO: 2020/09/16 16:48:11 [bili_danmu.go 测试] [开始心跳]
+```
+```
+//大航海进入
>>> 欢迎 舰长 茶摊儿在森林喝碗山海 进入直播间
+```
+```
+//普通弹幕
老鸡捉小鹰
你快扒拉他
你这好像是补刀
吓人
+```
+```
+//礼物
====
孤单猫与淋雨猪 投喂 1314 x 辣条 ( 131400 x 金瓜子 )
====
+```
+```
+//同字符串合并
7 x 原神公测B服冲冲冲
+```
+```
+//同字符忽略
原神公测B站冲冲冲
...B服冲冲冲
-
-ctrl+c退出,日志会同时追加记录到文件danmu.log中(文件记录完整信息,不会减少附加功能作用的弹幕)
```
+```
+//SC
+====
+SC: 吹舞火 ¥ 30
+我旁边的一万是幻觉吗?
+私の隣の一万は幻ですか?
+====
+```
+ctrl+c退出,会同时追加记录到文件danmu.log中(文件记录完整信息,不会减少附加功能作用的弹幕)
+- 流保存以及弹幕ass
+```
+结束后会保存为
+房间号_时间.mkv
+房间号_时间.ass
+```
+结束后的文件播放效果(显于左上)
+
+
更多内容详见注释,如有疑问请发issues,欢迎pr
p "github.com/qydysky/part"
)
+/*
+ F额外功能区
+*/
+
//功能开关
var AllF = map[string]bool{
"Saveflv":true,//保存直播流(仅高清)
obs https://obsproject.com/download
obs-websocket https://github.com/Palakis/obs-websocket/releases
*/
- "Ass":true,//Ass弹幕生成
+ "Ass":true,//Ass弹幕生成,由于时间对应关系,仅开启流保存时生效
"Autoban":true,//自动封禁(仅提示,未完成)
"Jiezou":true,//带节奏预警,提示弹幕礼仪
"Danmuji":true,//反射型弹幕机,回应弹幕
"Shortdanmu":true,//上下文相同文字缩减
}
+//功能开关选取函数
func IsOn(s string) bool {
if v, ok := AllF[s]; ok && v {
return true
}
return false
}
-//公共
+
+//字符重复度检查
+//a在buf中出现的字符占a的百分数
func cross(a string,buf []string) (float32) {
var s float32
var matched bool
}
return s / float32(len([]rune(a)))
}
+
+//在a中仅出现一次出现的字符占a的百分数
func selfcross(a string) (float32) {
buf := make(map[rune]bool)
for _,v := range a {
}
return 1 - float32(len(buf)) / float32(len([]rune(a)))
}
+
+//在a的每个字符串中
+//出现的字符次数最多的
+//占出现的字符总数的百分数
+//*单字符串中的重复出现计为1次
func selfcross2(a []string) (float32, string) {
buf := make(map[rune]float32)
for _,v := range a {
}
return max / all, maxS
}
+
//功能区
+
+//Ass 弹幕转字幕
type Ass struct {
Inuse bool
- file string
- startT time.Time
- header string
- rtb [7]time.Duration//通道是否被字节占用
- ri int
+ file string//弹幕ass文件名
+ startT time.Time//开始记录的基准时间
+ header string//ass开头
}
var (
- Ass_height = 720
- Ass_width = 1280
- Ass_font = 50
- Ass_T = 7
- Ass_loc = 7//小键盘对应的位置
+ Ass_height = 720//字幕高度
+ Ass_width = 1280//字幕宽度
+ Ass_font = 50//字幕字体大小
+ Ass_T = 7//单条字幕显示时间
+ Ass_loc = 7//å\97å¹\95ä½\8dç½® å°\8fé\94®ç\9b\98对åº\94ç\9a\84ä½\8dç½®
)
var ass = Ass {
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
`,
}
+
+//设定字幕文件名,为""时停止输出
func Ass_f(file string){
ass.file = file
}
+//传入要显示的单条字幕
func Assf(s string){
if !ass.Inuse {return}
if ass.file == "" {return}
var b string
// b += "Comment: " + strconv.Itoa(loc) + " "+ Dtos(showedt) + "\n"
b += `Dialogue: 0,`
- b += Dtos(st) + `,` + Dtos(et)
+ b += dtos(st) + `,` + dtos(et)
b += `,Default,,0,0,0,,{\fad(200,500)\blur3}` + s + "\n"
p.File().FileWR(p.Filel{
})
}
-func Dtos(t time.Duration) string {
+//时间转化为0:00:00.00规格字符串
+func dtos(t time.Duration) string {
M := int(math.Floor(t.Minutes())) % 60
S := int(math.Floor(t.Seconds())) % 60
Ns := t.Nanoseconds() / int64(time.Millisecond) % 1000 / 10
return fmt.Sprintf("%d:%02d:%02d.%02d", int(math.Floor(t.Hours())), M, S, Ns)
}
+//直播流保存
type Saveflv struct {
Inuse bool
path string
Inuse:IsOn("Saveflv"),
}
+//已go func形式调用,将会获取直播流
func Saveflvf(){
if !saveflv.Inuse {return}
l := p.Logf().New().Open("danmu.log").Base(-1, "saveflv")
}
}
+//已func形式调用,将会停止保存直播流
func Saveflv_wait(){
if !saveflv.Inuse {return}
saveflv.cancel.Done()
F "github.com/qydysky/bili_danmu/F"
)
+/*
+ HeartBeat数据分派
+*/
+
var heartlog = p.Logf().New().Base(-1, "Heart.go").Open("danmu.log").Fileonly(true)
+
+//HeartBeat类型处理方法map
var Heart_map = map[string]func(replyF, string) {
"heartbeat":replyF.heartbeat,//人气
}
+//HeartBeat类型,将人气4位byte转为字符串,并送到上述map指定的方法
func Heart(b []byte){
s := strconv.Itoa(int(F.Btoi32(b, 0)))
if F,ok := Heart_map["heartbeat"]; ok {
import (
p "github.com/qydysky/part"
)
+
/*
- 数据为WS_OP_MESSAGE类型的
+ 数据为WS_OP_MESSAGE类型的数据分派
*/
var msglog = p.Logf().New().Base(-1, "Msg.go").Open("danmu.log").Level(1)
+
+//Msg类型数据处理方法map
var Msg_map = map[string]func(replyF, string) {
"PK_BATTLE_PRE":nil,//人气pk
"PK_BATTLE_START":nil,//人气pk
"ROOM_REAL_TIME_MESSAGE_UPDATE":nil,//replyF.roominfo,//粉丝数
}
+//Msg类型数据处理方法挑选
+//识别cmd字段类型,查找上述map中设置的方法,并将json转为字符串型传入
func Msg(b []byte) {
s := string(b)
if cmd := p.Json().GetValFromS(s, "cmd");cmd == nil {
var replylog = p.Logf().New().Open("danmu.log").Base(-1, "Reply.go")
//返回数据分派
+//传入接受到的ws数据
+//判断进行解压,并对每个json对象进行分派
func Reply(b []byte) {
replylog.Base(-1, "返回分派")
defer replylog.Base(0)
}
}
+//所有的json对象处理子函数类
+//包含Msg和HeartBeat两大类
type replyF struct {}
+//默认未识别Msg
func (replyF) defaultMsg(s string){
msglog.Base(1, "Unknow").E(s)
}
+//msg-通常是大航海购买续费
func (replyF) user_toast_msg(s string){
username := p.Json().GetValFromS(s, "data.username");
op_type := p.Json().GetValFromS(s, "data.op_type");
msglog.Fileonly(true)
defer msglog.Fileonly(false)
- msglog.Base(1, "礼").I(sh...)}
+ msglog.Base(1, "礼").I(sh...)
+}
+//HeartBeat-心跳用来传递人气值
func (replyF) heartbeat(s string){
if s == "1" {return}//人气为1,不输出
heartlog.I("当前人气", s)
}
+//Msg-房间特殊活动
func (replyF) win_activity(s string){
msglog.Fileonly(true)
defer msglog.Fileonly(false)
msglog.Base(1, "房").I("活动", title, "已开启")
}
+//Msg-特殊礼物,当前仅观察到节奏风暴
func (replyF) special_gift(s string){
msglog.Fileonly(true)
defer msglog.Fileonly(false)
}
+//Msg-大航海购买,由于信息少,用user_toast_msg进行替代
func (replyF) guard_buy(s string){
username := p.Json().GetValFromS(s, "data.username");
gift_name := p.Json().GetValFromS(s, "data.gift_name");
msglog.Base(1, "礼").I(sh...)
}
+//Msg-房间信息改变,标题等
func (replyF) room_change(s string){
title := p.Json().GetValFromS(s, "data.title");
area_name := p.Json().GetValFromS(s, "data.area_name");
msglog.Base(1, "房").I(sh...)
}
+//Msg-大航海欢迎信息
func (replyF) welcome_guard(s string){
username := p.Json().GetValFromS(s, "data.username");
msglog.Base(1, "房").Fileonly(true).I(sh...).Fileonly(false)
}
+//Msg-礼物处理,对于小于30人民币的礼物不显示
func (replyF) send_gift(s string){
// coin_type := p.Json().GetValFromS(s, "data.coin_type");
num := p.Json().GetValFromS(s, "data.num");
msglog.Base(1, "礼").I(sh...)
}
+//Msg-房间封禁信息
func (replyF) room_block_msg(s string) {
msglog.Fileonly(true)
defer msglog.Fileonly(false)
}
}
+//Msg-房间准备信息,通常出现在下播而不出现在开播
func (replyF) preparing(s string) {
msglog.Base(1, "房")
}
}
+//Msg-房间开播信息
func (replyF) live(s string) {
msglog.Base(1, "房")
}
}
+//Msg-超级留言处理
func (replyF) super_chat_message(s string){
uname := p.Json().GetValFromS(s, "data.user_info.uname");
price := p.Json().GetValFromS(s, "data.price");
if price != nil {
sh = append(sh, "¥", price)
}
+ fmt.Println("\n====")
+ fmt.Println(sh...)
if message != nil {
+ fmt.Println(message)
sh = append(sh, message)
}
if message_jpn != nil && message != message_jpn {
+ fmt.Println(message_jpn)
sh = append(sh, message_jpn)
}
- msglog.Fileonly(true)
- defer msglog.Fileonly(false)
+ fmt.Print("====\n\n")
+
{//额外
Assf(fmt.Sprintln(sh...))
}
- fmt.Println("\n====")
- fmt.Println(sh...)
- fmt.Print("====\n\n")
- msglog.Base(1, "礼").I(sh...)
+ msglog.Base(1, "礼").Fileonly(true).I(sh...).Fileonly(false)
}
+//Msg-分区排行
func (replyF) panel(s string){
msglog.Fileonly(true).Base(1, "房")
defer msglog.Fileonly(false)
}
}
+//Msg-进入特效,大多为大航海进入,信息少,使用welcome_guard替代
func (replyF) entry_effect(s string){
msglog.Fileonly(true).Base(-1, "房")
defer msglog.Base(0).Fileonly(false)
}
+//Msg-房间禁言
func (replyF) roomsilent(s string){
msglog.Base(1, "房")
}
}
+//Msg-粉丝信息,常刷屏,不显示
func (replyF) roominfo(s string){
fans := p.Json().GetValFromS(s, "data.fans");
fans_club := p.Json().GetValFromS(s, "data.fans_club");
if len(sh) != 0 {msglog.Base(1, "粉").I(sh...)}
}
+//Msg-弹幕处理
func (replyF) danmu(s string) {
if info := p.Json().GetValFromS(s, "info");info == nil {
msglog.E("info", info)
}
}
+//弹幕发送
+//传入字符串即可发送
+//需要cookie
func Msg_senddanmu(msg string){
if c.Cookie == "" || c.Roomid == 0 {return}
S.Danmu_s(msg, c.Cookie, c.Roomid)
}
+//弹幕显示
+//由于额外功能有些需要显示,为了统一管理,使用此方法进行处理
func Msg_showdanmu(auth interface{}, msg string) {
{//附加功能 更少弹幕
if Lessdanmuf(msg, 20) > 0.7 {//与前20条弹幕重复的字数占比度>0.7的屏蔽