Add comment listing and reply commands (#1628)

This commit is contained in:
方程 authored and GitHub committed 2026-05-25 06:38:50 +00:00
1 parent b2b92e6fee
commit 697a525b55
4 files changed
+233 -1

No files matched your search

+32
View File
@@ -103,6 +103,38 @@ pub enum Commands {
// #[clap()]
vid: Vid,
},
/// 查看视频评论
Comments {
/// vid为稿件 av 或 bv 号
vid: Vid,
/// 排序方式,0为按时间,2为按热度
#[arg(long, default_value = "0")]
sort: u8,
/// 页码
#[arg(long, default_value = "1")]
pn: u32,
/// 每页条数
#[arg(long, default_value = "20")]
ps: u32,
},
/// 回复视频评论,默认只打印将要回复的内容
Reply {
/// vid为稿件 av 或 bv 号
vid: Vid,
/// 评论 rpid
rpid: u64,
/// 回复内容
message: String,
/// 实际发送回复
#[arg(long)]
execute: bool,
},
/// 输出flv元数据
DumpFlv {
#[arg()]
+22 -1
View File
@@ -3,7 +3,9 @@ use time::macros::format_description;
use biliup::uploader::util::SubmitOption;
use biliup_cli::cli::{Cli, Commands, expand_path};
use biliup_cli::downloader::{download, generate_json};
use biliup_cli::uploader::{append, list, login, renew, show, upload_by_command, upload_by_config};
use biliup_cli::uploader::{
append, comments, list, login, renew, reply, show, upload_by_command, upload_by_config,
};
use clap::Parser;
@@ -99,6 +101,25 @@ async fn main() -> AppResult<()> {
.await?
}
Commands::Show { vid } => show(user_cookie, vid, cli.proxy.as_deref()).await?,
Commands::Comments { vid, sort, pn, ps } => {
comments(user_cookie, vid, sort, pn, ps, cli.proxy.as_deref()).await?
}
Commands::Reply {
vid,
rpid,
message,
execute,
} => {
reply(
user_cookie,
vid,
rpid,
message,
execute,
cli.proxy.as_deref(),
)
.await?
}
Commands::DumpFlv { file_name } => {
let file_name = expand_path(file_name);
generate_json(file_name)?
+50
View File
@@ -263,6 +263,56 @@ pub async fn show(user_cookie: PathBuf, vid: Vid, proxy: Option<&str>) -> AppRes
Ok(())
}
pub async fn comments(
user_cookie: PathBuf,
vid: Vid,
sort: u8,
pn: u32,
ps: u32,
proxy: Option<&str>,
) -> AppResult<()> {
let bilibili = login_by_cookies(user_cookie, proxy).await?;
let reply_list = bilibili
.comments(&vid, sort, pn, ps, proxy)
.await
.change_context_lazy(|| AppError::Unknown)?;
for reply in reply_list.replies.unwrap_or_default() {
println!("rpid={} uname={}", reply.rpid, reply.member.uname);
println!("{}", reply.content.message);
println!();
}
Ok(())
}
pub async fn reply(
user_cookie: PathBuf,
vid: Vid,
rpid: u64,
message: String,
execute: bool,
proxy: Option<&str>,
) -> AppResult<()> {
if !execute {
println!("dry-run: reply to {vid} rpid={rpid}");
println!("{message}");
println!("use --execute to send");
return Ok(());
}
let bilibili = login_by_cookies(user_cookie, proxy).await?;
let ret = bilibili
.reply_comment(&vid, rpid, &message, proxy)
.await
.change_context_lazy(|| AppError::Unknown)?;
println!(
"{}",
serde_json::to_string_pretty(&ret).change_context_lazy(|| AppError::Unknown)?
);
Ok(())
}
pub async fn list(
user_cookie: PathBuf,
is_pubing: bool,
+129
View File
@@ -258,6 +258,40 @@ impl Display for Vid {
}
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ReplyMember {
pub uname: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct ReplyContent {
pub message: String,
}
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct Reply {
pub rpid: u64,
pub mid: u64,
pub member: ReplyMember,
pub content: ReplyContent,
pub ctime: u64,
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct ReplyPage {
pub count: Option<u64>,
pub num: Option<u64>,
pub size: Option<u64>,
}
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct ReplyList {
#[serde(default)]
pub page: ReplyPage,
#[serde(default)]
pub replies: Option<Vec<Reply>>,
}
#[derive(Clone, Debug)]
pub struct BiliBili {
pub client: reqwest::Client,
@@ -549,6 +583,101 @@ impl BiliBili {
.await?)
}
async fn aid_from_vid(&self, vid: &Vid) -> Result<u64> {
match vid {
Vid::Aid(aid) => Ok(*aid),
Vid::Bvid(bvid) => {
let res: ResponseData = self
.client
.get("https://api.bilibili.com/x/web-interface/view")
.query(&[("bvid", bvid)])
.send()
.await?
.json()
.await?;
match res {
ResponseData {
code: 0,
data: Some(data),
..
} => data["aid"].as_u64().ok_or("aid error".into()),
res => Err(Kind::Custom(format!("{res:?}"))),
}
}
}
}
/// 获取视频评论列表
pub async fn comments(
&self,
vid: &Vid,
sort: u8,
pn: u32,
ps: u32,
_proxy: Option<&str>,
) -> Result<ReplyList> {
let oid = self.aid_from_vid(vid).await?;
let res: ResponseData<ReplyList> = self
.client
.get("https://api.bilibili.com/x/v2/reply")
.query(&[
("type", "1".to_string()),
("oid", oid.to_string()),
("sort", sort.to_string()),
("pn", pn.to_string()),
("ps", ps.to_string()),
])
.send()
.await?
.json()
.await?;
match res {
ResponseData {
code: 0,
data: Some(data),
..
} => Ok(data),
res => Err(Kind::Custom(format!("{res:?}"))),
}
}
/// 回复视频评论
pub async fn reply_comment(
&self,
vid: &Vid,
rpid: u64,
message: &str,
_proxy: Option<&str>,
) -> Result<Value> {
let oid = self.aid_from_vid(vid).await?;
let res: ResponseData = self
.client
.post("https://api.bilibili.com/x/v2/reply/add")
.form(&[
("type", "1".to_string()),
("oid", oid.to_string()),
("root", rpid.to_string()),
("parent", rpid.to_string()),
("message", message.to_string()),
("csrf", self.get_csrf()?.to_string()),
])
.send()
.await?
.json()
.await?;
match res {
ResponseData {
code: 0,
data: Some(data),
..
} => Ok(data),
res => Err(Kind::Custom(format!("{res:?}"))),
}
}
pub async fn archive_pre(&self) -> Result<Value> {
Ok(self
.client