feat: 完善弹幕AI

This commit is contained in:
sealday 2024-04-07 03:23:34 +08:00
parent d723fc6cda
commit bfcc4a5ce3
7 changed files with 142 additions and 25 deletions

View File

@ -128,7 +128,7 @@ export default function Controls(): JSX.Element {
className="btn join-item" className="btn join-item"
onClick={() => { onClick={() => {
query({ query({
name: "红豆", name: "音乐弹幕",
}) })
.then((result) => { .then((result) => {
// eslint-disable-next-line no-console -- temp // eslint-disable-next-line no-console -- temp
@ -157,7 +157,7 @@ export default function Controls(): JSX.Element {
> >
<div className="text-center"> {offset / 1000} </div> <div className="text-center"> {offset / 1000} </div>
</div> </div>
<div className="w-full max-h-40 overflow-auto"> <div className="w-full h-[20vh] overflow-auto">
<ul className="timeline timeline-vertical"> <ul className="timeline timeline-vertical">
{history.map((item, i) => ( {history.map((item, i) => (
<li key={i}> <li key={i}>

View File

@ -3,10 +3,11 @@ import { useState, useEffect, useCallback, useRef } from "react";
import useWebSocket, { ReadyState } from "react-use-websocket"; import useWebSocket, { ReadyState } from "react-use-websocket";
import MessageDispatcher from "./message"; import MessageDispatcher from "./message";
import { Token } from "@/lib/types"; import { Token } from "@/lib/types";
import dayjs from "dayjs";
const MOMO_IM_URL = "wss://live-ws.immomo.com/ws/im"; const MOMO_IM_URL = "wss://live-ws.immomo.com/ws/im";
export const Danmu: React.FC<{ token: Token }> = ({ token }) => { export default function Danmu({ token }: { token: Token }) {
const [messageHistory, setMessageHistory] = useState<MessageEvent<any>[]>([]); const [messageHistory, setMessageHistory] = useState<MessageEvent<any>[]>([]);
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@ -52,15 +53,28 @@ export const Danmu: React.FC<{ token: Token }> = ({ token }) => {
useEffect(() => { useEffect(() => {
if (lastMessage !== null) { if (lastMessage !== null) {
if (lastMessage.data) { if (lastMessage.data) {
const current = dayjs().format("HH:mm:ss");
const message = JSON.parse(lastMessage.data); const message = JSON.parse(lastMessage.data);
if (message.type === "pong") { if (message.type === "pong") {
return; return;
} }
console.log(message); if (message.type === "group") {
setMessageHistory((prev) => prev.concat(message)); message.groups.forEach((group: any) => {
if (ref.current) { group.current = current;
setMessageHistory((prev) => prev.concat(group));
setTimeout(() => { setTimeout(() => {
if (ref.current) {
ref.current.scrollTop = ref.current.scrollHeight; ref.current.scrollTop = ref.current.scrollHeight;
}
}, 0);
});
} else {
message.current = current;
setMessageHistory((prev) => prev.concat(message));
setTimeout(() => {
if (ref.current) {
ref.current.scrollTop = ref.current.scrollHeight;
}
}, 0); }, 0);
} }
} }
@ -68,10 +82,10 @@ export const Danmu: React.FC<{ token: Token }> = ({ token }) => {
}, [lastMessage]); }, [lastMessage]);
return ( return (
<div className="h-[50vh] overflow-auto" ref={ref}> <div className="h-[60vh] overflow-auto" ref={ref}>
{messageHistory.map((message, idx) => ( {messageHistory.map((message, idx) => (
<MessageDispatcher key={idx} message={message} /> <MessageDispatcher key={idx} message={message} />
))} ))}
</div> </div>
); );
}; }

View File

@ -1,21 +1,50 @@
export default function MessageDispatcher({ message }: { message: any }) { import dayjs from 'dayjs';
const messageType = message?.groups?.[0]?.units?.[0]?.type;
let nick = "wait";
let content = "wait";
if (message?.type === 'sauth_ret') { export default function MessageDispatcher({ message }: { message: any }) {
return <div>{message?.data?.em}</div> const messageType = message?.units?.[0]?.type;
let nick = "";
let content = "[暂不支持该类型消息]";
let fortuneLv = 0;
let text = '';
let productId = '';
let amount = 0;
if (message?.type === "sauth_ret") {
if (message?.data?.em !== "ok") {
return <div>{message?.data?.em}</div>;
} else {
return null;
}
} }
if (messageType === "Message") { switch (messageType) {
nick = message?.groups?.[0]?.nick; case "Message":
content = message?.groups?.[0]?.units?.[0]?.["Msg.Message.data"]?.text; nick = message?.nick;
content = message?.units?.[0]?.["Msg.Message.data"]?.text;
break;
case "EnterRoom":
text = message?.units?.[0]?.["Set.EnterRoom.data"]?.text;
fortuneLv = message?.units?.[0]?.["Set.EnterRoom.data"]?.fortune_lv;
content = `有新人:${text} lv: ${fortuneLv}`;
break;
case "Version":
// TODO 解析
return null
case "GiftV3":
nick = message?.nick;
text = message?.['units']?.[0]?.['GiftV3.giftV3']?.['text']
productId = message?.['units']?.[0]?.['GiftV3.giftV3']?.['productId']
amount = message?.['units']?.[0]?.['GiftV3.giftV3']?.['weight']
content = `${nick} 送出礼物,内容为: ${text}产品ID为: ${productId}, 价值:${amount}`
nick = ''
break;
default:
console.log("debug....", messageType, message);
break;
} }
return ( return (
<div className="chat chat-start"> <div className="p-1 text-xs">
<div className="chat-bubble"> [{message.current}]{nick}{nick ? ': ' : ''}{content}
{nick}: {content}
</div>
</div> </div>
); );
} }

View File

@ -1,16 +1,56 @@
import { getToken } from "@/lib/actions/token"; import { getToken } from "@/lib/actions/token";
import Controls from "./controls"; import Controls from "./controls";
import { Danmu } from "./danmu"; import Danmu from "./danmu";
import { listLives } from "@/lib/actions/live";
import { listSongs } from "../songs/actions";
export default async function Page() { export default async function Page() {
const token = await getToken(); const token = await getToken();
const lives = await listLives();
const songs = await listSongs();
return ( return (
<div className="flex flex-col md:flex-row"> <div className="flex flex-col md:flex-row">
<section className="basis-1/3 card"> <section className="basis-1/3 card">
<div className="card-body max-h-[50vh] overflow-auto"></div> <div className="h-[50vh] md:h-[80vh] overflow-auto card bg-base-100 card-bordered m-8">
<div className="card-body">
<div className="card-title"></div>
<ul>
{lives.map((live, i) => (
<li key={i}>
<label>
{live.name} - {live.room_id}
<input
className="raido radio-primary"
type="radio"
name="live"
/>
</label>
</li>
))}
</ul>
</div>
</div>
</section> </section>
<section className="basis-1/3 card"> <section className="basis-1/3 card">
<div className="card-body max-h-[50vh] overflow-auto"></div> <div className="h-[50vh] md:h-[80vh] overflow-auto card bg-base-100 card-bordered m-8">
<div className="card-body">
<div className="card-title"></div>
<ul>
{songs.map((song, i) => (
<li key={i}>
<label>
{song.name} - {song.artist}
<input
className="raido radio-primary"
type="radio"
name="song"
/>
</label>
</li>
))}
</ul>
</div>
</div>
</section> </section>
<section className="basis-1/3 card"> <section className="basis-1/3 card">
<div className="card-body"> <div className="card-body">

26
lib/actions/live.ts Normal file
View File

@ -0,0 +1,26 @@
"use server";
import { cookies } from "next/headers";
export async function listLives(): Promise<any[]> {
const token = cookies().get("token");
const params = new URLSearchParams();
params.append("pageSize", "99999");
const lives = await fetch(
"https://tachy.daoyoucloud.com/api/lives:list?" + params.toString(),
{
method: "GET",
headers: {
"X-App": "danmu-sim",
"X-Authenticator": "basic",
authorization: "Bearer " + token?.value,
"content-type": "application/json",
},
next: {
tags: ["lives"],
},
}
);
const result = await lives.json();
return result?.data ?? [];
}

View File

@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.6.8", "axios": "^1.6.8",
"dayjs": "^1.11.10",
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"next": "14.1.4", "next": "14.1.4",
"react": "^18.2.0", "react": "^18.2.0",

View File

@ -8,6 +8,9 @@ dependencies:
axios: axios:
specifier: ^1.6.8 specifier: ^1.6.8
version: 1.6.8 version: 1.6.8
dayjs:
specifier: ^1.11.10
version: 1.11.10
fuse.js: fuse.js:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.0.0 version: 7.0.0
@ -826,6 +829,10 @@ packages:
is-data-view: 1.0.1 is-data-view: 1.0.1
dev: true dev: true
/dayjs@1.11.10:
resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==}
dev: false
/debug@3.2.7: /debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies: peerDependencies: