Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

28 changed files with 88 additions and 525 deletions

View File

@ -1,17 +1,16 @@
# BLive Coyote
> [!CAUTION]
> 本项目仅供学习使用,请勿用于直播过程中!
郊狼B站直播玩法送礼物控制郊狼主机输出
## 项目简介
> [!TIP]
> 本项目仍在开发中,目前已可以基本使用。
> 本项目仍在开发中,目前已可以基本使用,欢迎体验
>
> 本项目为个人学习项目,如有错误敬请批评指正,欢迎 PR。
本项目以 [Bilibili 直播&互玩 JavaScript Demo](https://open-live.bilibili.com/document/a7bd5377-ad7d-a273-25ae-28caf37a7a85) 和 [DG-LAB SOCKET控制-控制端开源](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE/tree/main/socket) 为基础,实现了直播间送礼物控制郊狼主机输出的功能。
演示视频:[郊狼直播互动玩法正式发布【BLive Coyote】](https://www.bilibili.com/video/BV1D1421i7WB/)
- 郊狼 WebSocket 后端请参考:[DG-LAB-OPENSOURCE](https://github.com/DG-LAB-OPENSOURCE/DG-LAB-OPENSOURCE/tree/main/socket/BackEnd(Node))
- 哔哩哔哩直播互动玩法服务端请参考:[JavaScript Demo](https://open-live.bilibili.com/document/a7bd5377-ad7d-a273-25ae-28caf37a7a85)
@ -28,7 +27,6 @@
```
BLive_Coyote
├─public
│ ├─img
│ └─css
└─src
├─assets
@ -38,26 +36,8 @@ BLive_Coyote
## 项目启动
```bash
cd BLive_Coyote
cd Client
npm install
npm run dev
```
主播身份码及 app_id 获取请参考 [开放平台-直播&互玩接入文档:常见问题](https://open-live.bilibili.com/document/5dffc297-6fd2-41ff-bd45-6e8b89e2a68e)
## 安全须知
> [!CAUTION]
> 使用本项目或郊狼前务必认真阅读安全须知
- **请保证在安全、清醒、自愿的情况下使用**
- 严禁**体内存在电子/金属植入物者、心脑血管疾病患者、孕妇、儿童或无法及时操作主机**的人群使用
- 严禁将电极置于**心脏投影区**(或任何可能使电流经过心脏的位置),以及**头部、颈部、皮肤破损处**等位置
- 严禁在驾驶或操纵机器等危险情况下使用
- 请勿在同一部位连续使用**30分钟以上**,以免造成损伤
- 请勿在**输出状态下**移动电极,以免造成刺痛或灼伤
- 在直播过程中使用**可能会导致直播间被封禁**,风险自负
- 在使用前需要**完整阅读郊狼产品安全须知,并设置好强度上限保护**。
## 特别感谢
感谢 VUP [鱼芷white](https://space.bilibili.com/3546608125872618/)、[叶秋zi](https://space.bilibili.com/415235891/) 帮助测试
排名不分先后

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="zh-cn">
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />

View File

@ -684,68 +684,3 @@ select {
background-color: #ffe99d;
color: #000;
}
.settings-window {
position: fixed;
top: 10%;
bottom: 10%;
height: 500px;
left: 10%;
right: 10%;
background-color: #171717;
border-radius: 20px;
border: #ffe99d 2px solid;
z-index: 100;
padding: 20px;
}
.settings-window-body {
overflow-y: auto;
height: 500px;
}
.settings-window-body::-webkit-scrollbar {
width: 10px; /* 宽度 */
}
.settings-window-body::-webkit-scrollbar-track {
background: transparent; /* 轨道颜色 */
}
.settings-window-body::-webkit-scrollbar-thumb {
background: #ffe99d; /* 滚动条颜色 */
}
.settings-window-body::-webkit-scrollbar-thumb:hover {
background: #ffe99d; /* 滚动条鼠标悬停颜色 */
}
.tag {
background: #fce9a7;
color: #000;
border-radius: 5px;
}
.settings-window-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99;
}
.animated-notice {
opacity: 0;
animation: fadeIn 1s ease-in-out forwards;
animation-delay: 1.5s;
}
@keyframes fadeIn {
to {
opacity: 1;
}
}
b {
color: #fce9a7;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 KiB

View File

@ -1,16 +1,15 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
import {computed, ref, watch} from "vue"
import { ref, watch } from "vue"
import axios from "axios"
import { Notyf } from 'notyf'
import {createSocket, destroySocket, waveCounter} from "./socket/index"
import { createSocket, destroySocket } from "./socket/index"
import {
createCoyoteSocket,
closeCoyoteSocket,
addOrIncrease,
sendWaveData,
coyoteState,
qrcodeSrc,
qrcodeShow,
channelAStrength,
@ -31,63 +30,20 @@ const api = axios.create({
//
let settings = ref()
let test_settings = ref()
const settings_text = localStorage.getItem('settings') || ''
const settings_version = 1
let showUpgrade = ref(false)
if (window.localStorage.getItem("settings")) {
settings.value = JSON.parse(window.localStorage.getItem("settings") || '{}')
if (settings.value.version != settings_version) {
//
showUpgrade.value = true
}
settings.value = JSON.parse(window.localStorage.getItem("settings") || '{}');
// console.log(settings.value)
} else {
// 使
settings.value = {
version: 1,
waveData: waveData,
strengthData: strengthData,
guardLevel: 0,
fansMedal: false
}
window.localStorage.setItem('settings', JSON.stringify(settings.value))
}
if (window.localStorage.getItem("test")) {
test_settings.value = JSON.parse(window.localStorage.getItem("test") || '{}')
} else {
test_settings.value = {
falloff: [0, 0]
}
window.localStorage.setItem('test', JSON.stringify(test_settings.value))
}
let showSafetyNotice = ref(localStorage.getItem('showSafetyNotice') !== 'false')
const upgradeSettings = () => {
//
const old_settings = JSON.parse(window.localStorage.getItem("settings") || '{}')
const old_waveData = old_settings?.waveData ? old_settings.waveData : waveData
const old_strengthData = old_settings?.strengthData ? old_settings.strengthData : strengthData
const old_guardLevel = old_settings?.guardLevel ? old_settings.guardLevel : "0"
const old_fansMedal = old_settings?.fansMedal ? old_settings.fansMedal : false
settings.value = {
version: settings_version,
waveData: old_waveData,
strengthData: old_strengthData,
guardLevel: old_guardLevel,
fansMedal: old_fansMedal
}
guardLevel: 0
};
window.localStorage.setItem('settings', JSON.stringify(settings.value));
console.log(settings.value)
showUpgrade.value = false
notyf.success("升级成功")
}
//
const codeId = ref("")
// app []
@ -105,69 +61,17 @@ clearInterval(heartBeatTimer.value!)
const waveTestData = ref("")
//
const showSettings = ref(false)
//
const showWarnWindow = ref(false)
const warnWindowCountdown = ref(5);
//
const showTestWindow = ref(false)
const maxHP = ref(1000)
const HP = ref(maxHP.value)
//
const gameState = ref(false)
const selectedGift = ref('')
const selectedWave = ref('')
const relations = ref(Object.entries(giftData).map(([key, value]) => ({ gift: key, wave: settings.value.waveData[key] })))
//
let canAcknowledgeSafetyNotice = ref(false)
let canAcknowledgeSafetyNoticeCountdown = ref(15)
let canAcknowledgeSafetyNoticeTimer = setInterval(() => {
canAcknowledgeSafetyNoticeCountdown.value--
if (canAcknowledgeSafetyNoticeCountdown.value <= 0) {
canAcknowledgeSafetyNotice.value = true
clearInterval(canAcknowledgeSafetyNoticeTimer)
}
}, 1000)
let safetyNotices = ref([
"<b>请保证在安全、清醒、自愿的情况下使用</b>",
"严禁<b>体内存在电子/金属植入物者、心脑血管疾病患者、孕妇、儿童或无法及时操作主机</b>的人群使用",
"严禁将电极置于<b>心脏投影区</b>(或任何可能使电流经过心脏的位置),以及<b>头部、颈部、皮肤破损处</b>等位置",
"严禁在驾驶或操纵机器等危险情况下使用",
"请勿在同一部位连续使用<b>30分钟以上</b>,以免造成损伤",
"请勿在<b>输出状态下</b>移动电极,以免造成刺痛或灼伤",
"在直播过程中使用<b>可能会导致直播间被封禁</b>,风险自负",
"在使用前需要<b>完整阅读郊狼产品安全须知,并设置好强度上限保护</b>",
])
// console.log(relations.value)
watch(selectedGift, (newGift) => {
selectedWave.value = waveData[newGift]
})
watch(showWarnWindow, (newVal) => {
if (newVal) {
let timer = setInterval(() => {
warnWindowCountdown.value--;
if (warnWindowCountdown.value <= 0) {
clearInterval(timer);
}
}, 1000);
} else {
warnWindowCountdown.value = 5; //
}
});
watch(waveCounter, () => {
HP.value = Math.max(0, HP.value - channelAStrength.value)
});
const fansMedal = computed({
get: () => settings.value.fansMedal,
set: (value) => { settings.value.fansMedal = value === 'true' }
});
/**
* 测试请求鉴权接口
@ -175,14 +79,8 @@ const fansMedal = computed({
const getAuth = () => {
api.post("/getAuth", {})
.then(({ data }) => {
if (typeof data.code !== "number") {
console.log("-----鉴权失败-----")
notyf.error({ message: "服务端异常" })
return
} else {
console.log("-----鉴权成功-----")
notyf.success({ message: "鉴权成功" })
}
})
.catch((err) => {
console.log("-----鉴权失败-----")
@ -229,8 +127,6 @@ const gameStart = () => {
heartBeatThis(game_info.game_id)
}, 20000)
handleCreateSocket()
showLiveWarn()
gameState.value = true
} else {
console.log("-----游戏开始失败-----")
console.log("原因:", data)
@ -264,7 +160,6 @@ const gameEnd = () => {
handleDestroySocket()
console.log("-----心跳关闭成功-----")
notyf.success({ message: "游戏关闭成功" })
gameState.value = false
} else {
console.log("-----游戏关闭失败-----")
console.log("原因:", data)
@ -333,10 +228,6 @@ const saveSettings = () => {
window.localStorage.setItem('settings', JSON.stringify(settings.value));
console.log(settings.value);
}
const saveTestSettings = () => {
window.localStorage.setItem('test', JSON.stringify(test_settings.value));
console.log(test_settings.value);
}
/**
* 添加并保存 waveData
@ -360,43 +251,6 @@ const addRelationAndSave = () => {
//
saveSettings();
}
/**
* 阅读安全须知
*/
const acknowledgeSafetyNotice = () => {
showSafetyNotice.value = false
localStorage.setItem('showSafetyNotice', 'false')
}
/**
* 显示直播警告
*/
const showLiveWarn = () => {
showWarnWindow.value = true
//
setTimeout(() => {
showWarnWindow.value = false
}, 5000)
}
/**
* 将数字大航海等级转换为文字
*/
const guardLevelText = computed(() => {
switch (settings.value.guardLevel) {
case "0":
return "观众";
case "1":
return "舰长";
case "2":
return "提督";
case "3":
return "总督";
default:
return "未知";
}
})
</script>
<template>
@ -413,120 +267,15 @@ const guardLevelText = computed(() => {
</div>
</div>
<div class="settings-window-bg" v-show="showUpgrade || showSettings || showSafetyNotice || showWarnWindow"></div>
<div class="settings-window" v-show="showUpgrade">
<h2> 本地设置数据升级</h2>
<p>
目前可读取的本地设置数据的储存结构版本为<input class="tag" v-model="settings_version" size="1" disabled>,你的设备上现有的设置数据的储存结构版本为<input class="tag" v-model="settings.version" size="1" disabled>
</p>
<p>
为了可以安全使用 BLive Coyote现在需要将你设备上储存的设置数据进行升级这个过程通常可以自动进行如果出现意外请联系开发者
</p>
<hr />
<p>注意下面是你的全部设置数据请先复制下面的全部内容以确保在升级失败时可以恢复</p>
<textarea v-model="settings_text" style="width: 100%; height: 64px;"></textarea>
<div class="form">
<button @click="upgradeSettings">尝试执行升级</button>
</div>
</div>
<div class="settings-window" v-show="showSafetyNotice">
<h2> 安全须知</h2>
<p v-for="(notice, index) in safetyNotices"
:key="index" class="animated-notice"
:style="`animation-delay: ${index*2}s`"
v-html="notice">
</p>
<hr />
<p>注意请仔细阅读以上内容后点击<b>我已知晓</b></p>
<div class="form">
<button @click="acknowledgeSafetyNotice" :disabled="!canAcknowledgeSafetyNotice">
我已知晓{{canAcknowledgeSafetyNoticeCountdown != 0 ? "" + canAcknowledgeSafetyNoticeCountdown + "" : ""}}
</button>
</div>
</div>
<div class="warn-window" v-show="showWarnWindow">
<div style="font-size: 52px">
<b></b>
</div>
<hr />
<div style="margin: auto">
<b>注意</b>请不要在直播过程中展示或使用郊狼主机及配件直播务必遵守平台规则否则可能导致账号被封禁继续即自愿承担可能存在的风险{{ warnWindowCountdown }}
</div>
</div>
<div class="settings-window" style="background-color: #0a9a38; font-weight: bold;" v-show="showTestWindow">
<div class="settings-window-btn">
<button @click="showTestWindow = false" style="float: right"></button>
</div>
<div class="settings-window-body">
<h2>测试界面</h2>
<hr />
<p>绿色背景方便用色度键抠像最大 HP <input v-model="maxHP" size="2" /></p>
<p><button @click="HP = maxHP">重置伤害</button> <button @click="HP -= 10">造成伤害</button></p>
<hr />
<div class="attack-box" style="padding-top: 25px;">
<div style="display: flex;">
<div>
武器A
</div>
<div class="attack-a-board">
<div class="attack-a-bg" :style="{ width: (channelAStrength / softAStrength) * 100 + '%'}">
{{ channelAStrength + "/" + softAStrength }}
</div>
</div>
</div>
<br />
<div style="display: flex;">
<div>
武器B
</div>
<div class="attack-a-board">
<div class="attack-a-bg" :style="{ width: (channelBStrength / softBStrength) * 100 + '%'}">
{{ channelBStrength + "/" + softBStrength }}
</div>
</div>
</div>
</div>
<br>
<div class="attribute-box">
<div style="display: flex;">
<div style="margin: auto 0">
HP
</div>
<div class="attribute-hp-board">
<div class="attribute-hp-bg" :style="{ width: (HP / maxHP) * 100 + '%' }">
{{ HP + "/" + maxHP }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="settings-window" v-show="showSettings">
<div class="settings-window-btn">
<button @click="showSettings = false" style="float: right"></button>
<button @click="saveSettings" style="float: right"></button>
</div>
<div class="settings-window-body">
<div>
<h2>大航海</h2>
<p>
身份至低为
<select v-model="settings.guardLevel">
<option value="0">观众</option>
<option value="1">舰长</option>
<option value="2">提督</option>
<option value="3">总督</option>
</select>
<select v-model="fansMedal">
<option value="true">需要</option>
<option value="false">不需要</option>
</select>
佩戴粉丝牌才可互动
大航海等级达到
<input v-model="settings.guardLevel">
才可互动
</p>
<h2>强度规则</h2>
@ -584,20 +333,9 @@ const guardLevelText = computed(() => {
</div>
<button @click="addRelationAndSave">新建或保存</button>
</div>
<h2>实验功能</h2>
<div>
<p><b> 请勿依赖实验功能实验功能可能在后续版本删除</b></p>
<p>
每输出<input size="1" v-model="test_settings.falloff[0]" />次波形强度衰减<input size="1" v-model="test_settings.falloff[1]" />
<span title="为了避免主包被电似,希望主包喜欢">?</span>
</p>
<button @click="saveTestSettings">保存实验功能数据</button>
</div>
</div>
</div>
<div class="game-tips">
! 请在连接前确保已经设置好强度上限
</div>
@ -605,7 +343,7 @@ const guardLevelText = computed(() => {
<div class="game-info">
<h2>主机状态</h2>
<div class="dashboard" style="display: flex">
<div style="display: flex">
<div class="channel-circle">
<div class="channel-tag">A</div>
<div class="channel-strength">{{ channelAStrength }}</div>
@ -622,13 +360,7 @@ const guardLevelText = computed(() => {
<h2>游戏玩法</h2>
<h3>大航海</h3>
<p>
身份至低为
<input class="tag" v-model="guardLevelText" size="1" disabled>
<span class="tag">{{ settings["fansMedal"] ? "需要" : "不需要" }}</span>
佩戴粉丝牌才可互动
</p>
<p>等级达到 <input class="tag" v-model="settings.guardLevel" size="1" disabled> 才可互动</p>
<h3>强度控制</h3>
<p>
赠送
@ -649,11 +381,13 @@ const guardLevelText = computed(() => {
<div v-for="relation in relations" :key="relation.gift">
<div v-if="relation && relation.wave">
<img :src="'/img/' + relation.gift + '.png'" width="24" :alt="giftData[relation.gift]">
<input class="tag" v-model="giftData[relation.gift]" size="8" disabled>
<input class="tag" v-model="giftData[relation.gift]" disabled>
</div>
</div>
</div>
<button @click="showSettings = true">修改玩法</button>
</div>
<div>
@ -663,17 +397,16 @@ const guardLevelText = computed(() => {
<input type="password" placeholder="填写主播身份码" v-model="codeId"/>
<label>app_id</label>
<input type="text" placeholder="填写 app_id" v-model="appId" />
<button @click="gameStart" v-show="!gameState">游戏开始</button>
<button @click="gameEnd" v-show="gameState">游戏结束</button>
<button @click="createCoyoteSocket" v-show="!coyoteState">连接郊狼</button>
<button @click="closeCoyoteSocket" v-show="coyoteState">断开郊狼</button>
<button @click="gameStart">游戏开始</button>
<button @click="gameEnd">游戏结束</button>
<button @click="createCoyoteSocket">连接郊狼</button>
<button @click="closeCoyoteSocket">断开郊狼</button>
</div>
<hr />
<h2>调试选项</h2>
<div class="form">
<button @click="showTestWindow = true">测试界面</button>
<button @click="getAuth">鉴权</button>
<label>波形数组 <a onclick="window.open('waveHelper.html', '', 'width=500,height=1000,left=700');">波形助手</a></label>
<input type="text" placeholder="填写欲测试的波形数组" v-model="waveTestData" />
@ -699,49 +432,23 @@ const guardLevelText = computed(() => {
font-size: 18px;
}
.warn-window {
display: flex;
.settings-window {
position: fixed;
top: 40%;
bottom: 40%;
left: 20%;
right: 20%;
background-color: #a00;
top: 10%;
bottom: 10%;
height: 500px;
left: 10%;
right: 10%;
background-color: #171717;
border-radius: 20px;
border: #ffe99d 2px solid;
z-index: 100;
padding: 20px;
}
.warn-window > * {
margin: 10px;
}
.attribute-hp-board {
width: 600px;
height: 40px;
border: #fff 2px solid;
margin-left: 16px;
}
.attribute-hp-bg {
height: 40px;
background-color: #ff5151;
position: relative;
color: #fff;
line-height: 40px;
text-align: center;
}
.attack-a-board {
width: 200px;
height: 20px;
border: #fff 2px solid;
margin-left: 16px;
}
.attack-a-bg {
height: 20px;
background-color: #fff;
position: relative;
.tag {
background: #fce9a7;
color: #000;
text-align: center;
border-radius: 5px;
}
</style>

View File

@ -11,7 +11,6 @@ const strengthData = ["31039", "31036"]
// 礼物id和礼物名的对应关系
const giftData = {
"39": "节奏风暴",
"30758": "这个好欸",
"30873": "花式夸夸",
"31028": "探索者启航",
@ -21,34 +20,17 @@ const giftData = {
"31044": "情书",
"31049": "干杯",
"31053": "告白花束",
"31087": "次元之城",
"31088": "为你加冕",
"31122": "星愿水晶球",
"31164": "粉丝团灯牌",
"31538": "超感摩托",
"31588": "星河入梦",
"31589": "我星永恒",
"31591": "星轨列车",
"31883": "璀璨烟火",
"31891": "明灯相伴",
"31932": "梦游仙境",
"31933": "为你摘星",
"32089": "极速超跑",
"32122": "小电视飞船",
"32126": "棉花糖",
"32199": "爱的抱抱",
"32201": "么么",
"32228": "爱的乐章",
"32292": "贴贴",
"32293": "暖暖爱意",
"32360": "璀璨钻石",
"32363": "许愿精灵",
"32575": "萌兔火箭",
"32609": "棒棒糖",
"32612": "爱神之箭",
"32692": "落樱缤纷",
"32734": "快乐时光",
"32735": "带你出游",
"32758": "爱心雨",
"32761": "可爱捏",
"32768": "携手同行",
@ -56,7 +38,6 @@ const giftData = {
"33032": "干杯工厂",
"33988": "人气票",
"34065": "为你打call",
"34115": "草莓蛋糕",
}
export { waveData, strengthData, giftData }

1
src/assets/notyf.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -11,8 +11,6 @@ let softBStrength = ref(0); // B通道软上限
let followAStrength = ref(false); //跟随A通道软上限
let followBStrength = ref(false); //跟随B通道软上限
const coyoteState = ref(false)
let connectionId = ""; // 从接口获取的连接标识符
let targetWSId = ""; // 发送目标
let fangdou = 500; //500毫秒防抖
@ -75,7 +73,6 @@ function createCoyoteSocket() {
console.log("收到targetId: " + msg.targetId + ", msg: " + msg.message);
qrcodeShow.value = false
notyf.success({message: "郊狼连接成功"})
coyoteState.value = true
}
break
case 'break':
@ -83,7 +80,6 @@ function createCoyoteSocket() {
return
console.log("收到断开连接指令")
notyf.error({ message: "收到断开连接指令" })
coyoteState.value = false
//location.reload();
break
case 'error':
@ -91,7 +87,6 @@ function createCoyoteSocket() {
return
console.log("对方已断开code" + msg.message)
notyf.error({ message: "对方已断开(" + msg.message + "" })
coyoteState.value = false
break
case 'msg':
const result: { type: string; numbers: number[] }[] = []
@ -131,13 +126,11 @@ function createCoyoteSocket() {
wsConn.onerror = function (event) {
console.log("WebSocket连接出错")
notyf.error({ message: "WebSocket连接出错" })
coyoteState.value = false
}
wsConn.onclose = function (event) {
console.log("WebSocket连接已关闭")
notyf.error({ message: "WebSocket连接已关闭" })
coyoteState.value = false
}
}
@ -146,37 +139,31 @@ function sendWsMsg(messageObj) {
messageObj.targetId = targetWSId;
if (!messageObj.hasOwnProperty('type'))
messageObj.type = "msg";
wsConn.send(JSON.stringify((messageObj)))
wsConn.send(JSON.stringify((messageObj)));
}
function addOrIncrease(type, channelIndex, strength) {
// 1 减少 2 增加 3 设置到
// 1 减少 2 增加 3 设置到
// channel:1-A 2-B
// 获取当前通道的当前值
// 获取当前频道元素和当前值
let channelStrength = channelIndex === 1 ? channelAStrength.value : channelBStrength.value;
// 如果是设置操作
if (type === 3) {
channelStrength = strength
channelStrength = strength; //固定为0
}
// 减少
// 减少
else if (type === 1) {
channelStrength = Math.max(channelStrength - strength, 0)
channelStrength = Math.max(channelStrength - strength, 0);
}
// 增加
// 增加
else if (type === 2) {
channelStrength = Math.min(channelStrength + strength, 200)
channelStrength = Math.min(channelStrength + strength, 200);
}
// 构造消息对象并发送
let data = {}
if (type === 3) {
data = { type, strength: channelStrength, message: "set channel", channel: channelIndex }
} else {
// 这里用 type 4 可以自定义增加减小是数值type 2/3 固定是 1
data = { type: 4, message: "strength-" + channelIndex + "+" + (type - 1) + "+" + strength }
}
const data = { type, strength: channelStrength, message: "set channel", channel: channelIndex };
console.log(data)
sendWsMsg(data);
}
@ -218,7 +205,6 @@ function closeCoyoteSocket() {
}
wsConn = null
notyf.success( {message: "郊狼连接已断开"} )
coyoteState.value = false
}
@ -229,7 +215,6 @@ export {
sendWaveData,
addOrIncrease,
clearAB,
coyoteState,
qrcodeSrc,
qrcodeShow,
channelAStrength,

View File

@ -8,56 +8,27 @@ let ws: DanmakuWebSocket
const notyf = new Notyf({ duration: 4000 })
interface SettingsType {
version: number;
strengthData: typeof strengthData;
waveData: typeof waveData;
guardLevel: number;
fansMedal: boolean;
}
interface TestSettingsType {
falloff: number[];
}
let settings: Ref<SettingsType> = ref({
version: 1,
waveData: waveData,
strengthData: strengthData,
guardLevel: 0,
fansMedal: false
})
let testSettings: Ref<TestSettingsType> = ref({
falloff: [0, 0],
})
guardLevel: 0
});
if (window.localStorage.getItem("settings")) {
settings.value = JSON.parse(window.localStorage.getItem("settings") || '{}');
console.log(settings.value)
}
if (window.localStorage.getItem("test")) {
testSettings.value = JSON.parse(window.localStorage.getItem("test") || '{}');
console.log(testSettings.value)
}
let waveCounter = ref(0)
/**
*
* @description B站返回的大航海等级是1-2-3-便
* @param guardLevel
*/
const transformGuardLevel = (guardLevel: number) => {
if (guardLevel == 0) {
return 0
} else if (guardLevel == 1) {
return 3
} else if (guardLevel == 2) {
return 2
} else if (guardLevel == 3) {
return 1
} else {
return 0
}
} else {
// 如果没有,使用默认值
settings.value = {
waveData: waveData,
strengthData: strengthData,
guardLevel: 0
};
}
/**
@ -70,33 +41,42 @@ function createSocket(authBody: string, wssLinks: string[]) {
...getWebSocketConfig(authBody, wssLinks),
// 收到消息,
onReceivedMessage: (res) => {
// 从本地存储中获取设置
console.log("收到"+ res.cmd +"消息:")
//console.log(res.data.uname + "(大航海" +res.data.guard_level + "级):" + res.data.msg)
// if (res.data.msg == "#UPA1") {
// try {
// addOrIncrease(2, 1, 1)
// notyf.success("A通道强度增加成功")
// }
// catch (e) {
// console.log(e)
// notyf.error("A通道强度增加失败")
// }
// }
settings = window.localStorage.getItem("settings") ? ref(JSON.parse(window.localStorage.getItem("settings") || '{}')) : null
// 粉丝勋章
let execute_1 = settings.value.fansMedal ? !!res.data.fans_medal_wearing_status : true
// 大航海
let execute_2 = transformGuardLevel(res.data.guard_level) >= settings.value.guardLevel
if (res.cmd == "LIVE_OPEN_PLATFORM_SEND_GIFT" && execute_1 && execute_2) {
if (res.cmd == "LIVE_OPEN_PLATFORM_SEND_GIFT" && res.data.guard_level >= settings.value.guardLevel) {
if (settings && res.data.gift_id.toString() === settings.value.strengthData[0]) {
// 加强度
// 牛哇牛哇加强度1
try {
addOrIncrease(2, 1, res.data.gift_num)
addOrIncrease(2, 2, res.data.gift_num)
notyf.success("收到" + res.data.gift_name + ",强度+" + res.data.gift_num)
console.log("开始操作")
addOrIncrease(2, 1, 1)
addOrIncrease(2, 2, 1)
console.log("结束操作")
notyf.success("收到" + res.data.gift_name + ",强度+1")
}
catch (e) {
console.log(e)
notyf.error("强度操作失败!")
}
} else if (settings && res.data.gift_id.toString() === settings.value.strengthData[1]) {
// 减强度
// 小花花:减强度1
try {
addOrIncrease(1, 1, res.data.gift_num)
addOrIncrease(1, 2, res.data.gift_num)
notyf.success("收到" + res.data.gift_name + ",强度-" + res.data.gift_num)
addOrIncrease(1, 1, 1)
addOrIncrease(1, 2, 1)
notyf.success("收到" + res.data.gift_name + ",强度-1")
}
catch (e) {
console.log(e)
@ -105,14 +85,8 @@ function createSocket(authBody: string, wssLinks: string[]) {
} else if(settings && settings.value.waveData[res.data.gift_id]) {
// 其他礼物,发送波形数据
try {
sendWaveData(5 * res.data.gift_num, 5 * res.data.gift_num, settings.value.waveData[res.data.gift_id], settings.value.waveData[res.data.gift_id])
notyf.success("收到礼物" + res.data.gift_name + "*"+res.data.gift_num)
waveCounter.value++
if (waveCounter.value % testSettings.value.falloff[0] == 0 && testSettings.value.falloff[0] > 0) {
notyf.success("触发强度衰减")
addOrIncrease(1, 1, testSettings.value.falloff[1])
addOrIncrease(1, 2, testSettings.value.falloff[1])
}
sendWaveData(5, 5, settings.value.waveData[res.data.gift_id], settings.value.waveData[res.data.gift_id])
notyf.success("收到礼物" + res.data.gift_name)
}
catch (e) {
console.log(e)
@ -186,4 +160,4 @@ function getWsClient() {
return ws
}
export { createSocket, destroySocket, getWebSocketConfig, getWsClient, waveCounter }
export { createSocket, destroySocket, getWebSocketConfig, getWsClient }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="zh-cn">
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />