Add streamer file info API and improve file listing
Introduces a new API endpoint `/v1/streamer-info/files/{id}` to fetch file lists for a specific streamer, and updates the frontend to fetch file lists dynamically per row. Also updates serialization for streamer date fields, improves comments and error handling, and changes the default downloader type for plugins. Minor documentation and code cleanup included.
This commit is contained in:
1 parent
2ede5cda33
commit
f5f2998d16
11 files changed
+69
-60
No files matched your search
Generated
+2
-2
@@ -395,7 +395,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "biliup-cli"
|
||||
version = "1.1.19"
|
||||
version = "1.1.20"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
@@ -3641,7 +3641,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "stream-gears"
|
||||
version = "1.1.19"
|
||||
version = "1.1.20"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-trait",
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ members = [
|
||||
resolver = "3"
|
||||
|
||||
[workspace.package]
|
||||
version = "1.1.19"
|
||||
version = "1.1.20"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
@@ -83,7 +83,7 @@ Options:
|
||||
2. 安装:`uv tool install biliup`
|
||||
3. 启动:`biliup server --auth`
|
||||
4. 访问 WebUI:`http://your-ip:19159`
|
||||
|
||||
* 后台运行 `nohup biliup server --auth &`
|
||||
### Termux
|
||||
- 详见[Wiki](https://github.com/biliup/biliup/wiki/Termux-%E4%B8%AD%E4%BD%BF%E7%94%A8-biliup)
|
||||
|
||||
|
||||
+28
-23
@@ -20,6 +20,7 @@ import Filter from "@/app/(app)/job/Filter";
|
||||
export default function Home() {
|
||||
const { Header, Footer, Sider, Content } = Layout
|
||||
const { data: data, error, isLoading } = useSWR<any[]>('/v1/streamer-info', fetcher)
|
||||
|
||||
if (isLoading) {
|
||||
return <Spin size="large" />
|
||||
}
|
||||
@@ -56,29 +57,7 @@ export default function Home() {
|
||||
render: (time: number) => humDate(time),
|
||||
},
|
||||
]
|
||||
const expandRowRender = (record: any, index: number | undefined) => {
|
||||
return (
|
||||
<>
|
||||
文件列表:
|
||||
{record.files.map(
|
||||
(it: {
|
||||
id: Key | null | undefined
|
||||
file:
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| ReactElement<any, string | JSXElementConstructor<any>>
|
||||
| Iterable<ReactNode>
|
||||
| ReactPortal
|
||||
| null
|
||||
| undefined
|
||||
}) => {
|
||||
return <div key={it.id}> 文件名:{it.file}</div>
|
||||
}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header style={{ backgroundColor: 'var(--semi-color-bg-1)' }}>
|
||||
@@ -123,3 +102,29 @@ export default function Home() {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// 创建一个子组件
|
||||
const FileLists = ({ recordId }: { recordId: string }) => {
|
||||
const { data: files, isLoading } = useSWR(
|
||||
`/v1/streamer-info/files/${recordId}`,
|
||||
fetcher
|
||||
)
|
||||
|
||||
if (isLoading) return <div>加载中...</div>
|
||||
if (!files || files.length === 0) return <div>暂无文件</div>
|
||||
|
||||
return (
|
||||
<>
|
||||
文件列表:
|
||||
{files.map((it: any) => (
|
||||
<div key={it.id}> 文件名:{it.file}</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// 在 expandRowRender 中使用
|
||||
const expandRowRender = (record: any) => {
|
||||
return <FileLists recordId={record.id} />
|
||||
}
|
||||
@@ -291,14 +291,24 @@ pub async fn get_streamer_info(
|
||||
.await
|
||||
.change_context(AppError::Unknown)
|
||||
.map_err(report_to_response)?;
|
||||
|
||||
Ok(Json(streamer_infos))
|
||||
}
|
||||
|
||||
pub async fn get_streamer_info_files(
|
||||
// Extension(streamers_service): Extension<DynUploadStreamersRepository>,
|
||||
State(pool): State<ConnectionPool>,
|
||||
Path(id): Path<i64>,
|
||||
) -> Result<Json<Vec<FileItem>>, Response> {
|
||||
let file_items = FileItem::select()
|
||||
.where_("streamer_info_id = ?")
|
||||
.bind(id)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.change_context(AppError::Unknown)
|
||||
.map_err(report_to_response)?;
|
||||
println!("{:?}", file_items);
|
||||
|
||||
Ok(Json(streamer_infos))
|
||||
Ok(Json(file_items))
|
||||
}
|
||||
|
||||
pub async fn get_upload_streamers_endpoint(
|
||||
|
||||
@@ -216,6 +216,8 @@ impl SegmentEventProcessor {
|
||||
move |event| {
|
||||
match event {
|
||||
SegmentEvent::Start { next_file_path } => {
|
||||
unreachable!("应没有任何位置发出此事件");
|
||||
// 开始下载时,获取到的是将要下载的文件名,此时文件还未生成
|
||||
// 触发弹幕滚动保存
|
||||
if let Some(ref client) = danmaku
|
||||
&& let Err(e) = client
|
||||
@@ -225,6 +227,7 @@ impl SegmentEventProcessor {
|
||||
}
|
||||
}
|
||||
SegmentEvent::Segment(event) => {
|
||||
// 分段时,获取到的是已下载的文件名
|
||||
// 触发弹幕滚动保存
|
||||
if let Some(ref client) = danmaku
|
||||
&& let Err(e) = client.rolling(
|
||||
|
||||
@@ -233,27 +233,25 @@ impl FfmpegDownloader {
|
||||
bail!(AppError::Custom("Process handle not found".to_string()));
|
||||
}
|
||||
};
|
||||
// 退出时,重命名文件
|
||||
let part_file = format!("{}.part", output_file.display());
|
||||
tokio::fs::rename(&part_file, &output_file)
|
||||
.await
|
||||
.change_context(AppError::Unknown)?;
|
||||
// let (tx, rx) = bounded(16);
|
||||
// 分段回调
|
||||
// 触发分段回调
|
||||
|
||||
callback(SegmentEvent::Segment(
|
||||
SegmentInfo {
|
||||
prev_file_path: output_file,
|
||||
segment_index: 0,
|
||||
next_file_path: None,
|
||||
}
|
||||
));
|
||||
// 根据退出码判断状态
|
||||
match status.code() {
|
||||
Some(0) => {
|
||||
// 正常退出,重命名文件
|
||||
let part_file = format!("{}.part", output_file.display());
|
||||
tokio::fs::rename(&part_file, &output_file)
|
||||
.await
|
||||
.change_context(AppError::Unknown)?;
|
||||
|
||||
// 触发分段回调
|
||||
|
||||
// segment_callback(SegmentEvent {
|
||||
// file_path: output_file,
|
||||
// segment_index: 0,
|
||||
// start_time: std::time::SystemTime::now(),
|
||||
// end_time: std::time::SystemTime::now(),
|
||||
// });
|
||||
|
||||
Ok(DownloadStatus::SegmentCompleted)
|
||||
}
|
||||
Some(255) => Ok(DownloadStatus::StreamEnded),
|
||||
|
||||
@@ -91,7 +91,7 @@ pub trait DownloadPlugin {
|
||||
raw_stream_url,
|
||||
config,
|
||||
Vec::new(),
|
||||
DownloaderType::FfmpegInternal,
|
||||
DownloaderType::FfmpegExternal,
|
||||
))
|
||||
}
|
||||
// Some(DownloaderType::StreamGears) => {
|
||||
|
||||
@@ -108,17 +108,15 @@ impl DownloadPlugin for YY {
|
||||
.to_string();
|
||||
|
||||
// 构建URL
|
||||
let url = format!(
|
||||
"https://stream-manager.yy.com/v3/channel/streams?uid=0&cid={}&sid={}&appid=0&sequence={}&encode=json",
|
||||
rid, rid, millis_13
|
||||
);
|
||||
|
||||
// 发送POST请求并处理响应
|
||||
let result = ctx
|
||||
.worker
|
||||
.client
|
||||
.client
|
||||
.post(&url)
|
||||
.post(&format!(
|
||||
"https://stream-manager.yy.com/v3/channel/streams?uid=0&cid={}&sid={}&appid=0&sequence={}&encode=json",
|
||||
rid, rid, millis_13
|
||||
))
|
||||
.timeout(std::time::Duration::from_secs(30))
|
||||
.headers(self.fake_headers.clone())
|
||||
.body(data)
|
||||
@@ -129,7 +127,6 @@ impl DownloadPlugin for YY {
|
||||
.await
|
||||
.change_context(AppError::Custom(format!("解析json出错 rid: {rid}")))?;
|
||||
|
||||
println!("{:#?}", result);
|
||||
let Some(stream_url) = result
|
||||
.get("avp_info_res")
|
||||
.and_then(|info| info.get("stream_line_addr"))
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod hook_step;
|
||||
pub mod live_streamer;
|
||||
pub mod upload_streamer;
|
||||
|
||||
use chrono::serde::ts_seconds;
|
||||
use chrono::{DateTime, Utc};
|
||||
use ormlite::{Insert, Model};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -19,6 +20,7 @@ pub struct StreamerInfo {
|
||||
pub url: String,
|
||||
/// 直播标题
|
||||
pub title: String,
|
||||
#[serde(with = "ts_seconds")]
|
||||
/// 直播开始时间
|
||||
pub date: DateTime<Utc>,
|
||||
/// 直播封面路径(可选)
|
||||
|
||||
@@ -1,14 +1,7 @@
|
||||
use crate::server::api::bilibili_endpoints::{
|
||||
archive_pre_endpoint, get_myinfo_endpoint, get_proxy_endpoint,
|
||||
};
|
||||
use crate::server::api::endpoints::{
|
||||
add_upload_streamer_endpoint, add_user_endpoint, delete_streamers_endpoint,
|
||||
delete_template_endpoint, delete_user_endpoint, get_configuration, get_qrcode, get_status,
|
||||
get_streamer_info, get_streamers_endpoint, get_upload_streamer_endpoint,
|
||||
get_upload_streamers_endpoint, get_users_endpoint, get_videos, login_by_qrcode,
|
||||
pause_streamers_endpoint, post_streamers_endpoint, post_uploads, put_configuration,
|
||||
put_streamers_endpoint,
|
||||
};
|
||||
use crate::server::api::endpoints::{add_upload_streamer_endpoint, add_user_endpoint, delete_streamers_endpoint, delete_template_endpoint, delete_user_endpoint, get_configuration, get_qrcode, get_status, get_streamer_info, get_streamer_info_files, get_streamers_endpoint, get_upload_streamer_endpoint, get_upload_streamers_endpoint, get_users_endpoint, get_videos, login_by_qrcode, pause_streamers_endpoint, post_streamers_endpoint, post_uploads, put_configuration, put_streamers_endpoint};
|
||||
use crate::server::infrastructure::service_register::ServiceRegister;
|
||||
use axum::Router;
|
||||
use axum::body::Body;
|
||||
@@ -36,6 +29,7 @@ pub fn router(service_register: ServiceRegister) -> Router<()> {
|
||||
)
|
||||
// 主播信息路由
|
||||
.route("/v1/streamer-info", get(get_streamer_info)) // 获取主播信息
|
||||
.route("/v1/streamer-info/files/{id}", get(get_streamer_info_files)) // 获取主播信息
|
||||
// 上传模板管理路由
|
||||
.route("/v1/upload/streamers", get(get_upload_streamers_endpoint)) // 获取上传模板列表
|
||||
.route(
|
||||
|
||||
Reference in new issue
Block a user