mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-08 01:32:41 +08:00
add support to download image
This commit is contained in:
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -394,6 +394,17 @@ version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.21"
|
||||
@@ -413,9 +424,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -890,11 +903,13 @@ version = "0.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dateparser",
|
||||
"futures-util",
|
||||
"getopts",
|
||||
"gettext",
|
||||
"html_parser",
|
||||
"json",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"spin_on",
|
||||
"tokio",
|
||||
|
||||
@@ -8,14 +8,16 @@ edition = "2018"
|
||||
[dependencies]
|
||||
chrono = "0.4"
|
||||
dateparser = "0.1.6"
|
||||
futures-util = "0.3"
|
||||
getopts = "0.2"
|
||||
gettext = "0.4"
|
||||
html_parser = "0.6.2"
|
||||
lazy_static = "1.4"
|
||||
json = "0.12"
|
||||
regex = "1"
|
||||
reqwest = { version = "0.11", features = ["brotli", "deflate", "gzip", "rustls-tls", "socks", "stream"] }
|
||||
spin_on = "0.1.1"
|
||||
tokio = { version = "1.17", features = ["rt", "macros", "rt-multi-thread"] }
|
||||
tokio = { version = "1.17", features = ["rt", "macros", "rt-multi-thread", "time"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = { version = "0.3.9", features = ["winnls", "stringapiset"] }
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: bili\n"
|
||||
"POT-Creation-Date: 2022-02-26 23:42+0800\n"
|
||||
"Project-Id-Version: pixiv_downloader\n"
|
||||
"POT-Creation-Date: 2022-02-27 17:47+0800\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <[email protected]>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
"X-Poedit-Basepath: ../src\n"
|
||||
"X-Poedit-SearchPath-0: .\n"
|
||||
|
||||
@@ -69,95 +69,171 @@ msgstr ""
|
||||
msgid "Can not parse expired time:"
|
||||
msgstr ""
|
||||
|
||||
#: cookies.rs:366 settings.rs:392
|
||||
#: cookies.rs:366 data/json.rs:39 settings.rs:392 webclient.rs:257
|
||||
msgid "Failed to remove file:"
|
||||
msgstr ""
|
||||
|
||||
#: cookies.rs:372 settings.rs:399
|
||||
#: cookies.rs:372 data/json.rs:50 settings.rs:399 webclient.rs:263
|
||||
msgid "Failed to create file:"
|
||||
msgstr ""
|
||||
|
||||
#: cookies.rs:379 settings.rs:405
|
||||
#: cookies.rs:379 data/json.rs:56 settings.rs:405 webclient.rs:276
|
||||
msgid "Failed to write file:"
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:9 pixiv.rs:47
|
||||
#: download.rs:15 pixiv_web.rs:53
|
||||
msgid "Failed to initialize pixiv web api client."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:69
|
||||
#: download.rs:22
|
||||
msgid "Warning: Web api client not logined, some future may not work."
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:45
|
||||
msgid "Failed to get page count."
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:54
|
||||
msgid "Failed to get pages' data."
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:61
|
||||
msgid "Failed to save metadata to JSON file."
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:69
|
||||
msgid "Failed to get original picture's link."
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:75
|
||||
msgid "Failed to get file name from url:"
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:82 download.rs:88 pixiv_web.rs:148
|
||||
msgid "Failed to download image:"
|
||||
msgstr ""
|
||||
|
||||
#: download.rs:93
|
||||
msgid "Downloaded image:"
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:76
|
||||
msgid "Warning: The specified config file not found."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:93
|
||||
#: opts.rs:100
|
||||
msgid "Usage:"
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:95
|
||||
#: opts.rs:102
|
||||
msgid "Download an artwork"
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:97
|
||||
#: opts.rs:104
|
||||
msgid "Fix the config file"
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:99
|
||||
#: opts.rs:106
|
||||
msgid "Print all available settings"
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:107
|
||||
#: opts.rs:114
|
||||
msgid "Print help message."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:111
|
||||
#: opts.rs:118
|
||||
msgid "The location of config file."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:117 settings_list.rs:8
|
||||
#: opts.rs:124 settings_list.rs:8
|
||||
msgid "The location of cookies file. Used for web API."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:123 settings_list.rs:9
|
||||
#: opts.rs:130 settings_list.rs:9
|
||||
msgid "The language of translated tags."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:145
|
||||
#: opts.rs:133
|
||||
msgid "Verbose logging."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:134
|
||||
msgid "Overwrite existing file."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:135
|
||||
msgid "Skip overwrite existing file."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:155
|
||||
msgid "Unknown command."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:156
|
||||
msgid "No URL specified."
|
||||
#: opts.rs:165
|
||||
msgid "Failed to parse ID:"
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:164
|
||||
#: opts.rs:171
|
||||
msgid "No URL or ID specified."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:179
|
||||
msgid "No detailed command specified."
|
||||
msgstr ""
|
||||
|
||||
#: opts.rs:177
|
||||
#: opts.rs:192
|
||||
msgid "Unknown config subcommand."
|
||||
msgstr ""
|
||||
|
||||
#: parser\mainpage.rs:53 parser/mainpage.rs:53
|
||||
#: parser/metadata.rs:54 pixiv_web.rs:107
|
||||
msgid "Failed to parse JSON:"
|
||||
msgstr ""
|
||||
|
||||
#: parser\mainpage.rs:75 parser/mainpage.rs:75
|
||||
#: parser/metadata.rs:76
|
||||
msgid "Failed to parse HTML:"
|
||||
msgstr ""
|
||||
|
||||
#: parser\mainpage.rs:80 parser/mainpage.rs:80
|
||||
#: parser/metadata.rs:81
|
||||
msgid "Some errors occured during parsing:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv.rs:63 pixiv.rs:68
|
||||
#: pixiv_web.rs:68 pixiv_web.rs:73
|
||||
msgid "Failed to get main page:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv.rs:74
|
||||
#: pixiv_web.rs:79
|
||||
msgid "Failed to parse main page."
|
||||
msgstr ""
|
||||
|
||||
#: pixiv.rs:88
|
||||
#: pixiv_web.rs:83
|
||||
msgid "Main page's data:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:98
|
||||
msgid "Network error:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:117
|
||||
msgid "Failed to detect error."
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:164 pixiv_web.rs:169
|
||||
msgid "Failed to get artwork page:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:175
|
||||
msgid "Failed to parse artwork page."
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:179
|
||||
msgid "Artwork's data:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:193
|
||||
msgid "Artwork's page data:"
|
||||
msgstr ""
|
||||
|
||||
#: pixiv_web.rs:217
|
||||
msgid "Warning: Failed to save cookies file:"
|
||||
msgstr ""
|
||||
|
||||
@@ -201,26 +277,54 @@ msgstr ""
|
||||
msgid "Pixiv's refresh tokens. Used to login."
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:59
|
||||
#: utils.rs:29
|
||||
msgid "Do you want to delete file \"<file>\"?"
|
||||
msgstr ""
|
||||
|
||||
#: utils.rs:49 webclient.rs:143
|
||||
msgid "Can not parse URL:"
|
||||
msgstr ""
|
||||
|
||||
#: utils.rs:56
|
||||
msgid "Failed to get file name from path:"
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:103
|
||||
msgid "Failed to parse Set-Cookie header."
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:64
|
||||
#: webclient.rs:108
|
||||
msgid "Failed to convert to string:"
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:94
|
||||
#: webclient.rs:150
|
||||
msgid "Parameters should be object or array:"
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:170
|
||||
msgid "Parameters should be array:"
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:174
|
||||
msgid "Parameters need at least a value:"
|
||||
msgstr ""
|
||||
|
||||
#: webclient.rs:209
|
||||
msgid "Error when request:"
|
||||
msgstr ""
|
||||
|
||||
#: main.rs:46
|
||||
#: webclient.rs:270
|
||||
msgid "Error when downloading file:"
|
||||
msgstr ""
|
||||
|
||||
#: main.rs:50
|
||||
msgid "Failed to save config file:"
|
||||
msgstr ""
|
||||
|
||||
#: main.rs:57
|
||||
#: main.rs:61
|
||||
msgid "All available settings:"
|
||||
msgstr ""
|
||||
|
||||
#: main.rs:89
|
||||
#: main.rs:93
|
||||
msgid "Can not read config file:"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: bili\n"
|
||||
"POT-Creation-Date: 2022-02-26 23:42+0800\n"
|
||||
"PO-Revision-Date: 2022-02-26 23:43+0800\n"
|
||||
"Project-Id-Version: pixiv_downloader\n"
|
||||
"POT-Creation-Date: 2022-02-27 17:47+0800\n"
|
||||
"PO-Revision-Date: 2022-02-27 17:48+0800\n"
|
||||
"Last-Translator: lifegpc <[email protected]>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: zh_CN\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"X-Poedit-Basepath: ../src\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
"X-Poedit-Basepath: ../src\n"
|
||||
"X-Poedit-SearchPath-0: .\n"
|
||||
|
||||
#: cookies.rs:75 cookies.rs:217
|
||||
@@ -70,95 +70,171 @@ msgstr "无效的Cookie:"
|
||||
msgid "Can not parse expired time:"
|
||||
msgstr "无法解析过期时间:"
|
||||
|
||||
#: cookies.rs:366 settings.rs:392
|
||||
#: cookies.rs:366 data/json.rs:39 settings.rs:392 webclient.rs:257
|
||||
msgid "Failed to remove file:"
|
||||
msgstr "无法删除文件:"
|
||||
|
||||
#: cookies.rs:372 settings.rs:399
|
||||
#: cookies.rs:372 data/json.rs:50 settings.rs:399 webclient.rs:263
|
||||
msgid "Failed to create file:"
|
||||
msgstr "无法创建文件:"
|
||||
|
||||
#: cookies.rs:379 settings.rs:405
|
||||
#: cookies.rs:379 data/json.rs:56 settings.rs:405 webclient.rs:276
|
||||
msgid "Failed to write file:"
|
||||
msgstr "无法写入文件:"
|
||||
|
||||
#: download.rs:9 pixiv.rs:47
|
||||
#: download.rs:15 pixiv_web.rs:53
|
||||
msgid "Failed to initialize pixiv web api client."
|
||||
msgstr "无法初始化 Pixiv 网页 API 客户端。"
|
||||
|
||||
#: opts.rs:69
|
||||
#: download.rs:22
|
||||
msgid "Warning: Web api client not logined, some future may not work."
|
||||
msgstr "警告:Web API 客户端未登录,一些功能可能无法工作。"
|
||||
|
||||
#: download.rs:45
|
||||
msgid "Failed to get page count."
|
||||
msgstr "无法获取页数。"
|
||||
|
||||
#: download.rs:54
|
||||
msgid "Failed to get pages' data."
|
||||
msgstr "无法获取每页数据。"
|
||||
|
||||
#: download.rs:61
|
||||
msgid "Failed to save metadata to JSON file."
|
||||
msgstr "无法将元数据保存到 JSON 文件。"
|
||||
|
||||
#: download.rs:69
|
||||
msgid "Failed to get original picture's link."
|
||||
msgstr "无法获取原图链接。"
|
||||
|
||||
#: download.rs:75
|
||||
msgid "Failed to get file name from url:"
|
||||
msgstr "无法从 URL 获取文件名:"
|
||||
|
||||
#: download.rs:82 download.rs:88 pixiv_web.rs:148
|
||||
msgid "Failed to download image:"
|
||||
msgstr "无法下载图片:"
|
||||
|
||||
#: download.rs:93
|
||||
msgid "Downloaded image:"
|
||||
msgstr "已下载图片:"
|
||||
|
||||
#: opts.rs:76
|
||||
msgid "Warning: The specified config file not found."
|
||||
msgstr "警告:没有找到指定的设置文件。"
|
||||
|
||||
#: opts.rs:93
|
||||
#: opts.rs:100
|
||||
msgid "Usage:"
|
||||
msgstr "使用方法:"
|
||||
|
||||
#: opts.rs:95
|
||||
#: opts.rs:102
|
||||
msgid "Download an artwork"
|
||||
msgstr "下载一个作品"
|
||||
|
||||
#: opts.rs:97
|
||||
#: opts.rs:104
|
||||
msgid "Fix the config file"
|
||||
msgstr "修复设置文件"
|
||||
|
||||
#: opts.rs:99
|
||||
#: opts.rs:106
|
||||
msgid "Print all available settings"
|
||||
msgstr "打印所有可用的设置"
|
||||
|
||||
#: opts.rs:107
|
||||
#: opts.rs:114
|
||||
msgid "Print help message."
|
||||
msgstr "打印帮助信息。"
|
||||
|
||||
#: opts.rs:111
|
||||
#: opts.rs:118
|
||||
msgid "The location of config file."
|
||||
msgstr "设置文件的位置。"
|
||||
|
||||
#: opts.rs:117 settings_list.rs:8
|
||||
#: opts.rs:124 settings_list.rs:8
|
||||
msgid "The location of cookies file. Used for web API."
|
||||
msgstr "cookies 文件的位置。用于网页 API。"
|
||||
|
||||
#: opts.rs:123 settings_list.rs:9
|
||||
#: opts.rs:130 settings_list.rs:9
|
||||
msgid "The language of translated tags."
|
||||
msgstr "翻译后的标签语言。"
|
||||
|
||||
#: opts.rs:145
|
||||
#: opts.rs:133
|
||||
msgid "Verbose logging."
|
||||
msgstr "启用详细内容输出。"
|
||||
|
||||
#: opts.rs:134
|
||||
msgid "Overwrite existing file."
|
||||
msgstr "覆盖已有文件。"
|
||||
|
||||
#: opts.rs:135
|
||||
msgid "Skip overwrite existing file."
|
||||
msgstr "跳过覆盖已有文件。"
|
||||
|
||||
#: opts.rs:155
|
||||
msgid "Unknown command."
|
||||
msgstr "未知指令。"
|
||||
|
||||
#: opts.rs:156
|
||||
msgid "No URL specified."
|
||||
msgstr "没有指定网址。"
|
||||
#: opts.rs:165
|
||||
msgid "Failed to parse ID:"
|
||||
msgstr "无法解析 ID:"
|
||||
|
||||
#: opts.rs:164
|
||||
#: opts.rs:171
|
||||
msgid "No URL or ID specified."
|
||||
msgstr "没有指定网址或 ID。"
|
||||
|
||||
#: opts.rs:179
|
||||
msgid "No detailed command specified."
|
||||
msgstr "没有指定更详细的指令。"
|
||||
|
||||
#: opts.rs:177
|
||||
#: opts.rs:192
|
||||
msgid "Unknown config subcommand."
|
||||
msgstr "未知的 config 子指令。"
|
||||
|
||||
#: parser\mainpage.rs:53 parser/mainpage.rs:53
|
||||
#: parser/metadata.rs:54 pixiv_web.rs:107
|
||||
msgid "Failed to parse JSON:"
|
||||
msgstr "无法解析 JSON:"
|
||||
|
||||
#: parser\mainpage.rs:75 parser/mainpage.rs:75
|
||||
#: parser/metadata.rs:76
|
||||
msgid "Failed to parse HTML:"
|
||||
msgstr "无法解析 HTML:"
|
||||
|
||||
#: parser\mainpage.rs:80 parser/mainpage.rs:80
|
||||
#: parser/metadata.rs:81
|
||||
msgid "Some errors occured during parsing:"
|
||||
msgstr "在解析中发生了一些错误:"
|
||||
|
||||
#: pixiv.rs:63 pixiv.rs:68
|
||||
#: pixiv_web.rs:68 pixiv_web.rs:73
|
||||
msgid "Failed to get main page:"
|
||||
msgstr "无法获取主页:"
|
||||
|
||||
#: pixiv.rs:74
|
||||
#: pixiv_web.rs:79
|
||||
msgid "Failed to parse main page."
|
||||
msgstr "无法解析主页。"
|
||||
|
||||
#: pixiv.rs:88
|
||||
#: pixiv_web.rs:83
|
||||
msgid "Main page's data:"
|
||||
msgstr "主页的数据:"
|
||||
|
||||
#: pixiv_web.rs:98
|
||||
msgid "Network error:"
|
||||
msgstr "网络错误:"
|
||||
|
||||
#: pixiv_web.rs:117
|
||||
msgid "Failed to detect error."
|
||||
msgstr "检测 error 字段失败。"
|
||||
|
||||
#: pixiv_web.rs:164 pixiv_web.rs:169
|
||||
msgid "Failed to get artwork page:"
|
||||
msgstr "无法获取作品页:"
|
||||
|
||||
#: pixiv_web.rs:175
|
||||
msgid "Failed to parse artwork page."
|
||||
msgstr "无法解析作品页。"
|
||||
|
||||
#: pixiv_web.rs:179
|
||||
msgid "Artwork's data:"
|
||||
msgstr "作品数据:"
|
||||
|
||||
#: pixiv_web.rs:193
|
||||
msgid "Artwork's page data:"
|
||||
msgstr "作品页面数据:"
|
||||
|
||||
#: pixiv_web.rs:217
|
||||
msgid "Warning: Failed to save cookies file:"
|
||||
msgstr "警告:无法保存 cookies 文件:"
|
||||
|
||||
@@ -206,26 +282,54 @@ msgstr "无法刷新文件缓冲区:"
|
||||
msgid "Pixiv's refresh tokens. Used to login."
|
||||
msgstr "Pixiv 的 refresh tokens。用于登录。"
|
||||
|
||||
#: webclient.rs:59
|
||||
#: utils.rs:29
|
||||
msgid "Do you want to delete file \"<file>\"?"
|
||||
msgstr "你想要删除文件 <file> 吗?"
|
||||
|
||||
#: utils.rs:49 webclient.rs:143
|
||||
msgid "Can not parse URL:"
|
||||
msgstr "无法解析 URL:"
|
||||
|
||||
#: utils.rs:56
|
||||
msgid "Failed to get file name from path:"
|
||||
msgstr "无法从路径获取文件名:"
|
||||
|
||||
#: webclient.rs:103
|
||||
msgid "Failed to parse Set-Cookie header."
|
||||
msgstr "无法解析 Set-Cookie 头部。"
|
||||
|
||||
#: webclient.rs:64
|
||||
#: webclient.rs:108
|
||||
msgid "Failed to convert to string:"
|
||||
msgstr "无法转换为字符串:"
|
||||
|
||||
#: webclient.rs:94
|
||||
#: webclient.rs:150
|
||||
msgid "Parameters should be object or array:"
|
||||
msgstr "参数应该是对象或数组:"
|
||||
|
||||
#: webclient.rs:170
|
||||
msgid "Parameters should be array:"
|
||||
msgstr "参数应该是数组:"
|
||||
|
||||
#: webclient.rs:174
|
||||
msgid "Parameters need at least a value:"
|
||||
msgstr "参数需要至少含有一个值:"
|
||||
|
||||
#: webclient.rs:209
|
||||
msgid "Error when request:"
|
||||
msgstr "请求时发生错误:"
|
||||
|
||||
#: main.rs:46
|
||||
#: webclient.rs:270
|
||||
msgid "Error when downloading file:"
|
||||
msgstr "下载文件时发生错误:"
|
||||
|
||||
#: main.rs:50
|
||||
msgid "Failed to save config file:"
|
||||
msgstr "无法保存设置文件:"
|
||||
|
||||
#: main.rs:57
|
||||
#: main.rs:61
|
||||
msgid "All available settings:"
|
||||
msgstr "所有可用的设置:"
|
||||
|
||||
#: main.rs:89
|
||||
#: main.rs:93
|
||||
msgid "Can not read config file:"
|
||||
msgstr "无法读取设置文件:"
|
||||
|
||||
@@ -234,7 +234,7 @@ impl Cookie {
|
||||
} else {
|
||||
self._domain.clone()
|
||||
};
|
||||
if subdomain && !host.starts_with(&domain) && host != &domain[1..] {
|
||||
if subdomain && !host.ends_with(&domain) && host != &domain[1..] {
|
||||
return false;
|
||||
}
|
||||
if !subdomain && host != domain {
|
||||
|
||||
76
src/data/json.rs
Normal file
76
src/data/json.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use crate::gettext;
|
||||
use crate::pixiv_link::PixivID;
|
||||
use crate::pixiv_link::ToPixivID;
|
||||
use json::JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::fs::remove_file;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub trait ToJson {
|
||||
fn to_json(&self) -> Option<JsonValue>;
|
||||
}
|
||||
|
||||
/// Store metadata informations in JSON file
|
||||
pub struct JSONDataFile {
|
||||
id: PixivID,
|
||||
maps: HashMap<String, JsonValue>,
|
||||
}
|
||||
|
||||
impl JSONDataFile {
|
||||
pub fn new<T: ToPixivID>(id: T) -> Option<Self> {
|
||||
let i = id.to_pixiv_id();
|
||||
if i.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
id: i.unwrap(),
|
||||
maps: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn save<S: AsRef<OsStr> + ?Sized>(&self, path: &S) -> bool {
|
||||
let p = Path::new(path);
|
||||
if p.exists() {
|
||||
let r = remove_file(p);
|
||||
if r.is_err() {
|
||||
println!("{} {}", gettext("Failed to remove file:"), r.unwrap_err());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let value = self.to_json();
|
||||
if value.is_none() {
|
||||
return false;
|
||||
}
|
||||
let value = value.unwrap();
|
||||
let f = File::create(p);
|
||||
if f.is_err() {
|
||||
println!("{} {}", gettext("Failed to create file:"), f.unwrap_err());
|
||||
return false;
|
||||
}
|
||||
let mut f = f.unwrap();
|
||||
let r = f.write(value.pretty(2).as_bytes());
|
||||
if r.is_err() {
|
||||
println!("{} {}", gettext("Failed to write file:"), r.unwrap_err());
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for JSONDataFile {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
let mut value = json::object! {};
|
||||
if value.insert("id", self.id.to_json()).is_err() {
|
||||
return None;
|
||||
}
|
||||
for (k, v) in self.maps.iter() {
|
||||
if value.insert(k, v.clone()).is_err() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
}
|
||||
1
src/data/mod.rs
Normal file
1
src/data/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod json;
|
||||
@@ -1,6 +1,12 @@
|
||||
use crate::data::json::JSONDataFile;
|
||||
use crate::gettext;
|
||||
use crate::pixiv::PixivWebClient;
|
||||
use crate::pixiv_link::PixivID;
|
||||
use crate::pixiv_web::PixivWebClient;
|
||||
use crate::utils::get_file_name_from_url;
|
||||
use crate::webclient::WebClient;
|
||||
use crate::Main;
|
||||
use json::JsonValue;
|
||||
use std::path::PathBuf;
|
||||
|
||||
impl Main {
|
||||
pub fn download(&mut self) -> i32 {
|
||||
@@ -10,6 +16,86 @@ impl Main {
|
||||
return 1;
|
||||
}
|
||||
pw.check_login();
|
||||
if !pw.logined() {
|
||||
println!(
|
||||
"{}",
|
||||
gettext("Warning: Web api client not logined, some future may not work.")
|
||||
);
|
||||
}
|
||||
for id in self.cmd.as_ref().unwrap().ids.iter() {
|
||||
match id {
|
||||
PixivID::Artwork(id) => {
|
||||
let r = self.download_artwork(&mut pw, id.clone());
|
||||
if r != 0 {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
pub fn download_artwork(&self, pw: &mut PixivWebClient, id: u64) -> i32 {
|
||||
let re = pw.get_artwork(id);
|
||||
if re.is_none() {
|
||||
return 1;
|
||||
}
|
||||
let pages = (&(re.as_ref().unwrap())["illust"][format!("{}", id).as_str()]["sl"]).as_u64();
|
||||
if pages.is_none() {
|
||||
println!("{}", gettext("Failed to get page count."));
|
||||
return 1;
|
||||
}
|
||||
let pages = pages.unwrap();
|
||||
let mut pages_data: Option<JsonValue> = None;
|
||||
if pages > 1 {
|
||||
pages_data = pw.get_illust_pages(id);
|
||||
}
|
||||
if pages > 1 && pages_data.is_none() {
|
||||
println!("{}", gettext("Failed to get pages' data."));
|
||||
return 1;
|
||||
}
|
||||
let base = PathBuf::from(".");
|
||||
let json_file = base.join(format!("{}.json", id));
|
||||
let json_data = JSONDataFile::new(id).unwrap();
|
||||
if !json_data.save(&json_file) {
|
||||
println!("{}", gettext("Failed to save metadata to JSON file."));
|
||||
return 1;
|
||||
}
|
||||
if pages_data.is_some() {
|
||||
let pages_data = pages_data.as_ref().unwrap();
|
||||
for page in pages_data.members() {
|
||||
let link = &page["urls"]["original"];
|
||||
if !link.is_string() {
|
||||
println!("{}", gettext("Failed to get original picture's link."));
|
||||
return 1;
|
||||
}
|
||||
let link = link.as_str().unwrap();
|
||||
let file_name = get_file_name_from_url(link);
|
||||
if file_name.is_none() {
|
||||
println!("{} {}", gettext("Failed to get file name from url:"), link);
|
||||
return 1;
|
||||
}
|
||||
let file_name = file_name.unwrap();
|
||||
let file_name = base.join(file_name);
|
||||
let r = pw.download_image(link);
|
||||
if r.is_none() {
|
||||
println!("{} {}", gettext("Failed to download image:"), link);
|
||||
return 1;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let re = WebClient::download_stream(&file_name, r, pw.helper.overwrite());
|
||||
if re.is_err() {
|
||||
println!("{} {}", gettext("Failed to download image:"), link);
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
"{} {} -> {}",
|
||||
gettext("Downloaded image:"),
|
||||
link,
|
||||
file_name.to_str().unwrap_or("(null)")
|
||||
)
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ impl I18n {
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
#[doc(hidden)]
|
||||
static ref I18NT: I18n = I18n::new();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
extern crate chrono;
|
||||
extern crate dateparser;
|
||||
extern crate futures_util;
|
||||
extern crate json;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
extern crate tokio;
|
||||
extern crate regex;
|
||||
extern crate reqwest;
|
||||
|
||||
mod cookies;
|
||||
mod data;
|
||||
mod download;
|
||||
mod i18n;
|
||||
mod opthelper;
|
||||
mod opts;
|
||||
mod parser;
|
||||
mod pixiv;
|
||||
mod pixiv_link;
|
||||
mod pixiv_web;
|
||||
mod settings;
|
||||
mod settings_list;
|
||||
mod utils;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
use crate::opts::CommandOpts;
|
||||
use crate::settings::SettingStore;
|
||||
|
||||
/// An sturct to access all available settings/command line switches
|
||||
#[derive(Clone)]
|
||||
pub struct OptHelper<'a> {
|
||||
/// Command Line Options
|
||||
opt: &'a CommandOpts,
|
||||
/// Settings
|
||||
settings: &'a SettingStore,
|
||||
}
|
||||
|
||||
impl<'a> OptHelper<'a> {
|
||||
/// return cookies location, no any check
|
||||
pub fn cookies(&self) -> Option<String> {
|
||||
if self.opt.cookies.is_some() {
|
||||
self.opt.cookies.clone()
|
||||
@@ -18,6 +22,7 @@ impl<'a> OptHelper<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// return language
|
||||
pub fn language(&self) -> Option<String> {
|
||||
if self.opt.language.is_some() {
|
||||
self.opt.language.clone()
|
||||
@@ -31,4 +36,12 @@ impl<'a> OptHelper<'a> {
|
||||
pub fn new(opt: &'a CommandOpts, settings: &'a SettingStore) -> Self {
|
||||
Self { opt, settings }
|
||||
}
|
||||
|
||||
pub fn overwrite(&self) -> Option<bool> {
|
||||
self.opt.overwrite
|
||||
}
|
||||
|
||||
pub fn verbose(&self) -> bool {
|
||||
self.opt.verbose
|
||||
}
|
||||
}
|
||||
|
||||
49
src/opts.rs
49
src/opts.rs
@@ -1,6 +1,7 @@
|
||||
extern crate getopts;
|
||||
|
||||
use crate::gettext;
|
||||
use crate::pixiv_link::PixivID;
|
||||
use crate::utils::check_file_exists;
|
||||
use crate::utils::get_exe_path_else_current;
|
||||
use getopts::Options;
|
||||
@@ -35,8 +36,8 @@ impl PartialEq<ConfigCommand> for &ConfigCommand {
|
||||
pub struct CommandOpts {
|
||||
/// Command
|
||||
pub cmd: Command,
|
||||
/// URLs
|
||||
pub urls: Vec<String>,
|
||||
/// IDs
|
||||
pub ids: Vec<PixivID>,
|
||||
/// Config location
|
||||
pub _config: Option<String>,
|
||||
/// Config command
|
||||
@@ -45,17 +46,23 @@ pub struct CommandOpts {
|
||||
pub cookies: Option<String>,
|
||||
/// The language of translated tags
|
||||
pub language: Option<String>,
|
||||
/// Verbose logging
|
||||
pub verbose: bool,
|
||||
/// Whether to overwrite file
|
||||
pub overwrite: Option<bool>,
|
||||
}
|
||||
|
||||
impl CommandOpts {
|
||||
pub fn new(cmd: Command) -> Self {
|
||||
Self {
|
||||
cmd,
|
||||
urls: Vec::new(),
|
||||
ids: Vec::new(),
|
||||
_config: None,
|
||||
config_cmd: None,
|
||||
cookies: None,
|
||||
language: None,
|
||||
verbose: false,
|
||||
overwrite: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +94,7 @@ impl CommandOpts {
|
||||
pub fn print_usage(prog: &str, opts: &Options) {
|
||||
let brief = format!(
|
||||
"{}
|
||||
{} download [options] <url> [<url>] {}
|
||||
{} download [options] <id/url> [<id/url>] {}
|
||||
{} config fix [options] {}
|
||||
{} config help [options] {}",
|
||||
gettext("Usage:"),
|
||||
@@ -123,6 +130,9 @@ pub fn parse_cmd() -> Option<CommandOpts> {
|
||||
gettext("The language of translated tags."),
|
||||
"LANG",
|
||||
);
|
||||
opts.optflag("v", "verbose", gettext("Verbose logging."));
|
||||
opts.optflag("y", "yes", gettext("Overwrite existing file."));
|
||||
opts.optflag("n", "no", gettext("Skip overwrite existing file."));
|
||||
let result = match opts.parse(&argv[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
@@ -148,16 +158,21 @@ pub fn parse_cmd() -> Option<CommandOpts> {
|
||||
}
|
||||
match re.as_ref().unwrap().cmd {
|
||||
Command::Download => {
|
||||
let mut urls = Vec::new();
|
||||
let mut ids = Vec::new();
|
||||
for url in result.free.iter().skip(1) {
|
||||
urls.push(url.to_string());
|
||||
let id = PixivID::parse(url);
|
||||
if id.is_none() {
|
||||
println!("{} {}", gettext("Failed to parse ID:"), url);
|
||||
return None;
|
||||
}
|
||||
ids.push(id.unwrap());
|
||||
}
|
||||
if urls.is_empty() {
|
||||
println!("{}", gettext("No URL specified."));
|
||||
if ids.is_empty() {
|
||||
println!("{}", gettext("No URL or ID specified."));
|
||||
print_usage(&argv[0], &opts);
|
||||
return None;
|
||||
}
|
||||
re.as_mut().unwrap().urls = urls;
|
||||
re.as_mut().unwrap().ids = ids;
|
||||
}
|
||||
Command::Config => {
|
||||
if result.free.len() < 2 {
|
||||
@@ -189,5 +204,21 @@ pub fn parse_cmd() -> Option<CommandOpts> {
|
||||
if result.opt_present("language") {
|
||||
re.as_mut().unwrap().language = Some(result.opt_str("language").unwrap());
|
||||
}
|
||||
re.as_mut().unwrap().verbose = result.opt_present("verbose");
|
||||
let yes = result.opt_present("yes");
|
||||
let no = result.opt_present("no");
|
||||
re.as_mut().unwrap().overwrite = if yes && no {
|
||||
if result.opt_positions("yes").last().unwrap() > result.opt_positions("no").last().unwrap() {
|
||||
Some(true)
|
||||
} else {
|
||||
Some(false)
|
||||
}
|
||||
} else if yes {
|
||||
Some(true)
|
||||
} else if no {
|
||||
Some(false)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
re
|
||||
}
|
||||
|
||||
@@ -2,14 +2,19 @@ use crate::gettext;
|
||||
use html_parser::Dom;
|
||||
use html_parser::Node;
|
||||
use json::JsonValue;
|
||||
use std::default::Default;
|
||||
|
||||
pub struct MainPageParser {
|
||||
pub struct MetaDataParser {
|
||||
pub key: String,
|
||||
pub value: Option<JsonValue>,
|
||||
}
|
||||
|
||||
impl MainPageParser {
|
||||
pub fn new() -> Self {
|
||||
impl MetaDataParser {
|
||||
/// Create an new instance
|
||||
/// * `key` - meta's name
|
||||
pub fn new(key: &str) -> Self {
|
||||
Self {
|
||||
key: key.to_string(),
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
@@ -26,13 +31,14 @@ impl MainPageParser {
|
||||
if name.is_none() {
|
||||
return false;
|
||||
}
|
||||
if name.as_ref().unwrap() != "global-data" {
|
||||
if name.as_ref().unwrap() != self.key.as_str() {
|
||||
return false;
|
||||
}
|
||||
if e.id.is_none() {
|
||||
return false;
|
||||
}
|
||||
if e.id.as_ref().unwrap() != "meta-global-data" {
|
||||
let mkey = format!("meta-{}", self.key.as_str());
|
||||
if e.id.as_ref().unwrap() != mkey.as_str() {
|
||||
return false;
|
||||
}
|
||||
let c = e.attributes.get("content");
|
||||
@@ -85,3 +91,9 @@ impl MainPageParser {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MetaDataParser {
|
||||
fn default() -> Self {
|
||||
Self::new("global-data")
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
extern crate html_parser;
|
||||
|
||||
pub mod mainpage;
|
||||
pub mod metadata;
|
||||
|
||||
91
src/pixiv.rs
91
src/pixiv.rs
@@ -1,91 +0,0 @@
|
||||
use crate::gettext;
|
||||
use crate::opthelper::OptHelper;
|
||||
use crate::parser::mainpage::MainPageParser;
|
||||
use crate::webclient::WebClient;
|
||||
use crate::Main;
|
||||
use spin_on::spin_on;
|
||||
|
||||
/// A client which use Pixiv's web API
|
||||
pub struct PixivWebClient<'a> {
|
||||
client: WebClient,
|
||||
helper: OptHelper<'a>,
|
||||
/// true if in is initialized
|
||||
inited: bool,
|
||||
}
|
||||
|
||||
impl<'a> PixivWebClient<'a> {
|
||||
pub fn new(m: &'a Main) -> Self {
|
||||
Self {
|
||||
client: WebClient::new(),
|
||||
helper: OptHelper::new(m.cmd.as_ref().unwrap(), m.settings.as_ref().unwrap()),
|
||||
inited: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> bool {
|
||||
let c = self.helper.cookies();
|
||||
if c.is_some() {
|
||||
if !self.client.read_cookies(c.as_ref().unwrap()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.client.set_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36");
|
||||
let l = self.helper.language();
|
||||
if l.is_some() {
|
||||
self.client.set_header("Accept-Language", l.as_ref().unwrap());
|
||||
} else {
|
||||
self.client.set_header("Accept-Language", "ja");
|
||||
}
|
||||
self.inited = true;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn auto_init(&mut self) {
|
||||
if !self.inited {
|
||||
let r = self.init();
|
||||
if !r {
|
||||
panic!("{}", gettext("Failed to initialize pixiv web api client."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_login(&mut self) -> bool {
|
||||
self.auto_init();
|
||||
let r = self.client.get("https://www.pixiv.net/");
|
||||
if r.is_none() {
|
||||
return false;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let status = r.status();
|
||||
let code = status.as_u16();
|
||||
if code >= 400 {
|
||||
println!("{} {}", gettext("Failed to get main page:"), status);
|
||||
return false;
|
||||
}
|
||||
let data = spin_on(r.text_with_charset("UTF-8"));
|
||||
if data.is_err() {
|
||||
println!("{} {}", gettext("Failed to get main page:"), data.unwrap_err());
|
||||
return false;
|
||||
}
|
||||
let data = data.unwrap();
|
||||
let mut p = MainPageParser::new();
|
||||
if !p.parse(data.as_str()) {
|
||||
println!("{}", gettext("Failed to parse main page."));
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> Drop for PixivWebClient<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.inited {
|
||||
let c = self.helper.cookies();
|
||||
if c.is_some() {
|
||||
if !self.client.save_cookies(c.as_ref().unwrap()) {
|
||||
println!("{} {}", gettext("Warning: Failed to save cookies file:"), c.as_ref().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/pixiv_link.rs
Normal file
73
src/pixiv_link.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use crate::data::json::ToJson;
|
||||
use json::JsonValue;
|
||||
use regex::Regex;
|
||||
|
||||
lazy_static! {
|
||||
#[doc(hidden)]
|
||||
static ref RE: Regex = Regex::new("^(https?://)?(www\\.)?pixiv\\.net/artworks/(?P<id>\\d+)").unwrap();
|
||||
}
|
||||
|
||||
/// Repesent an Pixiv ID
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PixivID {
|
||||
/// Artwork Id, include illust and manga
|
||||
Artwork(u64),
|
||||
}
|
||||
|
||||
pub trait ToPixivID {
|
||||
fn to_pixiv_id(&self) -> Option<PixivID>;
|
||||
}
|
||||
|
||||
impl PixivID {
|
||||
pub fn parse(s: &str) -> Option<PixivID> {
|
||||
let p = s.trim();
|
||||
let num = p.parse::<u64>();
|
||||
if num.is_ok() {
|
||||
return Some(PixivID::Artwork(num.unwrap()));
|
||||
}
|
||||
let re = RE.captures(s);
|
||||
if re.is_some() {
|
||||
let r = re.unwrap().name("id");
|
||||
if r.is_some() {
|
||||
let r = r.unwrap().as_str();
|
||||
let num = r.parse::<u64>();
|
||||
return Some(PixivID::Artwork(num.unwrap()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl ToJson for PixivID {
|
||||
fn to_json(&self) -> Option<JsonValue> {
|
||||
match *self {
|
||||
PixivID::Artwork(id) => {
|
||||
Some(json::value!({"type": "artwork", "id": id}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPixivID for &PixivID {
|
||||
fn to_pixiv_id(&self) -> Option<PixivID> {
|
||||
Some((*self).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPixivID for &str {
|
||||
fn to_pixiv_id(&self) -> Option<PixivID> {
|
||||
PixivID::parse(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPixivID for String {
|
||||
fn to_pixiv_id(&self) -> Option<PixivID> {
|
||||
PixivID::parse(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToPixivID for u64 {
|
||||
fn to_pixiv_id(&self) -> Option<PixivID> {
|
||||
Some(PixivID::Artwork(*self))
|
||||
}
|
||||
}
|
||||
222
src/pixiv_web.rs
Normal file
222
src/pixiv_web.rs
Normal file
@@ -0,0 +1,222 @@
|
||||
use crate::gettext;
|
||||
use crate::opthelper::OptHelper;
|
||||
use crate::parser::metadata::MetaDataParser;
|
||||
use crate::webclient::WebClient;
|
||||
use crate::Main;
|
||||
use json::JsonValue;
|
||||
use reqwest::IntoUrl;
|
||||
use reqwest::Response;
|
||||
use spin_on::spin_on;
|
||||
|
||||
/// A client which use Pixiv's web API
|
||||
pub struct PixivWebClient<'a> {
|
||||
client: WebClient,
|
||||
pub helper: OptHelper<'a>,
|
||||
/// true if in is initialized
|
||||
inited: bool,
|
||||
data: Option<JsonValue>,
|
||||
}
|
||||
|
||||
impl<'a> PixivWebClient<'a> {
|
||||
pub fn new(m: &'a Main) -> Self {
|
||||
Self {
|
||||
client: WebClient::new(),
|
||||
helper: OptHelper::new(m.cmd.as_ref().unwrap(), m.settings.as_ref().unwrap()),
|
||||
inited: false,
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> bool {
|
||||
let c = self.helper.cookies();
|
||||
if c.is_some() {
|
||||
if !self.client.read_cookies(c.as_ref().unwrap()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.client.set_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36");
|
||||
let l = self.helper.language();
|
||||
if l.is_some() {
|
||||
self.client.set_header("Accept-Language", l.as_ref().unwrap());
|
||||
} else {
|
||||
self.client.set_header("Accept-Language", "ja");
|
||||
}
|
||||
self.client.verbose = self.helper.verbose();
|
||||
self.inited = true;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn auto_init(&mut self) {
|
||||
if !self.inited {
|
||||
let r = self.init();
|
||||
if !r {
|
||||
panic!("{}", gettext("Failed to initialize pixiv web api client."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_login(&mut self) -> bool {
|
||||
self.auto_init();
|
||||
let r = self.client.get("https://www.pixiv.net/", None);
|
||||
if r.is_none() {
|
||||
return false;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let status = r.status();
|
||||
let code = status.as_u16();
|
||||
if code >= 400 {
|
||||
println!("{} {}", gettext("Failed to get main page:"), status);
|
||||
return false;
|
||||
}
|
||||
let data = spin_on(r.text_with_charset("UTF-8"));
|
||||
if data.is_err() {
|
||||
println!("{} {}", gettext("Failed to get main page:"), data.unwrap_err());
|
||||
return false;
|
||||
}
|
||||
let data = data.unwrap();
|
||||
let mut p = MetaDataParser::default();
|
||||
if !p.parse(data.as_str()) {
|
||||
println!("{}", gettext("Failed to parse main page."));
|
||||
return false;
|
||||
}
|
||||
if self.helper.verbose() {
|
||||
println!("{}\n{}", gettext("Main page's data:"), p.value.as_ref().unwrap().pretty(2).as_str());
|
||||
}
|
||||
self.data = Some(p.value.unwrap());
|
||||
true
|
||||
}
|
||||
|
||||
pub fn deal_json(&mut self, r: Response) -> Option<JsonValue> {
|
||||
let status = r.status();
|
||||
let code = status.as_u16();
|
||||
let is_status_err = code >= 400;
|
||||
let data = spin_on(r.text_with_charset("UTF-8"));
|
||||
if data.is_err() {
|
||||
if is_status_err {
|
||||
println!("HTTP ERROR {}", status);
|
||||
}
|
||||
println!("{} {}", gettext("Network error:"), data.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
let data = data.unwrap();
|
||||
let re = json::parse(data.as_str());
|
||||
if re.is_err() {
|
||||
if is_status_err {
|
||||
println!("HTTP ERROR {}", status);
|
||||
} else {
|
||||
println!("{} {}", gettext("Failed to parse JSON:"), re.unwrap_err());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let value = re.unwrap();
|
||||
let error = (&value["error"]).as_bool();
|
||||
if error.is_none() {
|
||||
if is_status_err {
|
||||
println!("HTTP ERROR {}", status);
|
||||
}
|
||||
println!("{}", gettext("Failed to detect error."));
|
||||
return None;
|
||||
}
|
||||
let error = error.unwrap();
|
||||
if error {
|
||||
let message = (&value["message"]).as_str();
|
||||
if is_status_err {
|
||||
println!("HTTP ERROR {}", status);
|
||||
}
|
||||
if message.is_some() {
|
||||
println!("{}", message.unwrap());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
let body = &value["body"];
|
||||
if body.is_empty() || body.is_null() {
|
||||
return Some(value);
|
||||
}
|
||||
Some(body.clone())
|
||||
}
|
||||
|
||||
pub fn download_image<U: IntoUrl>(&mut self, url: U) -> Option<Response> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(url, json::object!{"referer": "https://www.pixiv.net/"});
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let status = r.status();
|
||||
let code = status.as_u16();
|
||||
if code >= 400 {
|
||||
println!("{} {}", gettext("Failed to download image:"), status);
|
||||
return None;
|
||||
}
|
||||
Some(r)
|
||||
}
|
||||
|
||||
pub fn get_artwork(&mut self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(format!("https://www.pixiv.net/artworks/{}", id), None);
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let status = r.status();
|
||||
let code = status.as_u16();
|
||||
if code >= 400 {
|
||||
println!("{} {}", gettext("Failed to get artwork page:"), status);
|
||||
return None;
|
||||
}
|
||||
let data = spin_on(r.text_with_charset("UTF-8"));
|
||||
if data.is_err() {
|
||||
println!("{} {}", gettext("Failed to get artwork page:"), data.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
let data = data.unwrap();
|
||||
let mut p = MetaDataParser::new("preload-data");
|
||||
if !p.parse(data.as_str()) {
|
||||
println!("{}", gettext("Failed to parse artwork page."));
|
||||
return None;
|
||||
}
|
||||
if self.helper.verbose() {
|
||||
println!("{} {}", gettext("Artwork's data:"), p.value.as_ref().unwrap().pretty(2));
|
||||
}
|
||||
Some(p.value.unwrap())
|
||||
}
|
||||
|
||||
pub fn get_illust_pages(&mut self, id: u64) -> Option<JsonValue> {
|
||||
self.auto_init();
|
||||
let r = self.client.get(format!("https://www.pixiv.net/ajax/illust/{}/pages", id), None);
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
let r = r.unwrap();
|
||||
let v = self.deal_json(r);
|
||||
if self.helper.verbose() {
|
||||
println!("{} {}", gettext("Artwork's page data:"), v.as_ref().unwrap().pretty(2));
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
pub fn logined(&self) -> bool {
|
||||
if self.data.is_none() {
|
||||
return false;
|
||||
}
|
||||
let value = self.data.as_ref().unwrap();
|
||||
let d = &value["userData"];
|
||||
if d.is_object() {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a> Drop for PixivWebClient<'a> {
|
||||
fn drop(&mut self) {
|
||||
if self.inited {
|
||||
let c = self.helper.cookies();
|
||||
if c.is_some() {
|
||||
if !self.client.save_cookies(c.as_ref().unwrap()) {
|
||||
println!("{} {}", gettext("Warning: Failed to save cookies file:"), c.as_ref().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/utils.rs
40
src/utils.rs
@@ -1,3 +1,5 @@
|
||||
use crate::gettext;
|
||||
use reqwest::IntoUrl;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
@@ -22,3 +24,41 @@ pub fn check_file_exists(path: &str) -> bool {
|
||||
let p = Path::new(path);
|
||||
p.exists()
|
||||
}
|
||||
|
||||
pub fn ask_need_overwrite(path: &str) -> bool {
|
||||
let s = gettext("Do you want to delete file \"<file>\"?").replace("<file>", path);
|
||||
print!("{}(y/n)", s.as_str());
|
||||
let mut d = String::from("");
|
||||
loop {
|
||||
let re = std::io::stdin().read_line(&mut d);
|
||||
if re.is_err() {
|
||||
continue;
|
||||
}
|
||||
let d = d.trim().to_lowercase();
|
||||
if d == "y" {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_file_name_from_url<U: IntoUrl>(url: U) -> Option<String> {
|
||||
let u = url.into_url();
|
||||
if u.is_err() {
|
||||
println!("{} {}", gettext("Can not parse URL:"), u.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
let u = u.unwrap();
|
||||
let path = Path::new(u.path());
|
||||
let re = path.file_name();
|
||||
if re.is_none() {
|
||||
println!("{} {}", gettext("Failed to get file name from path:"), u.path());
|
||||
return None;
|
||||
}
|
||||
let r = re.unwrap().to_str();
|
||||
if r.is_none() {
|
||||
return None;
|
||||
}
|
||||
Some(String::from(r.unwrap()))
|
||||
}
|
||||
|
||||
175
src/webclient.rs
175
src/webclient.rs
@@ -3,9 +3,51 @@ extern crate spin_on;
|
||||
use crate::cookies::Cookie;
|
||||
use crate::cookies::CookieJar;
|
||||
use crate::gettext;
|
||||
use crate::utils::ask_need_overwrite;
|
||||
use futures_util::StreamExt;
|
||||
use json::JsonValue;
|
||||
use reqwest::{Client, IntoUrl, RequestBuilder, Response};
|
||||
use std::collections::HashMap;
|
||||
use spin_on::spin_on;
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::fs::remove_file;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
pub trait ToHeaders {
|
||||
fn to_headers(&self) -> Option<HashMap<String, String>>;
|
||||
}
|
||||
|
||||
impl ToHeaders for Option<HashMap<String, String>> {
|
||||
fn to_headers(&self) -> Option<HashMap<String, String>> {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHeaders for HashMap<String, String> {
|
||||
fn to_headers(&self) -> Option<HashMap<String, String>> {
|
||||
Some(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ToHeaders for JsonValue {
|
||||
fn to_headers(&self) -> Option<HashMap<String, String>> {
|
||||
if !self.is_object() {
|
||||
return None;
|
||||
}
|
||||
let mut h = HashMap::new();
|
||||
for (k, v) in self.entries() {
|
||||
let d = if v.is_string() {
|
||||
String::from(v.as_str().unwrap())
|
||||
} else {
|
||||
v.dump()
|
||||
};
|
||||
h.insert(String::from(k), d);
|
||||
}
|
||||
Some(h)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate `cookie` header for a url
|
||||
/// * `c` - Cookies
|
||||
@@ -31,6 +73,7 @@ pub struct WebClient {
|
||||
/// HTTP Headers
|
||||
pub headers: HashMap<String, String>,
|
||||
cookies: CookieJar,
|
||||
pub verbose: bool,
|
||||
}
|
||||
|
||||
impl WebClient {
|
||||
@@ -39,6 +82,7 @@ impl WebClient {
|
||||
client: Client::new(),
|
||||
headers: HashMap::new(),
|
||||
cookies: CookieJar::new(),
|
||||
verbose: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,10 +126,81 @@ impl WebClient {
|
||||
pub fn set_header(&mut self, key: &str, value: &str) -> Option<String> {
|
||||
self.headers.insert(String::from(key), String::from(value))
|
||||
}
|
||||
/// Send GET requests with parameters
|
||||
/// * `param` - GET parameters. Should be a JSON object/array. If value in map is not a string, will dump it
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// let client = WebClient::new();
|
||||
/// client.verbose = true;
|
||||
/// client.get_with_param("https://test.com/a", json::object!{"data": "param1"});
|
||||
/// client.get_with_param("https://test.com/a", json::object!{"daa": {"ad": "test"}});
|
||||
/// client.get_with_param("https://test.com/a", json::array![["daa", "param1"]]);
|
||||
/// ```
|
||||
/// It will GET `https://test.com/a?data=param1`, `https://test.com/a?daa=%7B%22ad%22%3A%22test%22%7D`, `https://test.com/a?daa=param1`
|
||||
pub fn get_with_param<U: IntoUrl>(&mut self, url: U, param: JsonValue) -> Option<Response> {
|
||||
let u = url.into_url();
|
||||
if u.is_err() {
|
||||
println!("{} \"{}\"", gettext("Can not parse URL:"), u.unwrap_err());
|
||||
return None;
|
||||
}
|
||||
let mut u = u.unwrap();
|
||||
if !param.is_object() && !param.is_array() {
|
||||
println!(
|
||||
"{} \"{}\"",
|
||||
gettext("Parameters should be object or array:"),
|
||||
param
|
||||
);
|
||||
return None;
|
||||
}
|
||||
{
|
||||
let mut query = u.query_pairs_mut();
|
||||
if param.is_object() {
|
||||
for (k, v) in param.entries() {
|
||||
let s: String;
|
||||
if v.is_string() {
|
||||
s = String::from(v.as_str().unwrap());
|
||||
} else {
|
||||
s = v.dump();
|
||||
}
|
||||
query.append_pair(k, s.as_str());
|
||||
}
|
||||
} else {
|
||||
for v in param.members() {
|
||||
if !v.is_object() {
|
||||
println!("{} \"{}\"", gettext("Parameters should be array:"), v);
|
||||
return None;
|
||||
}
|
||||
if v.len() < 2 {
|
||||
println!("{} \"{}\"", gettext("Parameters need at least a value:"), v);
|
||||
return None;
|
||||
}
|
||||
let okey = &v[0];
|
||||
let key: String;
|
||||
if okey.is_string() {
|
||||
key = String::from(okey.as_str().unwrap());
|
||||
} else {
|
||||
key = okey.dump();
|
||||
}
|
||||
let mut mems = v.members();
|
||||
mems.next();
|
||||
for val in mems {
|
||||
let s: String;
|
||||
if val.is_string() {
|
||||
s = String::from(val.as_str().unwrap());
|
||||
} else {
|
||||
s = val.dump();
|
||||
}
|
||||
query.append_pair(key.as_str(), s.as_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.get(u.as_str(), None)
|
||||
}
|
||||
|
||||
/// Send GET requests
|
||||
pub fn get<U: IntoUrl>(&mut self, url: U) -> Option<Response> {
|
||||
let r = self.aget(url);
|
||||
pub fn get<U: IntoUrl, H: ToHeaders>(&mut self, url: U, headers: H) -> Option<Response> {
|
||||
let r = self.aget(url, headers);
|
||||
let r = r.send();
|
||||
let r = spin_on(r);
|
||||
match r {
|
||||
@@ -97,19 +212,71 @@ impl WebClient {
|
||||
}
|
||||
let r = r.unwrap();
|
||||
self.handle_set_cookie(&r);
|
||||
if self.verbose {
|
||||
println!("{}", r.status());
|
||||
}
|
||||
Some(r)
|
||||
}
|
||||
|
||||
pub fn aget<U: IntoUrl>(&mut self, url: U) -> RequestBuilder {
|
||||
pub fn aget<U: IntoUrl, H: ToHeaders>(&mut self, url: U, headers: H) -> RequestBuilder {
|
||||
let s = url.as_str();
|
||||
if self.verbose {
|
||||
println!("GET {}", s);
|
||||
}
|
||||
let mut r = self.client.get(s);
|
||||
for (k, v) in self.headers.iter() {
|
||||
r = r.header(k, v);
|
||||
}
|
||||
let headers = headers.to_headers();
|
||||
if headers.is_some() {
|
||||
let h = headers.unwrap();
|
||||
for (k, v) in h.iter() {
|
||||
r = r.header(k, v);
|
||||
}
|
||||
}
|
||||
let c = gen_cookie_header(&mut self.cookies, s);
|
||||
if c.len() > 0 {
|
||||
r = r.header("Cookie", c.as_str());
|
||||
}
|
||||
r
|
||||
}
|
||||
|
||||
pub fn download_stream<S: AsRef<OsStr> + ?Sized>(file_name: &S, r: Response, overwrite: Option<bool>) -> Result<(), ()> {
|
||||
let p = Path::new(file_name);
|
||||
if p.exists() {
|
||||
let overwrite = if overwrite.is_none() {
|
||||
ask_need_overwrite(p.to_str().unwrap())
|
||||
} else {
|
||||
overwrite.unwrap()
|
||||
};
|
||||
if !overwrite {
|
||||
return Ok(());
|
||||
}
|
||||
let re = remove_file(p);
|
||||
if re.is_err() {
|
||||
println!("{} {}", gettext("Failed to remove file:"), re.unwrap_err());
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
let f = File::create(p);
|
||||
if f.is_err() {
|
||||
println!("{} {}", gettext("Failed to create file:"), f.unwrap_err());
|
||||
return Err(());
|
||||
}
|
||||
let mut f = f.unwrap();
|
||||
let mut stream = r.bytes_stream();
|
||||
while let Some(data) = spin_on(stream.next()) {
|
||||
if data.is_err() {
|
||||
println!("{} {}", gettext("Error when downloading file:"), data.unwrap_err());
|
||||
return Err(());
|
||||
}
|
||||
let data = data.unwrap();
|
||||
let r = f.write(&data);
|
||||
if r.is_err() {
|
||||
println!("{} {}", gettext("Failed to write file:"), r.unwrap_err());
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user