<fix>修复无法接收礼物信息问题
- 更新协议版本,使用较新协议(webcast100) - 增加登录相关 Cookie 配置入口 - 目前需登录才可正常接收礼物信息 - 未登录只能获取其它信息,无法获取礼物信息 - 配置操作可查看界面说明 => ? - 更新 emoji 列表,支持更多 emoji - 对界面做一些其它优化
This commit is contained in:
1 parent
4216ce84d5
commit
dc3ab9ad46
22 files changed
+2796
-432
No files matched your search
@@ -28,3 +28,5 @@ coverage
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
test/
|
||||
@@ -25,7 +25,7 @@
|
||||
- 主要为解析提取后的弹幕,通过序列化`json`的格式
|
||||
- 分类展示直播间弹幕
|
||||
- 聊天弹幕(包含文本、普通表情、会员表情、合并表情等)
|
||||
- 礼物弹幕
|
||||
- 礼物弹幕(需登录 cookie)
|
||||
- 关注弹幕
|
||||
- 点赞弹幕(包含点赞数量)
|
||||
- 进入弹幕
|
||||
@@ -82,6 +82,8 @@ export interface DyMessage {
|
||||
method?: CastMethod;
|
||||
// 用户信息
|
||||
user?: CastUser;
|
||||
// 送给用户
|
||||
toUser?: CastUser;
|
||||
// 礼物信息(当类型为礼物弹幕时有值)
|
||||
gift?: CastGift;
|
||||
// 弹幕文本
|
||||
@@ -194,7 +196,7 @@ export enum CastMethod {
|
||||
|
||||
- 项目运行后,具体界面展示如下
|
||||
|
||||

|
||||

|
||||
|
||||
- 整体界面为三栏布局:左侧为直播间信息及连接状态展示;中间为主要弹幕展示;右侧为输入及其它信息展示
|
||||
- 右侧主要包含两个输入框,第一个为房间号输入框,第二个为转发地址输入框;输入带有格式验证,格式不正确无法连接
|
||||
@@ -202,7 +204,7 @@ export enum CastMethod {
|
||||
|
||||
- 在右侧房间号输入框输入房间号后,点击**连接**,等待几秒后,会在左下方状态信息展示连接结果,有时可能出现网络拥堵情况,稍后再连接即可,正常连接成功/失败均会有相应的消息通知提示,也可以看控制台输出。连接成功后,大致展示如下:
|
||||
|
||||

|
||||

|
||||
|
||||
- 此时,用户可在转发信息框填入自己的`WebSocket`服务端地址,点击**转发**,即可建立连接,将弹幕信息实时传送到所设置后端
|
||||
|
||||
@@ -294,8 +296,8 @@ export enum CastMethod {
|
||||
# Nginx 不区分 ws / wss 协议
|
||||
# WebSocket 实际上是通过 HTTP 升级实现的
|
||||
# 故使用 https:// 非 wss://
|
||||
proxy_pass https://webcast5-ws-web-lf.douyin.com/;
|
||||
|
||||
proxy_pass https://webcast100-ws-web-lq.douyin.com/;
|
||||
|
||||
# WebSocket 关键配置
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dycast",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
/* 抖音美好体 */
|
||||
@font-face {
|
||||
font-family: 'dymht';
|
||||
;
|
||||
src: url('./DouyinSansBold.otf') format('opentype');
|
||||
}
|
||||
|
||||
@@ -14,7 +13,7 @@
|
||||
/* 图标 */
|
||||
@font-face {
|
||||
font-family: "castfont";
|
||||
src: url('castfont.ttf') format('truetype');
|
||||
src: url('./castfont.ttf') format('truetype');
|
||||
}
|
||||
|
||||
[class*=' ice-'],
|
||||
@@ -34,6 +33,22 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.ice-keya:before {
|
||||
content: "\e6b4";
|
||||
}
|
||||
|
||||
.ice-key:before {
|
||||
content: "\e66b";
|
||||
}
|
||||
|
||||
.ice-logout:before {
|
||||
content: "\e66c";
|
||||
}
|
||||
|
||||
.ice-del:before {
|
||||
content: "\e66d";
|
||||
}
|
||||
|
||||
.ice-fullscreen:before {
|
||||
content: "\e658";
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
+54
-20
@@ -15,7 +15,8 @@
|
||||
<label class="nickname">[{{ user?.name ? user.name : 'unknown' }}]:</label>
|
||||
<template v-for="item in doms">
|
||||
<span v-if="item.node === 'text'" class="text">{{ item.text }}</span>
|
||||
<span v-if="item.node === 'user'" class="touser">{{ item.text }}</span>
|
||||
<span v-if="item.node === 'user'" class="atuser">{{ item.text }}</span>
|
||||
<span v-if="item.node === 'touser'" class="touser">{{ item.text }}</span>
|
||||
<img v-if="item.node === 'icon'" class="icon" :title="item.text" :src="item.url" :alt="item.text" />
|
||||
<img v-if="item.node === 'emoji'" class="emoji" alt="会员表情" :src="item.url" />
|
||||
</template>
|
||||
@@ -26,10 +27,10 @@
|
||||
<script setup lang="ts">
|
||||
import { CastMethod, CastRtfContentType, type CastGift, type CastRtfContent, type CastUser } from '@/core/dycast';
|
||||
import { emojis } from '@/core/emoji';
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
interface CastContentDOM {
|
||||
node: 'text' | 'icon' | 'emoji' | 'user';
|
||||
node: 'text' | 'icon' | 'emoji' | 'user' | 'touser';
|
||||
url?: string;
|
||||
text?: string;
|
||||
}
|
||||
@@ -37,6 +38,7 @@ interface CastContentDOM {
|
||||
interface CastItemProps {
|
||||
method?: CastMethod;
|
||||
user?: CastUser;
|
||||
toUser?: CastUser;
|
||||
gift?: CastGift;
|
||||
content?: string;
|
||||
rtfContent?: CastRtfContent[];
|
||||
@@ -109,21 +111,43 @@ const doms = computed(() => {
|
||||
break;
|
||||
case CastMethod.GIFT:
|
||||
if (props.gift) {
|
||||
list = [
|
||||
{
|
||||
node: 'text',
|
||||
text: '送出了'
|
||||
},
|
||||
{
|
||||
node: 'icon',
|
||||
text: props.gift.name,
|
||||
url: props.gift.icon
|
||||
},
|
||||
{
|
||||
node: 'text',
|
||||
text: `× ${props.gift.count}`
|
||||
}
|
||||
];
|
||||
if (props.toUser) {
|
||||
list = [
|
||||
{
|
||||
node: 'text',
|
||||
text: '送给'
|
||||
},
|
||||
{
|
||||
node: 'touser',
|
||||
text: props.toUser?.name
|
||||
},
|
||||
{
|
||||
node: 'text',
|
||||
text: `${props.gift.count} 个`
|
||||
},
|
||||
{
|
||||
node: 'icon',
|
||||
text: props.gift.name,
|
||||
url: props.gift.icon
|
||||
}
|
||||
];
|
||||
} else {
|
||||
list = [
|
||||
{
|
||||
node: 'text',
|
||||
text: '送出了'
|
||||
},
|
||||
{
|
||||
node: 'icon',
|
||||
text: props.gift.name,
|
||||
url: props.gift.icon
|
||||
},
|
||||
{
|
||||
node: 'text',
|
||||
text: `× ${props.gift.count}`
|
||||
}
|
||||
];
|
||||
}
|
||||
} else {
|
||||
list = [
|
||||
{
|
||||
@@ -158,13 +182,15 @@ const doms = computed(() => {
|
||||
$prefixColor: #38b48b;
|
||||
$nameColor: #9079ad;
|
||||
$textColor: #6b798e;
|
||||
$toUserColor: #e95464;
|
||||
$atUserColor: #e95464;
|
||||
$toUserColor: #3271ae;
|
||||
|
||||
$prefixDarkColor: #38b48b;
|
||||
$nameDarkColor: #83ccd2;
|
||||
$textDarkColor: #f7fcfe;
|
||||
|
||||
$toUserDarkColor: #e83929;
|
||||
$atUserDarkColor: #e83929;
|
||||
$toUserDarkColor: #2ca9e1;
|
||||
|
||||
$giftText: #eba825;
|
||||
|
||||
@@ -189,14 +215,19 @@ $giftText: #eba825;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.text,
|
||||
.atuser,
|
||||
.touser {
|
||||
color: $textColor;
|
||||
// line-height: 1rem;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
.atuser {
|
||||
color: $atUserColor;
|
||||
}
|
||||
.touser {
|
||||
color: $toUserColor;
|
||||
margin: 0 3px;
|
||||
}
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
@@ -245,6 +276,9 @@ $giftText: #eba825;
|
||||
.text {
|
||||
color: $textDarkColor;
|
||||
}
|
||||
.atuser {
|
||||
color: $atUserDarkColor;
|
||||
}
|
||||
.touser {
|
||||
color: $toUserDarkColor;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
<CastItem
|
||||
:method="item.method"
|
||||
:user="item.user"
|
||||
:to-user="item.toUser"
|
||||
:gift="item.gift"
|
||||
:content="item.content"
|
||||
:rtf-content="item.rtfContent" />
|
||||
@@ -249,7 +250,10 @@ $darkText: #a5a5a5;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-shadow: 0px 5px 10px 0px $shadowB3, 0px 3px 6px 0px $shadowB15, 0 0 1px 0 $lightBd;
|
||||
box-shadow:
|
||||
0px 5px 10px 0px $shadowB3,
|
||||
0px 3px 6px 0px $shadowB15,
|
||||
0 0 1px 0 $lightBd;
|
||||
border-radius: 0.5rem;
|
||||
background-color: $lightBg;
|
||||
overflow: hidden;
|
||||
@@ -273,10 +277,15 @@ $darkText: #a5a5a5;
|
||||
&.theme-dark {
|
||||
background-color: $darkBg;
|
||||
border: 0.5px solid $shadowW2;
|
||||
box-shadow: 0px 6px 10px 0px $shadowB2, 0px 3px 6px 0px $shadowB12, inset 0px 0.5px 0.5px 0px $shadowW6;
|
||||
box-shadow:
|
||||
0px 6px 10px 0px $shadowB2,
|
||||
0px 3px 6px 0px $shadowB12,
|
||||
inset 0px 0.5px 0.5px 0px $shadowW6;
|
||||
.cast-list-header {
|
||||
background-color: $darkBarBg;
|
||||
box-shadow: 0px 1px 0px 0px $shadowB3, 0px 0.5px 0px 0px $shadowB0;
|
||||
box-shadow:
|
||||
0px 1px 0px 0px $shadowB3,
|
||||
0px 0.5px 0px 0px $shadowB0;
|
||||
}
|
||||
.title {
|
||||
color: $darkText;
|
||||
@@ -314,7 +323,9 @@ $darkText: #a5a5a5;
|
||||
padding: 3px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: 0px 1px 0px 0px $shadowB05, 0px 0.5px 0px 0px $shadowB1;
|
||||
box-shadow:
|
||||
0px 1px 0px 0px $shadowB05,
|
||||
0px 0.5px 0px 0px $shadowB1;
|
||||
.title {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<Transition name="opin" :duration="500">
|
||||
<div class="feed-dialog" v-show="visible">
|
||||
<div className="feed-dialog-close" @click="hideFeedDialog">
|
||||
<i className="ice-close-b icon"></i>
|
||||
</div>
|
||||
<div class="feed-dialog-main">
|
||||
<div className="coin">
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="128" height="128">
|
||||
<path
|
||||
d="M512 0c282.760533 0 512 229.239467 512 512 0 282.760533-229.239467 512-512 512-282.760533 0-512-229.239467-512-512C0 229.239467 229.239467 0 512 0z"
|
||||
fill="#FCE815"></path>
|
||||
<path
|
||||
d="M951.569067 249.309867L65.024 761.890133a510.498133 510.498133 0 0 1-13.687467-26.2144L935.662933 224.392533c5.5296 8.192 10.8544 16.4864 15.906134 24.917334z m-33.314134-48.9472L39.1168 708.608c-11.6736-28.023467-20.821333-57.002667-27.306667-86.664533l845.0048-488.448c22.391467 20.411733 42.973867 42.8032 61.44 66.8672z"
|
||||
fill="#FFDE92"></path>
|
||||
<path
|
||||
d="M512 34.133333C248.081067 34.133333 34.133333 248.081067 34.133333 512s213.947733 477.866667 477.866667 477.866667 477.866667-213.947733 477.866667-477.866667S775.918933 34.133333 512 34.133333z"
|
||||
fill="#FCE815"></path>
|
||||
<path
|
||||
d="M906.001067 241.527467c5.597867 8.123733 10.9568 16.418133 16.042666 24.917333L94.549333 744.789333c-4.778667-8.6016-9.352533-17.339733-13.653333-26.248533L906.0352 241.527467zM825.685333 151.483733a480.733867 480.733867 0 0 1 62.805334 66.116267l-819.541334 473.770667a474.248533 474.248533 0 0 1-25.941333-87.381334L825.685333 151.483733z"
|
||||
fill="#FFF9BC"></path>
|
||||
<path
|
||||
d="M512 136.533333C304.64 136.533333 136.533333 304.64 136.533333 512s168.106667 375.466667 375.466667 375.466667 375.466667-168.106667 375.466667-375.466667S719.36 136.533333 512 136.533333z"
|
||||
fill="#FFC91A"></path>
|
||||
<path
|
||||
d="M730.385067 206.5408A375.022933 375.022933 0 0 1 887.466667 512c0 207.36-168.106667 375.466667-375.466667 375.466667-194.901333 0-355.157333-148.548267-373.691733-338.602667L730.385067 206.506667z"
|
||||
fill="#FFB50D"></path>
|
||||
<path
|
||||
d="M409.6 307.2m65.8432 0l73.1136 0q65.8432 0 65.8432 65.8432l0 277.9136q0 65.8432-65.8432 65.8432l-73.1136 0q-65.8432 0-65.8432-65.8432l0-277.9136q0-65.8432 65.8432-65.8432Z"
|
||||
fill="#FFF48A"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="qr-box">
|
||||
<img className="qr" alt="QR" :src="qrCover" />
|
||||
</div>
|
||||
<div className="tip">
|
||||
<span>微信扫码,可投喂UP ٩( 'ω' )و</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import qrCover from '@/assets/images/feed.png';
|
||||
import { isBoolean } from '@/utils/typeUtil';
|
||||
|
||||
interface FeedDialogProps {
|
||||
modelValue?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<FeedDialogProps>(), {
|
||||
modelValue: false
|
||||
});
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
const emits = defineEmits({ 'update:modelValue': (value: boolean) => isBoolean(value) });
|
||||
|
||||
const hideFeedDialog = () => {
|
||||
visible.value = false;
|
||||
emits('update:modelValue', false);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
val => {
|
||||
if (val) {
|
||||
visible.value = true;
|
||||
} else {
|
||||
if (visible.value) visible.value = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$bg: #f7f6f5;
|
||||
|
||||
.opin-enter-active,
|
||||
.opin-leave-active {
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.opin-enter-from,
|
||||
.opin-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.feed-dialog {
|
||||
z-index: 9999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: $bg;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.feed-dialog-close {
|
||||
position: absolute;
|
||||
right: 3rem;
|
||||
top: 3rem;
|
||||
cursor: pointer;
|
||||
color: #e94829;
|
||||
will-change: transform;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
transform: rotateZ(-180deg);
|
||||
.icon {
|
||||
font-size: 2rem;
|
||||
}
|
||||
&:active {
|
||||
transform: rotateZ(0deg);
|
||||
transition-duration: 0s;
|
||||
}
|
||||
}
|
||||
|
||||
.feed-dialog-main {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.qr-box {
|
||||
width: 16rem;
|
||||
height: 16rem;
|
||||
border-radius: 5rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
background: #f7f6f5;
|
||||
box-shadow:
|
||||
9px 9px 12px #e1e0df,
|
||||
-9px -9px 12px #ffffff;
|
||||
.qr {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 20%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.tip {
|
||||
user-select: none;
|
||||
margin-top: 24px;
|
||||
font-size: 1.5rem;
|
||||
font-family: 'dymht';
|
||||
color: #9aa7b1;
|
||||
text-shadow:
|
||||
1px 1px 1px #333,
|
||||
-1px -1px 1px #fff;
|
||||
}
|
||||
.coin {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
animation: putCoin 3s ease infinite;
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes putCoin {
|
||||
0% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
24% {
|
||||
transform: translateY(-200%) rotateY(360deg) scale(1.2);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -100,6 +100,7 @@ $errorText: #e94829;
|
||||
gap: 12px;
|
||||
box-sizing: border-box;
|
||||
padding: 24px 18px;
|
||||
padding-bottom: 0;
|
||||
.live-info-cover {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,710 @@
|
||||
<template>
|
||||
<Transition name="opac" mode="out-in">
|
||||
<div v-if="!hasSSid" class="sid-tool" :class="{ active: isKeyActive, hover: isKeyHover }">
|
||||
<!-- tooltip -->
|
||||
<div class="sid-key-tooltip">
|
||||
<span class="key-tooltip_text">点击导入凭证</span>
|
||||
<span class="key-tooltip_arrow"></span>
|
||||
</div>
|
||||
<div class="sid-tool-main">
|
||||
<!-- 输入框 -->
|
||||
<input ref="input" class="sid-tool-input" type="text" placeholder="请输入sessionid" :disabled="inputDisabled" />
|
||||
<!-- 图标 -->
|
||||
<div class="sid-tool-key" @click="handleBtnClick" @mouseenter="handleKeyHover" @mouseleave="handleKeyNotHover">
|
||||
<i class="ice-keya icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 保存按钮 -->
|
||||
<div class="sid-save" title="保存" @click="handleSave">
|
||||
<span class="back"></span>
|
||||
<span class="front">
|
||||
<i class="ice-checkmark icon"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="sid-help" title="help" @click="handleHelpOpen">
|
||||
<i class="ice-help icon"></i>
|
||||
</div>
|
||||
<!-- 加载条 -->
|
||||
<div class="sid-loading" v-show="inputDisabled">
|
||||
<div class="sid-loading-mask"></div>
|
||||
<section class="dots-container">
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
<div class="dot"></div>
|
||||
</section>
|
||||
</div>
|
||||
<!-- 帮助弹窗 -->
|
||||
<SidToolHelp v-model="helpVisible" />
|
||||
</div>
|
||||
<div v-else class="sid-user">
|
||||
<div class="sid-user-card">
|
||||
<div class="sid-user-avatar">
|
||||
<img class="user-avatar" alt="头像" :src="uerAvatar" />
|
||||
</div>
|
||||
<div class="sid-user-info">
|
||||
<a class="user-name" target="_blank" :href="userLink">{{ userName }}</a>
|
||||
<div class="user-fs">
|
||||
<div class="user-fsi user-follow">
|
||||
<div class="name">关注</div>
|
||||
<div class="value">{{ userFollowing }}</div>
|
||||
</div>
|
||||
<div class="user-fsi user-fans">
|
||||
<div class="name">粉丝</div>
|
||||
<div class="value">{{ userFollower }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-sign">{{ userSign }}</div>
|
||||
</div>
|
||||
<div class="sid-user-logout" title="退出" @click="delSidCookie">
|
||||
<i class="ice-logout icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref, useTemplateRef } from 'vue';
|
||||
import SidToolHelp from './SidToolHelp.vue';
|
||||
import { getMeInfo } from '@/core/request';
|
||||
import SkMessage from '@/components/Message';
|
||||
import { CLog } from '@/utils/logUtil';
|
||||
import { hasCookie, removeCookie, setCookie } from '@/utils/cookieUtil';
|
||||
|
||||
// 输入框Ref
|
||||
const inputRef = useTemplateRef('input');
|
||||
// 是否激活
|
||||
const isKeyActive = ref(false);
|
||||
// 是否悬停
|
||||
const isKeyHover = ref(false);
|
||||
let keyActive = false;
|
||||
let keyHover = false;
|
||||
// 输入框禁用
|
||||
const inputDisabled = ref(false);
|
||||
|
||||
// 帮助弹窗
|
||||
const helpVisible = ref(false);
|
||||
|
||||
// 是否已有登录凭证
|
||||
const hasSSid = ref(false);
|
||||
|
||||
// 用户信息
|
||||
// 头像
|
||||
const uerAvatar = ref('');
|
||||
// 用户名
|
||||
const userName = ref('');
|
||||
// 粉丝
|
||||
const userFollower = ref(0);
|
||||
// 关注
|
||||
const userFollowing = ref(0);
|
||||
// 签名
|
||||
const userSign = ref('');
|
||||
// 用户主页链接
|
||||
const userLink = ref('');
|
||||
|
||||
// 秘钥按钮点击
|
||||
const handleBtnClick = function () {
|
||||
if (keyActive) {
|
||||
keyActive = false;
|
||||
isKeyActive.value = false;
|
||||
isKeyHover.value = true;
|
||||
keyHover = true;
|
||||
inputRef.value?.blur();
|
||||
} else {
|
||||
keyActive = true;
|
||||
isKeyActive.value = true;
|
||||
isKeyHover.value = false;
|
||||
keyHover = false;
|
||||
inputRef.value?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
// 悬停
|
||||
const handleKeyHover = function () {
|
||||
if (keyActive) return;
|
||||
isKeyHover.value = true;
|
||||
keyHover = true;
|
||||
};
|
||||
// 非悬停
|
||||
const handleKeyNotHover = function () {
|
||||
if (keyHover) {
|
||||
keyHover = false;
|
||||
isKeyHover.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 帮助弹窗点击 */
|
||||
const handleHelpOpen = function () {
|
||||
helpVisible.value = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 请求用户信息
|
||||
*/
|
||||
const requestMeInfo = function () {
|
||||
getMeInfo()
|
||||
.then(res => {
|
||||
if (res.code !== 0) {
|
||||
// 获取失败
|
||||
if (res.code === 20003) {
|
||||
// 用户未登录
|
||||
CLog.warn(`Request Me Info Error: (${res.code})${res.msg}`);
|
||||
SkMessage.warning('用户未登录,请先登录');
|
||||
} else {
|
||||
// 其它错误
|
||||
CLog.error(`Request Me Info Error: (${res.code})${res.msg}`);
|
||||
SkMessage.error('请求失败,请检查sessionid是否有误');
|
||||
}
|
||||
} else {
|
||||
// 获取成功
|
||||
if (res.data) {
|
||||
const info = res.data;
|
||||
// 设置信息
|
||||
uerAvatar.value = info.avatar;
|
||||
userName.value = info.nickname;
|
||||
userFollower.value = info.follower;
|
||||
userFollowing.value = info.following;
|
||||
userSign.value = info.sign || '';
|
||||
userLink.value = `https://www.douyin.com/user/${info.uid}`;
|
||||
hasSSid.value = true;
|
||||
} else {
|
||||
CLog.info(`用户信息为空: (${res.code})${res.msg}`);
|
||||
SkMessage.warning('用户信息为空,请检查sessionid是否有误');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
CLog.error(err);
|
||||
SkMessage.error('请求失败,请检查sessionid是否有误');
|
||||
})
|
||||
.finally(() => {
|
||||
inputDisabled.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
/** 保存cookie */
|
||||
const setSidCookie = function (sid: string) {
|
||||
// 保存cookie
|
||||
// 官网实际会添加多个相同值的cookie,但经测试有这一个就够用了
|
||||
setCookie({
|
||||
name: 'sessionid',
|
||||
value: sid,
|
||||
path: '/',
|
||||
maxAge: 604800,
|
||||
secure: true
|
||||
});
|
||||
// document.cookie = `sid_tt=${sid}; Path=/; Max-Age=604800; Secure`;
|
||||
// document.cookie = `sessionid_ss=${sid}; Path=/; Max-Age=604800; Secure`;
|
||||
// 一个自定义,标识作用的cookie
|
||||
setCookie({
|
||||
name: '__lg',
|
||||
value: 'true',
|
||||
path: '/',
|
||||
maxAge: 604800,
|
||||
secure: true
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除cookie */
|
||||
const delSidCookie = function () {
|
||||
removeCookie('sessionid');
|
||||
removeCookie('__lg');
|
||||
hasSSid.value = false;
|
||||
};
|
||||
|
||||
/** 保存 */
|
||||
const handleSave = function () {
|
||||
if (!keyActive) return;
|
||||
const ssid = inputRef.value?.value.trim();
|
||||
if (!ssid) return;
|
||||
// 保存cookie
|
||||
setSidCookie(ssid);
|
||||
// 请求用户信息
|
||||
// - 禁用输入框
|
||||
inputDisabled.value = true;
|
||||
requestMeInfo();
|
||||
};
|
||||
|
||||
// 组件挂载前
|
||||
onBeforeMount(() => {
|
||||
// 验证是否存在登录凭证 cookie
|
||||
if (hasCookie('__lg')) {
|
||||
inputDisabled.value = true;
|
||||
// 获取登录用户信息
|
||||
requestMeInfo();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use 'sass:math';
|
||||
|
||||
$bg: #f7f6f5;
|
||||
$sidToolH: 32px;
|
||||
$iconColor: #9aa7b1;
|
||||
$iconColorH: #f39800;
|
||||
$iconColorA: #38b48b;
|
||||
$btnSize: $sidToolH;
|
||||
$tooltipColor: #303133;
|
||||
$phColor: #b4c3cf;
|
||||
$inputColor: #6b798e;
|
||||
$inputDisabledColor: #a5acb1;
|
||||
|
||||
$saveBtnW: 24px;
|
||||
$saveBtnH: 20px;
|
||||
$saveBtnRgb: rgb(0, 253, 186);
|
||||
$saveBtnSRgb: rgb(0, 153, 121);
|
||||
|
||||
$helpSize: 16px;
|
||||
$helpColor: #9aa7b1;
|
||||
$helpColorH: #fcc800;
|
||||
|
||||
$loadingDotC: rgb(156, 219, 183);
|
||||
$loadingDotB: #65db98;
|
||||
$loadingDotSize: 15px;
|
||||
|
||||
// 用户信息卡片
|
||||
$avatarSize: 52px;
|
||||
$nameColor: #5f766a;
|
||||
$signColor: #ac9f8a;
|
||||
$fsiNColor: #bec2bc;
|
||||
$fsiVColor: #9d9d82;
|
||||
$fsiHrColor: #a8b092;
|
||||
$lgoutColor: #a7aaa1;
|
||||
$lgoutHColor: #e94829;
|
||||
|
||||
.opac-enter-active,
|
||||
.opac-leave-active {
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.opac-enter-from,
|
||||
.opac-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sid-tool {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: $sidToolH + 12px;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 12px;
|
||||
gap: 6px;
|
||||
&.active {
|
||||
.sid-tool-main {
|
||||
box-shadow:
|
||||
inset -2px -2px 3px 0 #ffffff,
|
||||
inset 2px 2px 3px 0 #c7c8c8,
|
||||
inset -0.5px -0.5px 0 0 #ffffff,
|
||||
inset 0.5px 0.5px 0 0 #a0a0a0;
|
||||
// width: 100%;
|
||||
flex-grow: 1;
|
||||
transition:
|
||||
box-shadow 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
flex-grow 0.5s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.sid-tool-key {
|
||||
color: $iconColorA;
|
||||
.icon {
|
||||
transform: rotateZ(0deg);
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
.sid-tool-input {
|
||||
width: 100%;
|
||||
}
|
||||
.sid-save {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition:
|
||||
0.5s transform 0.3s ease-in,
|
||||
0.5s opacity 0.3s ease-in;
|
||||
}
|
||||
}
|
||||
&.hover {
|
||||
.sid-key-tooltip {
|
||||
opacity: 1;
|
||||
transform: translate(6px, -130%);
|
||||
}
|
||||
.sid-tool-key {
|
||||
color: $iconColorH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sid-tool-main {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: $sidToolH;
|
||||
height: $sidToolH;
|
||||
border-radius: math.div($btnSize, 2);
|
||||
box-shadow:
|
||||
inset 2px 2px 3px 0 #ffffff,
|
||||
inset -2px -2px 3px 0 #c7c8c8,
|
||||
inset -0.5px -0.5px 0 0 #ffffff,
|
||||
inset 0.5px 0.5px 0 0 #a0a0a0,
|
||||
2px 2px 3px 0 rgba(0, 0, 0, 0.15),
|
||||
-2px -2px 3px 0 #ffffff;
|
||||
transition:
|
||||
box-shadow 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275),
|
||||
0.3s flex-grow 0.5s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
&:hover {
|
||||
box-shadow:
|
||||
inset 2px 2px 3px 0 #ffffff,
|
||||
inset -2px -2px 3px 0 #c7c8c8,
|
||||
inset -0.5px -0.5px 0 0 #ffffff,
|
||||
inset 0.5px 0.5px 0 0 #a0a0a0,
|
||||
1px 1px 1px 0 rgba(0, 0, 0, 0.15),
|
||||
-1px -1px 1px 0 #ffffff;
|
||||
}
|
||||
&:active {
|
||||
box-shadow:
|
||||
inset -2px -2px 3px 0 #ffffff,
|
||||
inset 2px 2px 3px 0 #c7c8c8,
|
||||
inset -0.5px -0.5px 0 0 #ffffff,
|
||||
inset 0.5px 0.5px 0 0 #a0a0a0;
|
||||
}
|
||||
}
|
||||
|
||||
.sid-tool-key {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $btnSize;
|
||||
height: $btnSize;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: $iconColor;
|
||||
transition: color 0.3s ease-in-out;
|
||||
.icon {
|
||||
display: inline-flex;
|
||||
font-size: 24px;
|
||||
transform: rotateZ(135deg);
|
||||
transition: 0.3s transform 0.3s ease-in-out;
|
||||
}
|
||||
}
|
||||
.sid-key-tooltip {
|
||||
opacity: 0;
|
||||
user-select: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
transform: translate(6px, -100%);
|
||||
background-color: $tooltipColor;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
transition:
|
||||
opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1),
|
||||
transform 0.3s cubic-bezier(0.23, 1, 0.32, 1);
|
||||
.key-tooltip_arrow {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
bottom: -5px;
|
||||
left: 12px;
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
z-index: -1;
|
||||
content: ' ';
|
||||
transform: rotate(45deg);
|
||||
background: $tooltipColor;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid $tooltipColor;
|
||||
border-bottom-right-radius: 2px;
|
||||
border-top-color: transparent;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sid-tool-input {
|
||||
// 清除样式
|
||||
outline: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
width: $btnSize;
|
||||
height: $sidToolH;
|
||||
padding-left: $btnSize;
|
||||
box-sizing: border-box;
|
||||
font-family: 'dymht';
|
||||
font-size: 14px;
|
||||
color: $inputColor;
|
||||
&::placeholder {
|
||||
color: $phColor;
|
||||
}
|
||||
&:disabled {
|
||||
color: $inputDisabledColor;
|
||||
}
|
||||
}
|
||||
|
||||
.sid-save {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
width: $saveBtnW;
|
||||
height: $saveBtnH;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
touch-action: manipulation;
|
||||
outline: 5px solid rgba($saveBtnRgb, 0.5);
|
||||
margin: 0 5px;
|
||||
border-radius: 100%;
|
||||
position: relative;
|
||||
transition:
|
||||
transform 0.3s ease-in,
|
||||
opacity 0.3s ease-in;
|
||||
.back {
|
||||
background: $saveBtnSRgb;
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.front {
|
||||
background: linear-gradient(0deg, rgba($saveBtnRgb, 0.6) 20%, $saveBtnRgb 50%);
|
||||
box-shadow: 0 6px 5px -2px rgba($saveBtnSRgb, 0.5);
|
||||
border-radius: 100%;
|
||||
position: absolute;
|
||||
border: 1px solid $saveBtnSRgb;
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
font-family: inherit;
|
||||
transform: translateY(-15%);
|
||||
transition: transform 0.15s ease-in;
|
||||
color: #fff;
|
||||
.icon {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
.front {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sid-help {
|
||||
margin-left: auto;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $helpSize;
|
||||
height: $helpSize;
|
||||
color: $helpColor;
|
||||
transition: color 0.2s ease;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: $helpColorH;
|
||||
}
|
||||
.icon {
|
||||
font-size: $helpSize;
|
||||
}
|
||||
}
|
||||
|
||||
.sid-loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 3px 6px;
|
||||
.sid-loading-mask {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.dots-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
|
||||
.dot {
|
||||
height: $loadingDotSize;
|
||||
width: $loadingDotSize;
|
||||
margin-right: math.div($loadingDotSize, 2);
|
||||
border-radius: math.div($loadingDotSize, 2);
|
||||
background-color: $loadingDotC;
|
||||
animation: pulse 1.5s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.dot:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.dot:nth-child(1) {
|
||||
animation-delay: -0.3s;
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: -0.1s;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
.sid-user {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 6px 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sid-user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0 12px 12px 0;
|
||||
padding: 8px 12px;
|
||||
background: linear-gradient(120deg, #f7f6f5, #ece9e6, #ffffff);
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-left: none;
|
||||
box-shadow: 5px 0 3px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.sid-user-avatar {
|
||||
width: $avatarSize;
|
||||
height: $avatarSize;
|
||||
border-radius: math.div($avatarSize, 2);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
.user-avatar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.sid-user-info {
|
||||
flex-grow: 1;
|
||||
margin-left: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
font-family: 'dymht';
|
||||
.user-name {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 6px;
|
||||
color: $nameColor;
|
||||
-webkit-app-region: no-drag;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
.user-fs {
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.user-fsi {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
& + .user-fsi::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 16px;
|
||||
border-left: 1px solid $fsiHrColor;
|
||||
margin: 0 8px;
|
||||
}
|
||||
.name {
|
||||
color: $fsiNColor;
|
||||
}
|
||||
.value {
|
||||
margin-left: 4px;
|
||||
color: $fsiVColor;
|
||||
max-width: 80px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
.user-sign {
|
||||
user-select: none;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: $signColor;
|
||||
line-height: 1.2;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.sid-user-logout {
|
||||
margin-left: 8px;
|
||||
color: $lgoutColor;
|
||||
transition: color 0.2s ease;
|
||||
.icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
&:hover {
|
||||
color: $lgoutHColor;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
background-color: $loadingDotC;
|
||||
box-shadow: 0 0 0 0 rgba($loadingDotC, 0.7);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
background-color: $loadingDotB;
|
||||
box-shadow: 0 0 0 10px rgba($loadingDotC, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.8);
|
||||
background-color: $loadingDotC;
|
||||
box-shadow: 0 0 0 0 rgba($loadingDotC, 0.7);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<Transition name="fade" @after-leave="handleTransLeave" :duration="500">
|
||||
<div class="sid-tool-help" v-show="visible">
|
||||
<!-- 遮罩 -->
|
||||
<div class="sid-tool-help_mask" @click="handleClose"></div>
|
||||
<!-- 内容 -->
|
||||
<div role="dialog" class="sid-tool-help_main">
|
||||
<h3 class="help-title">一则说明</h3>
|
||||
<div class="help-article">
|
||||
<div class="article-content">
|
||||
<p class="article-tip">
|
||||
由于官方做了某些更新,目前,未登录的话是无法获取到礼物信息的,只有登录后,才能正常获取礼物信息
|
||||
</p>
|
||||
<h4 class="article-h">怎么办?</h4>
|
||||
<p class="article-p">
|
||||
现在,如果想获取礼物信息,需要导入一个登录凭证(cookie),具体操作可参考下侧;如果不想,无需任何操作
|
||||
</p>
|
||||
<h4 class="article-h">参考操作</h4>
|
||||
<p class="article-p">
|
||||
首先,<a class="article-a" href="https://live.douyin.com/" target="_blank">打开官网</a
|
||||
>;同时,确保自己是已登录状态,未登录则先自行登录
|
||||
</p>
|
||||
<p class="article-p">
|
||||
接着,在官网界面打开浏览器开发者工具(可按<span class="article-code">F12</span>),具体操作可参考下图
|
||||
</p>
|
||||
<img class="article-img" alt="控制台" :src="cover" />
|
||||
<p class="article-p">复制官网cookie中sessionid的值</p>
|
||||
<p class="article-p">最后将复制的值粘贴到输入框内保存即可</p>
|
||||
<p class="article-p">
|
||||
如果使用过程中礼物信息接收不到就尝试更新下sessionid的值,保证它与你官网登录账户的sessionid一致
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import cover from '@/assets/images/help-article.jpg';
|
||||
import { isBoolean } from '@/utils/typeUtil';
|
||||
|
||||
interface SidToolHelpProp {
|
||||
modelValue?: boolean;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<SidToolHelpProp>(), {
|
||||
modelValue: false
|
||||
});
|
||||
|
||||
// 是否可见
|
||||
const visible = ref(false);
|
||||
|
||||
const emits = defineEmits({ 'update:modelValue': (value: boolean) => isBoolean(value) });
|
||||
|
||||
/** 关闭弹窗 */
|
||||
const handleClose = function () {
|
||||
close();
|
||||
};
|
||||
|
||||
const handleTransLeave = function () {
|
||||
emits('update:modelValue', false);
|
||||
};
|
||||
|
||||
const open = function () {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const close = function () {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.modelValue) {
|
||||
open();
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
val => {
|
||||
if (val) {
|
||||
// 打开
|
||||
open();
|
||||
} else {
|
||||
// 关闭
|
||||
if (visible.value) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$titleC: #d08635;
|
||||
$textC: #6b798e;
|
||||
$tipC: #98d98e;
|
||||
$hC: #354e6b;
|
||||
$linkC: #6ca8af;
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
.sid-tool-help_mask {
|
||||
transition: opacity 0.3s cubic-bezier(0.55, 0, 0.1, 1);
|
||||
}
|
||||
.sid-tool-help_main {
|
||||
transition:
|
||||
opacity 0.3s cubic-bezier(1, 0.5, 0.8, 1),
|
||||
transform 0.5s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 进入初 & 离开末
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
.sid-tool-help_mask {
|
||||
opacity: 0;
|
||||
}
|
||||
.sid-tool-help_main {
|
||||
opacity: 0;
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.sid-tool-help {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.sid-tool-help_mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.sid-tool-help_main {
|
||||
max-width: 750px;
|
||||
width: 60%;
|
||||
height: 80%;
|
||||
background-color: #fff;
|
||||
border-radius: 12px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 5px 5px 10px 0 rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 8px 12px;
|
||||
.help-title {
|
||||
font-family: 'dymht';
|
||||
font-size: 21px;
|
||||
font-weight: bold;
|
||||
color: $titleC;
|
||||
margin: 0;
|
||||
}
|
||||
.help-article {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.article-content {
|
||||
margin-top: 18px;
|
||||
font-family: 'mkwxy';
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: $textC;
|
||||
gap: 8px;
|
||||
padding-bottom: 36px;
|
||||
}
|
||||
.article-p {
|
||||
color: $textC;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
margin-left: 5px;
|
||||
line-height: 1.5;
|
||||
text-indent: 2em;
|
||||
}
|
||||
.article-tip {
|
||||
margin: 0;
|
||||
color: $tipC;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.article-h {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: $hC;
|
||||
margin: 0;
|
||||
}
|
||||
.article-a {
|
||||
color: $linkC;
|
||||
}
|
||||
.article-code {
|
||||
border: 1px solid #ecd452;
|
||||
border-radius: 3px;
|
||||
box-sizing: border-box;
|
||||
padding: 2px 5px;
|
||||
margin: 0 3px;
|
||||
background-color: rgba(236, 212, 82, 0.7);
|
||||
color: #d452ec;
|
||||
}
|
||||
.article-img {
|
||||
width: 70%;
|
||||
object-fit: cover;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
margin: 0 auto;
|
||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,551 @@
|
||||
/**
|
||||
* 生成 a_bougs 参数值
|
||||
* @param {string} query 请求的 query 参数
|
||||
* @param {string} ua 浏览器 User-Agent
|
||||
* @returns
|
||||
*/
|
||||
export const getAbogus = function (query, ua) {
|
||||
function enc_sum(n_str) {
|
||||
function ir(t) {
|
||||
return (
|
||||
(ir =
|
||||
'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator
|
||||
? function (t) {
|
||||
return typeof t;
|
||||
}
|
||||
: function (t) {
|
||||
return t && 'function' == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype
|
||||
? 'symbol'
|
||||
: typeof t;
|
||||
}),
|
||||
ir(t)
|
||||
);
|
||||
}
|
||||
function ur(t, r) {
|
||||
for (let e = 0; e < r.length; e++) {
|
||||
const n = r[e];
|
||||
((n.enumerable = n.enumerable || !1),
|
||||
(n.configurable = !0),
|
||||
'value' in n && (n.writable = !0),
|
||||
Object.defineProperty(t, sr(n.key), n));
|
||||
}
|
||||
}
|
||||
function sr(t) {
|
||||
const r = (function (t, r) {
|
||||
if ('object' != ir(t) || !t) return t;
|
||||
const e = t[Symbol.toPrimitive];
|
||||
if (void 0 !== e) {
|
||||
const n = e.call(t, r || 'default');
|
||||
if ('object' != ir(n)) return n;
|
||||
throw new TypeError('@@toPrimitive must return a primitive value.');
|
||||
}
|
||||
return ('string' === r ? String : Number)(t);
|
||||
})(t, 'string');
|
||||
return 'symbol' == ir(r) ? r : r + '';
|
||||
}
|
||||
const gr = (function () {
|
||||
function t() {
|
||||
if (
|
||||
((function (t, r) {
|
||||
if (!(t instanceof r)) throw new TypeError('Cannot call a class as a function');
|
||||
})(this, t),
|
||||
!(this instanceof t))
|
||||
)
|
||||
return new t();
|
||||
((this.reg = new Array(8)), (this.chunk = []), (this.size = 0), this.reset());
|
||||
}
|
||||
return (
|
||||
(function (t, r, e) {
|
||||
(r && ur(t.prototype, r),
|
||||
e && ur(t, e),
|
||||
Object.defineProperty(t, 'prototype', {
|
||||
writable: !1
|
||||
}));
|
||||
})(t, [
|
||||
{
|
||||
key: 'reset',
|
||||
value: function () {
|
||||
((this.reg[0] = 1937774191),
|
||||
(this.reg[1] = 1226093241),
|
||||
(this.reg[2] = 388252375),
|
||||
(this.reg[3] = 3666478592),
|
||||
(this.reg[4] = 2842636476),
|
||||
(this.reg[5] = 372324522),
|
||||
(this.reg[6] = 3817729613),
|
||||
(this.reg[7] = 2969243214),
|
||||
(this.chunk = []),
|
||||
(this.size = 0));
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'write',
|
||||
value: function (t) {
|
||||
const r =
|
||||
'string' == typeof t
|
||||
? (function (t) {
|
||||
const r = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (t, r) {
|
||||
return String.fromCharCode('0x' + r);
|
||||
}),
|
||||
e = new Array(r.length);
|
||||
return (
|
||||
Array.prototype.forEach.call(r, function (t, r) {
|
||||
e[r] = t.charCodeAt(0);
|
||||
}),
|
||||
e
|
||||
);
|
||||
})(t)
|
||||
: t;
|
||||
this.size += r.length;
|
||||
let e = 64 - this.chunk.length;
|
||||
if (r.length < e) this.chunk = this.chunk.concat(r);
|
||||
else
|
||||
for (this.chunk = this.chunk.concat(r.slice(0, e)); this.chunk.length >= 64; )
|
||||
(this._compress(this.chunk),
|
||||
e < r.length ? (this.chunk = r.slice(e, Math.min(e + 64, r.length))) : (this.chunk = []),
|
||||
(e += 64));
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'sum',
|
||||
value: function (t, r) {
|
||||
(t && (this.reset(), this.write(t)), this._fill());
|
||||
let e = 0;
|
||||
for (; e < this.chunk.length; e += 64) this._compress(this.chunk.slice(e, e + 64));
|
||||
let n,
|
||||
o,
|
||||
i,
|
||||
u = null;
|
||||
if ('hex' == r) {
|
||||
u = '';
|
||||
for (e = 0; e < 8; e++)
|
||||
u +=
|
||||
((n = this.reg[e].toString(16)),
|
||||
(o = 8),
|
||||
(i = '0'),
|
||||
n.length >= o ? n : i.repeat(o - n.length) + n);
|
||||
} else
|
||||
for (u = new Array(32), e = 0; e < 8; e++) {
|
||||
let s = this.reg[e];
|
||||
((u[4 * e + 3] = (255 & s) >>> 0),
|
||||
(s >>>= 8),
|
||||
(u[4 * e + 2] = (255 & s) >>> 0),
|
||||
(s >>>= 8),
|
||||
(u[4 * e + 1] = (255 & s) >>> 0),
|
||||
(s >>>= 8),
|
||||
(u[4 * e] = (255 & s) >>> 0));
|
||||
}
|
||||
return (this.reset(), u);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '_compress',
|
||||
value: function (t) {
|
||||
if (t < 64) console.error('compress error: not enough data');
|
||||
else {
|
||||
let r = (function (t) {
|
||||
const r = new Array(132);
|
||||
for (let e = 0; e < 16; e++) {
|
||||
r[e] = t[4 * e] << 24;
|
||||
r[e] |= t[4 * e + 1] << 16;
|
||||
r[e] |= t[4 * e + 2] << 8;
|
||||
r[e] |= t[4 * e + 3];
|
||||
r[e] >>>= 0;
|
||||
}
|
||||
|
||||
for (let n = 16; n < 68; n++) {
|
||||
let o = r[n - 16] ^ r[n - 9] ^ dr(r[n - 3], 15);
|
||||
o = o ^ dr(o, 15) ^ dr(o, 23);
|
||||
r[n] = (o ^ dr(r[n - 13], 7) ^ r[n - 6]) >>> 0;
|
||||
}
|
||||
for (let n = 0; n < 64; n++) r[n + 68] = (r[n] ^ r[n + 4]) >>> 0;
|
||||
return r;
|
||||
})(t);
|
||||
let e = this.reg.slice(0);
|
||||
for (let n = 0; n < 64; n++) {
|
||||
let o = dr(e[0], 12) + e[4] + dr(yr(n), n),
|
||||
i = ((o = dr((o = (4294967295 & o) >>> 0), 7)) ^ dr(e[0], 12)) >>> 0,
|
||||
u = br(n, e[0], e[1], e[2]);
|
||||
u = (4294967295 & (u = u + e[3] + i + r[n + 68])) >>> 0;
|
||||
let s = mr(n, e[4], e[5], e[6]);
|
||||
s = (4294967295 & (s = s + e[7] + o + r[n])) >>> 0;
|
||||
e[3] = e[2];
|
||||
e[2] = dr(e[1], 9);
|
||||
e[1] = e[0];
|
||||
e[0] = u;
|
||||
e[7] = e[6];
|
||||
e[6] = dr(e[5], 19);
|
||||
e[5] = e[4];
|
||||
e[4] = (s ^ dr(s, 9) ^ dr(s, 17)) >>> 0;
|
||||
}
|
||||
for (let c = 0; c < 8; c++) this.reg[c] = (this.reg[c] ^ e[c]) >>> 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '_fill',
|
||||
value: function () {
|
||||
let t = 8 * this.size,
|
||||
r = this.chunk.push(128) % 64;
|
||||
for (64 - r < 8 && (r -= 64); r < 56; r++) this.chunk.push(0);
|
||||
for (let e = 0; e < 4; e++) {
|
||||
let n = Math.floor(t / 4294967296);
|
||||
this.chunk.push((n >>> (8 * (3 - e))) & 255);
|
||||
}
|
||||
for (let e = 0; e < 4; e++) this.chunk.push((t >>> (8 * (3 - e))) & 255);
|
||||
}
|
||||
}
|
||||
]),
|
||||
t
|
||||
);
|
||||
})();
|
||||
function dr(t, r) {
|
||||
return ((t << (r %= 32)) | (t >>> (32 - r))) >>> 0;
|
||||
}
|
||||
function yr(t) {
|
||||
return 0 <= t && t < 16
|
||||
? 2043430169
|
||||
: 16 <= t && t < 64
|
||||
? 2055708042
|
||||
: void console.error('invalid j for constant Tj');
|
||||
}
|
||||
function br(t, r, e, n) {
|
||||
return 0 <= t && t < 16
|
||||
? (r ^ e ^ n) >>> 0
|
||||
: 16 <= t && t < 64
|
||||
? ((r & e) | (r & n) | (e & n)) >>> 0
|
||||
: (console.error('invalid j for bool function FF'), 0);
|
||||
}
|
||||
function mr(t, r, e, n) {
|
||||
return 0 <= t && t < 16
|
||||
? (r ^ e ^ n) >>> 0
|
||||
: 16 <= t && t < 64
|
||||
? ((r & e) | (~r & n)) >>> 0
|
||||
: (console.error('invalid j for bool function GG'), 0);
|
||||
}
|
||||
const enc_ = new gr();
|
||||
return enc_.sum(n_str);
|
||||
}
|
||||
function generate_lm_g_EP(uat = ua) {
|
||||
function get_sz256f() {
|
||||
let r = [],
|
||||
k = 0,
|
||||
y = [0, 1, 0];
|
||||
for (let i = 255; i >= 0; i--) {
|
||||
r.push(i);
|
||||
}
|
||||
for (let i = 0; i < r.length; i++) {
|
||||
let a = r[i];
|
||||
k = (k * a + k + y[i % 3]) % 256;
|
||||
let b = r[k];
|
||||
((r[i] = b), (r[k] = a));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
const sz256f = [
|
||||
233, 5, 1, 249, 162, 140, 57, 143, 19, 203, 254, 236, 99, 248, 93, 213, 79, 149, 216, 50, 145, 123, 240, 92, 23,
|
||||
113, 130, 53, 235, 220, 201, 136, 223, 155, 190, 242, 243, 42, 52, 214, 151, 232, 97, 187, 163, 222, 30, 78, 47,
|
||||
71, 49, 170, 247, 196, 25, 156, 183, 182, 217, 180, 147, 124, 208, 69, 215, 200, 161, 154, 91, 60, 133, 224, 119,
|
||||
164, 221, 45, 98, 40, 186, 120, 51, 167, 38, 90, 194, 212, 129, 56, 87, 195, 144, 44, 75, 84, 81, 13, 197, 245,
|
||||
36, 250, 115, 100, 105, 252, 206, 103, 112, 202, 114, 138, 192, 21, 116, 173, 181, 29, 82, 125, 141, 16, 211, 131,
|
||||
225, 118, 31, 101, 77, 146, 135, 150, 62, 66, 67, 176, 0, 41, 46, 59, 107, 178, 43, 26, 189, 128, 8, 207, 166,
|
||||
110, 3, 229, 85, 54, 63, 11, 32, 4, 234, 142, 72, 58, 33, 231, 12, 230, 102, 86, 70, 159, 226, 65, 237, 34, 244,
|
||||
76, 132, 122, 111, 95, 179, 152, 175, 18, 177, 6, 126, 193, 219, 74, 134, 2, 61, 251, 191, 168, 209, 241, 137,
|
||||
165, 88, 238, 160, 174, 153, 157, 199, 48, 22, 64, 246, 7, 139, 55, 27, 188, 148, 204, 127, 171, 89, 37, 172, 205,
|
||||
121, 20, 28, 17, 169, 15, 227, 117, 80, 218, 198, 10, 106, 9, 39, 210, 104, 83, 109, 24, 108, 228, 184, 96, 185,
|
||||
158, 14, 255, 239, 68, 94, 35, 73, 253
|
||||
];
|
||||
let k = 0,
|
||||
s = '';
|
||||
for (let i = 0; i < uat.length; i++) {
|
||||
let t = (i + 1) % 256;
|
||||
let a = sz256f[t];
|
||||
k = (k + a) % 256;
|
||||
let c = sz256f[k];
|
||||
sz256f[t] = c;
|
||||
sz256f[k] = a;
|
||||
s += String.fromCharCode(uat.charCodeAt(i) ^ sz256f[(a + c) % 256]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
function get_str_chr_list(one_str) {
|
||||
const r = [];
|
||||
for (let i = 0; i < one_str.length; i++) {
|
||||
r.push(one_str.charCodeAt(i));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
function generate_szenc_head8p1() {
|
||||
let z = Math.random() * 65535;
|
||||
let a = z & 255;
|
||||
let b = (z >> 8) & 255,
|
||||
d = [];
|
||||
d.push((a & 170) | 1);
|
||||
d.push((a & 85) | 0);
|
||||
d.push((b & 170) | 0);
|
||||
d.push((b & 85) | 0);
|
||||
return d;
|
||||
}
|
||||
function generate_szenc_head8p2() {
|
||||
let a = ((Math.random() * 240) >> 0) + 1;
|
||||
let b = ((Math.random() * 255) >> 0) & 77,
|
||||
c = [1, 4, 5, 7],
|
||||
d = [];
|
||||
for (let i = 0; i < c.length; i++) {
|
||||
b = b | (1 << c[i]);
|
||||
}
|
||||
d.push((a & 170) | 1);
|
||||
d.push((a & 85) | 0);
|
||||
d.push((b & 170) | 0);
|
||||
d.push((b & 85) | 0);
|
||||
return d;
|
||||
}
|
||||
function get_szenc_tail(sz96) {
|
||||
const zKeys = [145, 110, 66, 189, 44, 211];
|
||||
const a = [];
|
||||
for (let i = 0; i < 94; i += 3) {
|
||||
let b = sz96[i];
|
||||
let c = sz96[i + 1];
|
||||
let d = sz96[i + 2];
|
||||
let e = (Math.random() * 1000) & 255;
|
||||
a.push((e & zKeys[0]) | (b & zKeys[1]));
|
||||
a.push((e & zKeys[2]) | (c & zKeys[3]));
|
||||
a.push((e & zKeys[4]) | (d & zKeys[5]));
|
||||
a.push((b & zKeys[0]) | (c & zKeys[2]) | (d & zKeys[4]));
|
||||
}
|
||||
return a;
|
||||
}
|
||||
function generate_lm_g_ab_head4() {
|
||||
let s = '';
|
||||
const a = (Math.random() * 65535) & 255,
|
||||
b = (Math.random() * 40) >> 0;
|
||||
s += String.fromCharCode((a & 170) | 1);
|
||||
s += String.fromCharCode((a & 85) | 2);
|
||||
s += String.fromCharCode((b & 170) | 80);
|
||||
s += String.fromCharCode((b & 85) | 2);
|
||||
return s;
|
||||
}
|
||||
function get_list_str(one_list) {
|
||||
let s = '';
|
||||
for (let i = 0; i < one_list.length; i++) {
|
||||
s += String.fromCharCode(one_list[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
function get_lm_g_ab(lm_g_lm_n) {
|
||||
function getSZ256() {
|
||||
const raw = [];
|
||||
let z = 0;
|
||||
for (let i = 255; i >= 0; i--) {
|
||||
raw.push(i);
|
||||
}
|
||||
for (let i = 0; i < raw.length; i++) {
|
||||
z += 211;
|
||||
let a = z % 256;
|
||||
let b = raw[i];
|
||||
let c = raw[a];
|
||||
raw[a] = b;
|
||||
raw[i] = c;
|
||||
z = raw[i + 1] * a + a;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
const fixedSZ256 = [
|
||||
194, 249, 255, 165, 114, 67, 251, 187, 174, 231, 164, 237, 124, 235, 68, 83, 206, 79, 142, 167, 30, 77, 0, 93,
|
||||
118, 29, 32, 161, 2, 171, 243, 179, 42, 170, 223, 119, 98, 222, 219, 57, 245, 135, 197, 13, 186, 202, 88, 184,
|
||||
214, 12, 76, 185, 116, 74, 54, 53, 104, 208, 158, 163, 82, 173, 253, 240, 172, 63, 191, 207, 25, 15, 201, 203,
|
||||
215, 236, 183, 233, 145, 127, 72, 6, 16, 10, 228, 35, 232, 159, 66, 168, 108, 71, 217, 75, 33, 155, 112, 128, 36,
|
||||
24, 138, 50, 211, 23, 107, 14, 247, 137, 175, 242, 234, 157, 199, 49, 139, 85, 81, 17, 180, 86, 120, 78, 51, 205,
|
||||
169, 148, 181, 3, 94, 106, 252, 220, 150, 47, 151, 84, 212, 18, 149, 182, 100, 123, 121, 156, 154, 152, 126, 204,
|
||||
60, 133, 132, 248, 7, 91, 58, 59, 20, 97, 113, 117, 131, 46, 250, 224, 21, 73, 146, 31, 193, 69, 140, 125, 9, 39,
|
||||
89, 5, 65, 141, 218, 80, 1, 70, 64, 166, 87, 189, 55, 147, 22, 26, 143, 61, 144, 99, 92, 44, 129, 130, 227, 103,
|
||||
90, 192, 198, 244, 136, 101, 246, 153, 56, 38, 4, 178, 221, 162, 134, 37, 111, 28, 216, 96, 102, 210, 254, 196,
|
||||
195, 230, 241, 62, 11, 122, 52, 40, 41, 229, 226, 225, 48, 45, 160, 105, 8, 115, 34, 43, 209, 95, 239, 190, 188,
|
||||
109, 27, 19, 176, 213, 200, 238, 177, 110
|
||||
];
|
||||
// const fixedSZ256 = getSZ256();
|
||||
let z = 0;
|
||||
let st = '';
|
||||
for (let i = 0; i < lm_g_lm_n.length; i++) {
|
||||
let a = (i + 1) % 256;
|
||||
let c = fixedSZ256[a];
|
||||
z = (z + c) % 256;
|
||||
let e = fixedSZ256[z];
|
||||
fixedSZ256[a] = e;
|
||||
fixedSZ256[z] = c;
|
||||
let g = (e + c) % 256;
|
||||
let h = lm_g_lm_n.charCodeAt(i);
|
||||
let j = fixedSZ256[g];
|
||||
let k = h ^ j;
|
||||
let l = String.fromCharCode(k);
|
||||
st += l;
|
||||
}
|
||||
return st;
|
||||
}
|
||||
function get_raw_ab(lm_get_ab_n, key_str = info_dic.s4) {
|
||||
let s = '',
|
||||
bw = 0;
|
||||
for (let i = 0; i < lm_get_ab_n.length; i += 3) {
|
||||
let cl = 16;
|
||||
let tcz = 0;
|
||||
let sof = 16515072;
|
||||
for (let j = i; j < i + 3; j++) {
|
||||
if (j < lm_get_ab_n.length) {
|
||||
let tlcy = lm_get_ab_n.charCodeAt(j) & 255;
|
||||
tcz = tcz | (tlcy << cl);
|
||||
cl -= 8;
|
||||
} else {
|
||||
bw += 1;
|
||||
}
|
||||
}
|
||||
for (let h = 18; h >= 6 * bw; h -= 6) {
|
||||
let tsz = tcz & sof;
|
||||
s += key_str[tsz >> h];
|
||||
sof = sof / 64;
|
||||
}
|
||||
s += '='.repeat(bw);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
function get_random_number(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
const info_dic = {
|
||||
s0: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
|
||||
s1: 'Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=',
|
||||
s2: 'Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=',
|
||||
s3: 'ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe',
|
||||
s4: 'Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe'
|
||||
};
|
||||
const t1 = Date.now();
|
||||
const s = [];
|
||||
|
||||
const t2 = Date.now() - 1 + get_random_number(1, 3);
|
||||
|
||||
const EP = get_raw_ab(generate_lm_g_EP(ua), info_dic.s3);
|
||||
const eEP = enc_sum(EP);
|
||||
|
||||
s.push('env_fx_list', 'dpf_ua_dic', 1, 0, 8, 'dpf', '', 'ua', 6241, 6383, '1.0.1.19-fix.01', 'ink', 3, '0X21_dic'); // 固定即可
|
||||
|
||||
const t3 = Date.now() + get_random_number(4, 15);
|
||||
const eedp = enc_sum(enc_sum(query + 'dhzx'));
|
||||
|
||||
s.push(t3, 'reg_dic', 1, 0, eedp, 'eedh', EP, eEP, t2, [3, 82], 41, [1, 0, 1, 0, 1]);
|
||||
|
||||
const t4 = Date.now() + get_random_number(100, 1000);
|
||||
|
||||
const s1 = ((t4 - 1721836800000) / 1000 / 60 / 60 / 24 / 14) >> 0,
|
||||
szenc_o95_tail41 = [
|
||||
49, 52, 52, 49, 124, 56, 51, 56, 124, 49, 52, 52, 49, 124, 57, 49, 51, 124, 49, 52, 52, 49, 124, 57, 49, 51, 124,
|
||||
49, 52, 52, 49, 124, 57, 54, 49, 124, 87, 105, 110, 51, 50
|
||||
];
|
||||
|
||||
s.push(
|
||||
s1,
|
||||
6,
|
||||
(t3 - t1 + 3) & 255,
|
||||
t3 & 255,
|
||||
(t3 >> 8) & 255,
|
||||
(t3 >> 16) & 255,
|
||||
(t3 >> 24) & 255,
|
||||
(t3 / 256 / 256 / 256 / 256) & 255
|
||||
);
|
||||
|
||||
const s2 = (t3 / 256 / 256 / 256 / 256 / 256) & 255;
|
||||
|
||||
s.push(
|
||||
s2,
|
||||
(s2 % 256) & 255,
|
||||
(s2 / 256) & 255,
|
||||
[211, 2, 5, 1, 129],
|
||||
129,
|
||||
0,
|
||||
211,
|
||||
2,
|
||||
5,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
eedp[9],
|
||||
eedp[18],
|
||||
3,
|
||||
eedp[3],
|
||||
82,
|
||||
177,
|
||||
4,
|
||||
44,
|
||||
eEP[11],
|
||||
eEP[21],
|
||||
5,
|
||||
eEP[5],
|
||||
t2 & 255,
|
||||
(t2 >> 8) & 255,
|
||||
(t2 >> 16) & 255,
|
||||
(t2 >> 24) & 255,
|
||||
(t2 / 256 / 256 / 256 / 256) & 255,
|
||||
(t2 / 256 / 256 / 256 / 256 / 256) & 255,
|
||||
3,
|
||||
97,
|
||||
24,
|
||||
0,
|
||||
0,
|
||||
239,
|
||||
24,
|
||||
0,
|
||||
0,
|
||||
'screec_dic',
|
||||
'screen_str',
|
||||
szenc_o95_tail41,
|
||||
41,
|
||||
41,
|
||||
0
|
||||
);
|
||||
|
||||
const s3 = ((t3 + 3) & 255) + ',',
|
||||
s4 = get_str_chr_list(s3);
|
||||
|
||||
s.push(s3, s4, s4.length, s4.length & 255, (s4.length >> 8) & 255);
|
||||
|
||||
const szenc_head8_p1 = generate_szenc_head8p1(),
|
||||
szenc_head8_p2 = generate_szenc_head8p2(),
|
||||
szenc_head8 = szenc_head8_p1.concat(szenc_head8_p2),
|
||||
s5 = [],
|
||||
s6 = [
|
||||
24, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 55,
|
||||
56, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 79, 80, 84, 85
|
||||
];
|
||||
for (let i = 0; i < s6.length; i++) {
|
||||
s5.push(s[s6[i]]);
|
||||
}
|
||||
s.push(szenc_head8);
|
||||
const s7 = szenc_head8.concat(s5);
|
||||
|
||||
let s8 = s7[0];
|
||||
for (let i = 1; i < s7.length; i++) {
|
||||
s8 = s8 ^ s7[i];
|
||||
}
|
||||
s.push(s8);
|
||||
|
||||
const enc_s_i = [
|
||||
34, 44, 56, 61, 73, 29, 70, 45, 35, 49, 38, 66, 51, 68, 28, 48, 64, 47, 30, 71, 26, 55, 31, 69, 59, 40, 62, 63,
|
||||
27, 72, 41, 74, 57, 52, 42, 39, 33, 67, 53, 43, 65, 46, 36, 24, 60, 32, 79, 80, 84, 85
|
||||
],
|
||||
szenc_o95_head50 = [];
|
||||
for (let i = 0; i < enc_s_i.length; i++) {
|
||||
szenc_o95_head50.push(s[enc_s_i[i]]);
|
||||
}
|
||||
let szenc_o95 = [];
|
||||
szenc_o95 = szenc_o95.concat(szenc_o95_head50, szenc_o95_tail41, s4, [s8]);
|
||||
|
||||
const szenc_tail = get_szenc_tail(szenc_o95),
|
||||
szenc = szenc_head8.concat(szenc_tail),
|
||||
lm_get_ab_head4 = generate_lm_g_ab_head4();
|
||||
|
||||
const lm_get_lm = get_list_str(szenc);
|
||||
const lm_get_ab_tail = get_lm_g_ab(lm_get_lm);
|
||||
const lm_get_ab = lm_get_ab_head4 + lm_get_ab_tail;
|
||||
const ab = get_raw_ab(lm_get_ab);
|
||||
|
||||
return ab;
|
||||
};
|
||||
+16
-12
@@ -24,9 +24,9 @@ import type {
|
||||
Text,
|
||||
User
|
||||
} from './model';
|
||||
import { getImInfo, getLiveInfo } from './request';
|
||||
import { fetchUser, getImInfo, getLiveInfo } from './request';
|
||||
import { getSignature } from './signature';
|
||||
import { logUserCast } from '@/utils/debugUtil';
|
||||
// import { logUserCast } from '@/utils/debugUtil';
|
||||
|
||||
/**
|
||||
* 连接状态
|
||||
@@ -140,6 +140,7 @@ export interface DyMessage {
|
||||
id?: string;
|
||||
method?: CastMethod;
|
||||
user?: CastUser;
|
||||
toUser?: CastUser;
|
||||
gift?: CastGift;
|
||||
content?: string;
|
||||
rtfContent?: CastRtfContent[];
|
||||
@@ -308,11 +309,12 @@ enum PayloadType {
|
||||
}
|
||||
|
||||
/** API */
|
||||
// const BASE_URL = 'wss://webcast5-ws-web-lf.douyin.com/webcast/im/push/v2/';
|
||||
// wss://webcast5-ws-web-lf.douyin.com/webcast/im/push/v2/ => version: 1.0.14-beta.0
|
||||
// wss://webcast100-ws-web-lq.douyin.com/webcast/im/push/v2/ => version: 1.0.15
|
||||
const BASE_URL = `${location.origin.replace(/^http/, 'ws')}/socket/webcast/im/push/v2/`;
|
||||
|
||||
/** SDK 版本 */
|
||||
const VERSION = '1.0.14-beta.0';
|
||||
export const VERSION = '1.0.15';
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
@@ -766,6 +768,7 @@ export class DyCast {
|
||||
message = decodeGiftMessage(payload);
|
||||
data.method = CastMethod.GIFT;
|
||||
data.user = this._getCastUser(message.user);
|
||||
data.toUser = this._getCastUser(message.toUser);
|
||||
data.gift = this._getCastGift(message.gift, message.repeatCount || message.comboCount, message.repeatEnd);
|
||||
break;
|
||||
case CastMethod.LIKE:
|
||||
@@ -934,11 +937,11 @@ export class DyCast {
|
||||
});
|
||||
} else if (pieces[i].userValue) {
|
||||
// 艾特用户
|
||||
let toUser = pieces[i].userValue?.user;
|
||||
let atUser = pieces[i].userValue?.user;
|
||||
list.push({
|
||||
type: CastRtfContentType.USER,
|
||||
text: `@${toUser?.nickname}`,
|
||||
user: this._getCastUser(toUser)
|
||||
text: `@${atUser?.nickname}`,
|
||||
user: this._getCastUser(atUser)
|
||||
});
|
||||
} else {
|
||||
// 假定为普通文本类型
|
||||
@@ -992,7 +995,7 @@ export class DyCast {
|
||||
private _ping() {
|
||||
return encodePushFrame({
|
||||
payloadType: PayloadType.Hb
|
||||
});
|
||||
}) as Uint8Array<ArrayBuffer>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1008,9 +1011,9 @@ export class DyCast {
|
||||
index < 128
|
||||
? arr.push(index)
|
||||
: index < 2048
|
||||
? (arr.push(192 + (index >> 6)), arr.push(128 + (63 & index)))
|
||||
: index < 65536 &&
|
||||
(arr.push(224 + (index >> 12)), arr.push(128 + ((index >> 6) & 63)), arr.push(128 + (63 & index)));
|
||||
? (arr.push(192 + (index >> 6)), arr.push(128 + (63 & index)))
|
||||
: index < 65536 &&
|
||||
(arr.push(224 + (index >> 12)), arr.push(128 + ((index >> 6) & 63)), arr.push(128 + (63 & index)));
|
||||
}
|
||||
return new Uint8Array(arr);
|
||||
};
|
||||
@@ -1018,7 +1021,7 @@ export class DyCast {
|
||||
payloadType: PayloadType.Ack,
|
||||
payload: getPayload(ext),
|
||||
logId
|
||||
});
|
||||
}) as Uint8Array<ArrayBuffer>;
|
||||
}
|
||||
|
||||
/** 关闭后 */
|
||||
@@ -1080,6 +1083,7 @@ export class DyCast {
|
||||
const info = await getLiveInfo(roomNum);
|
||||
this.info = info;
|
||||
this.status = info.status;
|
||||
await fetchUser();
|
||||
const res = await getImInfo(info.roomId, info.uniqueId);
|
||||
this.imInfo = res;
|
||||
} catch (err) {
|
||||
|
||||
+609
-365
File diff suppressed because it is too large.
Load diff
+138
-1
@@ -1,3 +1,4 @@
|
||||
import { getAbogus } from './abogus';
|
||||
import type { DyImInfo } from './dycast';
|
||||
import { decodeResponse } from './model';
|
||||
import { getMsToken } from './signature';
|
||||
@@ -39,6 +40,24 @@ export const getLiveInfo = async function (id: string) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户请求
|
||||
* - 用于增加cookie
|
||||
*/
|
||||
export const fetchUser = async function () {
|
||||
try {
|
||||
await fetch(`/dylive/webcast/user/`, {
|
||||
method: 'HEAD',
|
||||
headers: {
|
||||
'X-Secsdk-Csrf-Request': '1',
|
||||
'X-Secsdk-Csrf-Version': '1.2.22'
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.reject(Error('Fetch Webcast User Error'));
|
||||
}
|
||||
};
|
||||
|
||||
const USER_AGENT =
|
||||
navigator.userAgent ||
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36';
|
||||
@@ -100,8 +119,16 @@ export const fetchImInfo = async function (roomId: string, uniqueId: string) {
|
||||
room_id: roomId,
|
||||
user_unique_id: uniqueId
|
||||
});
|
||||
const paramStr = makeUrlParams(
|
||||
Object.assign({}, defaultIMFetchParams, {
|
||||
room_id: roomId,
|
||||
user_unique_id: uniqueId,
|
||||
live_pc: roomId
|
||||
})
|
||||
);
|
||||
// 一个加密参数,须通过上侧 params 参数计算,感兴趣自己去逆向,这里不解析,不一定验证
|
||||
const aBogus = '00000000';
|
||||
// const aBogus = '00000000';
|
||||
const aBogus = getAbogus(paramStr, USER_AGENT);
|
||||
Object.assign(params, {
|
||||
live_pc: roomId,
|
||||
a_bogus: aBogus
|
||||
@@ -148,3 +175,113 @@ export const getImInfo = async function (roomId: string, uniqueId: string): Prom
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/** 默认请求参数 */
|
||||
const defaultMeFetchParam = {
|
||||
aid: '6383',
|
||||
app_name: 'douyin_web',
|
||||
browser_language: 'zh-CN',
|
||||
browser_name: 'Edge',
|
||||
browser_platform: 'Win32',
|
||||
browser_version: '146.0.0.0',
|
||||
cookie_enabled: 'true',
|
||||
device_platform: 'web',
|
||||
enter_from: 'web_live',
|
||||
language: 'zh-CN',
|
||||
live_id: '1',
|
||||
os_name: 'Windows',
|
||||
os_version: '10',
|
||||
room_id: '0',
|
||||
screen_height: '1080',
|
||||
screen_width: '1920'
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
* @returns
|
||||
*/
|
||||
export const fetchMeInfo = async function () {
|
||||
try {
|
||||
const params = Object.assign({}, defaultMeFetchParam);
|
||||
const paramStr = makeUrlParams(params);
|
||||
const msToken = getMsToken(184);
|
||||
const abogus = getAbogus(paramStr, USER_AGENT);
|
||||
Object.assign(params, {
|
||||
msToken,
|
||||
a_bogus: abogus
|
||||
});
|
||||
const url = `/dylive/webcast/user/me/?${makeUrlParams(params)}`;
|
||||
const res = await fetch(url).then(res => res.json());
|
||||
if (res) return res;
|
||||
else return Promise.reject(`Fetch Me Info Fail`);
|
||||
if (res && res['status_code'] !== 0) {
|
||||
const msg = res?.data?.message;
|
||||
return Promise.reject(`Fetch Me Info Fail: status => ${res['status_code']}, msg => ${msg}`);
|
||||
} else return res;
|
||||
} catch (err) {
|
||||
return Promise.reject(`Fetch Me Info Error: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
interface DyResult<T> {
|
||||
/** 状态码 */
|
||||
code: number;
|
||||
/** 状态描述 */
|
||||
msg: string;
|
||||
/** 数据 */
|
||||
data: T;
|
||||
}
|
||||
|
||||
interface DyMeInfo {
|
||||
/** sec_uid */
|
||||
uid: string;
|
||||
/** display_id : 抖音号 */
|
||||
did: string;
|
||||
/** 昵称 */
|
||||
nickname: string;
|
||||
/** 头像 */
|
||||
avatar: string;
|
||||
/** follower_count : 粉丝数 */
|
||||
follower: number;
|
||||
/** following_count : 关注数 */
|
||||
following: number;
|
||||
/** signature : 个性签名 */
|
||||
sign?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*/
|
||||
export const getMeInfo = async function (): Promise<DyResult<DyMeInfo | null>> {
|
||||
try {
|
||||
const res = await fetchMeInfo();
|
||||
let code = 0;
|
||||
let msg = '';
|
||||
let info: DyMeInfo | null = null;
|
||||
if (res['status_code'] !== 0) {
|
||||
// 获取失败
|
||||
const data = res?.data || {};
|
||||
code = res['status_code'];
|
||||
msg = data?.prompts || data?.message || 'get user info fail';
|
||||
} else {
|
||||
// 获取成功
|
||||
const data = res.data || {};
|
||||
info = {
|
||||
uid: data.sec_uid || '',
|
||||
did: data.display_id || '',
|
||||
sign: data.signature || '',
|
||||
nickname: data.nickname || '',
|
||||
avatar: data.avatar_medium?.url_list?.[0] || '',
|
||||
follower: data.follow_info?.follower_count || 0,
|
||||
following: data.follow_info?.following_count || 0
|
||||
};
|
||||
}
|
||||
return {
|
||||
code,
|
||||
msg,
|
||||
data: info
|
||||
};
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,5 @@
|
||||
import { VERSION } from './dycast';
|
||||
|
||||
const stringToBytes = function (t) {
|
||||
t = decodeURIComponent(encodeURIComponent(t));
|
||||
let n = [];
|
||||
@@ -21,7 +23,7 @@ const stringToBytes = function (t) {
|
||||
},
|
||||
bytesToHex = function (t) {
|
||||
let n = [];
|
||||
for (let e = 0; e < t.length; e++) n.push((t[e] >>> 4).toString(16)), n.push((15 & t[e]).toString(16));
|
||||
for (let e = 0; e < t.length; e++) (n.push((t[e] >>> 4).toString(16)), n.push((15 & t[e]).toString(16)));
|
||||
return n.join('');
|
||||
},
|
||||
_ff = function (t, n, e, p, r, o, i) {
|
||||
@@ -58,7 +60,7 @@ function un(t, n) {
|
||||
l = 271733878;
|
||||
for (let t = 0; t < e.length; t++)
|
||||
e[t] = (16711935 & ((e[t] << 8) | (e[t] >>> 24))) | (4278255360 & ((e[t] << 24) | (e[t] >>> 8)));
|
||||
(e[r >>> 5] |= 128 << r % 32), (e[14 + (((r + 64) >>> 9) << 4)] = r);
|
||||
((e[r >>> 5] |= 128 << (r % 32)), (e[14 + (((r + 64) >>> 9) << 4)] = r));
|
||||
let f = _ff,
|
||||
s = _gg,
|
||||
c = _hh,
|
||||
@@ -68,7 +70,7 @@ function un(t, n) {
|
||||
_ = i,
|
||||
h = u,
|
||||
a = l;
|
||||
(o = d(
|
||||
((o = d(
|
||||
(o = c(
|
||||
(o = c(
|
||||
(o = c(
|
||||
@@ -321,7 +323,7 @@ function un(t, n) {
|
||||
(o = (o + g) >>> 0),
|
||||
(i = (i + _) >>> 0),
|
||||
(u = (u + h) >>> 0),
|
||||
(l = (l + a) >>> 0);
|
||||
(l = (l + a) >>> 0));
|
||||
}
|
||||
return endian([o, i, u, l]);
|
||||
}
|
||||
@@ -337,7 +339,7 @@ function getSTUB(t) {
|
||||
* @return {string} signature
|
||||
*/
|
||||
export const getSignature = function (roomId, uniqueId) {
|
||||
const sdkVersion = '1.0.14-beta.0';
|
||||
const sdkVersion = VERSION;
|
||||
const e = getSTUB(
|
||||
`live_id=1,aid=6383,version_code=180800,webcast_sdk_version=${sdkVersion},room_id=${roomId},sub_room_id=,sub_channel_id=,did_rule=3,user_unique_id=${uniqueId},device_platform=web,device_type=,ac=,identity=audience`
|
||||
);
|
||||
@@ -356,7 +358,7 @@ export const getSignature = function (roomId, uniqueId) {
|
||||
* @returns
|
||||
*/
|
||||
export const getMsToken = function (length = 182) {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* 简单封装的 Cookie 操作工具
|
||||
* - 只能获取非 HttpOnly 的 Cookie 项(前端限制)
|
||||
* - 同样的,也不能设置 HttpOnly 的 Cookie 项
|
||||
*/
|
||||
|
||||
type SameSite = 'strict' | 'lax' | 'none';
|
||||
|
||||
/**
|
||||
* Cookie 结构
|
||||
*/
|
||||
interface CustomCookie {
|
||||
name: string;
|
||||
value: string;
|
||||
path?: string;
|
||||
// null 表示可能为会话 cookie
|
||||
expires?: string | number | null;
|
||||
domain?: string;
|
||||
maxAge?: number;
|
||||
secure?: boolean;
|
||||
sameSite?: SameSite;
|
||||
}
|
||||
|
||||
/**
|
||||
* CookieStore 项结构
|
||||
* - CookieStore.get 返回 Promise 兑现的对象
|
||||
*/
|
||||
interface CookieStoreItem {
|
||||
/** 记录 cookie 名称的字符串 */
|
||||
name: string;
|
||||
/** 记录 cookie 的值的字符串 */
|
||||
value: string;
|
||||
/** 记录 cookie 域名的字符串 */
|
||||
domain: string | null;
|
||||
/** 记录 cookie 的到期日期的 Unix 时间戳(以毫秒为单位表示)的数字 */
|
||||
expires: number | null;
|
||||
/**
|
||||
* 表示 cookie 是否是分区 cookie(true)或(false)
|
||||
* - 更多信息请参阅具有独立分区状态的 Cookie(CHIPS)
|
||||
*/
|
||||
partitioned: boolean;
|
||||
/** 记录 cookie 路径的字符串 */
|
||||
path: string;
|
||||
/**
|
||||
* 以下 SameSite 的值之一
|
||||
* - strict: Cookie 只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送
|
||||
* - lax: Cookie 不会在正常的跨站点子请求(例如将图像或框架加载到第三方站点)中发送,而是在用户在原始站点内导航时(即点击链接时)发送
|
||||
* - none: Cookie 将会在所有上下文中发送
|
||||
*/
|
||||
sameSite: SameSite;
|
||||
/** 表示 cookie 是否仅在安全上下文中使用(true)或(false) */
|
||||
secure: boolean;
|
||||
}
|
||||
|
||||
type SameSiteMap = {
|
||||
[K in SameSite]: string;
|
||||
};
|
||||
|
||||
const SAMESITE_MAP: SameSiteMap = {
|
||||
strict: 'Strict',
|
||||
lax: 'Lax',
|
||||
none: 'None'
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取某个 Cookie 项
|
||||
* - 通过 document.cookie,只能获取其值
|
||||
* @param name cookie 名称
|
||||
*/
|
||||
export const getCookie = function (name: string) {
|
||||
let cookie: CustomCookie | null = null;
|
||||
// 用正则匹配对应cookie项,包括path、expires等信息
|
||||
const reg = new RegExp(`(^|; )${name}=([^;]*)`);
|
||||
const match = document.cookie.match(reg);
|
||||
if (match && match.length > 2) {
|
||||
cookie = {
|
||||
name,
|
||||
value: decodeURIComponent(match[2])
|
||||
};
|
||||
}
|
||||
return cookie;
|
||||
};
|
||||
|
||||
/**
|
||||
* 异步获取某个 Cookie 项
|
||||
* - 通过 CookieStore,能获取其所有信息,如:path、expires 等
|
||||
* - 如浏览器不支持则通过 document.cookie 获取
|
||||
* @param name cookie 名称
|
||||
*/
|
||||
export const getCookieAsync = async function (name: string) {
|
||||
// 用正则匹配对应cookie项,包括path、expires等信息
|
||||
try {
|
||||
let cookie: CustomCookie | null = null;
|
||||
if (window.cookieStore && window.cookieStore instanceof CookieStore) {
|
||||
const ck = (await window.cookieStore.get(name)) as CookieStoreItem;
|
||||
if (ck) {
|
||||
cookie = {
|
||||
name: ck.name || name,
|
||||
value: ck.value || '',
|
||||
path: ck.path || '/',
|
||||
expires: ck.expires || null,
|
||||
secure: ck.secure || false,
|
||||
sameSite: ck.sameSite || 'lax'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
cookie = getCookie(name);
|
||||
}
|
||||
return cookie;
|
||||
} catch (err) {
|
||||
return Promise.reject(`Get Cookie ${name} failed: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置 Cookie 项
|
||||
* @param cookie
|
||||
*/
|
||||
export const setCookie = function (cookie: CustomCookie) {
|
||||
// 处理
|
||||
if (cookie['maxAge']) {
|
||||
// Max-Age 优先于 Expires
|
||||
cookie['expires'] = void 0;
|
||||
}
|
||||
// cookie 组成表
|
||||
const cks: string[] = [];
|
||||
// 先构造键值对
|
||||
cks.push(`${cookie.name}=${decodeURIComponent(cookie.value)}`);
|
||||
// 构造 cookie 其它属性
|
||||
for (const [key, value] of Object.entries(cookie)) {
|
||||
if (!value) continue;
|
||||
switch (key) {
|
||||
case 'path':
|
||||
cks.push(`Path=${value}`);
|
||||
break;
|
||||
case 'domain':
|
||||
cks.push(`Domain=${value}`);
|
||||
break;
|
||||
case 'maxAge':
|
||||
cks.push(`Max-Age=${value}`);
|
||||
break;
|
||||
case 'expires':
|
||||
cks.push(`Expires=${value}`);
|
||||
break;
|
||||
case 'secure':
|
||||
cks.push(`Secure`);
|
||||
break;
|
||||
case 'sameSite':
|
||||
cks.push(`SameSite=${SAMESITE_MAP[value as SameSite]}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 合并所有属性
|
||||
document.cookie = cks.join('; ');
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除某个 Cookie 项
|
||||
* @param name
|
||||
*/
|
||||
export const removeCookie = function (name: string) {
|
||||
// 将 cookie 设置为过期时间已过期
|
||||
document.cookie = `${name}=; Max-Age=0;`;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否存在某个 Cookie 项
|
||||
* @param name
|
||||
*/
|
||||
export const hasCookie = function (name: string) {
|
||||
const regex = new RegExp(`(^|; )${name}=`);
|
||||
return regex.test(document.cookie);
|
||||
};
|
||||
|
||||
/** 获取 Cookie 的键值列表 */
|
||||
export const getCookieKeys = function () {
|
||||
const cookies = document.cookie.split(';');
|
||||
const keys = [];
|
||||
|
||||
for (const cookie of cookies) {
|
||||
const [key] = cookie.trim().split('=');
|
||||
if (key) keys.push(key);
|
||||
}
|
||||
|
||||
return keys;
|
||||
};
|
||||
@@ -2,6 +2,7 @@ export const objectToString = Object.prototype.toString;
|
||||
export const toTypeString = (value: unknown): string => objectToString.call(value);
|
||||
|
||||
export const isNumber = (val: any): val is number => typeof val === 'number';
|
||||
export const isBoolean = (val: any): val is boolean => typeof val === 'boolean';
|
||||
export const isArray = Array.isArray;
|
||||
export const isMap = (val: unknown): val is Map<any, any> => toTypeString(val) === '[object Map]';
|
||||
export const isSet = (val: unknown): val is Set<any> => toTypeString(val) === '[object Set]';
|
||||
|
||||
+65
-15
@@ -11,9 +11,13 @@
|
||||
:user-count="userCount"
|
||||
:like-count="likeCount" />
|
||||
<div class="view-left-bottom">
|
||||
<SidTool />
|
||||
<div class="view-left-tools">
|
||||
<div class="view-left-tool" title="保存弹幕" @click.stop="saveCastToFile">
|
||||
<i class="ice-save"></i>
|
||||
<div class="view-left-tool feed-btn" title="投喂" @click.stop="openFeedDialog">
|
||||
<i class="ice-coin icon"></i>
|
||||
</div>
|
||||
<div class="view-left-tool cm-btn" title="保存弹幕" @click.stop="saveCastToFile">
|
||||
<i class="ice-save icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="hr" />
|
||||
@@ -50,6 +54,8 @@
|
||||
<CastList ref="otherEl" :types="['social', 'like', 'member']" pos="left" no-prefix theme="dark" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 投喂弹窗 -->
|
||||
<FeedDialog v-model="fdVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -58,6 +64,8 @@ import ConnectInput from '@/components/ConnectInput.vue';
|
||||
import LiveInfo from '@/components/LiveInfo.vue';
|
||||
import LiveStatusPanel from '@/components/LiveStatusPanel.vue';
|
||||
import CastList from '@/components/CastList.vue';
|
||||
import SidTool from '@/components/SidTool/SidTool.vue';
|
||||
import FeedDialog from '@/components/FeedDialog.vue';
|
||||
import {
|
||||
CastMethod,
|
||||
DyCast,
|
||||
@@ -91,6 +99,9 @@ const relayInputRef = useTemplateRef('relayInput');
|
||||
// 状态面板
|
||||
const statusPanelRef = useTemplateRef('panel');
|
||||
|
||||
// 投喂弹窗可见性
|
||||
const fdVisible = ref(false);
|
||||
|
||||
/** 直播间信息 */
|
||||
const cover = ref<string>('');
|
||||
const title = ref<string>('*****');
|
||||
@@ -184,8 +195,9 @@ const handleMessages = function (msgs: DyMessage[]) {
|
||||
try {
|
||||
for (const msg of msgs) {
|
||||
if (!msg.id) continue;
|
||||
if (castSet.has(msg.id)) continue;
|
||||
castSet.add(msg.id);
|
||||
const msgId = `${msg.method}-${msg.id}`;
|
||||
if (castSet.has(msgId)) continue;
|
||||
castSet.add(msgId);
|
||||
switch (msg.method) {
|
||||
case CastMethod.CHAT:
|
||||
newCasts.push(msg);
|
||||
@@ -428,6 +440,13 @@ const saveCastToFile = function () {
|
||||
CLog.error('弹幕保存出错了 =>', err);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开投喂弹窗
|
||||
*/
|
||||
const openFeedDialog = function () {
|
||||
fdVisible.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -435,6 +454,7 @@ $bg: #f7f6f5;
|
||||
$bd: #b2bfc3;
|
||||
$theme: #68be8d;
|
||||
$tool: #8b968d;
|
||||
$gold: #e6b422;
|
||||
|
||||
.index-view {
|
||||
position: relative;
|
||||
@@ -486,19 +506,34 @@ $tool: #8b968d;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: $tool;
|
||||
transition: color 0.2s ease-in-out, background-color 0.3s ease-in-out, opacity 0.2s ease;
|
||||
background-color: transparent;
|
||||
border-radius: 0.4em;
|
||||
i {
|
||||
&.cm-btn {
|
||||
transition:
|
||||
color 0.2s ease-in-out,
|
||||
background-color 0.3s ease-in-out,
|
||||
opacity 0.2s ease;
|
||||
background-color: transparent;
|
||||
border-radius: 0.4em;
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: $theme;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
&.feed-btn {
|
||||
animation: coinAni 1s ease-in-out;
|
||||
transition: color 0.2s ease-in-out;
|
||||
&:hover {
|
||||
color: $gold;
|
||||
}
|
||||
&:active {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
font-size: 1em;
|
||||
}
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: $theme;
|
||||
}
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.view-center {
|
||||
flex-grow: 4.5;
|
||||
@@ -560,4 +595,19 @@ $tool: #8b968d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes coinAni {
|
||||
0% {
|
||||
color: $gold;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
50% {
|
||||
color: $gold;
|
||||
transform: translateY(-120%) rotateY(360deg) scale(1.2);
|
||||
}
|
||||
100% {
|
||||
color: $gold;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+2
-1
@@ -58,7 +58,8 @@ export default defineConfig({
|
||||
}
|
||||
},
|
||||
'/socket': {
|
||||
target: 'wss://webcast5-ws-web-lf.douyin.com',
|
||||
// target: 'wss://webcast5-ws-web-lf.douyin.com',
|
||||
target: 'wss://webcast100-ws-web-lq.douyin.com',
|
||||
changeOrigin: true, // 保持原始 Host,利于服务端识别 Cookie
|
||||
secure: true,
|
||||
ws: true, // 启用 WebSocket 代理
|
||||
|
||||
Reference in new issue
Block a user