Add comment listing and reply commands (#1628)
This commit is contained in:
4 files changed
+233
-1
No files matched your search
@@ -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()]
|
||||
|
||||
@@ -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)?
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user