diff --git a/docs/misc/bvid_desc.md b/docs/misc/bvid_desc.md index f480b1c6b4..90541e58b7 100644 --- a/docs/misc/bvid_desc.md +++ b/docs/misc/bvid_desc.md @@ -12,7 +12,7 @@ ### 格式 -“bvid”恒为长度为 12 的字符串,前两个固定为“BV1”,后 9 个为 base58 计算结果(不包含数字 `0` 和大写字母 `I`、 `O` 以及小写字母 `l`) +“bvid”恒为长度为 12 的字符串,前 3 个固定为“BV1”,后 9 个为 base58 计算结果(不包含数字 `0` 和大写字母 `I`、 `O` 以及小写字母 `l`) ### 实质 @@ -328,6 +328,64 @@ public class AVBVConverter { ``` +### C++ +```c++ +#include +#include +#include +#include + +constexpr int64_t XOR_CODE = 0x1552356C4CDB; +constexpr int64_t MAX_AID = 0x8000000000000; +constexpr int64_t MASK_CODE = MAX_AID - 1; +constexpr int64_t BASE = 58; +constexpr char Table[BASE + 1] = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf"; +constexpr char ReverseTable[128] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2c, 0x2d, 0x0b, 0x1a, 0x0e, 0x27, 0x11, 0x1d, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x03, 0x25, 0x2a, 0x31, 0x12, 0x00, 0x0c, 0x15, 0x00, 0x13, 0x06, 0x0f, 0x08, 0x05, 0x00, + 0x04, 0x32, 0x35, 0x30, 0x07, 0x2f, 0x0d, 0x17, 0x33, 0x20, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1f, 0x1c, 0x01, 0x36, 0x21, 0x39, 0x0a, 0x1e, 0x23, 0x10, 0x29, 0x00, 0x2e, 0x14, 0x37, + 0x16, 0x24, 0x28, 0x18, 0x1b, 0x09, 0x22, 0x02, 0x19, 0x2b, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +std::string Av2bv(const int64_t Avid) { + assert(Avid > 0 && "Avid must be greater than 0"); + std::string bv = "BV1"; + bv.resize(12, '\0'); + + int64_t tmp = (Avid | MAX_AID) ^ XOR_CODE; + for (size_t i = bv.size() - 1; tmp > 0 && i > 2; --i) { + bv[i] = Table[tmp % BASE]; + tmp /= BASE; + } + std::ranges::swap(bv.at(3), bv.at(9)); + std::ranges::swap(bv.at(4), bv.at(7)); + return bv; +} + +int64_t Bv2av(const std::string &Bvid) { + assert(Bvid.starts_with("BV1") && "Bvid must start with 'BV1'"); + + auto Bvid_ = Bvid; + std::ranges::swap(Bvid_.at(3), Bvid_.at(9)); + std::ranges::swap(Bvid_.at(4), Bvid_.at(7)); + + int64_t tmp = 0; + for (int i = 3; i < Bvid_.size(); ++i) { + tmp = ReverseTable[Bvid_.at(i)] + BASE * tmp; + } + return (tmp & MASK_CODE) ^ XOR_CODE; +} + +int main() { + assert(Av2bv(1004871019) == "BV16x4y1H7M1"); + assert(Bv2av("BV16x4y1H7M1") == 1004871019); +} +``` + ## 老版算法存档 diff --git a/docs/misc/sign/APP.md b/docs/misc/sign/APP.md index fa14fee773..b1dc9e8a01 100644 --- a/docs/misc/sign/APP.md +++ b/docs/misc/sign/APP.md @@ -23,7 +23,7 @@ ## Demo -该 Demo 提供 [Python](#Python) 和 [Java](#Java) 和 [TS/JS](#TypeScript/JavaScript) 和 [Swift](#Swift) 语言例程 +该 Demo 提供 [Python](#Python)、[Java](#Java)、[TS/JS](#TypeScript/JavaScript)、[Swift](#Swift)、[C++](#CplusPlus) 语言例程 使用 appkey = `1d8b6e7d45233436`, appsec = `560c52ccd288fed045859ed18bffd973` 对如下 `params` 参数进行签名 @@ -214,3 +214,76 @@ print(signResult) 输出结果为:01479cf20504d865519ac50f33ba3a7d + + +### CplusPlus + +需要 c++ 23 标准库,[cpr](https://github.com/libcpr/cpr)、[cryptopp](https://github.com/weidai11/cryptopp)、[nlohmann/json](https://github.com/nlohmann/json) 等依赖 + +```c++ +#include // std::println + +/// thrid party libraries +#include // cpr::util::urlEncode() +#include +#include +#include + +/* + * 注意,假定不会发生错误! + */ + +/* 获取 md5 hex(lower) */ +std::string Get_md5_hex(const std::string &Input_str) { + CryptoPP::Weak1::MD5 hash; + std::string md5_hex; + + CryptoPP::StringSource ss(Input_str, true, + new CryptoPP::HashFilter(hash, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(md5_hex) + ) + ) + ); + + std::ranges::for_each(md5_hex, [](char &x) { x = std::tolower(x); }); + return md5_hex; +} + +/* 将 json 转换为 url 编码字符串 */ +std::string Json_to_url_encode_str(const nlohmann::json &Json) { + std::string encode_str; + for (const auto &[key, value]: Json.items()) { + encode_str.append(key).append("=").append(cpr::util::urlEncode(value.is_string() ? value.get() : to_string(value))).append("&"); + } + + // remove the last '&' + encode_str.resize(encode_str.size() - 1, '\0'); + return encode_str; +} + +std::string App_sign(nlohmann::json &Params, const std::string &App_key, const std::string &App_sec) { + Params["appkey"] = App_key; + Params["sign"] = Get_md5_hex(Json_to_url_encode_str(Params) + App_sec); + return Json_to_url_encode_str(Params); +} + +int main() { + nlohmann::json Params; + Params["id"] = 114514; + Params["str"] = "1919810"; + Params["test"] = "いいよ,こいよ"; + + constexpr auto App_key = "1d8b6e7d45233436"; + constexpr auto App_sec = "560c52ccd288fed045859ed18bffd973"; + std::string sign = App_sign(Params, App_key, App_sec); + std::println("{}", to_string(Params)); + std::println("{}", sign); +} +``` + +```text +{"appkey":"1d8b6e7d45233436","id":114514,"sign":"01479cf20504d865519ac50f33ba3a7d","str":"1919810","test":"いいよ,こいよ"} +appkey=1d8b6e7d45233436&id=114514&sign=01479cf20504d865519ac50f33ba3a7d&str=1919810&test=%E3%81%84%E3%81%84%E3%82%88%EF%BC%8C%E3%81%93%E3%81%84%E3%82%88 +``` + diff --git a/docs/misc/sign/wbi.md b/docs/misc/sign/wbi.md index ab5bd8b504..571cba0362 100644 --- a/docs/misc/sign/wbi.md +++ b/docs/misc/sign/wbi.md @@ -120,7 +120,7 @@ ## Demo -含 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)、[Java](#Java) 和 [Swift](#Swift) 语言编写的 Demo 。 +含 [Python](#Python)、[JavaScript](#JavaScript)、[Golang](#Golang)、[C#](#CSharp)、[Java](#Java)、[Swift](#Swift)、[C++](#CPlusPlus) 语言编写的 Demo 。 ### Python @@ -934,3 +934,125 @@ func biliWbiSign(param: String, completion: @escaping (String?) -> Void) { } ``` + + +### CPlusPlus + +需要 c++ 23 标准库,[cpr](https://github.com/libcpr/cpr)、[cryptopp](https://github.com/weidai11/cryptopp)、[nlohmann/json](https://github.com/nlohmann/json) 等依赖 + +```c++ +#include // std::array +#include // std::locale +#include // std::println + +/// thrid party libraries +#include +#include +#include +#include + +/* + * 注意,假定不会发生错误! + */ +class Wbi { + constexpr static std::array MIXIN_KEY_ENC_TAB_ = { + 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, + 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, + 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, + 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 + }; + + /* 获取 md5 hex(lower) */ + static std::string Get_md5_hex(const std::string &Input_str) { + CryptoPP::Weak1::MD5 hash; + std::string md5_hex; + + CryptoPP::StringSource ss(Input_str, true, + new CryptoPP::HashFilter(hash, + new CryptoPP::HexEncoder( + new CryptoPP::StringSink(md5_hex) + ) + ) + ); + + std::ranges::for_each(md5_hex, [](char &x) { x = std::tolower(x); }); + return md5_hex; + } + +public: + /* 将 json 转换为 url 编码字符串 */ + static std::string Json_to_url_encode_str(const nlohmann::json &Json) { + std::string encode_str; + for (const auto &[key, value]: Json.items()) { + encode_str.append(key).append("=").append(cpr::util::urlEncode(value.is_string() ? value.get() : to_string(value))).append("&"); + } + + // remove the last '&' + encode_str.resize(encode_str.size() - 1, '\0'); + return encode_str; + } + + /* 获取 wbi key */ + static std::pair Get_wbi_key() { + const auto url = cpr::Url {"https://api.bilibili.com/x/web-interface/nav"}; + const auto cookie = cpr::Cookies { + {"SESSDATA", "xxxxxxxxxxxx"}, + }; + const auto header = cpr::Header { + {"User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3"}, + {"Referer", "https://www.bilibili.com/"}, + }; + const auto response = cpr::Get(url, cookie, header); + + nlohmann::json json = nlohmann::json::parse(response.text); + + const std::string img_url = json["data"]["wbi_img"]["img_url"]; + const std::string sub_url = json["data"]["wbi_img"]["sub_url"]; + + std::string img_key = img_url.substr(img_url.find("wbi/") + 4, img_url.find(".png") - img_url.find("wbi/") - 4); + std::string sub_key = sub_url.substr(sub_url.find("wbi/") + 4, sub_url.find(".png") - sub_url.find("wbi/") - 4); + return {img_key, sub_key}; + } + + /* 获取 mixin key */ + static std::string Get_mixin_key(const std::string &Img_key, const std::string &Sub_key) { + std::string raw_wbi_key_str = Img_key + Sub_key; + std::string result; + + std::ranges::for_each(MIXIN_KEY_ENC_TAB_, [&result, &raw_wbi_key_str](const uint8_t x) { + result.push_back(raw_wbi_key_str.at(x)); + }); + + return result.substr(0, 32); + } + + /* 计算签名(w_rid) */ + static std::string Calc_sign(nlohmann::json &Params, const std::string &Mixin_key) { + Params["wts"] = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + + const std::string encode_str = Json_to_url_encode_str(Params).append(Mixin_key); + return Get_md5_hex(encode_str); + } +}; + + +int main() { + nlohmann::json Params; + // qn=32&fnver=0&fnval=4048&fourk=1&avid=1755630705&cid=1574294582 + Params["qn"] = 32; + Params["fnver"] = 0; + Params["fnval"] = 4048; + Params["fourk"] = 1; + Params["avid"] = 1755630705; + Params["cid"] = 1574294582; + + auto [img_key, sub_key] = Wbi::Get_wbi_key(); + const auto mixin_key = Wbi::Get_mixin_key(img_key, sub_key); + const auto w_rid = Wbi::Calc_sign(Params, mixin_key); + std::println("{}", Wbi::Json_to_url_encode_str(Params) + "&w_rid=" + w_rid); +} +``` + +```text +avid=1755630705&cid=1574294582&fnval=4048&fnver=0&fourk=1&qn=32&wts=1717922933&w_rid=43571b838a1611fa121189083cfc1784 +``` \ No newline at end of file