Skip to content

Commit

Permalink
增加Proxy配置 (#187)
Browse files Browse the repository at this point in the history
* 增加Proxy配置

* Add 添加参与话题(需要自己获取topic_id)

* update 版本号

* v0.2.2版本升级

* v0.2.2版本升级

* 0.2.2

* rustfmt

* Refactor String & proxy

* Update README

---------

Co-authored-by: Hao Guan <10684225+hguandl@users.noreply.github.com>
  • Loading branch information
LokmenoWer and hguandl authored Jan 26, 2025
1 parent 0786dc2 commit 1f3b252
Show file tree
Hide file tree
Showing 17 changed files with 304 additions and 151 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/py-')"
if: startsWith(github.ref, 'refs/tags/py-')
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v4
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

91 changes: 51 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,56 +47,60 @@ Arguments:
[VIDEO_PATH]... 需要上传的视频路径,若指定配置文件投稿不需要此参数

Options:
--submit <SUBMIT> 提交接口 [default: client] [possible values: client, app, web]
-c, --config <FILE> Sets a custom config file
-l, --line <LINE> 选择上传线路 [possible values: bda2, ws, qn, bldsa, tx, txa, bda]
--limit <LIMIT> 单视频文件最大并发数 [default: 3]
--copyright <COPYRIGHT> 是否转载, 1-自制 2-转载 [default: 1]
--source <SOURCE> 转载来源 [default: ]
--tid <TID> 投稿分区 [default: 171]
--cover <COVER> 视频封面 [default: ]
--title <TITLE> 视频标题 [default: ]
--desc <DESC> 视频简介 [default: ]
--dynamic <DYNAMIC> 空间动态 [default: ]
--tag <TAG> 视频标签,逗号分隔多个tag [default: ]
--dtime <DTIME> 延时发布时间,距离提交大于4小时,格式为10位时间戳
--interactive <INTERACTIVE> [default: 0]
--submit <SUBMIT> 提交接口 [default: client] [possible values: client, app, web]
-c, --config <FILE> Sets a custom config file
-l, --line <LINE> 选择上传线路 [possible values: bda2, ws, qn, bldsa, tx, txa, bda, alia]
--limit <LIMIT> 单视频文件最大并发数 [default: 3]
--copyright <COPYRIGHT> 是否转载, 1-自制 2-转载 [default: 1]
--source <SOURCE> 转载来源 [default: ]
--tid <TID> 投稿分区 [default: 171]
--cover <COVER> 视频封面 [default: ]
--title <TITLE> 视频标题 [default: ]
--desc <DESC> 视频简介 [default: ]
--dynamic <DYNAMIC> 空间动态 [default: ]
--tag <TAG> 视频标签,逗号分隔多个tag [default: ]
--topic-id <TOPIC_ID> 视频参与话题(需要自己获取topic_id配合填写mission_id)
--dtime <DTIME> 延时发布时间,距离提交大于4小时,格式为10位时间戳
--interactive <INTERACTIVE> [default: 0]
--mission-id <MISSION_ID>
--dolby <DOLBY> 是否开启杜比音效, 0-关闭 1-开启 [default: 0]
--hires <LOSSLESS_MUSIC> 是否开启 Hi-Res, 0-关闭 1-开启 [default: 0]
--no-reprint <NO_REPRINT> 0-允许转载,1-禁止转载 [default: 0]
--open-elec <OPEN_ELEC> 是否开启充电, 0-关闭 1-开启 [default: 0]
--up-selection-reply 是否开启精选评论,仅提交接口为app时可用
--up-close-reply 是否关闭评论,仅提交接口为app时可用
--up-close-danmu 是否关闭弹幕,仅提交接口为app时可用
-h, --help Print help
--dolby <DOLBY> 是否开启杜比音效, 0-关闭 1-开启 [default: 0]
--hires <LOSSLESS_MUSIC> 是否开启 Hi-Res, 0-关闭 1-开启 [default: 0]
--no-reprint <NO_REPRINT> 0-允许转载,1-禁止转载 [default: 0]
--open-elec <OPEN_ELEC> 是否开启充电, 0-关闭 1-开启 [default: 0]
--up-selection-reply 是否开启精选评论,仅提交接口为app时可用
--up-close-reply 是否关闭评论,仅提交接口为app时可用
--up-close-danmu 是否关闭弹幕,仅提交接口为app时可用
--extra-fields <EXTRA_FIELDS> 自定义提交参数
-h, --help Print help
```

- 下载视频:`./biliup download https://xxxx`
- 查看转码失败具体分p:`./biliup show BVxxxxx`
- 查看完整用法命令行输入 `biliup -h`

```shell
biliup 0.1.14
$ biliup help
Upload video to bilibili.

USAGE:
biliup.exe [OPTIONS] <SUBCOMMAND>

OPTIONS:
-h, --help Print help information
-u, --user-cookie <USER_COOKIE> 登录信息文件 [default: cookies.json]
-V, --version Print version information

SUBCOMMANDS:
append 是否要对某稿件追加视频
download 下载视频
dump-flv 输出flv元数据
help Print this message or the help of the given subcommand(s)
login 登录B站并保存登录信息
renew 手动验证并刷新登录信息
show 打印视频详情
upload 上传视频
Usage: biliup [OPTIONS] <COMMAND>

Commands:
login 登录B站并保存登录信息
renew 手动验证并刷新登录信息
upload 上传视频
append 是否要对某稿件追加视频
show 打印视频详情
dump-flv 输出flv元数据
download 下载视频
list 列出所有已上传的视频
help Print this message or the help of the given subcommand(s)

Options:
-p, --proxy <PROXY> 配置代理
-u, --user-cookie <USER_COOKIE> 登录信息文件 [default: cookies.json]
--rust-log <RUST_LOG> [default: tower_http=debug,info]
-h, --help Print help
-V, --version Print version
```

### 多账号支持
Expand All @@ -109,6 +113,13 @@ $biliup --user-cookie user2.json upload ...
$biliup renew # ./cookies.json
```

### 代理支持

请在子命令**之前**通过 `-p` 或者 `--proxy` 参数传入 代理 的地址。例如:
```powershell
.\biliup.exe -p http://username:password@proxy.example.com:8080 upload
```

### Windows 演示

登录:
Expand Down
3 changes: 2 additions & 1 deletion crates/biliup/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[package]
name = "biliup"
version = "0.2.1"
version = "0.2.2"
edition = "2021"
description = "Upload video to bilibili."
license = "MIT OR Apache-2.0"


# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[[bin]]
Expand Down
12 changes: 6 additions & 6 deletions crates/biliup/src/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::retry;
use crate::{retry, ReqwestClientBuilderExt};
use rand::Rng;
use reqwest::header::HeaderMap;
use reqwest::{header, Response};
Expand All @@ -17,8 +17,8 @@ pub struct StatelessClient {
}

impl StatelessClient {
pub fn new(headers: HeaderMap) -> Self {
let client = reqwest::Client::builder()
pub fn new(headers: HeaderMap, proxy: Option<&str>) -> Self {
let client = reqwest::Client::proxy_builder(proxy)
.user_agent("Mozilla/5.0 (X11; Linux x86_64; rv:60.1) Gecko/20100101 Firefox/60.1")
.default_headers(headers)
// .timeout(Duration::new(60, 0))
Expand Down Expand Up @@ -64,12 +64,12 @@ pub struct StatefulClient {
}

impl StatefulClient {
pub fn new(headers: HeaderMap) -> Self {
pub fn new(headers: HeaderMap, proxy: Option<&str>) -> Self {
let cookie_store = reqwest_cookie_store::CookieStore::default();
let cookie_store = CookieStoreMutex::new(cookie_store);
let cookie_store = Arc::new(cookie_store);
StatefulClient {
client: reqwest::Client::builder()
client: reqwest::Client::proxy_builder(proxy)
.cookie_provider(std::sync::Arc::clone(&cookie_store))
.user_agent(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108",
Expand All @@ -87,7 +87,7 @@ impl StatefulClient {

impl Default for StatelessClient {
fn default() -> Self {
Self::new(header::HeaderMap::new())
Self::new(header::HeaderMap::new(), None)
}
}

Expand Down
8 changes: 5 additions & 3 deletions crates/biliup/src/downloader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use tracing::{debug, error, info};
use crate::downloader::util::{LifecycleFile, Segmentable};

use crate::client::StatelessClient;
use std::str::FromStr;
use crate::downloader::extractor::CallbackFn;
use std::str::FromStr;

pub mod error;
pub mod extractor;
Expand All @@ -26,8 +26,9 @@ pub async fn download(
file_name: &str,
segment: Segmentable,
file_name_hook: Option<CallbackFn>,
proxy: Option<&str>,
) -> anyhow::Result<()> {
let client = StatelessClient::new(headers);
let client = StatelessClient::new(headers, proxy);
let response = client.retryable(url).await?;
let mut connection = Connection::new(response);
// let buf = &mut [0u8; 9];
Expand Down Expand Up @@ -90,7 +91,7 @@ pub fn construct_headers(hash_map: HashMap<String, String>) -> HeaderMap {
#[cfg(test)]
mod tests {
use crate::downloader::download;
use crate::downloader::util::{Segmentable};
use crate::downloader::util::Segmentable;
use anyhow::Result;
use reqwest::header::{HeaderMap, HeaderValue, REFERER};

Expand All @@ -111,6 +112,7 @@ mod tests {
// Segment::Size(20 * 1024 * 1024, 0),
Segmentable::new(Some(std::time::Duration::from_secs(6000)), None),
None,
None,
)?;
Ok(())
}
Expand Down
16 changes: 16 additions & 0 deletions crates/biliup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ where
}
}

trait ReqwestClientBuilderExt {
fn proxy_builder<U: reqwest::IntoUrl>(proxy: Option<U>) -> reqwest::ClientBuilder;
}

impl ReqwestClientBuilderExt for reqwest::Client {
fn proxy_builder<U: reqwest::IntoUrl>(proxy: Option<U>) -> reqwest::ClientBuilder {
match proxy {
Some(proxy) => {
tracing::debug!("使用代理: {}", proxy.as_str());
Self::builder().proxy(reqwest::Proxy::all(proxy).unwrap())
}
None => Self::builder(),
}
}
}

#[cfg(test)]
mod tests {
use crate::uploader::bilibili::Vid;
Expand Down
30 changes: 20 additions & 10 deletions crates/biliup/src/uploader/bilibili.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::error::{Kind, Result};
use crate::uploader::credential::LoginInfo;
use crate::ReqwestClientBuilderExt;
use serde::ser::Error;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
Expand Down Expand Up @@ -64,6 +65,11 @@ pub struct Studio {
#[clap(long, default_value_t)]
pub tag: String,

/// 视频参与话题(需要自己获取topic_id配合填写mission_id)
#[clap(long)]
#[serde(default)]
pub topic_id: Option<u32>,

#[serde(default)]
#[builder(!default)]
#[clap(skip)]
Expand Down Expand Up @@ -235,8 +241,8 @@ pub struct BiliBili {
}

impl BiliBili {
pub async fn submit(&self, studio: &Studio) -> Result<ResponseData> {
let ret: ResponseData = reqwest::Client::builder()
pub async fn submit(&self, studio: &Studio, proxy: Option<&str>) -> Result<ResponseData> {
let ret: ResponseData = reqwest::Client::proxy_builder(proxy)
.user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108")
.timeout(Duration::new(60, 0))
.build()?
Expand All @@ -258,7 +264,11 @@ impl BiliBili {
}
}

pub async fn submit_by_app(&self, studio: &Studio) -> Result<ResponseData> {
pub async fn submit_by_app(
&self,
studio: &Studio,
proxy: Option<&str>,
) -> Result<ResponseData> {
let payload = {
let mut payload = json!({
"access_key": self.login_info.token_info.access_token,
Expand All @@ -283,7 +293,7 @@ impl BiliBili {
payload
};

let ret: ResponseData = reqwest::Client::builder()
let ret: ResponseData = reqwest::Client::proxy_builder(proxy)
.user_agent("Mozilla/5.0 BiliDroid/7.80.0 (bbcallen@gmail.com) os/android model/MI 6 mobi_app/android build/7800300 channel/bili innerVer/7800310 osVer/13 network/2")
.timeout(Duration::new(60, 0))
.build()?
Expand All @@ -303,8 +313,8 @@ impl BiliBili {
}
}

pub async fn edit(&self, studio: &Studio) -> Result<serde_json::Value> {
let ret: serde_json::Value = reqwest::Client::builder()
pub async fn edit(&self, studio: &Studio, proxy: Option<&str>) -> Result<serde_json::Value> {
let ret: serde_json::Value = reqwest::Client::proxy_builder(proxy)
.user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108")
.timeout(Duration::new(60, 0))
.build()?
Expand All @@ -327,8 +337,8 @@ impl BiliBili {
}

/// 查询视频的 json 信息
pub async fn video_data(&self, vid: &Vid) -> Result<Value> {
let res: ResponseData = reqwest::Client::builder()
pub async fn video_data(&self, vid: &Vid, proxy: Option<&str>) -> Result<Value> {
let res: ResponseData = reqwest::Client::proxy_builder(proxy)
.user_agent("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/63.0.3239.108")
.timeout(Duration::new(60, 0))
.build()?
Expand All @@ -354,8 +364,8 @@ impl BiliBili {
}
}

pub async fn studio_data(&self, vid: &Vid) -> Result<Studio> {
let mut video_info = self.video_data(vid).await?;
pub async fn studio_data(&self, vid: &Vid, proxy: Option<&str>) -> Result<Studio> {
let mut video_info = self.video_data(vid, proxy).await?;

let mut studio: Studio = serde_json::from_value(video_info["archive"].take())?;
let videos: Vec<Video> = serde_json::from_value(video_info["videos"].take())?;
Expand Down
17 changes: 11 additions & 6 deletions crates/biliup/src/uploader/credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ impl AppKeyStore {
}
}

pub async fn login_by_cookies(file: impl AsRef<Path>) -> Result<BiliBili> {
let client = Credential::new();
pub async fn login_by_cookies(file: impl AsRef<Path>, proxy: Option<&str>) -> Result<BiliBili> {
let client = Credential::new(proxy);
// let path = file.as_ref();
let mut file = std::fs::File::options().read(true).write(true).open(file)?;
let login_info: LoginInfo = serde_json::from_reader(std::io::BufReader::new(&file))?;
Expand Down Expand Up @@ -127,13 +127,13 @@ pub struct OAuthInfo {
pub struct Credential(StatefulClient);

impl Credential {
pub fn new() -> Self {
pub fn new(proxy: Option<&str>) -> Self {
let mut headers = header::HeaderMap::new();
headers.insert(
"Referer",
header::HeaderValue::from_static("https://www.bilibili.com/"),
);
Self(StatefulClient::new(headers))
Self(StatefulClient::new(headers, proxy))
}

async fn validate_tokens(&self, login_info: &LoginInfo) -> Result<ResponseData<ResponseValue>> {
Expand Down Expand Up @@ -424,7 +424,12 @@ impl Credential {
.error_for_status()?;
let full = raw.bytes().await?;

let res: ResponseData<ResponseValue> = serde_json::from_slice(&full).map_err(|_| Kind::Custom(format!("error decoding response body, content: {:#?}", String::from_utf8_lossy(&full))))?;
let res: ResponseData<ResponseValue> = serde_json::from_slice(&full).map_err(|_| {
Kind::Custom(format!(
"error decoding response body, content: {:#?}",
String::from_utf8_lossy(&full)
))
})?;
match res {
ResponseData {
code: 0,
Expand Down Expand Up @@ -609,6 +614,6 @@ impl Credential {

impl Default for Credential {
fn default() -> Self {
Self::new()
Self::new(None)
}
}
Loading

0 comments on commit 1f3b252

Please sign in to comment.