Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
28
README.md
@ -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/) 帮助测试
|
||||
|
||||
排名不分先后
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|
@ -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;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 523 KiB |
353
src/App.vue
@ -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>
|
||||
|
@ -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
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
// 如果没有,使用默认值
|
||||
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 }
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
|